将浮点数与零进行比较

Comparing floating point number to zero

本文关键字:比较 浮点数      更新时间:2023-10-16

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
}
  1. 是否正确,这意味着唯一等于零的数字是+0-0
  2. 在测试零或更确切地说是像|x| < epsilon这样的测试时是否也应该使用此函数?

更新

正如Daniel Daranas所指出的,这个函数可能应该更好地称为isNearlyEqual(这是我关心的情况)。

有人指出"比较浮点数",我想更突出地分享。

你的观察是正确的。

如果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.

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

如果你只对+0.0-0.0感兴趣,你可以使用<cmath>中的fpclassify。例如:

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

No.

平等

就是平等。

您编写的函数不会像其名称所承诺的那样测试两个双精度的相等性。它只会测试两个双打是否"足够接近"。

如果你真的想测试两个双精度的相等性,请使用这个:

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

编码标准通常建议不要比较两个双精度值以获得完全相等。但这是一个不同的主题。如果您确实想比较两个双精度值以获得完全相等,x == y就是您想要的代码。

10.0000000000000001

不等于 10.0,无论他们告诉你什么。

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

const double NO_DATA = -1.0;
double myData = getSomeDataWhichIsAlwaysNonNegative(someParameters);
if (myData != NO_DATA)
{
    ...
}

您可以将std::nextafter与值epsilon的固定factor一起使用,如下所示:

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.003",但它可能正在存储"0.0033333333334"。

如果执行"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: %dn", errors);
}

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

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

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

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

正如@Exceptyon指出的那样,这个函数与你正在比较的值是"相对的"。Epsilon * abs(x)度量值将根据 x 的值进行缩放,因此无论 x 或 y 中的值范围如何,您都将获得与 epsilon 一样准确的比较结果。

如果你将零(y)与另一个非常小的值(x)进行比较,比如1e-8,abs(x-y) = 1e-8仍然会比epsilon *abs(x) = 1e-13大得多。因此,除非您正在处理无法以双精度类型表示的极小数字,否则此函数应该可以完成这项工作,并且仅将零与+0-0匹配。

该函数似乎对零比较完全有效。如果你打算使用它,我建议你在涉及浮点数的任何地方使用它,而不是像零这样的事情有特殊情况,只是为了让代码统一

附言:这是一个简洁的功能。感谢您指出它。

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.00000000000000001

比较

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

所有其他方法都会给你带来不平等的错误,这可能很难抓住

考虑这个例子:

bool isEqual = (23.42f == 23.42);

什么是isEqual?10个人中有9个人会说"当然true",10个人中有9个人是错的:https://rextester.com/RVL15906

这是因为浮点数不是精确的数字表示形式。

作为二进制数,它们甚至不能精确地表示所有可以精确表示为十进制数的数字。 例如,虽然0.1可以精确地表示为十进制数(它正好是1的第十部分),但它不能使用浮点表示,因为它0.00011001100110011...周期性地表示为二进制。 0.1用于浮点,1/3用于十进制(0.33333...为十进制)

结果是像0.3 + 0.6这样的计算可以产生0.89999999999999991,这并不0.9,尽管它接近于此。因此测试0.1 + 0.2 - 0.3 == 0.0可能会失败,因为计算结果可能不会0,尽管它将非常接近0

==是一个精确的测试,对不精确的数字执行精确的测试通常不是很有意义。由于许多浮点计算都包含舍入误差,因此您通常希望比较也允许小误差,这就是您发布的测试代码的全部内容。它不是测试"A 等于 B",而是测试"A 非常接近 B",因为非常接近通常是浮点计算中可以期望的最佳结果。

请注意,该代码是:

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

您要求 VaR 上的"相对误差"为 <= epsilon,而不是绝对差异为