#!/usr/bin/env perl my $number1 = 2.198696207; my $number2 = 2.134326286; my $diff = $number1 - $number2; print STDOUT "\n 2.198696207 - 2.134326286: $diff\n"; $number1 = 0.449262271; $number2 = 0.401361096; $diff = $number1 - $number2; print STDOUT "\n 2.198696207 - 2.134326286: $diff\n";
PERL 5.16.3: –
perl -v This is perl 5,version 16,subversion 3 (v5.16.3) built for x86_64-linux file `which perl` /sv/app/perx/third-party/bin/perl: ELF 64-bit LSB executable,x86-64,version 1 (SYSV),dynamically linked (uses shared libs),for GNU/Linux 2.6.18,not stripped 2.198696207 - 2.134326286: 0.0643699210000004 2.198696207 - 2.134326286: 0.047901175
PERL 5.8.7: –
perl -v
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,for GNU/Linux 2.2.5,not stripped 2.198696207 - 2.134326286: 0.0643699209999999 2.198696207 - 2.134326286: 0.047901175
我无法找到任何说明上述两个版本之间引入的浮点数精度/舍入差异的文档.
解决方法
总结:它因为字符串中的小差异而双重对话.看起来这些差异是由32位和64位运行时相同代码的不同行为引起的.
细节
This is perl,v5.8.7 built for i686-linux-thread-multi-64int
这是用于32位架构的Perl
This is perl 5,subversion 3 (v5.16.3) built for x86_64-linux
这适用于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,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位的表现如何.但是有一个重要的平台相关值,它表示在将无符号整数添加到浮点的内部表示之前,实现将在unsigned int中累积多少数据:
#define MAX_ACCUMULATE ( (UV) ((UV_MAX - 9)/10))
显然,UV_MAX在32位和64位上是不同的,因此它将在32位中引起不同的累加步骤,这导致不同的浮点加法以及潜在的精度问题.我的猜测是,它以某种方式解释了32位和64位之间行为的微小差异.