如果用户想要“使用一组小数点(有效数字)比较两个浮点数”,这实际上意味着我们有一个函数
对于所有可能的 XXX 和 YYY,AlmostEquals(14.3XXXXXXXX, 14.3YYYYYYY, 1) == true,最后一个参数是小数点后的小数位。
有一个简单但不幸的答案:
不可能编程这个函数来履行这个合同。可以编写一些通常会给出正确结果的东西,但是您无法预见何时会出现这种情况,因此该功能实际上毫无价值。
这里给出的解决方案已经与AlmostEquals(0.06f, 0.14f, 1) = true but 0 != 1 不同。
为什么 ?第一个原因是极度敏感。例如: 0.0999999999.... 和 0.100000...1 一开始有不同的数字,但它们几乎无法区分,它们几乎完全相等。无论神话中的函数做什么,它都不允许计算上的微小差异。
第二个原因是我们要实际计算数字。我使用 VC 2008 和 C# 打印出 Math.pow 函数的正确值。第一个是精度参数,第二个是结果浮点数的十六进制值,第三个是精确的十进制值。
1 3dcccccd 0.100000001490116119384765625
2 3c23d70a 0.00999999977648258209228515625
3 3a83126f 0.001000000047497451305389404296875
4 38d1b717 0.0000999999974737875163555145263671875
5 3727c5ac 0.00000999999974737875163555145263671875
6 358637bd 9.999999974752427078783512115478515625E-7
如您所见,序列 0.1、0.01、0.001 等产生的数字非常近似,但要么太小要么太大。
如果我们强制指定位置必须有正确的数字怎么办?让我们枚举 4 位的 16 个二进制值
0.0
0.0625
0.125
0.1875
0.25
0.3125
0.375
0.4375
0.5
0.5625
0.625
0.6875
0.75
0.8125
0.875
0.9375
如果我们只想计算小数点后一位,16 个不同的二进制数应该足以满足 10 个十进制数。虽然 0.5 完全相等,但强制相同的十进制数字意味着 0.4 需要 0.4375 而 0.9 需要 0.9375,从而引入严重错误。
违反极端敏感的第一个条件意味着你不能对这些数字做任何合理的事情。如果你知道一个数字的小数位有一个特定的值,你就不需要首先计算。
C# 文档甚至引用了一个示例:http:
//msdn.microsoft.com/en-us/library/75ks3aby.aspx
来电者须知
由于将十进制值表示为浮点数或对浮点值执行算术运算可能导致精度损失,因此在某些情况下 Round(Double, Int32) 方法可能不会将中点值四舍五入到最接近的偶数数字小数位的值。这在以下示例中进行了说明,其中 2.135 舍入为 2.13 而不是 2.14。发生这种情况是因为该方法在内部将值乘以 10 位,并且这种情况下的乘法运算会损失精度。