smith
smith

Reputation: 3282

perl inconsistent negative zero result

I have the following code :

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; 

when I run the script the result is -0.0 and not 0.0 could someone explain why and how i can change it to be 0.0.

Upvotes: 6

Views: 1520

Answers (7)

Chris Betti
Chris Betti

Reputation: 2923

Answer: use the absolute value function, abs()

Code

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 standard

abs(x) copies a floating-point operand x to a destination in the same format, setting the sign bit to 0 (positive).

EDIT (addressing Justin's feedback):

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

Upvotes: 0

Marcelo Cantos
Marcelo Cantos

Reputation: 186098

Very strange. I note that the problem disappears if you replace 1.5 with a negative integer:

$ 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

All I can think of is to treat -0.0 as a special case:

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

(EDIT: This was a dumb suggestion, since -0.0 == 0.0.)

I also checked Python's behaviour, which consistently retains the sign, which suggests that the negative sign is not really a bug in Perl, just a little strange (though I'd say that treating integers and non-integers differently is a bug):

$ 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

Upvotes: 1

user166390
user166390

Reputation:

This doesn't address the post directly, but it does address the "odd" behavior that exists in perl.


(I believe) This issue is caused because perl is converting the numbers to integers and then using INTEGER/ALU math instead of FP/FPU math. However, there is no -0 integer [in two's complement] -- only an -0 integral which is really a floating point value -- so the floating point value -0.0 is converted to the integer 0 before the multiplication :-)

Here is my "demonstration":

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;

And my "result/reasoning" is:

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

Happy musings.


Python does not exhibit these quirks because it has strongly-typed numbers: it will not convert an integral floating point value to an integer prior to a math operation. (Python will still perform standard type-widening.) Try divide by 0.0 (FP, not INTEGER math!) in perl ;-)

Upvotes: 1

Sinan Ünür
Sinan Ünür

Reputation: 118166

Data::Float has some useful information as well as routines to check if a floating point value is zero.

The short answer is, when dealing with floating point, you cannot assume algebraic identities will be preserved.

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;

Upvotes: 1

piotr
piotr

Reputation: 5745

Nothing to see here, move along...

Zero is represented by the exponent emin - 1 and a zero significand. Since the sign bit can take on two different values, there are two zeros, +0 and -0.

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

Upvotes: 1

ikegami
ikegami

Reputation: 386621

First, this has nothing to do with Perl. It's your processor that's returning -0.0. You'll see this same behaviour in other languages.


You ask why, presumably asking why this is useful. Honestly, I don't know. Some scientists and engineers probably take advantage of it.

+0.0 would indicate "zero or something very slightly larger on the positive side".

-0.0 would indicate "zero or something very slightly larger on the negative side."


You also ask how to get rid of the sign.

Negative zero is false, so $x || 0 does the trick.

Upvotes: 5

Keith Thompson
Keith Thompson

Reputation: 263627

You've run into something very strange. My first thought was that you were seeing some very small negative number that sprintf rounds to -0.0, but in fact the result of the expression is an actual negative zero.

Here's a simpler program that exhibits the same issue:

#!/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;

and the output:

x = 0.000000
y = -0.000000

My best guess is that -1.0 * 0.0 is being computed at compile time, but -1.5 * 0.0 is being computed at execution time, and the computations are yielding different results. EDIT: Strike that; a modified version of the program that replaces all the constants with function calls has the same behavior.

I can avoid the negative zero display by adding these lines before the printf calls:

$x += 0.0;
$y += 0.0;

but that's ugly.

(Incidentally, I get the same results with the "bleading-edge" version of Perl 5.15.2 from about a month ago.)

A similar C program prints -0.000000 for both x and y.

EDIT: Further experiment shows that multiplying a negative integral value by 0.0 yields 0.0, but multiplying a negative non-integral value by 0.0 yields -0.0. I've submitted a Perl bug report.

Upvotes: 3

Related Questions