Perl 5.16.3和5.8.7中的浮动精度差异

时间:2016-02-01 13:47:22

标签: perl floating-point

使用不同版本的perl运行时,下面的代码会给出不同的输出:

 This is perl, v5.8.7 built for i686-linux-thread-multi-64int
file `which perl`
/sv/app/perx/third-party/bin/perl: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), for GNU/Linux 2.2.5, dynamically linked (uses shared libs), for GNU/Linux 2.2.5, not stripped
 2.198696207 - 2.134326286: 0.0643699209999999
 2.198696207 - 2.134326286: 0.047901175

PERL 5.16.3: -

{{1}}

PERL 5.8.7: -      perl -v

{{1}}

我无法找到任何说明上述两个版本之间引入的浮点数精度/舍入差异的文档。

4 个答案:

答案 0 :(得分:13)

编辑:感谢Mark Dickinson指出我的初步答案中存在违规行为。由于他的侦探工作,结论发生了变化。非常感谢ikegami对初步分析的怀疑。

总结:它因为字符串中的小差异而双重对话。看起来这些差异是由32位和64位运行时相同代码的不同行为引起的。

详情

  

这是为i686-linux-thread-multi-64int构建的perl,v5.8.7

这是32位架构的Perl

  

这是为x86_64-linux构建的perl 5,版本16,subversion 3(v5.16.3)

这适用于64位架构。

这意味着这些Perl版本是针对不同的CPU架构而构建的,可能是不同的编译时选项。这可能会导致浮点运算的精度不同。 但正如ikegami的评论中指出的那样,它也可能与字符串与双重对话相关

有关架构之间的差异,请参阅Problem with floating-point precision when moving from i386 to x86_64 x87 FPU vs. SSE2 on Wikipedia

我在同一台计算机上使用相同版本的Ubuntu(15.10)在LXC容器内完成了以下测试,但一个用于32位,另一个用于64位。

# on 32 bit bit
$ perl -v
This is perl 5, version 20, subversion 2 (v5.20.2) built for i686-linux-gnu-thread-multi-64int
$ perl -V:nvsize
$ nvsize='8';
$ perl -E 'say 2.198696207-2.134326286'
0.0643699209999999

# on 64 bit
$ perl -v
This is perl 5, version 20, subversion 2 (v5.20.2) built for x86_64-linux-gnu-thread-multi
$ perl -V:nvsize
$ nvsize='8';
$ perl -E 'say 2.198696207-2.134326286'                                                                                                                                                                              
0.0643699210000004

这表明差异与Perl版本或使用的浮点大小无关。要获得更多详细信息,我们将使用unpack('H*',pack('N',$double))查看数字的内部表示。 对于2.134326286,表示是相同的,即0xb7e7eaa819130140。但是对于2.198696207,我们得到了不同的表示:

32 bit: 2.198696207 -> 0xe*5*3b7709ee960140 
64 bit: 2.198696207 -> 0xe*6*3b7709ee960140 

这意味着数字的内部表示在64位和32位上是不同的。这可能是由于使用了不同的函数,因为对不同平台进行了优化,或者因为相同的函数在32位和64位上表现略有不同。使用libc函数atof进行检查表明,这也会在64位上返回0xe53b7709ee960140,因此看起来Perl正在使用不同的函数进行对话。

深入挖掘表明,我在两个平台上使用的Perl都设置了USE_PERL_ATOF,这表明Perl正在使用自己的atof函数实现。可以找到此函数的某些当前实现的源代码here

看看这段代码,很难看出32位和64位的行为方式如何。但是有一个重要的平台相关值表示在将无符号整数添加到浮点的内部表示之前,实现将在无符号整数内积累多少数据:

#define MAX_ACCUMULATE ( (UV) ((UV_MAX - 9)/10))

显然UV_MAX在32位和64位上是不同的,因此它将在32位中引起不同的累加步骤,这导致不同的浮点加法具有潜在的精度问题。我的猜测是,它以某种方式解释了32位和64位之间行为的微小差异。

答案 1 :(得分:4)

有一些因素可以产生影响。为了在这种特殊情况下增加可能性,它们如下:

  • 这两个版本可能有不同的浮动指针编号。

    • 如果perl -V:nvsize给出8,则该版本使用双精度浮动指针编号。

    • 如果perl -V:nvsize给出16,则该版本使用四倍精度浮动指针编号。

  • C库用于解析数字和格式化数字。由于体系结构的不同,这两个版本使用不同的C库。 (由于使用了不同的编译器供应商,安装了不同的库版本等,他们也可以使用不同的C库)。在进行这些转换时,有些库比其他库更好(即有些库有错误)。

  • 使用的指令集(x87 FPU与SSE2)可能因架构而异,这很重要,因为它们在内部使用不同的重要性执行操作。有关详细信息,请参阅Steffen Ullrich's answer

答案 2 :(得分:1)

问题当然是每个Perl安装都要使用的浮点架构。但你真的需要这些价值观相同吗?如果是这样,你就会受到无尽的失望

单精度(32位)浮点数通常具有七位小数的精度,因此您的程序显示远远超出该限制

除非你试图比较两个浮点值的相等性(即使在同一个指令集中,永远不可能),你可能遇到的唯一问题是没有一个值足够准确性为您的目的

0.0643699209999999等于0.0643699210000004,准确度为7位数,这就是您可以从任何计算机或语言中获得的所有内容

答案 3 :(得分:1)

您需要的文档是perlnumber

  

Perl可以在内部以3种不同的方式表示数字:作为本机整数,作为本机浮点数和作为十进制字符串。十进制字符串可能具有指数表示法部分,如“12.34e-56”中所示。 Native在这里的意思是“C编译器支持的格式,用于构建perl”。

由C编译器和编译时选项决定。

但是,您不必使用本机号码。如果您能够容忍性能损失,则可以使用bignum来获取确切的数字。