一尘不染

为什么浮点数打印方式如此不同?

php

众所周知,(大多数)浮点数没有精确存储(使用IEEE-754格式时)。因此,不应这样做:

0.3 - 0.2 === 0.1; // very wrong

......因为它会导致false,除非一些特定的任意精度的类型/类使用(BigDecimal的中的Java/Ruby的,bcmath时在PHP中,[数学::BigInt有 / 数学::BigFloat在Perl,仅举几例)来代替。

但是,我想知道为什么当尝试打印该表达式的结果时0.3 - 0.2,脚本语言(PerlPHP)给出了0.1,而“虚拟机”语言(JavaJavaScriptErlang)却给出了类似的东西0.09999999999999998吗?

为什么在Ruby中也不一致?1.8.6版(键盘)提供0.11.9.3版(ideone)提供0.0999...


阅读 301

收藏
2020-05-29

共1个答案

一尘不染

由于打印是出于不同的目的而进行的,因此浮点数的打印方式有所不同,因此如何进行浮点选择。

打印浮点数是一种转换操作:以内部格式编码的值将转换为十进制数字。但是,可以选择有关转换的详细信息。

(A)
如果您要进行精确的数学运算并希望查看内部格式表示的实际值,则转换必须是精确的:它必须产生一个与输入值完全相同的十进制数字。(每个浮点数正好表示一个数字。IEEE754标准中定义的浮点数不表示一个间隔。)有时,这可能需要产生大量的数字。

(B)
如果您不需要确切的值,但需要在内部格式和十进制之间来回转换,则需要将其准确地(准确地)转换为十进制数字,以区别于其他任何结果。也就是说,您必须产生足够的数字,以使结果与转换内部格式中相邻的数字所得到的结果有所不同。这可能需要产生大量数字,但又不能产生过多的数字。

(C) 如果您只想让读者理解数字,而无需产生确切的值以使您的应用程序按需运行,那么您只需要产生所需位数即可特定的应用程序。

转换应该执行以下哪项操作?

不同的语言具有不同的默认值,这是因为它们是为不同的目的而开发的,或者因为在开发过程中不方便地进行所有操作以产生准确的结果,或者是由于其他各种原因。

(A)需要仔细的代码,并且某些语言或其中的实现不提供或不保证提供此行为。

我相信(B)是Java要求的。但是,正如我们在最近的问题中看到的那样,它可能会有一些意外的行为。(65.12被打印为“
65.12”,因为后者有足够的数字来将其与附近的值区分开,但65.12-2被打印为“
63.120000000000005”,因为它与63.12之间存在另一个浮点值,因此您需要额外的数字来将它们区分开。 )

(C)是某些语言默认使用的语言。从本质上讲,这是错误的,因为要打印多少位数的单个值都不能适合所有应用。的确,几十年来,我们已经看到它在很大程度上通过隐瞒所涉及的真实价值而助长了对浮点数的持续误解。但是,它易于实现,因此对某些实现者具有吸引力。理想情况下,默认情况下,一种语言应打印正确的浮点数值。如果要显示的位数较少,则位数应仅由应用程序实现者选择,希望包括考虑适当的位数以产生期望的结果。

更糟糕的是,某些语言除了不显示实际值或足够的数字以区别它之外,甚至不保证所产生的数字在某种意义上是正确的(例如,您可以通过将精确值四舍五入而得到的值)显示的位数)。在无法提供有关此行为保证的实现中进行编程时,您未在进行工程设计。

2020-05-29