Perl不一致的负零结果

6
我有以下代码:
my $m=0;
my $e =0 ;
my $g=0;

my $x=  sprintf( "%0.1f", (0.6*$m+ 0.7 * $e-1.5)*$g);

print $x; 

当我运行脚本时,结果为-0.0而不是0.0,请问有人能解释一下为什么以及如何将其更改为0.0吗?

1
我想减去-0.0不是你想要的。 :) [这是一个玩笑] - tchrist
1
我已经删除了[sprintf]标签(因为它不是问题的原因),并添加了[浮点数]标签。 - Keith Thompson
就我所知,我在Windows上使用各种perl时没有看到这个问题。另请参阅http://www.perlmonks.org/?node_id=656432 - Sinan Ünür
你找到了一个可接受的解决方案吗? - Chris Betti
7个回答

5
首先,这与Perl无关。是你的处理器返回了-0.0。其他语言也会出现这种行为。
你问为什么,可能是想知道这有什么用。老实说,我不知道。一些科学家和工程师可能会利用它。
+0.0表示“零或稍微大于正面的东西”。
-0.0表示“零或稍微小于负面的东西”。
你还问如何去掉符号。
负零是假的,所以$x || 0就可以了。

3

你遇到了一些非常奇怪的事情。我最初的想法是你看到了一些非常小的负数,sprintf将其四舍五入为-0.0,但实际上表达式的结果是实际的负零。

这里有一个更简单的程序,也出现了相同的问题:

#!/usr/bin/perl

use strict;
use warnings;

my $x = -1.0 * 0.0;
my $y = -1.5 * 0.0;
printf "x = %f\n", $x;
printf "y = %f\n", $y;

以及输出:

x = 0.000000
y = -0.000000

我的最佳猜测是编译时计算了-1.0 * 0.0,但是在执行时计算了-1.5 * 0.0,这些计算结果不同。 编辑: 划去上面的内容; 将所有常量替换为函数调用的修改版本具有相同的行为。
我可以通过在printf调用之前添加以下代码来避免负零的显示:
$x += 0.0;
$y += 0.0;

但那很丑陋。

(顺便说一下,我使用大约一个月前的“最新版”Perl 5.15.2得到了相同的结果。)

类似的C程序打印出-0.000000,x和y都一样。

编辑:进一步实验表明,将负整数值乘以0.0会产生0.0,但将负非整数值乘以0.0会产生-0.0。我已经提交了一个Perl错误报告


1
非常奇怪。我注意到,如果您将1.5替换为负整数,则问题会消失:
$ perl -e '
my @a=(-9.0, -3.0, -2.0, -1.5, -1.2, -1.0, -0.8, -0.5);
for my $a (@a) {
  $bin = join("", map {sprintf("%02x", ord($_))} split(//, pack("d>", $a*0)));
  printf("%4.1f * 0 = %4.1f %s\n", $a, $a*0, $bin);
}'
-9.0 * 0 =  0.0 0000000000000000
-3.0 * 0 =  0.0 0000000000000000
-2.0 * 0 =  0.0 0000000000000000
-1.5 * 0 = -0.0 8000000000000000
-1.2 * 0 = -0.0 8000000000000000
-1.0 * 0 =  0.0 0000000000000000
-0.8 * 0 = -0.0 8000000000000000
-0.5 * 0 = -0.0 8000000000000000

我所能想到的就是将 -0.0 视为一种特殊情况:

my $ans = (0.6*$m+ 0.7 * $e-1.5)*$g;
my $x=  sprintf("%0.1f", $ans == -0.0 ? 0.0 : $ans)

编辑:这是一个愚蠢的建议,因为-0.0 == 0.0。)

我还检查了Python的行为,它始终保留符号,这表明负号并不是Perl中的一个错误,只是有点奇怪(尽管我认为将整数和非整数区分对待是一个错误):

$ python -c '
for a in [-9.0, -3.0, -2.0, -1.5, -1.2, -1.0, -0.8, -0.5]:
  print "%0.1f" % (a*0,)
'
-0.0
-0.0
-0.0
-0.0
-0.0
-0.0
-0.0
-0.0

我认为你的解决方案不起作用。你试过了吗?至少在我的系统上,my $f = 0.0; print "neg zero\n" if $f == -0.0; 输出 neg zero - musiKk

1
这并没有直接回应这篇帖子,但它确实解决了 Perl 中存在的“奇怪”行为。
(我相信)这个问题是由于perl将数字转换为整数,然后使用INTEGER / ALU数学而不是FP / FPU数学所致。但是,在二进制补码中没有-0整数,只有一个实际上是浮点值的-0整数,因此在乘法之前,浮点值-0.0被转换为整数0 :-)。
以下是我的“演示”:
printf "%.f\n", 2.0 * -0.0;
printf "%.f\n", 1.5 * -0.0;
printf "%.f\n", 1.0 * -0.0;
printf "%.f\n", 1e8 * -0.0;
printf "%.f\n", 1e42 * -0.0;

我的“结果/推理”是:

0   # 2.0 -> 2 and -0.0 -> 0: INTEGER math
-0  # 1.5 is not an integral: FP math, no conversions
0   # 1.0 -> 1 and -0.0 -> 0: INTEGER math
0   # 1e8 -> 100000000 and -0.0 -> 0: INTEGER math
-0  # 1e42 is an integral OUTSIDE the range of integers: FP math, no conversions

愉快的思考。


Python没有这些怪癖,因为它具有强类型数字:在数学运算之前,它不会将整数浮点值转换为整数。(Python仍将执行标准类型扩展。)尝试在perl中除以0.0(FP,而不是INTEGER math!)


注意:在补码机器上存在一个-0整数。 - ikegami

1

Data::Float包含一些有用的信息,以及检查浮点值是否为零的例程。

简短的回答是,在处理浮点数时,您不能假设代数恒等式将被保留。

use strict;
use warnings;

use Data::Float qw(float_is_zero);

my $m = 0;
my $e = 0;
my $g = 0;

my $result = (0.6 * $m + 0.7 * $e - 1.5) * $g;
$result = 0.0 if float_is_zero($result);

my $x = sprintf( "%0.1f", $result);

print $x;

1

1
是的,但问题是为什么有些计算会产生-0.0,而其他计算会产生+0.0。 - Keith Thompson

0
答案:使用绝对值函数,abs() 代码
printf "%f\n", -0.0;
printf "%f\n", abs(-0.0);

Perl 5.10.1

-0.000000
0.000000

Perl 5.12.1

-0.000000
0.000000

Perl 6(rakudo-2010.08)

0.000000
0.000000

IEEE 754标准

abs(x)将浮点操作数x复制到相同格式的目标中,将符号位设置为0(正数)。

编辑(回应Justin的反馈):

my $result = possible_negative_zero();
$result = abs($result) if $result == 0.0; # because -0.0 == 0.0
printf "%f\n", $result;

这可能会导致问题,当期望的结果可能是负数时。 - Justin

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接