Math.Floor(double)和Math.Ceiling(double)的意外行为

12

这个问题涉及到Math.Floor(double)Math.Ceiling(double)决定给出上一个或下一个整数值的阈值。我很惊讶地发现,这个阈值似乎与Double.Epsilon无关,而Double.Epsilon是可以用double表示的最小值。例如:

double x = 3.0;
Console.WriteLine( Math.Floor( x - Double.Epsilon ) );  // expected 2, got 3
Console.WriteLine( Math.Ceiling( x + Double.Epsilon) ); // expected 4, got 3

即使将Double.Epsilon乘以相当大的数也无法实现:

Console.WriteLine( Math.Floor( x - Double.Epsilon*1000 ) );  // expected 2, got 3
Console.WriteLine( Math.Ceiling( x + Double.Epsilon*1000) ); // expected 4, got 3
通过一些试验,我能够确定这个阈值大约在2.2E-16左右,虽然很小,但是比Double.Epsilon要大得多。
这个问题的原因是,我试图使用公式var digits=Math.Floor(Math.Log(n,10))+1计算一个数字的位数。但是这个公式对于n=1000并不适用,因为Math.Log(1000,10)返回的数字与其实际值相差4.44E-16(这完全是意外发现的)。后来我发现内置的Math.Log10(double)提供了更准确的结果。
这个阈值是否应该与Double.Epsilon相关联呢?如果不是的话,难道这个阈值就不应该被记录下来吗?(我在官方MSDN文档中找不到任何提及此事的内容)

这可能与在IEEE格式中存储这些数字的能力有关。我想知道3.0和3.0 - Double.Epsilon的比较结果会是什么。 - Rich
@Rich:它们应该相等比较。 - sarnold
1
毫不意外,3.0也比0.0要大得多。它们看起来只相差3,但这是超过表示正数的一半(其中一半在0和1之间)的差异。 - harold
3个回答

16

阈值是否应该绑定到Double.Epsilon?

不应该。

可表示的双精度浮点数在实数范围内分布是不均匀的。靠近零,有很多可表示的值。但随着距离零越远,双精度可表示值之间的距离也越大。对于非常大的数字,即使将1加到双精度浮点数中也不会得到新值。

因此,您正在寻找的阈值取决于数字有多大。它不是一个常数。


11

Double.Epsilon的值为4.94065645841247e-324。由于浮点运算的方式,将该值加或减3会得到3。

double具有53位的尾数,因此您可以添加的最小值约为变量的2^53倍。因此,大约1e-16左右的值是正确的(数量级)。

所以回答您的问题:没有“阈值”;floorceil只是按照您预期的方式对其参数进行操作。


基本上,双精度浮点数中没有足够的有效数字来表示3和3-Double.Epsilon之间的差异。因此,原帖作者的阈值对于11或111等数字会有所不同,因为需要更多的有效数字位数。 - Rich
@Rich:确实如此,但是这个“阈值”只适用于基本的浮点运算,而不适用于floorceil - Oliver Charlesworth
我本来期望 Double.Epsilon 的值是 2.220446049250313e-16。 - dan04
@dan04:为什么?这是最小的非零值。 - Oliver Charlesworth
因为这就是机器精度的定义。 - dan04
@dan04: 机器 epsilon 是相对误差。Double.Epsilon 不声称自己是机器 epsilon(在文档中明确说明)。 - Oliver Charlesworth

3

这将是更多手势而不是参考规范,但我希望我的“直观解释”能够适合你。

Epsilon表示可以表示的最小量,与零不同。考虑到双精度浮点数的尾数和指数,这将非常微小——想象一下10 ^ -324。在小数点和第一个非零数字之间有三百多个零。

然而,Double表示大约14-15位数字的精度。这仍然留下了310位数字的零,介于Epsilon和整数之间。

Double被固定为一定的位长度。如果您真的需要任意精度计算,则应使用任意精度库。请准备好它会显着变慢——表示存储像2 + epsilon这样的数字所需的325个数字将每个数字需要大约75倍的存储空间。这种存储并不免费,使用它进行计算肯定不能以全CPU速度进行。


1
不错的回答。我会重新表述一下,即在浮点数算术中,将具有接近精度边缘和更小的幅度的数字相加或相减可能没有影响,这应该是操作的预期结果。因此,这并不是“意外”的。 - user215054

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接