0 + 0 + 0... + 0!= 0

У меня есть программа, которая находит пути в графе и выводит совокупный вес. Все ребра на графике имеют индивидуальный вес от 0 до 100 в виде поплавка с не более чем двумя знаками после запятой.

В Windows/Visual Studio 2010 для определенного пути, состоящего из ребер с весом 0, он выводит правильный общий вес 0. Однако в Linux/GCC программа говорит, что путь имеет вес 2.35503e-38. У меня было много опыта с сумасшедшими ошибками, вызванными поплавками, но когда 0 + 0 когда-либо равнялось бы чему-либо, кроме 0?

Единственное, что я могу думать об этом, это то, что программа обрабатывает некоторые из весов как целые числа и использует неявное принуждение, чтобы добавить их в общую. Но 0 + 0.0f все равно равно 0.0f! В качестве быстрого решения я уменьшаю общее число до 0 при менее 0,00001, и этого достаточно для моих нужд. Но что такое vodoo?

ПРИМЕЧАНИЕ.. Я на 100% уверен, что ни один из весов в графе не превышает диапазон, о котором я говорил, и что все веса этого конкретного пути равны 0.

РЕДАКТИРОВАТЬ: Чтобы проработать, я попытался как прочитать весы из файла, так и установить их в коде вручную как равное 0.0f Никакая другая операция не выполняется на них, кроме добавления их в Общая.

Ответ 2

[...] в виде поплавка с не более чем двумя десятичными знаками.

Нет такой вещи, как float с не более чем двумя десятичными знаками. Поплавки почти всегда представлены как двоичное число с плавающей запятой (дробная двоичная мантисса и целочисленная экспонента). Так много (большинство) чисел с 2 десятичными знаками невозможно точно представить.

Например, 0.20f может выглядеть невинной и круглой, но

printf("%.40f\n", 0.20f);

напечатает: 0.2000000029802322387695312500000000000000.

Смотрите, у него нет двух знаков после запятой, у него есть 26!!!

Естественно, для большинства практических применений разница в незначительности. Но если вы выполните некоторые вычисления, вы можете в конечном итоге увеличить ошибку округления и сделать ее видимой, особенно около 0.

Ответ 3

Возможно, ваши поплавки, содержащие значения "0.0f", на самом деле не являются 0.0f (представление бит 0x00000000), но очень и очень небольшое число, которое оценивается примерно до 0,0. Из-за того, как спецификация IEEE754 определяет представления с плавающей запятой, если у вас есть, например, очень маленькая мантисса и показатель 0, а она не равна абсолютному 0, она будет округлена до 0. Однако, если вы добавите эти числа вместе достаточно количество раз, очень небольшое количество будет накапливаться в значение, которое в конечном итоге станет ненулевым.

Вот пример, который дает иллюзию 0 отличной от нуля:

float f = 0.1f / 1000000000;
printf("%f, %08x\n", f, *(unsigned int *)&f);
float f2 = f * 10000;
printf("%f, %08x\n", f2, *(unsigned int *)&f2);

Если вы назначаете литералы вашим переменным и добавляете их, возможно, что либо компилятор не переводит 0 в 0x0 в память. Если это так, и это все еще происходит, тогда также возможно, что ваше аппаратное обеспечение процессора имеет ошибку, связанную с превращением 0s в ненулевое значение при выполнении операций ALU, которые, возможно, скрипели от их усилий по проверке.

Однако хорошо помнить, что плавающая точка IEEE является только приближением, а не точным представлением какого-либо определенного значения float. Таким образом, любые операции с плавающей запятой должны иметь некоторое количество ошибок.