Perl中的数值到字符串的隐式转换
我在尝试了解在Perl 5.28.2下,下面这段脚本的行为。
sub split_and_print {
my $label = $_[0];
my $x = $_[1];
my @parts = split('\.', $x);
print sprintf("%s -> %s %s %.20f\n", $label, $parts[0], $parts[1], $x);
}
my @raw_values = ('253.38888888888889', '373.49999999999994');
for my $raw_value (@raw_values) {
split_and_print("'$raw_value'", $raw_value);
split_and_print("1.0 * '$raw_value'", 1.0 * $raw_value);
}
对我而言,这样输出是:
'253.38888888888889' -> 253 38888888888889 253.38888888888888573092
1.0 * '253.38888888888889' -> 253 388888888889 253.38888888888888573092
'373.49999999999994' -> 373 49999999999994 373.49999999999994315658
1.0 * '373.49999999999994' -> 373 5 373.49999999999994315658
上述内容都在预期之内,除了最后一行:我不明白在对 split 的调用中,将 $x 从数字自动转换为字符串时,为什么会得到 373.5。print(373.49999999999994 - 373.5) 表示 -5.6843418860808e-14,因此Perl知道这些数字不相等(也就是说,这并不是因为Perl的浮点数精度有限导致的)。
perlnumber 说:
如前所述,Perl可以将数字存储为三种格式中的任意一种,但大多数运算符通常只理解其中一种。当一个数值被作为参数传递给此类运算符时,它会被转换为运算符所理解的格式。
[...]
- 如果源数字超出目标形式可表示的范围,则使用最接近的边界表示。(信息丢失)
- 如果源数字介于目标形式可表示的两个数字之间,则使用这两个数字中的一个表示。(信息丢失)
但 '373.5' 似乎并不是把 373.49999999999994 表示成字符串时的“最接近的边界值”——那应该是 '373.49999999999994',或者其他在再次转回数字时能得到原始值的十进制表示。
另外:253.38888888888889 有什么不同?
我在寻找一个明确的参考,能够解释在Perl中数字自动转换为字符串到底是如何工作的。
解决方案
*需要数字,因此它会对操作数进行数值化。split需要第二个操作数是字符串,因此它会将其转换为字符串。
因此,实际的过程是这样的:
- 你有字符串
253.38888888888889,把它分成253和38888888888889。 - 你对字符串
253.38888888888889进行数值化,得到253.388888888888885730921174399554729461669921875[1](因为253.38888888888889不能被精确表示[2])。这个数字会被转换成字符串253.388888888889,你再把它分成253和38888888888889。 - 你有字符串
373.49999999999994,把它分成373和49999999999994。 - 你对字符串
373.49999999999994进行数值化,得到373.49999999999994315658113919198513031005859375[1](因为373.49999999999994不能被精确表示[2])。这个数字会被转换成字符串373.5,你把它分成373和5。
所以真正的问题是:
为什么
253.388888888888885730921174399554729461669921875字符串化成253.388888888889
而
373.49999999999994315658113919198513031005859375字符串化成373.5?
要存储一个任意的16位十进制数字,需要的位宽超过了IEEE双精度所能提供的位数,因此输出16位数字没有意义。Perl因此 将结果四舍五入到15个有效数字。[1]
sprintf 对字符串化提供了对齐的控制(但从数字中获取更多精度几乎没有意义)。
$ perl -Mv5.14 -e'say sprintf "%.751g", $_ for @ARGV' 253.38888888888889 373.49999999999994
253.388888888888885730921174399554729461669921875
373.49999999999994315658113919198513031005859375
$ perl -Mv5.14 -e'say 0+$_ for @ARGV' 253.38888888888889 373.49999999999994
253.388888888889
373.5
$ perl -Mv5.14 -e'say sprintf "%.15g", $_ for @ARGV' 253.38888888888889 373.49999999999994
253.388888888889
373.5
$ perl -Mv5.14 -e'say sprintf "%.16g", $_ for @ARGV' 253.38888888888889 373.49999999999994
253.3888888888889
373.4999999999999
$ perl -Mv5.14 -e'say sprintf "%.12f", $_ for @ARGV' 253.38888888888889 373.49999999999994
253.388888888889
373.500000000000
$ perl -Mv5.14 -e'say sprintf "%.13f", $_ for @ARGV' 253.38888888888889 373.49999999999994
253.3888888888889
373.4999999999999
$ perl -Mv5.14 -e'say sprintf "%.12f", $_ for @ARGV' 253.38888888888889 373.49999999999994
253.388888888889
373.500000000000
$ perl -Mv5.14 -e'say sprintf "%.13f", $_ for @ARGV' 253.38888888888889 373.49999999999994
253.3888888888889
373.4999999999999
- 本文假设所使用的Perl构建采用IEEE双精度浮点数。
- 在二进制中很可能是循环的,就像十进制中的1/3那样是循环的。