楼层的奇怪行为()

时间:2013-05-08 12:45:57

标签: c++ math double

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

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不同的二进制编码:

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

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

4 个答案:

答案 0 :(得分:6)

通常的问题:并非所有精确的小数部分都可以用二进制表示。在您的情况下,它也是0.114.7。除以0.1,你实际上没有达到147,而是低于它的数量:

  

14.699999999999999289457264239899814128875732421875

乘以0.1:

  

0.1000000000000000055511151231257827021181583404541

你到达:

  

146.999999999999971578290569595992565155029296875

我认为你现在开始看到这个问题吧?这个数字的地板显然会给你146。

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

哦,还有一个附注:不,的相同数字有不同的double表示。只是你解释数字是什么以及它的行为方式与浮点数学不同。

答案 1 :(得分:3)

  

据我所知,相同的数字可以编码为a   双

你不明白的是,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只返回一个(可能)不同的值。

答案 2 :(得分:3)

问题是您使用的数字都不能完全以二进制浮点格式表示。这是二进制浮点的本质。有关详细信息,请阅读What Every Computer Scientist Should Know About Floating-Point Arithmetic

您的两个号码的closest single precision floats是:

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

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

  

据我所知,相同的数字可以不同方式编码为double。

一般情况并非如此。你的问题是14.7和0.1都不能完全表示。这个问题与代表的独特性无关。

因此,要完全执行总和,您需要使用十进制算术。这不是C ++中内置的东西,你需要使用第三方库。

答案 3 :(得分:3)

除了关于十进制和小数的浮点表示不精确的其他答案之外,在计算浮点数的有效位数的十进制表示时,floor()也是错误的选择。你应该改为rounding - 并且结果不应该以浮点数存储,而是以其他方式存储。