Я знаю что используя ==
для проверки равенства переменных с плавающей точкой не очень хороший способ. Но я просто хочу знать это со следующими утверждениями:
float x = ...
float y = x;
assert(y == x)
Так как y
это скопировано x
, будет ли утверждение верным?
c++
floating-point
Вэй Ли
источник
источник
-m32
), либо указав GCC использовать x87 FPU (-mfpmath=387
).Ответы:
Кроме
assert(NaN==NaN);
случая, указанного kmdreko, у вас может быть ситуация с x87-math, когда 80-разрядные числа с плавающей запятой временно сохраняются в памяти и позже сравниваются со значениями, которые все еще хранятся в регистре.Возможный минимальный пример, который завершается с gcc9.2 при компиляции с
-O2 -m32
:Демонстрация Godbolt: https://godbolt.org/z/X-Xt4R
volatile
, Вероятно , может быть опущена, если вам удастся создать достаточное количество регистров давления , чтобы иметьy
сохраняются и загружаются из памяти (но путать компилятор достаточно, чтобы не опускать сравнение все вместе).См. Ссылку GCC FAQ:
источник
float
стандартной точности с дополнительной точностью.-ffloat-store
похоже, это способ предотвратить это.Это не будет верно , если
x
этоNaN
, так как сравнения наNaN
это всегда ложно (да, дажеNaN == NaN
). Для всех остальных случаев (нормальные значения, субнормальные значения, бесконечности, нули) это утверждение будет верным.Рекомендация по избежанию операций с плавающей точкой
==
применима к вычислениям, поскольку числа с плавающей запятой не могут точно выразить многие результаты при использовании в арифметических выражениях. Присвоение не является расчетом, и нет причин, по которым присваивание могло бы дать значение, отличное от исходного.Оценка расширенной точности не должна быть проблемой при соблюдении стандарта. Из
<cfloat>
унаследованного от C [5.2.4.2.2.8] ( выделено мое ):Однако, как отмечалось в комментариях, некоторые случаи с определенными компиляторами, опциями сборки и целями могут сделать это парадоксально ложным.
источник
x
вычисляется в регистре в первой строке, сохраняя большую точность, чем минимум для afloat
.y = x
Может находиться в памяти, сохраняя толькоfloat
точность. Затем будет проведен тест на равенство с памятью в регистре, с разной точностью, и, следовательно, без гарантии.x+pow(b,2)==x+pow(a,3)
может отличаться от того,auto one=x+pow(b,2); auto two=y+pow(a,3); one==two
что можно сравнивать с использованием большей точности, чем с другой (если одно / два являются 64-битными значениями в оперативной памяти, а промежуточные значения - 80-ю битами в fpu). Так что назначение может что-то сделать, иногда.gcc -ffloat-store
для строгого соответствия. Но этот вопрос о том,x=y; x==y;
чтобы ничего не делать между ними. Если значениеy
уже округлено до значения типа float, преобразование в double или long double и обратно не изменит значения. ...Да,
y
безусловно, примет значениеx
:Не существует возможности для других назначаемых значений.
(Другие уже указывали, что сравнение эквивалентности
==
, тем не менее, будет оцениватьfalse
для значений NaN.)Обычная проблема с плавающей запятой
==
заключается в том, что легко не иметь того значения, которое, как вы думаете, вы делаете. Здесь мы знаем, что эти два значения, какими бы они ни были, одинаковы.источник
[expr]
. Если я игнорирую ссылки и сосредотачиваюсь на цитатах, я остаюсь в замешательстве, что, например, C.5.3 , кажется, не касается использования термина «значение» или термина «результат» (хотя это и делает используйте «результат» один раз в обычном английском контексте). Возможно, вы могли бы более четко описать, где, по вашему мнению, стандарт проводит различие, и дать однозначную ссылку на это событие. Спасибо!Да, во всех случаях (без учета NaN и проблем с x87) это будет правдой.
Если вы сделаете для
memcmp
них проверку, вы сможете проверить на равенство и одновременно сравнить NaN и sNaN. Это также потребует от компилятора взять адрес переменной, которая приведет значение к 32-битному,float
а не к 80-битному. Это устранит проблемы с x87. Второе утверждение здесь не предназначено для демонстрации того, что==
NaN не будут сравниваться как истинные:Обратите внимание, что если NaN имеют другое внутреннее представление (то есть различную мантиссу),
memcmp
сравнение не будет истинным.источник
В обычных случаях это будет оценено как истинное. (или утверждение assert ничего не сделает)
Редактировать :
Под «обычными случаями» я подразумеваю исключение вышеупомянутых сценариев (таких как значения NaN и единицы с плавающей запятой 80x87), как указано другими пользователями.
Принимая во внимание устаревшее количество микросхем 8087 в сегодняшнем контексте, проблема является довольно изолированной, и вопрос применим к текущему состоянию используемой архитектуры с плавающей запятой, что справедливо для всех случаев, за исключением NaN.
(ссылка около 8087 - https://home.deec.uc.pt/~jlobo/tc/artofasm/ch14/ch143.htm )
Спасибо @chtz за хороший пример и @kmdreko за упоминание NaNs - раньше о них не знали!
источник
x
быть в регистре с плавающей запятой, покаy
загружен из памяти. Память может иметь меньшую точность, чем регистр, что приводит к сбою сравнения.float
значением без дополнительной точности.int a=1; int b=a; assert( a==b );
утверждение, я думаю, что имеет смысл ответить на этот вопрос только в отношении правильно функционирующего компилятора (хотя, возможно, отмечая, что некоторые версии некоторых компиляторов имеют / имеют -известно-чтобы понять это неправильно). С практической точки зрения, если по какой-то причине компилятор не удаляет дополнительную точность из результата присваиваемого регистру присваивания, он должен сделать это, прежде чем использовать это значение.Да, он вернет True всегда, кроме случаев, когда это NaN . Если значение переменной равно NaN, оно всегда возвращает False !
источник