Сравнение констант с плавающей запятой

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

double x; 
double y; 
x = f();
y = g();

if (fabs(x-y)<epsilon) {
   // they are equal!
} else {
   // they are not!
}

Однако, если я просто присвою постоянное значение без каких-либо вычислений, нужно ли мне проверять эпсилон?

double x = 1;
double y = 1;

if (x==y) {
   // they are equal!
} else {
   // no they are not!
}

Достаточно ли хорошо == сравнение? Или мне нужно сделать fabs(x-y)<epsilon снова? Можно ли внести ошибку в назначение? Я слишком параноик?

Как насчет кастинга (double x = static_cast<double>(100))? Это также приведет к ошибке с плавающей запятой?

Я использую C++ в Linux, но если он отличается по языку, я хотел бы это понять.


person Vendetta    schedule 23.03.2012    source источник
comment
Нужен ли вам эпсилон, зависит от ситуации. Например. когда вам нужно транзитивное равенство (a==b && b==c подразумевает a==c), вы не можете использовать эпсилон. Кстати, double x = 1 уже означает double x = static_cast<double>(1)   -  person MSalters    schedule 23.03.2012


Ответы (5)


На самом деле, это зависит от стоимости и реализации. Стандарт С++ (черновик n3126) говорит об этом в 2.14.4 Floating literals:

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

Другими словами, если значение точно представимо (и 1 в IEEE754, как и 100 в вашем статическом приведении), вы получите значение. В противном случае (например, с 0.1) вы получите определяемое реализацией близкое соответствие (a). Меня бы очень беспокоила реализация, выбирающая другое близкое совпадение на основе того же входного токена, но это возможно.


(a) На самом деле этот абзац может быть прочитан двумя способами: либо реализация может выбрать ближайшее большее или ближайшее меньшее значение независимо от того, какое из них на самом деле является ближайшим, либо она должна выбрать ближайшее значение. до желаемого значения.

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

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

person paxdiablo    schedule 23.03.2012
comment
Некоторые реализации оценивают литералы float, находя значение double, ближайшее к float, а затем округляют его до float. Иногда это может привести к тому, что литералам float, фактическое значение которых немного выше или ниже double, которое находится точно между двумя соседними значениями float, будет присвоено значение, которое не является ближайшим. - person supercat; 18.06.2014

Нет, если вы назначаете литералы, они должны быть одинаковыми :)

Также, если вы начинаете с одного и того же значения и выполняете одни и те же операции, они должны быть одинаковыми.

Значения с плавающей запятой неточны, но операции должны давать согласованные результаты :)

person joshuahealy    schedule 23.03.2012

Оба случая в конечном счете подлежат реализации определенным представлениям.

Хранение значений с плавающей запятой и их представления могут принимать различные формы - загрузка по адресу или константа? оптимизированы быстрой математикой? какова ширина регистра? он хранится в регистре SSE? Существует множество вариаций.

Если вам нужно точное поведение и переносимость, не полагайтесь на поведение, определенное реализацией.

person justin    schedule 23.03.2012

IEEE-754, который является стандартной общепринятой реализацией чисел с плавающей запятой, требует, чтобы операции с плавающей запятой давали результат, который является ближайшим представимым значением к бесконечно точному результату. Таким образом, единственная неточность, с которой вы столкнетесь, — это округление после каждой выполняемой вами операции, а также распространение ошибок округления от операций, выполненных ранее в цепочке. Поплавки сами по себе не являются неточными. И, кстати, эпсилон можно и нужно вычислять, вы можете обратиться к любой книге по числовым вычислениям.

Числа с плавающей запятой могут представлять целые числа точно до длины их мантиссы. Так, например, если вы выполняете преобразование из int в double, оно всегда будет точным, но для преобразования в число с плавающей запятой оно больше не будет точным для очень больших целых чисел.

Существует один важный пример широкого использования чисел с плавающей запятой в качестве замены целых чисел — это язык сценариев LUA, который не имеет встроенного целочисленного типа, а числа с плавающей запятой широко используются для логики, управления потоком и т. д. Производительность а штраф за хранение из-за использования чисел с плавающей запятой оказывается меньше, чем штраф за разрешение нескольких типов во время выполнения, и делает реализацию легче. LUA широко используется не только на ПК, но и на игровых приставках.

Теперь многие компиляторы имеют необязательный переключатель, отключающий совместимость с IEEE-754. Затем идут компромиссы. Денормализованные числа (очень-очень маленькие числа, где показатель степени достигает наименьшего возможного значения) часто обрабатываются как нуль, и могут быть сделаны аппроксимации в реализации степени, логарифма, sqrt и 1/(x^2), но сложение/вычитание, сравнение и умножение должны сохранять свои свойства для чисел, которые могут быть точно представлены.

person 3yE    schedule 23.03.2012

Простой ответ: для констант == это нормально. Есть два исключения, о которых вам следует знать:

Первое исключение:

0.0 == -0.0

Существует отрицательный ноль, который сравнивается с равным для стандарта IEEE 754. Это означает, что 1/INFINITY == 1/-INFINITY, что нарушает f(x) == f(y) => x == y

Второе исключение:

NaN != NaN

Это специальное предостережение NotaNumber, которое позволяет узнать, является ли число NaN в системах, в которых нет доступной функции проверки (да, такое бывает).

person Thorsten S.    schedule 23.03.2012