Reputation: 3282
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
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
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
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
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
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
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
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 EDIT: Strike that; a modified version of the program that replaces all the constants with function calls has the same behavior.-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.
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