Сравнение числа с плавающей запятой с нулем
в C++ чаво облегченная "[29.17] почему мое сравнение с плавающей запятой не работает?" рекомендует этот тест равенства:
#include <cmath> /* for std::abs(double) */
inline bool isEqual(double x, double y)
{
const double epsilon = /* some small number such as 1e-5 */;
return std::abs(x - y) <= epsilon * std::abs(x);
// see Knuth section 4.2.2 pages 217-218
}
- правильно ли, что это означает, что единственными числами, равными нулю, являются
+0
и-0
? - следует ли использовать эту функцию также при тестировании на ноль или, скорее, тест, такой как
|x| < epsilon
?
обновление
как отметил Даниил Daranas функции наверное, лучше назвать isNearlyEqual
(что меня волнует).
кто-то указал этой ссылке, которым я хочу поделиться более заметно.
8 ответов
вы правы в своем наблюдении.
если x == 0.0
, потом abs(x) * epsilon
равно нулю, и вы проверяете, является ли abs(y) <= 0.0
.
если y == 0.0
затем вы проверяете abs(x) <= abs(x) * epsilon
что значит epsilon >= 1
(это не так) или x == 0.0
.
так как is_equal(val, 0.0)
или is_equal(0.0, val)
было бы бессмысленно, а вы могли бы просто сказать val == 0.0
. Если вы хотите только принять ровно +0.0
и -0.0
.
рекомендация FAQ В этом случае имеет ограниченная полезность. нет сравнения с плавающей запятой "один размер подходит всем". вам нужно подумать о семантике ваших переменных, допустимом диапазоне значений и величине ошибки, введенной вашими вычислениями. Даже в FAQ упоминается предостережение, говоря, что эта функция обычно не является проблемой,"когда значения x и y значительно больше, чем epsilon, но ваш пробег может отличаться".
нет.
равенство-это равенство.
функция, которую вы написали, не будет проверять два двойника на равенство, как обещает ее название. Он будет проверять только, если два двойника "достаточно близки" друг к другу.
Если вы действительно хотите проверить два двойника на равенство, используйте этот:
inline bool isEqual(double x, double y)
{
return x == y;
}
стандарты кодирования обычно рекомендуют не сравнивать два двойника для точного равенства. Но это совсем другая тема. Если вы на самом деле хотите сравнить два двойника для точного равенства,x == y
- это код, который вы хотите.
10.00000000000000001 не равно 10.0, независимо от того, что они вам говорят.
An пример использование точного равенства-это когда определенное значение double используется в качестве синонима некоторого специального состояния, такого как "ожидающая калькуляция" или "нет доступных данных". Это возможно только в том случае, если фактические числовые значения после этого ожидающего вычисления являются только подмножество возможных значений double. Наиболее типичный частный случай, когда это значение неотрицательно, и вы используете -1.0 в качестве (точного) представления "ожидающего вычисления" или "нет доступных данных". Вы можете представить это с константой:
const double NO_DATA = -1.0;
double myData = getSomeDataWhichIsAlwaysNonNegative(someParameters);
if (myData != NO_DATA)
{
...
}
можно использовать std::nextafter
фиксированный factor
на epsilon
стоимостью следующим образом:
bool isNearlyEqual(double a, double b)
{
int factor = /* a fixed factor of epsilon */;
double min_a = a - (a - std::nextafter(a, std::numeric_limits<double>::lowest())) * factor;
double max_a = a + (std::nextafter(a, std::numeric_limits<double>::max()) - a) * factor;
return min_a <= b && max_a >= b;
}
2 + 2 = 5(*)
(для некоторых значений с плавающей точностью 2)
эта проблема часто возникает, когда мы думаем о"плавающей точке" как о способе повышения точности. Затем мы сталкиваемся с "плавающей" частью, что означает, что нет никакой гарантии номера могут быть представлены.
поэтому, хотя мы могли бы легко представлять "1.0, -1.0, 0.1, -0.1", когда мы добираемся до больших чисел, мы начинаем видеть приближения - или мы должны, за исключением того, что мы часто скрываем их, усекая числа для отображения.
что произойдет, если вы выполните "0.0003 - 0.0002"? Мы ожидаем .0001, но фактические сохраняемые значения могут быть больше похожи на "0.00033" - "0.00029", что дает" 0.000004",или ближайшее представимое значение, который может быть 0, или это может быть "0.000006".
с текущими математическими операциями с плавающей запятой,не гарантируется, что (a / b) * b == A.
#include <stdio.h>
// defeat inline optimizations of 'a / b * b' to 'a'
extern double bodge(int base, int divisor) {
return static_cast<double>(base) / static_cast<double>(divisor);
}
int main() {
int errors = 0;
for (int b = 1; b < 100; ++b) {
for (int d = 1; d < 100; ++d) {
// b / d * d ... should == b
double res = bodge(b, d) * static_cast<double>(d);
// but it doesn't always
if (res != static_cast<double>(b))
++errors;
}
}
printf("errors: %d\n", errors);
}
ideone сообщает 599 экземпляров, где (b * d) / d != b, используя только 10 000 комбинаций из 1
решение, описанное в FAQ, по существу заключается в применении ограничения детализации-для тестирования if (a == b +/- epsilon)
.
альтернативный подход заключается в том, чтобы полностью избежать проблемы, используя фиксированный точность точки или с помощью желаемой детализации в качестве базового блока для хранения. Е. Г. если вы хотите раз сохраняется с точностью до наносекунды, используйте наносекунд единицы хранения.
C++11 вводит std:: ratio в качестве основы для преобразования фиксированной точки между различными единицами времени.
если вас интересует только +0.0
и -0.0
, вы можете использовать fpclassify
С <cmath>
. Например:
if( FP_ZERO == fpclassify(x) ) do_something;
как указал @Exceptyon, эта функция "относительно" значений, которые вы сравниваете. The Epsilon * abs(x)
мера будет масштабироваться на основе значения x, так что вы получите результат сравнения так же точно, как epsilon
, независимо от диапазона значений в x или y.
если вы сравниваете ноль(y
) к другому очень малые значения(x
), скажем 1е-8, abs(x-y) = 1e-8
все равно будет гораздо больше, чем epsilon *abs(x) = 1e-13
. Поэтому, если вы не имеете дело с очень небольшим числом, которое не может быть представленная в двойном типе, эта функция должна выполнять задание и будет соответствовать нулю только против +0
и -0
.
функция кажется совершенно допустимым для нулевого сравнения. Если вы планируете использовать его, я предлагаю вам использовать его везде, где есть поплавки, и не иметь особых случаев для таких вещей, как ноль, просто чтобы было единообразие в коде.
ps: Это аккуратная функция. Спасибо, что указал на него.
простое сравнение чисел FP имеет свою специфику, и его ключом является понимание формата FP (см. https://en.wikipedia.org/wiki/IEEE_floating_point)
когда числа FP вычисляются по-разному, один через sin(), другой, хотя exp (), строгое равенство не будет работать, даже если математически числа могут быть равными. Точно так же не будет работать равенство с константой. Фактически, во многих ситуациях номера FP не должны сравниваться с использованием строгое равенство (==)
в таких случаях следует использовать константу DBL_EPSIPON, которая имеет минимальное значение не менять представление 1.0 добавляется к числу более 1.0. Для чисел с плавающей запятой, которые больше 2.0 DBL_EPSIPON не существует вообще. Между тем, DBL_EPSILON имеет показатель -16, что означает, что все числа, скажем, с показателем -34, будут абсолютно равны по сравнению с DBL_EPSILON.
Также см. пример, почему 10.0 == 10.0000000000000001
сравнение чисел с плавающей запятой dwo зависит от природы этих чисел, мы должны вычислить DBL_EPSILON для них, что было бы значимым для сравнения. Просто мы должны умножить DBL_EPSILON на одно из этих чисел. Кто из них? Максимум конечно
bool close_enough(double a, double b){
if (fabs(a - b) <= DBL_EPSILON * std::fmax(fabs(a), fabs(b)))
{
return true;
}
return false;
}
все другие способы дадут вам ошибки с неравенством, которые могут быть очень трудно поймать
обратите внимание, что код:
std::abs((x - y)/x) <= epsilon
вы требуете, чтобы "относительная ошибка" на var была