perl不一致的负零结果

时间:2011-10-12 21:26:47

标签: perl floating-point

我有以下代码:

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。

7 个答案:

答案 0 :(得分:6)

首先,这与Perl无关。这是你的处理器返回-0.0。你会在其他语言中看到同样的行为。


你问为什么,大概会问为什么这很有用。老实说,我不知道。一些科学家和工程师可能会利用它。

+0.0表示“零或正面稍微大一些”。

-0.0表示“零或者在负面上稍微大一些。”


你也会问如何摆脱这个标志。

负零是假的,所以$x || 0可以解决问题。

答案 1 :(得分: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的“bleading-edge”版本相同的结果。)

类似的C程序为x和y打印-0.000000

编辑:进一步的实验表明,将负整数值乘以0.0会产生0.0,但将负非整数值乘以0.0会产生-0.0。我已提交了Perl bug report

答案 2 :(得分: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

答案 3 :(得分:1)

这里什么都看不到,继续......

  

零由指数emin - 1和零有效数表示。   由于符号位可以采用两个不同的值,因此有两个   零,+ 0和-0。

http://download.oracle.com/docs/cd/E19957-01/806-3568/ncg_goldberg.html

答案 4 :(得分:1)

这不会直接解决帖子,但它确实解决了perl中存在的“奇怪”行为。


(我相信)这个问题是因为perl将数字转换为整数然后使用 INTEGER / ALU 数学而不是 FP / FPU 数学。但是,没有-0整数[在二进制补码中] - 只有一个-0整数,它实际上是一个浮点值 - 所以浮点值-0.0被转换为整数0 < em>之前乘法: - )

这是我的“演示”:

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!); - )

答案 5 :(得分: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;

答案 6 :(得分: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's反馈):

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