不同编程语言的浮点数学

wal*_*uss 23 python floating-point haskell r julia

我知道浮点数学最多可能是丑陋的,但我想知道是否有人可以解释以下的怪癖.在我测试的大多数编程语言中,0.4到0.2的加法给出了轻微的误差,其中0.4 + 0.1 + 0.1给出了非.

是什么原因导致计算的不平等以及可以在各自的编程语言中采取哪些措施来获得正确的结果.

在python2/3中

.4 + .2
0.6000000000000001
.4 + .1 + .1
0.6
Run Code Online (Sandbox Code Playgroud)

Julia 0.3也是如此

julia> .4 + .2
0.6000000000000001

julia> .4 + .1 + .1
0.6
Run Code Online (Sandbox Code Playgroud)

和斯卡拉:

scala> 0.4 + 0.2
res0: Double = 0.6000000000000001

scala> 0.4 + 0.1 + 0.1
res1: Double = 0.6
Run Code Online (Sandbox Code Playgroud)

和哈斯克尔:

Prelude> 0.4 + 0.2
0.6000000000000001    
Prelude> 0.4 + 0.1 + 0.1
0.6
Run Code Online (Sandbox Code Playgroud)

但是R v3是正确的:

> .4 + .2
[1] 0.6
> .4 + .1 + .1
[1] 0.6
Run Code Online (Sandbox Code Playgroud)

Mar*_*son 45

所有这些语言都使用系统提供的浮点格式,它表示二进制而不是十进制的值.像0.20.4不能以该格式精确表示值,因此存储最接近的可表示值,从而导致小错误.例如,数字文字0.2会生成一个浮点数,其精确值为0.200000000000000011102230246251565404236316680908203125.类似地,对浮点数进行的任何给定算术运算都可能导致一个不能完全表示的值,因此真正的数学结果将替换为最接近的可表示值.这些是您所看到的错误的根本原因.

但是,这并不能解释语言之间的差异:在所有示例中,正在进行完全相同的计算,并且得到了完全相同的结果.区别在于各种语言选择显示结果的方式.

严格地说,您展示的答案都不正确.使用舍入到最近的舍入模式(相当安全)假设IEEE 754二进制64算术,第一个和的精确值是:

0.600000000000000088817841970012523233890533447265625
Run Code Online (Sandbox Code Playgroud)

而第二笔金额的确切值是:

0.59999999999999997779553950749686919152736663818359375
Run Code Online (Sandbox Code Playgroud)

但是,这些输出都不是特别方便用户使用,显然您测试的所有语言都是在打印时缩小输出的合理决定.但是,他们并没有采用相同的策略来格式化输出,这就是为什么你会看到差异.

格式化有许多可能的策略,但有三种特别常见:

  1. 计算并显示17个正确舍入的有效数字,可能会在它们出现的地方剥离尾随零.17位数的输出保证了不同的binary64浮点数将具有不同的表示形式,因此可以从其表示中明确地恢复浮点值; 17是具有此属性的最小整数.例如,这是Python 2.6使用的策略.

  2. 计算并显示在通常的round-ties-to-even舍入模式下舍入到给定binary64值的最短十进制字符串.这比策略1实现起来要复杂得多,但保留了不同浮点数具有不同表示的属性,并且倾向于产生令人愉快的输出.这似乎是您测试的所有语言(除了R)正在使用的策略.

  3. 计算并显示15(或更少)正确舍入的有效数字.这具有隐藏十进制到二进制转换中涉及的错误的效果,给出了精确十进制算术的错觉.它的缺点是不同的浮点数可以具有相同的表示.这似乎是R正在做的事情.(感谢@hadley在评论中指出有一个R设置控制用于显示的位数;默认是使用7位有效数字.)

  • 很好的解释。以在输入中重现相同值所需的最少十进制数字来打印二进制浮点值是一个非常困难的问题。一种不需要任意精度算术的高效算法只是 [Florian Loitsch 于 2010 年发布](http://florian.loitsch.com/publications/dtoa-pldi2010.pdf)。Julia 使用了 Florian 为 V8 JavaScript 引擎开发的出色的[双转换库](https://code.google.com/p/double-conversion/)。 (3认同)

com*_*orm 6

你应该知道,0.6不能在IEEE精确表示浮点,也不可以0.4,0.20.1.这是因为该比率1/5是二进制的无限重复分数,就像比例例如1/31/7十进制一样.由于您的初始常量都不准确,因此您的结果也不准确也就不足为奇了.(注意:如果你想更好地处理这种缺乏准确性的问题,请尝试从计算结果中减去你期望的值......)

同样存在许多其他潜在的陷阱.例如,浮点运算只是近似关联的:在不同的顺序中将相同的数字组合在一起通常会给您略微不同的结果(偶尔会给您带来截然不同的结果).因此,在精度很重要的情况下,您应该注意如何累积浮点值.

针对这种情况通常的建议是阅读"什么每台计算机科学家应该知道关于浮点运算",由大卫·戈德堡.要点:浮点不准确,可能不支持关于其行为的天真假设.