C++ 将浮点数与零进行比较

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/19837576/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-27 23:11:21  来源:igfitidea点击:

Comparing floating point number to zero

c++floating-point

提问by Micha Wiedenmann

The C++ FAQ lite "[29.17] Why doesn't my floating-point comparison work?"recommends this equality test:

C++ FAQ 精简版“[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
}
  1. Is it correct, that this implies that the only numbers which are equal to zero are +0and -0?
  2. Should one use this function also when testing for zero or rather a test like |x| < epsilon?
  1. 这是否正确,这意味着唯一等于零的数字是+0-0
  2. 在测试零或类似的测试时,是否也应该使用此功能|x| < epsilon

Update

更新

As pointed out by Daniel Daranas the function should probably better be called isNearlyEqual(which is the case I care about).

正如丹尼尔·达拉纳斯 (Daniel Daranas) 指出的那样,该函数可能最好被调用isNearlyEqual(这是我关心的情况)。

Someone pointed out this link, which I want to share more prominently.

有人指出了这个链接,我想更突出地分享它。

采纳答案by Ben Voigt

You are correct with your observation.

你的观察是正确的。

If x == 0.0, then abs(x) * epsilonis zero and you're testing whether abs(y) <= 0.0.

如果x == 0.0,则abs(x) * epsilon为零,并且您正在测试是否abs(y) <= 0.0

If y == 0.0then you're testing abs(x) <= abs(x) * epsilonwhich means either epsilon >= 1(it isn't) or x == 0.0.

如果y == 0.0那么你正在测试abs(x) <= abs(x) * epsilon这意味着epsilon >= 1(它不是)或x == 0.0.

So either is_equal(val, 0.0)or is_equal(0.0, val)would be pointless, and you could just say val == 0.0. If you want to only accept exactly+0.0and -0.0.

因此,无论是is_equal(val, 0.0)is_equal(0.0, val)将是毫无意义的,你可以只说val == 0.0。如果您只想接受完全+0.0-0.0.

The FAQ's recommendation in this case is of limited utility. There is no "one size fits all" floating-point comparison.You have to think about the semantics of your variables, the acceptable range of values, and the magnitude of error introduced by your computations. Even the FAQ mentions a caveat, saying this function is not usually a problem "when the magnitudes of x and y are significantly larger than epsilon, but your mileage may vary".

在这种情况下,FAQ 的建议作用有限。 没有“一刀切”的浮点比较。您必须考虑变量的语义、可接受的值范围以及计算引入的误差大小。甚至 FAQ 也提到了一个警告,说这个函数通常不是问题,“当 x 和 y 的大小明显大于 epsilon 时,但你的里程可能会有所不同”。

回答by Daniel Daranas

No.

不。

Equality is equality.

平等就是平等。

The function you wrote will not test two doubles for equality, as its name promises. It will only test if two doubles are "close enough" to each other.

正如其名称所承诺的那样,您编写的函数不会测试两个双精度数是否相等。它只会测试两个双打是否彼此“足够接近”。

If you reallywant to test two doubles for equality, use this one:

如果你真的想测试两个双打是否相等,请使用这个:

inline bool isEqual(double x, double y)
{
   return x == y;
}

Coding standards usually recommend against comparing two doubles for exact equality. But that is a different subject. If you actuallywant to compare two doubles for exact equality, x == yis the code you want.

编码标准通常建议不要比较两个双精度值以确保完全相等。但这是一个不同的主题。如果你真的想比较两个双精度值是否完全相等,x == y你想要的代码是什么。

10.000000000000001 is not equal to 10.0, no matter what they tell you.

10.000000000000001 不等于 10.0,不管他们告诉你什么。

An exampleof using exact equality is when a particular value of a double is used as a synonym of some special state, such as "pending calulation" or "no data available". This is possible only if the actual numeric values after that pending calculation are only a subset of the possible values of a double. The most typical particular case is when that value is nonnegative, and you use -1.0 as an (exact) representation of a "pending calculation" or "no data available". You could represent that with a constant:

使用精确相等的一个例子是当 double 的特定值用作某些特殊状态的同义词时,例如“等待计算”或“无可用数据”。仅当挂起计算之后的实际数值只是双精度值的可能值的子集时,这才是可能的。最典型的特殊情况是该值是非负的,并且您使用 -1.0 作为“未决计算”或“无可用数据”的(精确)表示。你可以用一个常量来表示:

const double NO_DATA = -1.0;

double myData = getSomeDataWhichIsAlwaysNonNegative(someParameters);

if (myData != NO_DATA)
{
    ...
}

回答by DaBler

If you are only interested in +0.0and -0.0, you can use fpclassifyfrom <cmath>. For instance:

如果您只对+0.0and感兴趣-0.0,则可以使用fpclassifyfrom <cmath>。例如:

if( FP_ZERO == fpclassify(x) ) do_something;

if( FP_ZERO == fpclassify(x) ) do_something;

回答by Daniel Laügt

You can use std::nextafterwith a fixed factorof the epsilonof a value like the following:

您可以使用std::nextafter固定factorepsilon值,如下所示:

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;
}

回答by kfsone

2 + 2 = 5(*)

2 + 2 = 5(*)

(for some floating-precision values of 2)

对于某些 2 的浮点精度值

This problem frequently arises when we think of"floating point" as a way to increase precision. Then we run afoul of the "floating" part, which means there is no guarantee of which numberscan be represented.

当我们将“浮点”作为提高精度的一种方式时,经常会出现这个问题。然后我们与“浮动”部分发生冲突,这意味着无法保证可以表示哪些数字

So while we might easily be able to represent "1.0, -1.0, 0.1, -0.1" as we get to larger numbers we start to see approximations - or we should, except we often hide them by truncating the numbers for display.

因此,虽然我们可以很容易地表示“1.0, -1.0, 0.1, -0.1”,但随着我们获得更大的数字,我们开始看到近似值 - 或者我们应该看到,除非我们经常通过截断数字来隐藏它们以进行显示。

As a result, we might think the computer is storing "0.003" but it may instead be storing "0.0033333333334".

因此,我们可能认为计算机正在存储“0.003”,但实际上它可能存储“0.0033333333334”。

What happens if you perform "0.0003 - 0.0002"? We expect .0001, but the actual values being stored might be more like "0.00033" - "0.00029" which yields "0.000004", or the closest representable value, which might be 0, or it might be "0.000006".

如果执行“0.0003 - 0.0002”会发生什么?我们期望 .0001,但实际存储的值可能更像是 "0.00033" - "0.00029",产生 "0.000004",或者最接近的可表示值,可能是 0,也可能是 "0.000006"。

With current floating point math operations, it is not guaranteed that (a / b) * b == a.

对于当前的浮点数学运算,不能保证 (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 reports 599 instances where (b * d) / d != b using just the 10,000 combinations of 1 <= b <= 100 and 1 <= d <= 100 .

ideone 报告了 599 个实例,其中 (b * d) / d != b 仅使用 1 <= b <= 100 和 1 <= d <= 100 的 10,000 个组合。

The solution described in the FAQ is essentially to apply a granularity constraint - to test if (a == b +/- epsilon).

FAQ 中描述的解决方案本质上是应用粒度约束来测试if (a == b +/- epsilon)

An alternative approach is to avoid the problem entirely by using fixed point precision or by using your desired granularity as the base unit for your storage. E.g. if you want times stored with nanosecond precision, use nanoseconds as your unit of storage.

另一种方法是通过使用定点精度或使用所需的粒度作为存储的基本单位来完全避免该问题。例如,如果您希望以纳秒精度存储时间,请使用纳秒作为您的存储单位。

C++11 introduced std::ratioas the basis for fixed-point conversions between different time units.

C++11 引入了std::ratio作为不同时间单位之间定点转换的基础。

回答by kfsone

Simple comparison of FP numbers has it's own specific and it's key is the understanding of FP format (see https://en.wikipedia.org/wiki/IEEE_floating_point)

FP 数字的简单比较有其特定性,关键是对 FP 格式的理解(参见https://en.wikipedia.org/wiki/IEEE_floating_point

When FP numbers calculated in a different ways, one through sin(), other though exp(), strict equality won't be working, even though mathematically numbers could be equal. The same way won't be working equality with the constant. Actually, in many situations FP numbers must not be compared using strict equality (==)

当 FP 数以不同的方式计算时,一个通过 sin(),另一个通过 exp(),严格相等将不起作用,即使数学上的数字可能相等。同样的方式不会与常量相等。实际上,在许多情况下,不能使用严格相等 (==) 来比较 FP 数

In such cases should be used DBL_EPSIPON constant, which is minimal value do not changerepresentation of 1.0 being added to the number more than 1.0. For floating point numbers that more than 2.0 DBL_EPSIPON does not exists at all. Meanwhile, DBL_EPSILON has exponent -16, which means that all numbers, let's say, with exponent -34, would be absolutely equal in compare to DBL_EPSILON.

在这种情况下应该使用 DBL_EPSPON 常量,它是最小值不要改变表示 1.0 被添加到超过 1.0 的数字。对于超过 2.0 DBL_EPSPON 根本不存在的浮点数。同时,DBL_EPSILON 的指数为 -16,这意味着所有数字(假设指数为 -34)与 DBL_EPSILON 相比绝对相等。

Also, see example, why 10.0 == 10.0000000000000001

另外,请参见示例,为什么 10.0 == 10.0000000000000001

Comparing dwo floating point numbers depend on these number nature, we should calculate DBL_EPSILON for them that would be meaningful for the comparison. Simply, we should multiply DBL_EPSILON to one of these numbers. Which of them? Maximum of course

比较两个浮点数取决于这些数字的性质,我们应该为它们计算 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;
}

All other ways would give you bugs with inequality which could be very hard to catch

所有其他方式都会给你带来不平等的错误,这可能很难捕捉

回答by Arun R

Like @Exceptyon pointed out, this function is 'relative' to the values you're comparing. The Epsilon * abs(x)measure will scale based on the value of x, so that you'll get a comparison result as accurately as epsilon, irrespective of the range of values in x or y.

就像@Exceptyon 指出的那样,此函数与您要比较的值“相关”。该Epsilon * abs(x)度量将根据 x 的值进行缩放,因此epsilon无论 x 或 y 中的值范围如何,您都将获得与 一样准确的比较结果。

If you're comparing zero(y) to another really small value(x), say 1e-8, abs(x-y) = 1e-8will still be much larger than epsilon *abs(x) = 1e-13. So unless you're dealing with extremely small number that can't be represented in a double type, this function should do the job and will match zero only against +0and -0.

如果您将 zero( y) 与另一个非常小的 value( x) 进行比较,例如 1e-8,abs(x-y) = 1e-8仍然会比epsilon *abs(x) = 1e-13. 因此,除非您正在处理无法用 double 类型表示的极小数字,否则此函数应该可以完成这项工作,并且只会将零与+0和匹配-0

The function seems perfectly valid for zero comparison. If you're planning to use it, I suggest you use it everywhere there're floats involved, and not have special cases for things like zero, just so that there's uniformity in the code.

该函数对于零比较似乎完全有效。如果您打算使用它,我建议您在涉及浮点数的任何地方使用它,并且对于零之类的东西没有特殊情况,只是为了代码的一致性。

ps: This is a neat function. Thanks for pointing to it.

ps:这是一个整洁的功能。感谢您指出它。

回答by Exceptyon

notice, that code is:

请注意,该代码是:

std::abs((x - y)/x) <= epsilon

you are requiring that the "relative error" on the var is <= epsilon, not that the absolute difference is

您要求 var 上的“相对误差”是 <= epsilon,而不是绝对差异是