将浮点数与零进行比较

Mic*_*ann 52 c++ floating-point

C++ FAQ lite "[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
}
Run Code Online (Sandbox Code Playgroud)
  1. 这是否正确,这意味着唯一等于零的数字是+0-0
  2. 当测试零或者测试时,是否应该使用此功能|x| < epsilon

更新

正如Daniel Daranas所指出的那样,最好调用函数isNearlyEqual(我关心的是这种情况).

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

Ben*_*igt 42

你的观察是正确的.

如果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,但你的里程可能会有所不同".

  • +1这里确实没有"一刀切"的建议. (12认同)

Dan*_*nas 17

没有.

平等就是平等.

你编写的函数不会测试两个双精度的等价,正如它的名字所承诺的那样.它只会测试两个双打是否足够"接近".

如果你真的想测试两个双打的相等性,那就用这个:

inline bool isEqual(double x, double y)
{
   return x == y;
}
Run Code Online (Sandbox Code Playgroud)

编码标准通常建议不要将两个双精度数进行比较以确保相等.但那是一个不同的主题.如果你真的想要比较两个完全相等的双打,x == y那么你需要的是代码.

无论他们告诉你什么,10.00000000000000001都不等于10.0.

使用精确相等的一个示例是当double的特定值用作某个特殊状态的同义词时,例如"pending calulation"或"no data available".仅当挂起计算之后的实际数值仅是double的可能值的子集时,才可以执行此操作.最典型的特殊情况是该值为非负值,并使用-1.0作为"待定计算"或"无可用数据"的(精确)表示.你可以用常数表示:

const double NO_DATA = -1.0;

double myData = getSomeDataWhichIsAlwaysNonNegative(someParameters);

if (myData != NO_DATA)
{
    ...
}
Run Code Online (Sandbox Code Playgroud)

  • 你的`isEqual`函数有一天会让你大吃一惊:)当你意识到浮点数是多么精确的时候. (13认同)
  • @BЈовић不会.我不会感到惊讶.我很少使用它,但是当我这样做时,我的意思是检查一个双*是否等于*另一个.10.00000000000000001不等于10.0. (9认同)
  • 我想最终问题是人们使用浮动精度时他们真正想要的东西是固定的.Daniel的论点(10.00000000000000001不是10.0)是公平的,只是人们在比较"5.0 + 5.0"和"15.0 - 5.0"时并不认为它是一个因素. (4认同)
  • @ user3698909这与我的论点并不矛盾.我的论点是,相等的值恰好是**,**等于**值.如果您想知道两个浮点值是否相等,请测试它们是否相等.如果你对此不感兴趣,那就不要测试它们是否相等 - 测试它们是否足够接近. (4认同)
  • Daniel的论点的问题是,一旦我将它存储为浮点数,10.00000000000000001也不是10.00000000000000001.浮点数不准确,它们是近似值. (3认同)
  • @Yuri 不,不会。 (3认同)
  • 这取决于什么是错误。无论如何,浮动寄存器可以是80位,而浮动值只能是32位。比较浮点数有时会非常棘手。 (2认同)
  • @DanielDaranas您应该熟悉https://en.wikipedia.org/wiki/IEEE_floating_point比较浮点数小于DBL_EPSILON的数字是没有意义的,因为它们的DBL_EPSILON是0.0,并且不存在。例如,1.0 == 1 + 1e-16,因为1e-16是无法以FP逐位表示的差异 (2认同)
  • @YuriS.Cherkasov 平等就是平等。double 类型的两个表达式要么相等,要么不相等。一旦两个值都是 double 类型,检查它们是否相等是有意义的,如果您有兴趣知道的话。 (2认同)
  • @YuriS.Cherkasov 你描述的情况没有任何我不明白的地方,或者与我的回答相矛盾。 (2认同)
  • 这个答案包含错误信息:“10.00000000000000001 不等于 10.0,无论他们告诉你什么”。您在发布代码之前尝试过代码吗?https://onlinegdb.com/SJ4SMWv-L (2认同)
  • @barsan-md 我的观点是平等意味着平等,而不是“非常接近”。对于双打来说,我的例子是错误的。我编辑了答案以使用 10.000000000000001。 (2认同)
  • @Herrgott 我的期望并不重要。表达式“10.2f - 0.1f”和“10.0f + 0.1f”都是 float 类型的有效表达式。如果在评估它们之后,它们产生不同的浮点值,则“(10.2f - 0.1f) == (10.0f + 0.1f)”为 false。这正是“==”的含义:如果两边的表达式相等则为 true,如果不相等则为 false。 (2认同)

Dan*_*ügt 6

您可以使用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;
}
Run Code Online (Sandbox Code Playgroud)


kfs*_*one 5

2 + 2 = 5(*)

对于某些浮点精度值 2

当我们将“浮点”视为提高精度的一种方法时,这个问题经常出现。然后我们就遇到了“浮动”部分,这意味着无法保证可以表示哪些数字。

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

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

如果执行“0.0003 - 0.0002”会发生什么?我们期望 0.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);
}
Run Code Online (Sandbox Code Playgroud)

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

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

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

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


DaB*_*ler 5

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

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