简体   繁体   English

将浮点数与零进行比较

[英]Comparing floating point number to zero

The C++ FAQ lite "[29.17] Why doesn't my floating-point comparison work?" C++ FAQ 精简版“[29.17] 为什么我的浮点比较不起作用?” recommends this equality test:推荐这个平等测试:

#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 +0 and -0 ?这是否正确,这意味着唯一等于零的数字是+0-0
  2. Should one use this function also when testing for zero or rather a test like |x| < epsilon在测试零或类似|x| < epsilon类的测试时,是否也应该使用此功能? |x| < epsilon ? |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 "Comparing Floating Point Numbers" , which I want to share more prominently.有人指出“比较浮点数” ,我想更突出地分享。

You are correct with your observation.你的观察是正确的。

If x == 0.0 , then abs(x) * epsilon is zero and you're testing whether abs(y) <= 0.0 .如果x == 0.0 ,则abs(x) * epsilon为零,并且您正在测试abs(y) <= 0.0

If y == 0.0 then you're testing abs(x) <= abs(x) * epsilon which 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 .所以is_equal(val, 0.0)is_equal(0.0, val)都是没有意义的,你可以说val == 0.0 If you want to only accept exactly +0.0 and -0.0 .如果您只想完全接受+0.0-0.0

The FAQ's recommendation in this case is of limited utility.在这种情况下,FAQ 的建议作用有限。 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 也提到了一个警告,说这个函数通常不是问题,“当 x 和 y 的大小明显大于 epsilon 时,但你的里程可能会有所不同”。

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 really want 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 actually want to compare two doubles for exact equality, x == y is 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 example of 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".使用精确相等的一个例子是当 double 的特定值用作某些特殊状态的同义词时,例如“等待计算”或“无可用数据”。 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".最典型的特殊情况是该值是非负的,并且您使用 -1.0 作为“未决计算”或“无可用数据”的(精确)表示。 You could represent that with a constant:你可以用一个常量来表示:

const double NO_DATA = -1.0;

double myData = getSomeDataWhichIsAlwaysNonNegative(someParameters);

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

If you are only interested in +0.0 and -0.0 , you can use fpclassify from <cmath> .如果您只对+0.0-0.0感兴趣,则可以使用<cmath> fpclassify For instance:例如:

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

You can use std::nextafter with a fixed factor of the epsilon of 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;
}

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 numbers can 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"?如果执行“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".我们期望 .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) .常见问题解答中描述的解决方案本质上是应用粒度约束 - 测试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.另一种方法是通过使用定点精度或使用所需的粒度作为存储的基本单位来完全避免该问题。 Eg if you want times stored with nanosecond precision, use nanoseconds as your unit of storage.例如,如果您希望以纳秒精度存储时间,请使用纳秒作为您的存储单位。

C++11 introduced std::ratio as the basis for fixed-point conversions between different time units. C++11 引入了std::ratio作为不同时间单位之间定点转换的基础。

Like @Exceptyon pointed out, this function is 'relative' to the values you're comparing.就像@Exceptyon 指出的那样,此函数与您要比较的值“相关”。 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. Epsilon * abs(x)度量将根据 x 的值进行缩放,因此无论 x 或 y 中的值范围如何,您都将获得与epsilon一样准确的比较结果。

If you're comparing zero( y ) to another really small value( x ), say 1e-8, abs(xy) = 1e-8 will still be much larger than epsilon *abs(x) = 1e-13 .如果您将零 ( y ) 与另一个非常小的值 ( x ) 进行比较,例如 1e-8, abs(xy) = 1e-8仍将远大于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 +0 and -0 .因此,除非您正在处理无法用 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. ps:这是一个整洁的功能。 Thanks for pointing to it.感谢您指出它。

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.当 FP 数以不同的方式计算时,一个通过 sin(),另一个通过 exp(),严格相等将不起作用,即使数学上的数字可能相等。 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 数

In such cases should be used DBL_EPSIPON constant, which is minimal value do not change representation of 1.0 being added to the number more than 1.0.在这种情况下应该使用 DBL_EPSPON 常量,它是最小值不要改变表示 1.0 被添加到超过 1.0 的数字。 For floating point numbers that more than 2.0 DBL_EPSIPON does not exists at all.对于超过 2.0 DBL_EPSPON 根本不存在的浮点数。 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_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.比较两个浮点数取决于这些数字的性质,我们应该为它们计算 DBL_EPSILON 对比较有意义。 Simply, we should multiply DBL_EPSILON to one of these numbers.简单地说,我们应该将 DBL_EPSILON 乘以这些数字之一。 Which of them?他们中的哪一个? Maximum of course当然最大

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所有其他方式都会给你带来不平等的错误,这可能很难捕捉

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,而不是绝对差异是

Consider this example:考虑这个例子:

bool isEqual = (23.42f == 23.42);

What is isEqual ?什么是isEqual 9 out of 10 people will say " It's true , of course " and 9 out of 10 people are wrong: https://rextester.com/RVL15906 10 人中有 9 人会说“这是true ,当然”,10 人中有 9 人是错的: https : //rextester.com/RVL15906

That's because floating point numbers are no exact numeric representations.那是因为浮点数不是精确的数字表示。

Being binary numbers, they cannot even exactly represent all numbers that can be exact represented as decimal numbers.作为二进制数,它们甚至不能精确地表示所有可以精确表示为十进制数的数字。 Eg while 0.1 can be exactly represented as a decimal number (it is exactly the tenth part of 1 ), it cannot be represented using floating point because it is 0.00011001100110011... periodic as binary.例如,虽然0.1可以精确地表示为十进制数(它恰好是1的十分之一),但不能使用浮点数表示,因为它是0.00011001100110011...作为二进制周期。 0.1 is for floating point what 1/3 is for decimal (which is 0.33333... as decimal) 0.1是浮点数, 1/3是十进制数(即0.33333...作为十进制数)

The consequence is that calculations like 0.3 + 0.6 can result in 0.89999999999999991 , which is not 0.9 , albeit it's close to that.结果是像0.3 + 0.6这样的计算可能会导致0.89999999999999991 ,这不是0.9 ,尽管它接近于此。 And thus the test 0.1 + 0.2 - 0.3 == 0.0 might fail as the result of the calculation may not be 0 , albeit it will be very close to 0 .因此测试0.1 + 0.2 - 0.3 == 0.0可能会失败,因为计算结果可能不是0 ,尽管它会非常接近0

== is an exact test and performing an exact test on inexact numbers is usually not very meaningful. ==是一个精确的测试,对不精确的数字进行精确的测试通常意义不大。 As many floating point calculations include rounding errors, you usually want your comparisons to also allow small errors and this is what the test code you posted is all about.由于许多浮点计算包括舍入误差,您通常希望比较也允许小错误,这就是您发布的测试代码的全部内容。 Instead of testing " Is A equal to B " it tests " Is A very close to B " as very close is quite often the best result you can expect from floating point calculations.它不是测试“ A 是否等于 B ”,而是测试“ A 是否非常接近 B ”,因为非常接近通常是您可以从浮点计算中获得的最佳结果。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM