Можно ли считать тесты на равенство и неравенство с плавающей точкой последовательными и повторяемыми? - PullRequest
6 голосов
/ 16 января 2020

Предположим, что два числа с плавающей точкой x и y, ни один из которых не является Nan.

Можно ли предположить, что тесты на равенство и неравенство будут:

a. Будьте согласны друг с другом: Например, гарантируется следующее истина: (x >= y) == ((x > y) || (x == y))

b. Будьте повторяемыми, например, (x == y) всегда будет давать один и тот же результат каждый раз, когда он был оценен, если ни x, ни y не были изменены. Я спрашиваю об этом из-за проблемы, заключающейся в том, что модуль с плавающей запятой может хранить промежуточные результаты с более высокой точностью, чем они хранятся в памяти, и поэтому значение переменной может измениться в зависимости от того, было ли это результатом недавнего вычисления, которое все еще находится в FPU, или это пришло из памяти. Вероятно, первый тест может быть в первом случае, а более поздний - во втором.

Ответы [ 2 ]

6 голосов
/ 16 января 2020

Если в вопросе x и y указаны идентификаторы (а не аббревиатуры для выражений в целом, например, x означает b + sqrt(c)), тогда стандарт C ++ требует, чтобы (x >= y) == (x > y || x == y) было истинным.

C ++ 2017 (черновик N4659) 8 13 позволяет оценивать выражения с плавающей запятой с большей точностью и дальностью, чем требуется для их номинальных типов. Например, при оценке оператора с float операндами реализация может использовать double arithmeti c. Однако в сноске 64 мы ссылаемся на 8.4, 8.2.9 и 8.18, чтобы понять, что операторы приведения и присваивания должны выполнять свои конкретные c преобразования, которые создают значение, представимое в номинальном типе.

Таким образом после того, как x и y были назначены значения, нет избыточной точности, и они не имеют разных значений в разных целях. Тогда (x >= y) == (x > y || x == y) должно быть истинным, потому что оно оценивается так, как оно выглядит и обязательно математически верно.

Существование G CC ошибка 323 означает, что вы не можете полагаться на G CC при компиляции для i386, но это связано с ошибкой в ​​G CC, которая нарушает стандарт C ++. Стандарт C ++ не допускает этого.

Если сравниваются выражения, например:

double y = b + sqrt(c);
if (y != b + sqrt(c))
    std::cout << "Unequal\n";

, тогда значение, присвоенное y, может отличаться от значения, вычисленного для правого оператора. b + sqrt(c), и строка может быть напечатана, потому что b + sqrt(c) может иметь избыточную точность, тогда как y не должна.

Поскольку для удаления избыточной точности также требуется приведение, то y != (double) (b + sqrt(c)) всегда должен быть ложным (с учетом определения y выше).

5 голосов
/ 16 января 2020

Независимо от стандарта C ++, такие несоответствия встречаются на практике в различных настройках.

Существует два примера, которые легко вызвать:

Для 32-битной x86 не все так хорошо. Добро пожаловать в g cc номер ошибки 323 , из-за которого 32-разрядные приложения не соответствуют стандарту. Что происходит, так это то, что регистры с плавающей запятой в x86 имеют 80 бит, независимо от типа в программе (C, C ++ или Fortran). Это означает, что следующее обычно сравнивает 80-битные значения, а не 64-битные:

bool foo(double x, double y) 
{
     // comparing 80 bits, despite sizeof(double) == 8, i.e., 64 bits
     return x == y;
}

Это не будет большой проблемой, если g cc может гарантировать, что double всегда принимает 80 бит. К сожалению, число регистров с плавающей запятой конечно, и иногда значение сохраняется в памяти. Таким образом, для тех же x и y x==y может быть оценено как true после разлива в память и false без разлива в память. Нет никаких гарантий относительно (отсутствия) утечки в память. Поведение изменяется, по-видимому, случайным образом в зависимости от флагов компиляции и, по-видимому, несущественных изменений кода.

Таким образом, даже если x и y должны быть логически равны, а x становится разлитым, тогда x == y может оцените как false, так как y содержит бит 1 в его младшем значащем бите мантиссы, но x урезал этот бит из-за разлива. Тогда ответ на ваш второй вопрос: x ==y может возвращать разные результаты в разных местах, в зависимости от разлива или отсутствия в 32-битной программе x86.

Аналогично, x >= y может возвращать true, даже если у должен быть немного больше, чем x. Это может произойти, если после разлива в 64-битную переменную в памяти значения станут равными. В этом случае, если ранее в коде x > y || x == y вычисляется без разлива в память, он будет оцениваться как false. Чтобы сделать вещи более запутанными, замена одного выражения на другое может привести к тому, что компилятор сгенерирует немного другой код с разной потерей памяти. Разница в разливе для двух выражений может привести к непоследовательному различию результатов.

Та же проблема может возникнуть в любой системе, где операции с плавающей запятой выполняются с разной шириной (например, 80 бит для 32 бит x86) чем то, что хочет код (64 бита). Единственный способ обойти это несоответствие - заставить разлив после каждой операции с плавающей запятой усечь превышение точности. Большинство программистов не заботятся об этом из-за снижения производительности.

Второй случай, который может вызвать несоответствия , - это небезопасные оптимизации компилятора. По умолчанию многие коммерческие компиляторы выбрасывают согласованность FP из окна, чтобы получить несколько процентов времени выполнения. Компилятор может решить изменить порядок операций FP, даже если они могут давать разные результаты. Например:

v1 = (x + y) + z;
v2 = x + (y + z);
bool b = (v1 == v2);

Понятно, что скорее всего v1 != v2, из-за разного округления. Например, если x == -y, y > 1e100 и z == 1, то v1 == 1, но v2 == 0. Если компилятор слишком агрессивен, он может просто подумать об алгебре и сделать вывод, что b должно быть true, даже не оценивая ничего. Это то, что происходит при запуске gcc -ffast-math.

Вот пример , который показывает это.

Такое поведение может сделать x == y несовместимым и в значительной степени зависеть от того, что компилятор может вывести в конкретном c фрагменте кода.

...