Ответ 1
Потому что это число с плавающей запятой IEEE, и оно не точно равно нулю.
http://www.cygnus-software.com/papers/comparingfloats/comparingfloats.htm
У меня есть программа, которая находит пути в графе и выводит совокупный вес. Все ребра на графике имеют индивидуальный вес от 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 Никакая другая операция не выполняется на них, кроме добавления их в Общая.
Потому что это число с плавающей запятой IEEE, и оно не точно равно нулю.
http://www.cygnus-software.com/papers/comparingfloats/comparingfloats.htm
[...] в виде поплавка с не более чем двумя десятичными знаками.
Нет такой вещи, как float с не более чем двумя десятичными знаками. Поплавки почти всегда представлены как двоичное число с плавающей запятой (дробная двоичная мантисса и целочисленная экспонента). Так много (большинство) чисел с 2 десятичными знаками невозможно точно представить.
Например, 0.20f
может выглядеть невинной и круглой, но
printf("%.40f\n", 0.20f);
напечатает: 0.2000000029802322387695312500000000000000.
Смотрите, у него нет двух знаков после запятой, у него есть 26!!!
Естественно, для большинства практических применений разница в незначительности. Но если вы выполните некоторые вычисления, вы можете в конечном итоге увеличить ошибку округления и сделать ее видимой, особенно около 0.
Возможно, ваши поплавки, содержащие значения "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. Таким образом, любые операции с плавающей запятой должны иметь некоторое количество ошибок.