当我使用带整数的C ++数学函数时,我是否应该担心精度?

时间:2015-02-28 08:53:30

标签: c++ algorithm math floating-point floating-accuracy

例如,由于浮点数的精度,下面的代码会产生不良结果。

double a = 1 / 3.0;
int b = a * 3;      // b will be 0 here

我想知道如果我使用数学函数是否会出现类似的问题。例如

int a = sqrt(4);       // Do I have guarantee that I will always get 2 here?
int b = log2(8);       // Do I have guarantee that I will always get 3 here?

如果没有,如何解决这个问题?

编辑:

实际上,当我为算法任务编程时,我遇到了这个问题。在那里我想得到

  

最大整数,幂为2且小于或等于整数 N

所以圆函数无法解决我的问题。我知道我可以通过循环来解决这个问题,但它看起来不是很优雅。

我想知道是否

int a = pow(2, static_cast<int>(log2(N)));

始终可以给出正确的结果。例如,如果N == 8,log2(N)是否有可能给我2.9999999999999,最终结果变为4而不是8?

2 个答案:

答案 0 :(得分:8)

不准确的操作数与不准确的结果

  

我想知道如果我使用数学函数是否会出现类似的问题。

实际上,基本操作(包括log2(8))不存在可能阻止*为3的问题。但它存在log2函数。

你混淆了两个不同的问题:

double a = 1 / 3.0;
int b = a * 3;      // b will be 0 here

在上面的示例中,a不完全是1/3,因此a*3可能不会生成1.0。该产品可能碰到了1.0,它只是没有。但是,如果a不知何故恰好是1/3,则a乘以3的乘积恰好是1.0,因为这是IEEE 754浮点数的工作原理< / strong>:基本操作的结果是对相同操作数上相同操作的数学结果的最接近的可表示值。如果确切的结果可以表示为浮点数,那么表示就是你得到的。

sqrt和log2的准确度

sqrt是“基本操作”的一部分,因此sqrt(4)保证始终(在IEEE 754系统中始终没有例外)为2.0 }。

log2不是基本操作的一部分。 IEEE 754标准不保证实现此功能的结果最接近数学结果。它可以是更远的另一个可表示的数字。因此,如果没有关于您使用的log2函数的更多假设,则无法确定log2(8.0)可能是什么。

但是,大多数基本函数(例如log2)的合理质量的实现保证了实现的结果在数学结果的1 ULP之内。当数学结果不可表示时,这意味着上面的可表示值或下面的值(但不一定是两者中最接近的一个)。当数学结果完全可以表示时(例如3.0),那么这个表示仍然是唯一保证返回的表示。

关于log2(8),答案是“如果你有log2的合理质量实现,你可以期望结果为3.0”。

不幸的是,并非每个基本功能的每个实现都是高质量的实现。请参阅此blog post,这是由广泛使用的pow实施在计算pow(10.0, 2.0)时误差超过1 ULP而导致的,因此返回99.0而不是100.0

舍入到最接近的整数

接下来,在每种情况下,您将浮点分配给具有隐式转换的int。此转换在C ++标准中定义为截断浮点值(即向零舍入)。如果希望浮点计算的结果为整数,则可以在分配浮点值之前将浮点值舍入为最近的整数。在错误未累积到大于1/2的值的所有情况下,它将有助于获得所需的答案:

int b = std::nearbyint(log2(8.0));

最后简单回答标题:是的,在使用浮点函数产生积分最终结果时,应该担心准确性。即使有基本操作的保证,这些功能也不会出现。

答案 1 :(得分:0)

不幸的是,在C ++中从浮点数到整数的默认转换真的很疯狂,因为它通过删除小数部分来工作。

这有两个原因:

  1. 浮点数确实非常接近正整数,但低于它将转换为前一个整数(例如3-1×10 -10 = 2.9999999999将被转换到2)

  2. 一个浮点数真的非常接近负整数,但高于它将转换为下一个整数(例如-3 + 1×10 -10 = -2.9999999999将转换为-2)

  3. (1)和(2)的组合也意味着使用int(x + 0.5)将无法合理地工作,因为它将向上舍入负数。

    有一个合理的round函数,但不幸的是返回另一个浮点数,因此你需要写int(round(x))

    使用C99或C ++ 11时,您可以使用lround(x)

    请注意,在浮点中可以正确表示的唯一数字是分母为2的整数幂的商。

    例如,1/65536 = 0.0000152587890625可以正确表示,但即使只是0.1也无法正确表示,因此任何涉及该数量的计算都将被近似。

    当然,当使用0.1近似值时,可以取消偶尔保留正确的结果,但即使只增加10次0.1,在使用IEEE754双精度浮点数进行计算时也不会给出1.0。

    更糟糕的是,编译器可以使用更高精度来获得中间结果。这意味着如果编译器决定使用更高的精度并在最后舍入到最接近的double,则在转换为整数时,添加10次0.1 可能返回1。

    这更“糟糕”,因为尽管精度更高,但结果是编译器和编译器选项依赖,使得计算的推理更加困难,并且使得精确结果在不同系统之间不可移植(即使它们使用相同的精度和格式)

    大多数编译器都有特殊选项来避免这个特定问题。