2

我开发了一个 c++ 应用程序(Windows 7、64 位、VS 2008),其中使用了以下公式(所有变量都是 double 类型):

mValue = floor(mValue/mStepping)*mStepping;

这个想法是将数字缩短到给定的小数位数。我认为有更好的方法可以做到这一点,但这不是这里的问题(但如果你有更好的选择,请提出来!)。

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

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

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

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

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

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

4

4 回答 4

6

常见问题:并非所有精确的十进制小数都可以用二进制精确表示。在你的情况下,它0.1也是14.7。通过除以 0.1,你实际上并没有达到 147,而是低于它的一个微不足道的数量:

14.699999999999999289457264239899814128875732421875

乘以 0.1:

0.10000000000000000055511151231257827021181583404541

你到达:

146.999999999999971578290569595992565155029296875

我想你现在开始看到问题了,对吧?铺设这个数字显然会给你 146。

你能做些什么来对抗它?如果您想要精确的十进制结果,请使用表示小数的数字类型或 bignum 库。

哦,附带说明:不,相同的数字没有不同double表示。只是您对数字是什么以及它的行为方式的解释与浮点数学不同。

于 2013-05-08T12:52:15.577 回答
3

我知道相同的数字可以以不同的方式编码为双精度数。

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

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

if (14.7/0.1 == 147.0)
{
    cout << "We are equal!";      
}
else
{
    cout << "We are different!";
}

您会看到 14.7/0.1 不等于 147.0。这不是“相同的数字编码不同”,而是不同的数字。

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

于 2013-05-08T12:52:19.700 回答
3

问题是您使用的数字都不能以二进制浮点格式精确表示。这就是二进制浮点的本质。有关更多详细信息,请阅读每位计算机科学家应了解的浮点运算知识

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

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

因此,14.7/0.1首先评估,并且因为这些值不能完全表示,所以恰好评估为小于 的值147。所以当你floor它时,你把它变成 146。因此你观察到的结果。

我知道相同的数字可以以不同的方式编码为双精度数。

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

因此,要准确计算总和,您需要使用十进制算术。这不是 C++ 内置的东西,您需要为此使用第三方库。

于 2013-05-08T12:52:40.220 回答
3

除了关于十进制和小数的浮点表示的不精确性的其他答案之外,在计算浮点数的多个有效数字的十进制表示时, floor() 也是错误的选择。您应该四舍五入- 结果不应该存储在浮点数中,而是以其他方式存储。

于 2013-05-08T12:58:29.850 回答