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

时间:2012-03-28 22:50:24

标签: c# floating-point

此问题与Math.Floor(double)Math.Ceiling(double)决定为您提供上一个或下一个整数值的阈值有关。我很不安地发现阈值似乎与Double.Epsilon无关,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 乘以公平的位置也无法解决问题:

Double.Epsilon

通过一些实验,我能够确定阈值大约在2.2E-16附近,这非常小,但比var digits = Math.Floor( Math.Log( n, 10 ) ) + 1大得多。

出现这个问题的原因是我试图用公式n=1000计算数字中的位数。此公式不适用于Math.Log( 1000, 10 )(我偶然偶然发现),因为Math.Log10(double)返回的数字比其实际值低4.44E-16。 (我后来发现内置的Double.Epsilon提供了更准确的结果。)

阈值是否应该与{{1}}相关联,或者如果不是,则不应该记录阈值(我在MSDN官方文档中找不到任何相关内容)?

3 个答案:

答案 0 :(得分:15)

  

阈值不应与Double.Epsilon相关联

没有

可表示的双精度数不均匀分布在实数上。接近于零有许多可表示的值。但是从零开始越远,可表示的双打越远。对于非常大数字,即使将1加到double也不会给你一个新值。

因此,您要查找的阈值取决于您的号码有多大。这不是一个常数。

答案 1 :(得分:10)

The value of Double.Epsilon is 4.94065645841247e-324。由于浮点的工作方式,将此值加到3会导致3,结果为3。

double有53位尾数,因此您可以添加的最小值会产生任何影响,比您的变量小约2 ^ 53倍。所以1e-16左右的声音听起来是正确的(数量级)。

所以回答你的问题:没有“门槛”; floorceil只是按照您期望的方式对他们的论点采取行动。

答案 2 :(得分:3)

这将是挥手而不是参考规格,但我希望我的直观解释"很适合你。

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

但是,Double表示大约14-15位的精度。这仍然在Epsilon和整数之间留下310位零。

Double被固定为某个位长度。如果您真的想要任意精度计算,则应使用任意精度库。并准备好它显着更慢 - 代表存储一个数字所需的所有325位数,如2+epsilon,每个数字需要大约75倍的存储空间。该存储空间不可用,并且使用它进行计算肯定无法达到CPU的全速。