Perl and sub-normal floating-point zeros

103 Views Asked by At

Playing with the floating-point format in Perl, I noticed some oddity: It seems Perl 5.18.2 (on x86_64 Linux at least) does not handle sub-normal numbers (like -0) correctly (given the IEEE 64-bit binary representation, "2**(-1023 - 52)" should be a sub-normal number different from zero).

main::(-e:1):   1
  DB<1> $x=2**-1076

  DB<2> x $x
0  0
  DB<3> x $x > 0
0  ''
  DB<4> printf('%g', $x)
0
  DB<5> printf('%e', $x)
0.000000e+00
  DB<6> $x=-2**-1076

  DB<7> x $x
0  0
  DB<8> x $x < 0
0  ''
  DB<9> printf('%g', $x)
-0
  DB<10> printf('%e', $x)
-0.000000e+00

Number 2**-1075 still prints as zero, but 2**-1074 prints as 4.94065645841247e-324.

  DB<20> $x = -2**-1074

  DB<21> x $x < 0
0  1
  DB<22> $x = -2**-1075

  DB<23> x $x < 0
0  ''
  DB<24> printf('%g', $x)
-0

Can anybody confirm this, or did a make a thinking error?

1

There are 1 best solutions below

4
ikegami On

[This answer, like the OP, assumes a machine with IEEE doubles for NVs.]


(given the IEEE 64-bit binary representation, 2**(-1023 - 52)" should be a sub-normal number different from zero).

No.

  • Largest subnormal:

    printf "%.1000g\n", unpack "d>", pack "H*", "000FFFFFFFFFFFFF";
    
    2.2250738585072008[...]375e-308    # 100s of digits omitted.
    
  • Smallest (non-zero) subnormal:

    printf "%.1000g\n", unpack "d>", pack "H*", "0000000000000001";
    
    4.9406564584124654[...]625e-324    # 100s of digits omitted.
    

    This is 2-1074, not 2-1075.

  • Smallest "subnormal":

    printf "%.1000g\n", unpack "d>", pack "H*", "0000000000000000";
    
    0
    

    A long sequence of zeroes multiplied by a power of two is going to be zero.


It seems Perl [...] does not handle sub-normal numbers

Perl handles subnormals perfectly fine.

printf "%.1000g\n", 2**(-1074);   # Smallest positive number possible, a subnormal.
4.9406564584124654[...]625e-324   # 100s of digits omitted.
printf "%.1000g\n", -2**(-1074);  # Smallest negative number possible, a subnormal.
-4.9406564584124654[...]625e-324  # 100s of digits omitted.

It seems Perl [...] does not handle sub-normal numbers (like -0)

While the encoding of zero (positive and negative) is what one would obtain if one encoded the value zero as a subnormal, it seems a bit odd to me to call it a subnormal. But that's just nitpicking.

Perl does support -0.

printf "%f\n", -0.0;
-0.000000

-0 can also be obtained by underflowing a negative number.

$_ = -2**(-1074);        # Smallest negative number
$_ /= 2;                 # Underflow
printf "%.1000g\n", $_;
-0

Also,

printf "%.1000g\n", -2**(-1075);  # Too small
-0

Note that -0 is equal to +0, as it should.

say -0.0 == 0.0 ? "Equal" : "Not equal";
Equal

You can obtain the sign using POSIX's signbit (5.22+).

use POSIX qw( signbit );

say signbit(  0.0 );
say signbit( -0.0 );
0
1