subnormal f/p hex literals and g++

On blead compiled with gcc:

    $ p -le'print "bad" if 0x1.fffffffffffffp-1022 == 0.0'
    $ 

On blead compiled with g++:

    $ p -le'print "bad" if 0x1.fffffffffffffp-1022 == 0.0'
    bad
    $ 

It turns out that perl's handling of subnormal f/p hex literals goes wrong
under 'g++ -ansi'.  scan_num() in toke.c does effectively the following
when toking 0x1.fffffffffffffp-1022 :


    hexfp_frac_bits = 52;         # the number of bits in .fffffffffffff
    hexfp_exp = -1022;
    hexfp_exp -= hexfp_frac_bits; # now -1074

    hexfp_mult = Perl_pow(2.0, hexfp_exp);

On linux at least, the Perl_pow() macro expands to plain pow(),
and under 'gcc' and 'g++' it returns the smallest possible subnormal
value; under 'g++ -ansi' it returns 0.0.

It seems that under -ansi, a different inline wrapper function from
/usr/include/c++/7/cmath is used, which returns 0 rather than a denorm
value.

So, two questions:

is this a reasonable thing for pow() to do;
in that case, should we modify scan_num() to avoid pow() and/or avoid
heading into subnormal territory?

For 5.28, I've marked some tests in t/op/sprintf2.t as TODO if
the literal value gets returned as zero.

-- 
My get-up-and-go just got up and went.
0
davem
5/1/2018 3:27:26 PM
perl.perl5.porters 47365 articles. 0 followers. Follow

7 Replies
46 Views

Similar Articles

[PageSpeed] 2

-----Original Message----- 
From: Dave Mitchell
Sent: Wednesday, May 02, 2018 1:27 AM
To: perl5-porters@perl.org
Subject: subnormal f/p hex literals and g++

.....

>  scan_num() in toke.c does effectively the following when toking 
> 0x1.fffffffffffffp-1022 :
>
> hexfp_frac_bits = 52;         # the number of bits in .fffffffffffff
> hexfp_exp = -1022;
> hexfp_exp -= hexfp_frac_bits; # now -1074
>
> hexfp_mult = Perl_pow(2.0, hexfp_exp);

This procedure is also buggy on gcc builds (with perl-5.27.11 on both linux 
and windows,  at least).

$ perl -Mwarnings -le 'printf "%.16e\n", 0x1.ffffffffffffp-1023;'
Hexadecimal float: exponent underflow at -e line 1.
2.2250738585071974e-308


$ perl -Mwarnings -le 'printf "%.16e\n", 0x1.ffffffffffff0p-1023;'
Hexadecimal float: exponent underflow at -e line 1.
0.0000000000000000e+00

The former gets it right because, I presume, hexfp_frac_bits is set to 48 - 
which keeps hexfp_exp above -1075.
But the latter fails to deliver the correct value because hexfp_frac_bits is 
set to 52, hexfp_exp becomes -1075, and 2 ** -1075 is indeed 0.

Cheers,
Rob 
0
sisyphus1
5/2/2018 1:27:55 AM
--Sig_/JzCuJR+SqvGbcMI626i/Eob
Content-Type: text/plain; charset=US-ASCII
Content-Transfer-Encoding: quoted-printable

On Tue, 1 May 2018 16:27:26 +0100, Dave Mitchell <davem@iabyn.com>
wrote:

> On blead compiled with gcc:
>=20
>     $ p -le'print "bad" if 0x1.fffffffffffffp-1022 =3D=3D 0.0'
>     $=20
>=20
> On blead compiled with g++:
>=20
>     $ p -le'print "bad" if 0x1.fffffffffffffp-1022 =3D=3D 0.0'
>     bad
>     $=20
>=20
> It turns out that perl's handling of subnormal f/p hex literals goes wrong
> under 'g++ -ansi'.  scan_num() in toke.c does effectively the following
> when toking 0x1.fffffffffffffp-1022 :
>=20
>=20
>     hexfp_frac_bits =3D 52;         # the number of bits in .fffffffffffff
>     hexfp_exp =3D -1022;
>     hexfp_exp -=3D hexfp_frac_bits; # now -1074
>=20
>     hexfp_mult =3D Perl_pow(2.0, hexfp_exp);
>=20
> On linux at least, the Perl_pow() macro expands to plain pow(),
> and under 'gcc' and 'g++' it returns the smallest possible subnormal
> value; under 'g++ -ansi' it returns 0.0.
>=20
> It seems that under -ansi, a different inline wrapper function from
> /usr/include/c++/7/cmath is used, which returns 0 rather than a denorm
> value.
>=20
> So, two questions:
>=20
> is this a reasonable thing for pow() to do;
> in that case, should we modify scan_num() to avoid pow() and/or avoid
> heading into subnormal territory?
>=20
> For 5.28, I've marked some tests in t/op/sprintf2.t as TODO if
> the literal value gets returned as zero.

The problem goes away under -Duselongdouble
I've seen this for a long time now, but never digged, so thank you!

http://perl5.test-smoke.org/report/55613 is from may 2017


--=20
H.Merijn Brand  http://tux.nl   Perl Monger  http://amsterdam.pm.org/
using perl5.00307 .. 5.27   porting perl5 on HP-UX, AIX, and openSUSE
http://mirrors.develooper.com/hpux/        http://www.test-smoke.org/
http://qa.perl.org   http://www.goldmark.org/jeff/stupid-disclaimers/

--Sig_/JzCuJR+SqvGbcMI626i/Eob
Content-Type: application/pgp-signature
Content-Description: OpenPGP digital signature

-----BEGIN PGP SIGNATURE-----
Version: GnuPG v2

iQEcBAEBAgAGBQJa6VVLAAoJEAOhR6E+XcCYQvMIAKN2Rm1CM81vDCZLrMGgAlDR
vDwVKe3LoyIXaMYigKoEti7BRx0dkw8yMUClha3WNhmztAE+FZw97xk+ILKyQol6
/wXowCVSq7kkyfO44oIbbROBfV9g/HhJs43Y9bOPAKkGDpFZ0BZJOlRHWUYVQMto
a8kXor79oX6D3UqP5prLG9ImPi7krushZiVPhtoOvNwMfAoS5LCzzSFDplcTJZSC
JyBY2QNa9X+RQpUB6DTBpwzPXX/ap4ZnXytrdXK6jCGesV7twk7deZ3V0Epr2QrB
74vRngH7plpTebcqNWE0RD3/QAGean1E+LTjz/2TNu+HTPldwDDLVaPogkqQubo=
=NfbW
-----END PGP SIGNATURE-----

--Sig_/JzCuJR+SqvGbcMI626i/Eob--
0
h
5/2/2018 6:05:54 AM
--Sig_/akXnlybYNNRnUo/v0=iiN06
Content-Type: text/plain; charset=US-ASCII
Content-Transfer-Encoding: quoted-printable

On Tue, 1 May 2018 16:27:26 +0100, Dave Mitchell <davem@iabyn.com>
wrote:

> On blead compiled with gcc:
>=20
>     $ p -le'print "bad" if 0x1.fffffffffffffp-1022 =3D=3D 0.0'
>     $=20
>=20
> On blead compiled with g++:
>=20
>     $ p -le'print "bad" if 0x1.fffffffffffffp-1022 =3D=3D 0.0'
>     bad
>     $=20
>=20
> It turns out that perl's handling of subnormal f/p hex literals goes wrong
> under 'g++ -ansi'.  scan_num() in toke.c does effectively the following
> when toking 0x1.fffffffffffffp-1022 :
>=20
>=20
>     hexfp_frac_bits =3D 52;         # the number of bits in .fffffffffffff
>     hexfp_exp =3D -1022;
>     hexfp_exp -=3D hexfp_frac_bits; # now -1074
>=20
>     hexfp_mult =3D Perl_pow(2.0, hexfp_exp);
>=20
> On linux at least, the Perl_pow() macro expands to plain pow(),
> and under 'gcc' and 'g++' it returns the smallest possible subnormal
> value; under 'g++ -ansi' it returns 0.0.
>=20
> It seems that under -ansi, a different inline wrapper function from
> /usr/include/c++/7/cmath is used, which returns 0 rather than a denorm
> value.

Just tested with g++-8

All tests successful.
Files=3D2659, Tests=3D1169864, 273 wallclock secs (158.04 usr 20.12 sys + 1=
195.51 cusr 105.41 csys =3D 1479.08 CPU)
Result: PASS

not ok 1530 - subnormal 0x1.fffffffffffffp-1022 %a 0x1.fffffffffffffp-1022 =
got 0x0p+0 # TODO denorm literals treated as zero
# Failed test 1530 - subnormal 0x1.fffffffffffffp-1022 %a 0x1.fffffffffffff=
p-1022 got 0x0p+0 at op/sprintf2.t line 822
#      got "0x0p+0"
# expected "0x1.fffffffffffffp-1022"
not ok 1531 - subnormal 0x0.fffffffffffffp-1022 %a 0x1.ffffffffffffep-1023 =
got 0x0p+0 # TODO denorm literals treated as zero
# Failed test 1531 - subnormal 0x0.fffffffffffffp-1022 %a 0x1.ffffffffffffe=
p-1023 got 0x0p+0 at op/sprintf2.t line 822
#      got "0x0p+0"
# expected "0x1.ffffffffffffep-1023"
not ok 1532 - subnormal 0x0.7ffffffffffffp-1022 %a 0x1.ffffffffffffcp-1024 =
got 0x0p+0 # TODO denorm literals treated as zero
# Failed test 1532 - subnormal 0x0.7ffffffffffffp-1022 %a 0x1.ffffffffffffc=
p-1024 got 0x0p+0 at op/sprintf2.t line 822
#      got "0x0p+0"
# expected "0x1.ffffffffffffcp-1024"
not ok 1533 - subnormal 0x0.3ffffffffffffp-1022 %a 0x1.ffffffffffff8p-1025 =
got 0x0p+0 # TODO denorm literals treated as zero
# Failed test 1533 - subnormal 0x0.3ffffffffffffp-1022 %a 0x1.ffffffffffff8=
p-1025 got 0x0p+0 at op/sprintf2.t line 822
#      got "0x0p+0"
# expected "0x1.ffffffffffff8p-1025"
not ok 1534 - subnormal 0x0.1ffffffffffffp-1022 %a 0x1.ffffffffffffp-1026 g=
ot 0x0p+0 # TODO denorm literals treated as zero
# Failed test 1534 - subnormal 0x0.1ffffffffffffp-1022 %a 0x1.ffffffffffffp=
-1026 got 0x0p+0 at op/sprintf2.t line 822
#      got "0x0p+0"
# expected "0x1.ffffffffffffp-1026"
not ok 1535 - subnormal 0x0.0ffffffffffffp-1022 %a 0x1.fffffffffffep-1027 g=
ot 0x0p+0 # TODO denorm literals treated as zero
# Failed test 1535 - subnormal 0x0.0ffffffffffffp-1022 %a 0x1.fffffffffffep=
-1027 got 0x0p+0 at op/sprintf2.t line 822
#      got "0x0p+0"
# expected "0x1.fffffffffffep-1027"

    config_args=3D'-Dusedevel -Duse64bitall -Dusethreads -Duseithreads -Dcc=
=3Dg++-8 -des'
    uselongdouble=3Dundef
    cc=3D'g++-8'
    ccflags =3D'-D_REENTRANT -D_GNU_SOURCE -fPIC -DDEBUGGING -fwrapv -fno-s=
trict-aliasing -pipe -fstack-protector-strong -I/pro/local/include -D_LARGE=
FILE_SOURCE -D_FILE_OFFSET_BITS=3D64'
    cppflags=3D'-D_REENTRANT -D_GNU_SOURCE -fPIC -DDEBUGGING -fwrapv -fno-s=
trict-aliasing -pipe -fstack-protector-strong -I/pro/local/include'
    gccversion=3D'8.0.1 20180425 (prerelease) [gcc-8-branch revision 259638=
]'

> So, two questions:
>=20
> is this a reasonable thing for pow() to do;
> in that case, should we modify scan_num() to avoid pow() and/or avoid
> heading into subnormal territory?
>=20
> For 5.28, I've marked some tests in t/op/sprintf2.t as TODO if
> the literal value gets returned as zero.
>=20


--=20
H.Merijn Brand  http://tux.nl   Perl Monger  http://amsterdam.pm.org/
using perl5.00307 .. 5.27   porting perl5 on HP-UX, AIX, and openSUSE
http://mirrors.develooper.com/hpux/        http://www.test-smoke.org/
http://qa.perl.org   http://www.goldmark.org/jeff/stupid-disclaimers/

--Sig_/akXnlybYNNRnUo/v0=iiN06
Content-Type: application/pgp-signature
Content-Description: OpenPGP digital signature

-----BEGIN PGP SIGNATURE-----
Version: GnuPG v2

iQEcBAEBAgAGBQJa6eYlAAoJEAOhR6E+XcCYEm0H/0LPslw5g0dSs0kE2o57t5W+
aCpxcVX5+cHgFi2CnX5QPXREroth7YRsg2VqqN1LDuX7MIC1oQ2fvznUNpTX+LbR
GobspaXGMyOziZiuyZc5hvQYN9s7O11cql5KFrrPV/ayNXzOxkf8U1oi2RL3+Oja
i6rzW0GSqEXwM9ys/nRdCT6fY2Ez31O+qI3o7DQcsdQ0Vs+Q8q5su3YzWScD5pdl
G6hP+7r/+lVeumsJFXXjDC+rM1aDGorgmI9J5mTlHD0WlPG3DBV+ksqnYPEKCCTw
1oOzQbhGNxPm8JW+lSko53StkkiS+JKT9+voRiLQQoh73SY1Ydxtlri1uEbb1uk=
=1CSY
-----END PGP SIGNATURE-----

--Sig_/akXnlybYNNRnUo/v0=iiN06--
0
h
5/2/2018 4:23:56 PM

-----Original Message----- 
From: sisyphus1@optusnet.com.au
Sent: Wednesday, May 02, 2018 11:27 AM
To: Dave Mitchell ; perl5-porters@perl.org
Subject: Re: subnormal f/p hex literals and g++

-----Original Message----- 
From: Dave Mitchell
Sent: Wednesday, May 02, 2018 1:27 AM
To: perl5-porters@perl.org
Subject: subnormal f/p hex literals and g++

> $ perl -Mwarnings -le 'printf "%.16e\n", 0x1.ffffffffffff0p-1023;'
> Hexadecimal float: exponent underflow at -e line 1.
> 0.0000000000000000e+00

This failing can be eliminated by replacing (in toke.c) :

#if NVSIZE == 8 && defined(HAS_QUAD) && defined(Uquad_t)
with:
#if 0

However, 2 tests in t/op/hexfp.t then fail:

not ok 96
# Failed test 96 - at t/op/hexfp.t line 220
#      got "Hexadecimal float: mantissa overflow at (eval 30) line 1.\n"
# expected undef
.....
not ok 102
# Failed test 102 - at t/op/hexfp.t line 235
#      got "Hexadecimal float: mantissa overflow at (eval 33) line 1.\n"
# expected undef

This is in response to the tests:

undef $a;
eval '$a = 0xfffffffffffff.8p0'; # 53 bits.
is(get_warn(), undef);

and:

undef $a;
eval '$a = 0xf.ffffffffffff8p0'; # 53 bits.
is(get_warn(), undef);

In both cases, $a is still being set correctly, it's just that the trailing 
'000' is now being counted as part of the mantissa.
(This is with perl-5.27.11.)

Cheers,
Rob
0
sisyphus1
5/3/2018 12:06:42 PM
On Thu, May 03, 2018 at 10:06:42PM +1000, sisyphus1@optusnet.com.au wrote:
> 
> 
> -----Original Message----- From: sisyphus1@optusnet.com.au
> Sent: Wednesday, May 02, 2018 11:27 AM
> To: Dave Mitchell ; perl5-porters@perl.org
> Subject: Re: subnormal f/p hex literals and g++
> 
> -----Original Message----- From: Dave Mitchell
> Sent: Wednesday, May 02, 2018 1:27 AM
> To: perl5-porters@perl.org
> Subject: subnormal f/p hex literals and g++
> 
> > $ perl -Mwarnings -le 'printf "%.16e\n", 0x1.ffffffffffff0p-1023;'
> > Hexadecimal float: exponent underflow at -e line 1.
> > 0.0000000000000000e+00
> 
> This failing can be eliminated by replacing (in toke.c) :
> 
> #if NVSIZE == 8 && defined(HAS_QUAD) && defined(Uquad_t)
> with:
> #if 0
> 
> However, 2 tests in t/op/hexfp.t then fail:

My current vague plan (post 5.28), is, for -ve exponents, to
use half the exponent and apply twice; i.e. rather than using

    mantissa * (2**(exp))

use:

    mantissa * (2**(exp >> 1)) * (2**(exp >> 1))

(plus a final divide by 2 if exp is odd)

That way, if the exponent is heading beyond subnormal territory, it won't
underflow before (too late) being brought back into a usable range
when multiplied by the mantissa.

-- 
Wesley Crusher gets beaten up by his classmates for being a smarmy git,
and consequently has a go at making some friends of his own age for a
change.
    -- Things That Never Happen in "Star Trek" #18
0
davem
5/3/2018 1:34:57 PM

-----Original Message----- 
From: Dave Mitchell
Sent: Thursday, May 03, 2018 11:34 PM
To: sisyphus1@optusnet.com.au
Cc: perl5-porters@perl.org
Subject: Re: subnormal f/p hex literals and g++

> My current vague plan (post 5.28), is, for -ve exponents, to use half the
> exponent and apply twice; i.e. rather than using
>
>    mantissa * (2**(exp))
>
> use:
>
>    mantissa * (2**(exp >> 1)) * (2**(exp >> 1))
>
> (plus a final divide by 2 if exp is odd)
>
> That way, if the exponent is heading beyond subnormal territory, it won't
> underflow before (too late) being brought back into a usable range when
> multiplied by the mantissa.

Doing right shifts on negative integers is bound to produce incorrect 
results - but that, of course, can be avoided by (eg) instead doing integer 
division by 2.

But that means that many subnormals will be rounded twice - once on the 
second multiplication by 2**(exp / 2), and again on the final division by 2.

Hence (eg), for 0x1.048p-1069, we would have :

perl -le "printf '%a', 0x1.048p0 * (2 ** -534) * (2 ** -534) / 2;"
0x1p-1069

when the correct result for 0x1.048p-1069 is 0x1.08p-1069.

Better, I think, to do something like:

mantissa * (2**(exp / 2)) * (2**(exp - (exp / 2)))

perl -le "printf '%a', 0x1.048p0 * (2 ** -534) * (2 ** -535);"
0x1.08p-1069

Cheers,
Rob
0
sisyphus1
5/5/2018 3:36:28 AM
On Sat, May 05, 2018 at 01:36:28PM +1000, sisyphus1@optusnet.com.au wrote:
> Doing right shifts on negative integers is bound to produce incorrect
> results - but that, of course, can be avoided by (eg) instead doing integer
> division by 2.

(I just used shifts in the example to emphasise that it was integer
division)

> But that means that many subnormals will be rounded twice - once on the
> second multiplication by 2**(exp / 2), and again on the final division by 2.
> 
> Hence (eg), for 0x1.048p-1069, we would have :
> 
> perl -le "printf '%a', 0x1.048p0 * (2 ** -534) * (2 ** -534) / 2;"
> 0x1p-1069
> 
> when the correct result for 0x1.048p-1069 is 0x1.08p-1069.
> 
> Better, I think, to do something like:
> 
> mantissa * (2**(exp / 2)) * (2**(exp - (exp / 2)))
> 
> perl -le "printf '%a', 0x1.048p0 * (2 ** -534) * (2 ** -535);"
> 0x1.08p-1069

Interesting. I would have assumed that multiplying by 0.5 or dividing by
2.0 would Do the Right Thing as regards subnormal values. Apparently not.

I was suggesting using the same value twice to avoid two calls to pow().
Dividing the mantissa by 2 first seems to do the trick:

$ perl -e 'printf "%a\n", 0x1.048p0 /2 * (2 ** -534) * (2 ** -534);'
0x1.08p-1069

-- 
"You may not work around any technical limitations in the software"
    -- Windows Vista license
0
davem
5/5/2018 8:46:41 AM
Reply: