在C++中使用地板函数的舍入误差

Rounding error using the floor function in C++

本文关键字:函数 舍入误差 C++      更新时间:2023-10-16

有人问我以下代码的输出是什么:

floor((0.7+0.6)*10);

它返回 12。

我知道浮点表示不允许以无限的精度表示所有数字,并且我应该预料到一些差异。

我的问题是:

  1. 我怎么知道这段代码返回的是 12,而不是 13?为什么(0.7+0.6(*10比13少一点,而不是一点?

  2. 我什么时候可以预期地板功能无法正常工作,何时可以确定正常工作?

注意:我不是在问浮动表示是什么样子的,或者为什么输出不完全是 13。我想知道如何推断 (0.7+0.6(*10 比 13 一点。

我怎么知道这段代码返回的是 12,而不是 13?为什么(0.7+0.6(*10比13少一点,而不是多一点?

假设您的编译平台严格使用 IEEE 754 标准格式和操作。然后,将所有涉及的常量转换为二进制,保留 53 个有效数字,并通过计算数学结果并在每一步四舍五入为 53 个有效二进制数字来应用 IEEE 754 中定义的基本运算。计算机不需要在任何阶段参与,但您可以通过使用 C99 的十六进制浮点格式进行输入和输出来使您的生活更轻松。

我什么时候可以预期地板功能无法正常工作,何时可以确定正常工作?

对于所有积极的论点,floor()都是准确的。它在您的示例中工作正常。让你感到惊讶的行为不是源于floor,也与floor无关。令人惊讶的行为始于这样一个事实,即 6/10 和 7/10 不能完全表示为二进制浮点值,并且继续说,由于这些值具有较长的扩展,因此浮点运算+*可以产生略微四舍五入的结果,而数学结果可以从它们实际应用的参数中获得。 floor()是代码中唯一不涉及近似的地方。

示例程序以查看正在发生的事情:

#include <stdio.h>
#include <math.h>
int main(void) {
  printf("%an%an%an%an%an",
         0.7,
         0.6,
         0.7 + 0.6,
         (0.7+0.6)*10,
         floor((0.7+0.6)*10));
}

结果:

0x1.66666666666666p-10x1.33333333333333p-10x1.4中交中建ccp+00x1.9fffff+30x1.8p+3

IEEE 754 双精度实际上是根据二进制定义的,但为了简洁起见,有效数以十六进制编写。p后的指数表示 2 的幂。例如,最后两个结果的形式都是 <大约介于 _x0031_=" 和=" _x0032_=">*23 之间的数字。

0x1.8p+3是 12。下一个整数 13 是 0x1.ap+3 ,但计算没有完全达到该值,因此floor()的行为是向下舍入到 12。

  1. 我怎么知道这段代码返回的是 12,而不是 13?

您应该知道它可以并且可能是 12 或 13。您可以通过在给定 CPU 上进行测试来验证。

通常,您无法知道值是多少,因为C++标准没有指定浮点数的表示形式。如果您知道给定架构(假设 IEEE 754(上的格式,那么您可以手动执行计算,但该结果仅适用于该特定表示形式。

为什么(0.7+0.6(*10比13少一点,而不是多一点?

这是一个实现细节,对程序员来说不是有用的知识。你需要知道的可能是其中之一。依靠它是一个或另一个的知识,会让你依赖于实现细节。

  1. 我什么时候可以预期地板功能无法正常工作,何时可以确定正常工作?

它始终正常工作,这是适应指定的工作方式。

现在,说到您期望看到的价值。如果您知道您的数字非常接近整数,但由于表示错误而可能有点偏离,则可以在地板之前添加0.5

double calculated_integer = (0.7+0.6)*10;
floor(calculated_integer + 0.5);

这样,您将始终获得预期值,除非错误超过 0.5 ,这将是一个相当大的错误。

如果您不知道结果应该是整数,那么您只需要接受这样一个事实,即floorceil运算会将计算的最大误差增加到1.0

有一些标准,如IEEE浮点标准,它试图使浮点计算至少具有一点预测性通过定义规则应如何实现加法和舍入等操作。要知道结果,您需要计算表达式根据标准规则。然后你可以肯定,它在实施该标准的每台机器上给出相同的结果。

我怎么知道这段代码返回的是 12,而不是 13?

由于这取决于所涉及的数字,因此请尝试

为什么(0.7+0.6(*10比13少一点,而不是多一点?

好吧,因为这是计算的结果。

我什么时候可以预期地板功能无法正常工作,何时可以确定正常工作?

可以肯定的是:仅在 2 的幂倍数上,您的浮点数以二进制表示

要真正消除所有的困惑:

如果不计算结果,你就无法知道结果;这取决于所涉及的机器/算法和数字。

非常简短的回答:你不能。这取决于平台和此平台上使用的浮点 iso。

一般来说,你不能。根本问题是,从文本表示到浮点值的转换通常没有尽可能准确地实现。这部分是动量,部分是因为获取最接近文本中表示的值的浮点值可能很昂贵,在某些情况下需要大整数计算。因此,转换通常与理想值相差几个ULP(即低端位(,这是您无法先预测的方式。因此,该代码将产生什么的问题是无法回答的。它应该产生什么的问题可能更容易处理,但它仍然是一种浪费时间的做法。