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()处理方式不同呢?我能做些什么来反对它?
通常的问题是:并非所有精确的十进制分数都能精确地用二进制表示。在你的例子中,它是
14.699999999999999289457264239899814128875732421875
号
乘以0.1:
0.1000000000000000055511151231257827021181583404541
号
您将到达:
146.999999999999971578290569595992565155029296875
号
我想你现在开始发现问题了,对吧?地板这个数字显然会给你146。
你能做些什么来对付它?如果需要精确的十进制结果,请使用表示小数部分的数字类型或Bignum库。
哦,还有旁注:不,同一个数字没有不同的
除了其他有关小数和分数的浮点表示不精确的答案外,当计算浮点数的有效位数的小数表示时,floor()也是错误的选择。您应该改为四舍五入-结果不应该存储在浮点数中,而是以其他方式存储。
问题是,您使用的数字都不能以二进制浮点格式准确表示。这就是二进制浮点的性质。要了解更多的细节,请阅读每个计算机科学家应该知道的关于浮点运算的知识。
最接近两个数字的单精度浮点数是:
- 0.1=+0.10000 00014 90116 11938 47656 25
- 14.7=+14.69999 98092 65136 71875
因此,首先对
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。它不是"相同的号码编码不同",而是不同的号码。