呈现浮点数:你甚至不知道自己有问题的进展

2020-10-30 13:22:49

我打赌您从来没有想过这样一件事,而且理由很充分:浮点数是如何呈现为文本字符串的?这是一个令人惊讶的棘手问题,但自1990年左右以来,人们一直认为它基本上得到了解决。

在Steele和White的“如何准确打印浮点数”之前,printf和类似的呈现函数的实现都尽了最大努力来呈现浮点数,但是它们的表现差别很大。例如,可以将诸如1.3号之类的数字表示为1.29999999,或者如果将一个数字放入写出其书面表示的反馈循环,并且读回它的书面表示,则每个连续的结果可能会越来越远离原始结果。

斯蒂尔和怀特用一种名为“龙”的聪明算法有效地解决了这个问题(“龙”算法的第四个版本,之所以得名,是因为作者受到海威的龙曲线的启发,想要模糊双关语)。

Dragon4算法在语言运行时中迅速传播,以至于今天很少有程序员理解这曾经是一个问题,更不用说它曾经(现在是如此)令人毛骨悚然了。事实上,在去年之前,这方面几乎没有任何活动:有两篇论文提出了对Dragon4进行广泛使用的改进,仅此而已。(唉,这个问题最初是在斯蒂尔和怀特发表他们的作品之前十年左右解决的,但没有人注意到。如果你有一个聪明的想法和足够的厚颜无耻,那就试着招募盖伊·斯蒂尔(Guy Steele)作为合著者。您的作品将会被阅读。)。

但是问题是如何解决的呢?Dragon4及其衍生品复杂而棘手,而且它们的性能成本很高,因为它们依赖于任意精度的整数运算来计算它们的结果。如果有人能够找出如何使用本机整数,则可能会获得显著的性能提升。

2010年,Florian Loitsch在PLDI上发表了一篇精彩的论文,用整数快速准确地打印浮点数,这代表着该领域20年来最大的进步:他主要解决了如何使用机器整数执行精确渲染!为什么我说主要是?因为虽然Loitsch的Grisu3&34;算法非常快,但它放弃了大约0.5%的数字,在这种情况下,你必须退回到Dragon4或一个派生的数字。

如果您是一位语言运行时作者,那么Grisu算法非常重要:例如,Grisu3大约比GNU libc中printf使用的算法快5倍。一些语言实现者已经注意到了这一点:Google雇佣了Loitsch,Grisu家族充当V8和Mozilla Javascript引擎中的默认呈现算法(取代David Gay长达17年的dtoa代码)。Loitsch友好地将他的Grisu算法的实现作为一个名为Double-Conversion的库发布。

当然,在谈到性能时,我不能不在某个地方提到Haskell:-)我使用了Loitsch的库并编写了一个Haskell接口,我测量到它比Haskell运行时库中使用的默认渲染器快30倍。这有一些很好的连锁反应:例如,我的Aeson JSON库现在呈现大型浮点数组的速度提高了10倍。在这项工作的过程中,我意外地注意到我的Haskell text Unicode库的UTF-8编码器速度不够快,因此我在此过程中将其性能提高了约50%。为更快的代码欢呼!

(顺便说一句,算法命名中的双关语还在继续:Grisu算法是以小龙格里斯奥命名的。)