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

时间:2012-12-29 13:50:00

标签: php javascript ruby floating-point ieee-754

一种常见的知识是(大多数)浮点数不能精确存储(使用IEEE-754格式时)。所以不应该这样做:

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

...因为它将导致false,除非使用了某些特定的任意精度类型/类(Java / Ruby中的BigDecimal,PHP中的BCMath ,在Perl中Math::BigInt / Math::BigFloat,仅举几例)。

但我想知道为什么当一个人试图打印这个表达式的结果0.3 - 0.2时,脚本语言(PerlPHP)会给0.1,但是“虚拟 - 机器“一个(JavaJavaScriptErlang)会给出更类似于0.09999999999999998的内容吗?

为什么它在Ruby中也不一致? version 1.8.6 (codepad)提供0.1version 1.9.3 (ideone)提供0.0999...

5 个答案:

答案 0 :(得分:7)

对于php,输出与精度的ini设置有关:

ini_set('precision', 15);
print 0.3 - 0.2; // 0.1

ini_set('precision', 17);
print 0.3 - 0.2; //0.099999999999999978 

这也可能是其他语言的原因

答案 1 :(得分:4)

浮点数的打印方式不同,因为打印是为了不同的目的,因此对如何进行打印有不同的选择。

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

(A)如果您正在进行精确的数学运算并想要查看内部格式所代表的实际值,那么转换必须精确:它必须产生一个完全相同的十进制数字值作为输入。 (每个浮点数只代表一个数字。浮点数,如IEEE 754标准中所定义,不代表间隔。)有时,这可能需要产生大量的数字。

(B)如果您不需要确切的值,但需要在内部格式和小数之间来回转换,那么您需要精确地将其转换为十进制数字(并且准确无误)足以将其与任何其他结果区分开来。也就是说,您必须生成足够的数字,结果与通过转换内部格式中相邻的数字所获得的结果不同。这可能需要产生大量数字,但不能太多以至于无法管理。

(C)如果您只想让读者了解数字,并且不需要生成确切的值以使您的应用程序按需运行,那么您只需要生成您的特定应用程序所需的数字。

转换应该做哪些?

不同的语言有不同的默认值,因为它们是为不同的目的而开发的,或者因为在开发过程中不适合进行产生精确结果所需的所有工作,或者出于各种其他原因。

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

我相信,Java需要(B)。但是,正如我们在a recent question中看到的那样,它可能会有一些意想不到的行为。 (65.12打印为“65.12”,因为后者有足够的数字来区分它与附近的值,但65.12-2打印为“63.120000000000005”,因为它与63.12之间有另一个浮点值,所以你需要额外的数字来区分它们。)

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

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

答案 2 :(得分:2)

PHP会自动将数字舍入为任意精度。

浮点数通常不准确(如您所述),如果您只需要与几个小数位进行比较,则应使用特定于语言的round()函数。否则,取等式的绝对值,并测试它们是否在给定范围内。

来自php.net的PHP示例:

$a = 1.23456789;
$b = 1.23456780;
$epsilon = 0.00001;
if(abs($a - $b) < $epsilon) {
  echo "true";
}

至于Ruby问题,他们似乎使用不同的版本。键盘使用1.8.6,而Ideaone使用1.9.3,但它更可能与某处的配置相关。

答案 3 :(得分:2)

如果我们想要这个属性

  • 每两个不同的浮点数具有不同的打印表示

或者对REPL更有用的一个

  • 印刷表示应重新解释

然后我看到3个用于打印float / double的解决方案,其中base 2内部表示到base 10

  1. 打印EXACT表示。
  2. 打印足够的十进制数字(适当的舍入)
  3. 打印可以重新解释的最短十进制表示
  4. 因为在基数2中,浮点数是an_integer * 2 ^ an_exponent,其基数10精确表示具有有限的位数。
    不幸的是,这可能导致很长的字符串...... 例如,1.0e-10完全表示为1.0000000000000000364321973154977415791655470655996396089904010295867919921875e-10

    解决方案2很简单,你使用带有17位数字的printf用于IEEE-754双...
    缺点:它不准确,也不是最短的!如果你输入0.1,你得到 0.100000000000000006

    解决方案3是REPL语言的最佳选择,如果输入0.1,则打印0.1
    不幸的是,在标准库中找不到它(遗憾) 至少,Scheme,Python和最近的Squeak / Pharo Smalltalk做得对,我认为Java也是。

答案 4 :(得分:0)

对于Javascript,base2正在内部用于计算。

> 0.2 + 0.4
0.6000000000000001

为此,如果得到的base2号不是周期性的,Javascript只能提供偶数。

0.6是base2中的0.10011 10011 10011 10011 ...(周期性),而0.5则没有,因此正确打印。