Perl inconsistent negative null 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, not 0.0, can someone explain why and how I can change it to 0.0.

+6
source share
7 answers

Firstly, it has nothing to do with Perl. This is your processor that returns -0.0. You will see similar behavior in other languages.


You ask why, apparently, you ask why this is useful. Honestly, I do not know. Some scientists and engineers are probably using it.

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

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


You also ask how to get rid of the sign.

The negative zero is false, so $x || 0 $x || 0 performs the trick.

+6
source

You are faced with something very strange. My first thought was that you saw a very small negative number that sprintf rounded to -0.0, but in fact the result of the expression is the actual negative zero.

Here's a simpler program that shows the same problem:

 #!/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 conclusion:

 x = 0.000000 y = -0.000000 

It is best to assume that -1.0 * 0.0 calculated at compile time, but -1.5 * 0.0 computed at runtime, and the calculations give different results. EDIT : Hit it; a modified version of the program that replaces all constants with function calls has the same behavior.

I can avoid displaying a negative zero by adding these lines before printf calls:

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

but it is ugly.

(By the way, I get the same results with Perl version 5.15.2 with a โ€œpale edgeโ€ about a month ago.)

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

EDIT . Further experimentation shows that multiplying the negative integral value by 0.0 gives 0.0, but multiplying the negative noninteger value by 0.0 gives -0.0. I submitted a Perl error report .

+3
source

Very strange. I note that the problem goes away if you replace 1.5 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 consider -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: That was a dumb suggestion, since -0.0 == 0.0 .)

I also tested Python's behavior, which preserves a character in sequence, which assumes that a negative sign is not really an error in Perl, is just a bit strange (although I would say that reversing integers and non-integers in different ways is an error):

 $ 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 
+1
source

Do not notice anything here, move forward ...

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

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

+1
source

This does not apply to the message directly, but it refers to the "odd" behavior that exists in perl.


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

Here is my "demo":

 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":

 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 thoughts.


Python does not demonstrate these quirks because it has strongly typed numbers: it will not convert an integer floating point value to an integer before a mathematical operation. (Python will still do the standard type extension.) Try dividing by 0.0 (FP, not INTEGER math!) In perl; -)

+1
source

Data :: Float contains useful information as well as routines to check if a floating point value is equal.

The short answer is that when dealing with a floating point, you cannot assume that 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; 
+1
source

Answer: use the absolute value function, abs()

the 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 the floating-point operand x to the destination in the same format by 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; 
0
source

Source: https://habr.com/ru/post/899168/


All Articles