Добавление числа с плавающей запятой увеличено вдвое?
-
12-09-2019 - |
Вопрос
Сегодня утром у меня случился небольшой WTF-момент.Этот 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
гарантированно будет повышен до удвоения или это на усмотрение составителя?
ОБНОВЛЯТЬ: Поскольку многие люди утверждают, что не следует использовать ==
Что касается плавающей запятой, я просто хотел заявить, что в конкретном случае, с которым я работаю, точное сравнение оправдано.
Сравнение с плавающей запятой является сложно, вот интересно связь по теме, которая, я думаю, не была упомянута.
Решение
Вы не можете вообще предполагать, что ==
будет работать должным образом для типов с плавающей запятой.Сравните округленные значения или используйте такие конструкции, как abs(a-b) < tolerance
вместо.
Повышение полностью на усмотрение компилятора (и будет зависеть от целевого оборудования, уровня оптимизации и т. д.).
В этом конкретном случае почти наверняка значения хранятся в регистрах FPU с более высокой точностью, чем в памяти - в общем, современное оборудование FPU работает с двойной или более высокой точностью внутри, независимо от точности, которую запросил программист, с помощью компилятора, генерирующего код. выполнять соответствующие преобразования при сохранении значений в памяти;в неоптимизированной сборке результат x+y
все еще находится в регистре в момент сравнения, но z
будут сохранены в памяти и извлечены обратно и, таким образом, усечены до точности с плавающей запятой.
Другие советы
А Рабочий проект следующего стандарта C++0x в разделе 5 пункте 11 сказано
Значения плавающих операндов и результаты плавающих выражений могут быть представлены с большей точностью и диапазоном, чем это требуется для типа;типы при этом не изменяются
Так что на усмотрение компилятора.
Используя gcc 4.3.2, утверждение нет сработало, и действительно, значение rvalue вернулось из x + y
это float
, а не double
.
Так что все зависит от компилятора.Вот почему никогда не разумно полагаться на точное равенство между двумя значениями с плавающей запятой.
В C++ FAQ lite есть дальнейшее обсуждение этой темы:
Это проблема, поскольку преобразование числа с плавающей запятой в двоичный код не дает точной точности.
И внутри 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
Я думаю, что это будет на усмотрение компилятора, но вы всегда можете принудительно выполнить приведение типов, если вы так думаете?
Еще одна причина никогда не сравнивать числа с плавающей запятой напрямую.
if (fabs(result - expectedResult) < 0.00001)