Сплав добавлен в двойное?
Сегодня утром у меня был небольшой момент WTF. Ths WTF можно резюмировать следующим образом:
float x = 0.2f;
float y = 0.1f;
float z = x + y;
assert(z == x + y); //This assert is triggered! (Atleast with visual studio 2008)
Похоже, что причина в том, что выражение x + y
продвигается в два раза и сравнивается с усеченной версией в z
. (Если я изменяю z
на double
, утверждение не запускается).
Я вижу, что по прецизионным соображениям было бы целесообразно выполнять всю арифметику с плавающей точкой в двойной точности, прежде чем преобразовать результат в единую точность. Я нашел следующий параграф в стандарте (который, я думаю, я уже знал, но не в этом контексте):
4.6.1.
"Значение типа float
может быть преобразовано в rvalue типа double
. Значение не изменяется"
Мой вопрос в том, что x + y
гарантируется, что он будет удвоен или находится на усмотрение компилятора?
ОБНОВЛЕНИЕ:. Поскольку многие люди утверждали, что нельзя использовать ==
для плавающей запятой, я просто хотел сказать, что в конкретном случае, с которым я работаю, точное сравнение оправданными.
Сравнение с плавающей точкой сложно, вот интересная ссылка по теме, о которой, как я думаю, не упоминалось.
Ответы
Ответ 1
Обычно вы не можете предположить, что ==
будет работать как ожидалось для типов с плавающей запятой. Сравните округленные значения или используйте вместо них конструкцию типа abs(a-b) < tolerance
.
Продвижение полностью зависит от усмотрения компилятора (и будет зависеть от целевого оборудования, уровня оптимизации и т.д.).
То, что происходит в этом конкретном случае, почти наверняка состоит в том, что значения хранятся в регистрах FPU с большей точностью, чем в памяти. В общем, современное оборудование FPU работает с двойной или более высокой точностью, независимо от точности, которую запросил программист, компилятор генерирует код, чтобы сделать соответствующие преобразования, когда значения хранятся в памяти; в неоптимизированной сборке результат x+y
все еще находится в регистре в точке, где выполняется сравнение, но z
будет храниться в памяти и возвращаться назад и, таким образом, усекается с точностью до плавания.
Ответ 2
Рабочий черновик для следующего стандартного С++ 0x в разделе 5, пункт 11, говорит
Значения плавающих операндов и результаты плавающих выражений могут быть представлены в большей точности и дальности, чем требуемые типом; типы не изменяются таким образом
Итак, по усмотрению компилятора.
Ответ 3
Используя gcc 4.3.2, утверждение не запускается, и действительно, значение rvalue, возвращаемое из x + y
, является float
, а не a double
.
Так это до компилятора. Вот почему никогда не следует полагаться на точное равенство между двумя значениями с плавающей запятой.
Ответ 4
Это проблема, поскольку число с плавающей точкой для двоичного преобразования не дает точной точности.
И в пределах sizeof(float)
байтов он не может разместить точное значение числа с плавающей точкой, а арифметическая операция может привести к аппроксимации, и, следовательно, равенство терпит неудачу.
См. ниже, например.
float x = 0.25f; //both fits within 4 bytes with precision
float y = 0.50f;
float z = x + y;
assert(z == x + y); // it would work fine and no assert
Ответ 5
В С++ FAQ lite есть несколько дополнительных обсуждений по теме:
Ответ 6
Я бы подумал, что это будет по усмотрению компилятора, но вы всегда можете заставить его с помощью актера, если это было вашим мнением?
Ответ 7
Еще одна причина никогда не сравнивать прямые поплавки.
if (fabs(result - expectedResult) < 0.00001)