LINUX.ORG.RU

Опрокидывание uint при использовании 1.0 * UINT_MAX

 ,


0

1

Есть такой код:

int32_t i;
for (i = 0; i <= 1000; i++)
{
   func(i / 1000.0);  
}

А в func такое:

void func(float pos)
{
   if ((pos > 1.0) || (pos < 0.0))
        return;

   uint32_t normpos = pos * UINT32_MAX;
   ...
}

Вопрос - почему на последней итерации (i == 1000) normpos == 0? При этом на проверке входного параметра ([1 => pos => 0]) не вылетает.

★★

Потому что там получается 4294967296?

1.000000 != 1

yoghurt ★★★★★
()

Проблемы округления:

uint32_t( float( UINT32_MAX ) ) тоже дает 0. Даже умножать не надо.

grondek
()

Всё правильно ответили, добавлю цитату из K&R:

2.7 Type Conversions
...
• If either operand is long double, convert the other to long double.
• Otherwise, if either operand is double, convert the other to double.
• Otherwise, if either operand is float, convert the other to float.
• Otherwise, convert char and short to int.
• Then, if either operand is long, convert the other to long.

У тебя случилось то, на что указал grondek, т.е. третий пункт. А если бы твоя функция принимала double, ошибки бы ты не заметил.

d ★★★★
()
Ответ на: комментарий от d

Хорошо,как мне это поправить не меняя тип входного параметра? Я так понимаю, что fract не стандарт С99, поэтому пришлось так вывернуться с float'ом. Не используя double или int64_t здесь можно как-то починить багу?

sambist ★★
() автор топика
Ответ на: комментарий от sambist

Здесь вообще нельзя использовать float или double. Поправить можно если будешь передавать значение вместе с максимальным значением.

mashina ★★★★★
()
Ответ на: комментарий от mashina

Здесь вообще нельзя использовать float или double

Если без нормализации приведения к интам, то оно в два раза дольше выполняется на голых флоатах. А внутрь по идеологии надо значение от 0 до 1 в дробном формате запускать, это позиция стоп-точки.

sambist ★★
() автор топика
Ответ на: комментарий от sambist

А внутрь по идеологии надо значение от 0 до 1 в дробном формате запускать

пара (pos, 1000) это и есть такой формат. Для передачи этого значения нельзя использовать числа с плавающей точкой если внтури всё равно интерпретируешь его как целочисленное.

mashina ★★★★★
()
Ответ на: комментарий от sambist

Не меняя тип float pos ты максимум можешь явно привести UINT32_MAX к double, тогда сработает второй пункт и в конкретном случае точности хватит.

d ★★★★
()
Ответ на: комментарий от d

Не меняя тип float pos ты максимум можешь явно привести UINT32_MAX к double, тогда сработает второй пункт и в конкретном случае точности хватит.

double в си в общем случае не может точно представить UINT32_MAX

mashina ★★★★★
()
Ответ на: комментарий от mashina

Поэтому я написал

и в конкретном случае точности хватит

в конкретном случае

Мне ещё нравится у K&R такая вещь:

2.2 Data Types and Sizes

As with integers, the sizes of floating-point objects are implementation-defined; float, double and long double could represent one, two or three distinct sizes.

d ★★★★
()

1. По какой причине ты используешь float как тип параметра? Забудь ты об этом типе и используй только double. На 64-битных системах - это вообще бесполезно, т.к. передача действительных параметров через математический сопроцессор. Есть только ода причина использовать float - GPU.

2. Как тебе уже выше сказали, используй целочисленные параметры, например value, value_max.

З.Ы.: Если нужна быстрая функция преобразования из диапазона в диапазон, то вот лови.

#include <stdio.h>
#include <stdint.h>

uint32_t scale(uint32_t value, uint32_t value_max, uint32_t out_max)
{
    uint32_t result;
    __asm(
"       xor     %%edx, %%edx    \n"
"       movl    %1, %%eax       \n"
"       movl    %3, %%ecx       \n"
"       mul     %%ecx           \n"
"       movl    %2, %%ecx       \n"
"       div     %%ecx           \n"
"       movl    %%eax, %0       \n"
    :"=rm"(result):"rm"(value),"rm"(value_max),"rm"(out_max)
    );
    return result;
}

int main() {
    printf("%x\n", scale(0, UINT32_MAX, UINT32_MAX));
    printf("%x\n", scale(100, UINT32_MAX, UINT32_MAX));
    printf("%x\n", scale(1000, 1000, UINT32_MAX));
    printf("%x\n", scale(UINT32_MAX, UINT32_MAX, UINT32_MAX));
    return 0;
}
Ассемблер рулит!!!

AlexVR ★★★★★
()

Здравствуйте, это анонимный клуб людей, у которых проблемы с преждевременной оптимизацией?

buddhist ★★★★★
()
Последнее исправление: buddhist (всего исправлений: 1)
Ответ на: комментарий от buddhist

И? Как вы тут задетектили отсутствие предварительных тестов и нескольких запуском профайлера? Я заранее знаю, что функция при отрисовке при частоте кадров 24 и размерах 800х600 будет выполнятся 11 520 000 раз в секунду. Не находите, что я должен был задуматься о времени ее выполнения?

sambist ★★
() автор топика
Ответ на: комментарий от AlexVR

не x86(_64) единым живы... так что нет, не рулит :p

anonymous
()

Попробуй floor/ceil и магическую константу 0.5

IEEE-float работает через жопу, тут не в коем случае нельзя полагаться на точные вычисления. Основная проблема заключается в том, что число представляется в виде двоичной дроби. При таком представлении числа типа 0.1 (в десятичной системе счисления) становятся рациональными дробями с бесконечным количеством знаков. Это все равно что спрашивать почему последовательное вычисление (1./3)*3 даст в десятичной системе 0.9(9). При этом в системе с основанием 3 это будет конечное 0.1 и для хранения достаточно одного разряда.

Обычно, если нужна максимальная адекватность, алгоритм переделывают полностью, чтобы было в стиле алгоритмов рисования Брезенхайма - только целые, только сложения и умножения, никаких делений, никаких float. Можно попробовать алгоритмы Кэхэна для борьбы с накапливающимися ошибками. Можно аккуратно заюзать floor/ceil+0.5, они дают более-менее адекватные результаты.

anonymous
()
Ответ на: комментарий от anonymous

чтобы было в стиле алгоритмов рисования Брезенхайма - только целые, только сложения и умножения, никаких делений, никаких float.

Уже. Ускорилось на неадекватное количество времени, нет багов с плавающей запятой.

sambist ★★
() автор топика

Давай я тебе намекну: sizeof(int32_t) = 4, sizeof(float) = 4. А во float нужно кроме мантиссы еще и экспоненту держать, поэтому

printf("%f\n", ((1U << 31) - 1) * 1.0f);
Дает нам
2147483648.000000
С double будут аналогичные эффекты на 2^63-1

kawaii_neko ★★★★
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.