关于c ++:floor()的奇怪行为

Strange behavior of floor()

本问题已经有最佳答案,请猛点这里访问。

我开发了一个C++应用程序(Windows 7, 64位,VS 2008),其中使用下面的公式(所有VAR都是双类型):

1
mValue = floor(mValue/mStepping)*mStepping;

其思想是将一个数字缩短到给定的小数位数。我认为有更好的方法可以做到这一点,但这并不是问题所在(但如果你有更好的选择,请继续!).

mValue来自用户输入,所以在大多数情况下,小数位数已经可以了。但在某些情况下,输出与输入不同。

例如,mstepping的值为0.1(应四舍五入到小数点后一位)。现在,如果mValue的值为14.6,则一切正常。如果mValue为14.7,则结果为14.6。

那么,为什么地板(14.7/0.1)*0.1=14.6?

我测试了其他值,其中20%的值相差0.1。我进一步挖掘发现,14.7/0.1的二进制编码与147.0不同:

1
2
14.7/0.1 = ff ff ff ff ff 5f 62 40
147.0    = 00 00 00 00 00 60 62 40

我知道同一个数字可以以不同的方式编码为双精度数。但是为什么floor()处理方式不同呢?我能做些什么来反对它?


通常的问题是:并非所有精确的十进制分数都能精确地用二进制表示。在你的例子中,它是0.114.7。除以0.1,你实际上不会达到147,而是比它低一点点:

14.699999999999999289457264239899814128875732421875

乘以0.1:

0.1000000000000000055511151231257827021181583404541

您将到达:

146.999999999999971578290569595992565155029296875

我想你现在开始发现问题了,对吧?地板这个数字显然会给你146。

你能做些什么来对付它?如果需要精确的十进制结果,请使用表示小数部分的数字类型或Bignum库。

哦,还有旁注:不,同一个数字没有不同的double表示。只是你对数字的理解和它的行为方式不同于浮点数学。


除了其他有关小数和分数的浮点表示不精确的答案外,当计算浮点数的有效位数的小数表示时,floor()也是错误的选择。您应该改为四舍五入-结果不应该存储在浮点数中,而是以其他方式存储。


问题是,您使用的数字都不能以二进制浮点格式准确表示。这就是二进制浮点的性质。要了解更多的细节,请阅读每个计算机科学家应该知道的关于浮点运算的知识。

最接近两个数字的单精度浮点数是:

  • 0.1=+0.10000 00014 90116 11938 47656 25
  • 14.7=+14.69999 98092 65136 71875

因此,首先对14.7/0.1进行评估,由于这些值不完全可表示,所以恰好对小于147的值进行评估。所以当你把它变成146。这就是你观察到的结果。

I understand that the same number can be encoded differently as a double.

一般来说,情况并非如此。你的问题是14.7和0.1都不能精确表示。这个问题与表示的唯一性无关。

所以,为了精确地执行和运算,需要使用十进制算术。这不是内置在C++中的东西,你需要为此使用第三方库。


I understand that the same number can be encoded differently as a
double.

你不明白的是14.7/0.1和147.0不是同一个数字。

例如,编写以下测试代码:

1
2
3
4
5
6
7
8
if (14.7/0.1 == 147.0)
{
    cout <<"We are equal!";      
}
else
{
    cout <<"We are different!";
}

您将看到14.7/0.1不等于147.0。它不是"相同的号码编码不同",而是不同的号码。

floor与你的惊喜没有任何关系。当使用不同的输入调用时,floor只返回(可能)不同的值。