你能解释一下天花板方法的行为吗?

本文关键字:方法 天花板 一下 能解释 | 更新日期: 2023-09-27 18:24:26

我刚刚用LINQPad做了一个测试:

你能解释一下为什么/如何天花板方法会做出这样的反应吗?注意中间的 123.12。

Math.Ceiling(123.121 * 100) / 100 'display 123.13
Math.Ceiling(123.1200000000001 * 100) / 100 'display 123.13
Math.Ceiling(123.12000000000001 * 100) / 100 'display 123.12
Math.Ceiling(123.12000000000002 * 100) / 100 'display 123.13

我在 VB.NET 中进行了测试,但在 C# 中应该是一样的。

你能解释一下天花板方法的行为吗?

这是

浮点舍入。C# 将 123.12000000000001 和 123.12 分析为具有相同的值。123.12000000000002 被解析为下一个可用的双精度。

 var bytes = BitConverter.ToString(BitConverter.GetBytes(123.12));
 // outputs 48-E1-7A-14-AE-C7-5E-40
 var bytes1 = BitConverter.ToString(BitConverter.GetBytes(123.12000000000001));
 // outputs 48-E1-7A-14-AE-C7-5E-40
 var bytes2 = BitConverter.ToString(BitConverter.GetBytes(123.12000000000002));
 // outputs 49-E1-7A-14-AE-C7-5E-40

Ceiling返回传递给它的数字,如果它们是整数,或者下一个最高的整数。所以5.0保持5.05.00001变得6.0

因此,在这些示例中,以下内容是显而易见的:

Math.Ceiling(123.121 * 100) / 100 // Obtain 12312.1, next highest is 12313.0, then divide by 100 is 123.13
Math.Ceiling(123.1200000000001 * 100) / 100 // Likewise
Math.Ceiling(123.12000000000002 * 100) / 100 // Likewise

更令人困惑的是:

Math.Ceiling(123.12000000000001 * 100) / 100 //display 123.12

但是,让我们看一下:

123.12000000000001 * 100 - 12312.0 // returns 0

与以下相比:

123.1200000000001 * 100 - 12312.0 // returns 1.09139364212751E-11
123.12000000000002 * 100 - 12312.0 // returns 1.81898940354586E-12

后两个乘法的结果略高于 12312.0,因此虽然(123.12000000000002 * 100).ToString()返回"12312" 123.12000000000002 * 100产生的实际数字在数学上是12312.000000000002123.12000000000002最接近的可能double123.1200000000000181898940354586所以这就是工作的内容。

如果您习惯于只做十进制算术,那么123.12000000000002被"四舍五入"到 123.1200000000000181898940354586 似乎很奇怪,但请记住,这些数字以二进制值存储,舍入取决于您正在使用的基数。

因此,虽然字符串表示没有指示它,但它确实略高于12312,因此它的上限是12313

同时对于123.12000000000001 * 100,这在数学上是12312.000000000001但最接近123.12000000000001的可能double是它可以适应是123.12。所以这就是用于乘法的,当结果传递给后续调用以Ceiling()时,它的结果是 12312

这是

由于浮点舍入而不是 Math.Ceiling 本身,这是因为浮点值不能以 100% 的准确度表示所有值。

无论如何,您的示例有点做作,因为如果您尝试在 Visual Studio 中键入 123.12000000000001 会将其更改为 123.12,因为它知道该值不能表示为双精度值。

在这里阅读: 每个计算机科学家都应该知道的关于浮点运算的知识(顺便说一句,这不是特定于.NET(

解决此问题,您可以使用十进制值而不是双精度值。Math.Ceiling 有一个重载,它接受小数(所有这些都显示 123.13(:

   Debug.WriteLine(Math.Ceiling(123.121D * 100) / 100) 
   Debug.WriteLine(Math.Ceiling(123.1200000000001D * 100) / 100)
   Debug.WriteLine(Math.Ceiling(123.12000000000001D * 100) / 100) 
   Debug.WriteLine(Math.Ceiling(123.12000000000002D * 100) / 100) 

当然,此修复是否合适取决于您所需的精度级别。

Ceiling 方法返回下一个较高的整数等效项。

所以天花板(123.01( = 124

天花板(123.0( = 123