Если я скопирую float в другую переменную, будут ли они равны? - PullRequest
167 голосов
/ 13 января 2020

Я знаю, что использование == для проверки равенства переменных с плавающей точкой не очень хороший способ. Но я просто хочу знать, что со следующими утверждениями:

float x = ...

float y = x;

assert(y == x)

Поскольку y скопировано из x, будет ли утверждение верным?

Ответы [ 6 ]

125 голосов
/ 14 января 2020

Помимо случая assert(NaN==NaN);, на который указывает kmdreko, у вас может быть ситуация с x87-math, когда 80-разрядные числа с плавающей запятой временно сохраняются в памяти и позже сравниваются со значениями, которые все еще хранятся в регистре.

Возможный минимальный пример, который завершается с gcc9.2 при компиляции с -O2 -m32:

#include <cassert>

int main(int argc, char**){
    float x = 1.f/(argc+2);
    volatile float y = x;
    assert(x==y);
}

Демонстрация Годболта: https://godbolt.org/z/X-Xt4R

Возможно volatile быть опущенным, если вам удастся создать достаточное давление в регистре, чтобы y сохранялось и перезагружалось из памяти (но достаточно запутало компилятор, чтобы не пропускать сравнение все вместе).

См. G CC Ссылка на часто задаваемые вопросы:

116 голосов
/ 13 января 2020

Это не будет верно, если x равно NaN, поскольку сравнения по NaN равны всегда ложно (да, даже NaN == NaN). Для всех остальных случаев (нормальные значения, субнормальные значения, бесконечности, нули) это утверждение будет верным.

Рекомендация по избежанию == для чисел с плавающей запятой применима к вычислениям из-за чисел с плавающей запятой будучи не в состоянии express получить много результатов точно при использовании в арифметических c выражениях. Присвоение не является расчетом, и нет никаких причин, по которым присваивание могло бы дать значение, отличное от исходного.


Оценка расширенной точности не должна быть проблемой, если следовать стандарту. От <cfloat> унаследовано от C [5.2.4.2.2.8] ( упорная мина ):

За исключением назначения и приведения (которые удаляют весь дополнительный диапазон и точность) , значения операций с плавающими операндами и значения, подлежащие обычным арифметическим преобразованиям c и плавающих констант, оцениваются в формате, диапазон и точность которого могут быть больше, чем требуется для типа.

Однако, как отмечалось в комментариях, некоторые случаи с определенными компиляторами, опциями компоновки и целями могут сделать это парадоксально ложным.

34 голосов
/ 13 января 2020

Да, y обязательно примет значение x:

[expr.ass]/2: в простом назначении (=) объект, на который ссылается Левый операнд изменяется ([defns.access]), заменяя его значение результатом правого операнда.

Для других назначаемых значений не существует никакой возможности.

(Другие уже указывали, что сравнение эквивалентности ==, тем не менее, оценивается как false для значений NaN.)

Обычная проблема с плавающей точкой == заключается в том, что это легко не имеют вполне ту ценность, которую, как вы думаете, вы делаете. Здесь мы знаем, что эти два значения, какими бы они ни были, одинаковы.

3 голосов
/ 15 января 2020

Да, во всех случаях (не принимая во внимание проблемы с NaN и x87), это будет верно.

Если вы сделаете для них memcmp, вы сможете проверить на равенство, а также сравнить NaN и sNaNs. Это также потребует, чтобы компилятор взял адрес переменной, которая приведет значение к 32-битному float вместо 80-битного. Это устранит проблемы с x87. Второе утверждение здесь не предназначено для того, чтобы показать, что == не будет сравнивать NaN как истинные:

#include <cmath>
#include <cassert>
#include <cstring>

int main(void)
{
    float x = std::nan("");
    float y = x;
    assert(!std::memcmp(&y, &x, sizeof(float)));
    assert(y == x);
    return 0;
}

Обратите внимание, что если NaN имеют другое внутреннее представление (т.е. различную мантиссу), memcmp не будет сравнивать true.

1 голос
/ 13 января 2020

В обычных случаях это оценивается как истина. (или утверждение assert ничего не сделает)

Edit :

Под «обычными случаями» я имею в виду исключение вышеупомянутого сценария ios (например, NaN значения и 80x87 единиц с плавающей запятой), как указано другими пользователями.

Учитывая устаревшее количество микросхем 8087 в сегодняшнем контексте, проблема является довольно изолированной, и вопрос применим к текущему состоянию используемой архитектуры с плавающей запятой, это справедливо для всех случаев, за исключением NaN.

(ссылка о 8087 - https://home.deec.uc.pt/~jlobo/tc/artofasm/ch14/ch143.htm)

Благодарность @chtz за воспроизведение хорошего примера и @kmdreko за упоминание NaNs - раньше о них не знали !

0 голосов
/ 28 января 2020

Да, он будет возвращать True всегда, кроме случаев, когда это NaN . Если значение переменной равно NaN , оно всегда возвращает False !

...