Подтвердить что ты не робот

Сплав добавлен в двойное?

Сегодня утром у меня был небольшой момент 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 гарантируется, что он будет удвоен или находится на усмотрение компилятора?

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

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

4b9b3361

Ответ 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

Ответ 6

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

Ответ 7

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

if (fabs(result - expectedResult) < 0.00001)