浮点运算的奇怪结果

时间:2014-07-14 09:44:10

标签: vb.net floating-point

这样的问题让我发疯。这是相关的代码:

Dim RES As New Size(Math.Floor(Math.Round(mPageSize.Width - mMargins.Left - mMargins.Right - mLabelSize.Width, 4) / (mLabelSize.Width + mSpacing.Width) + 1),
                    Math.Floor((mPageSize.Height - mMargins.Top - mMargins.Bottom - mLabelSize.Height) / (mLabelSize.Height + mSpacing.Height)) + 1)

变量的值(均为Single类型):

mPageSize.Width = 8.5 
mMargins.Left = 0.18
mMargins.Right = 0.18
mLabelSize.Width = 4.0
mSpacing.Width = 0.14

对于上帝知道的原因,RES评估为{Width=1,Height=5}而不是{Width=2,Height=5}。我已经单独评估了右侧的表达式,并将它们正确评估为{2,5},但RES永远不会得到正确的值。不知道我在这里错过了什么。

修改

我进一步简化了问题。如果QuickWatch RHS,以下代码将生成2.0,但执行此行后,LHS上的变量将获得1.0:

Dim X = Math.Floor(Math.Round(mPageSize.Width - mMargins.Left - mMargins.Right - mLabelSize.Width, 4) / (mLabelSize.Width + mSpacing.Width) + 1)

MS检查出来的时间?

编辑2

更多信息。以下结果给出了正确的结果:

Dim Temp = mPageSize.Width - mMargins.Left - mMargins.Right - mLabelSize.Width
Dim X = Math.Floor(Temp / CDec(mLabelSize.Width + mSpacing.Width)) + 1

2 个答案:

答案 0 :(得分:1)

这是由于舍入误差造成的:您正在取一个非常接近2的值,但小于2(而数学值为2)。您应该使用整数进行所有计算,或者在使用像floor之类的操作之前考虑舍入错误(如果您想要真值,则不总是可行)。

编辑:由于vb.net具有Decimal数据类型,您也可以使用它而不是整数。在某些情况下这可能会有所帮助:避免了0.18和0.14的基本转换(无法用二进制表示),并且这里将完全执行加法和减法,以便精确计算除法的操作数。因此,如果除法的结果是一个整数,你将完全得到它(而不是可能是下面的值,就像你用二进制得到的那样)。但请确保您的输入已经是小数。

答案 1 :(得分:1)

问题是以下表达式的计算结果是低于1的值:

Math.Round(mPageSize.Width - mMargins.Left - mMargins.Right - mLabelSize.Width, 4) / (mLabelSize.Width + mSpacing.Width)
    = 0.99999999985602739   (Double)

但原因是什么?事实是,我并不完全清楚。 MSDN没有提供有关/实施的足够信息,但这是我的猜测:

Math.Round返回值为Double的{​​{1}}。该部门的右侧是4.14。因此,您要将Single除以Double。这会产生Single(请参阅MSDN)。到现在为止还挺好。 MSDN声明在划分之前所有积分数据类型都扩展为Double。虽然Double不是一个完整的数据类型,但可能会发生这种情况。这就是问题所在。似乎没有对加法的结果进行扩展,而是对其操作数进行扩展。

如果你写

Single

此处Dim sum = (mLabelSize.Width + mSpacing.Width) 'will be 4.14 Single Math.Round(mPageSize.Width - mMargins.Left - mMargins.Right - mLabelSize.Width, 4) / sum = 1 (Double) 转换为double(结果为sum),一切都很好。但是,如果我们将两个操作数都转换为double,那么4.14的转换会引入一些浮点错误:

0.14

总和略大于Dim dblLabelSizeWidth As Double = mLabelSize.Width ' will be 4.0 Dim dblSpacing As Double = mSpacing.Width ' will be 0.14000000059604645 ,导致商数略小于4.14

原因是转换为double不是在除法的操作数上执行,而是在操作数的操作数上执行,这会引入浮点错误。

您可以通过在舍入之前向商添加小1来克服此问题。或者,您可以考虑使用更精确的数据类型,例如epsilon。但在某些时候,Decimal也会出现浮点错误。