Поплавок дополнения повышен в два раза? - PullRequest
12 голосов
/ 03 декабря 2009

Сегодня утром у меня был небольшой 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 может быть преобразовано в значение типа double. Значение не изменяется»

У меня такой вопрос, гарантированно ли x + y будет повышен до двойного или по усмотрению компилятора?

ОБНОВЛЕНИЕ: Поскольку многие утверждали, что не следует использовать == для плавающей запятой, я просто хотел заявить, что в конкретном случае, с которым я работаю, точное сравнение оправдано .

Сравнение с плавающей запятой - это сложно, вот интересная ссылка на предмет, который, я думаю, не упоминался.

Ответы [ 7 ]

14 голосов
/ 03 декабря 2009

Обычно вы не можете предполагать, что == будет работать так, как ожидается для типов с плавающей запятой. Сравните округленные значения или используйте конструкции типа abs(a-b) < tolerance.

Продвижение осуществляется исключительно на усмотрение компилятора (и будет зависеть от целевого оборудования, уровня оптимизации и т. Д.).

В данном конкретном случае почти наверняка происходит то, что значения сохраняются в регистрах FPU с более высокой точностью, чем в памяти - в общем, современное аппаратное обеспечение FPU работает с двойной или более высокой точностью внутри, независимо от точности, запрошенной программистом, с компилятор, генерирующий код для выполнения соответствующих преобразований при сохранении значений в памяти; в неоптимизированной сборке результат x+y все еще находится в регистре в тот момент, когда производится сравнение, но z будет сохранен в памяти и извлечен обратно и, таким образом, обрезан до точности с плавающей запятой.

8 голосов
/ 03 декабря 2009

Рабочий проект для следующего стандарта C ++ 0x раздел 5, пункт 11, говорит

Значения плавающих операндов и результаты плавающих выражений могут быть представлены с большей точностью и диапазоном, чем требуется типом; типы не меняются при этом

Так что на усмотрение компилятора.

1 голос
/ 05 мая 2010

В C ++ FAQ Lite есть дальнейшее обсуждение по теме:

1 голос
/ 03 декабря 2009

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

И в пределах 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
1 голос
/ 03 декабря 2009

При использовании gcc 4.3.2 утверждение не сработало, и действительно, значение, возвращаемое из x + y, представляет собой float, а не double.

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

0 голосов
/ 03 декабря 2009

Еще одна причина никогда не сравнивать поплавки напрямую.

if (fabs(result - expectedResult) < 0.00001)
0 голосов
/ 03 декабря 2009

Я бы подумал, что это будет на усмотрение компилятора, но вы всегда можете вызвать его с помощью приведения, если вы так думаете?

...