Hi,
Most perls assign floating point values using perl's internal Atof function - and that includes perls that define "Perl_strtod".
But Perl's Atof function is notoriously incorrect, and a far better alternative IMO is to have floats assigned using Perl_strtod, which is just a wrapper around C's strtod() or strtold() or strtoflt128() - whichever is appropriate for the particular perl's nvtype.
First up, I should point out that -Dusequadmath builds (ie builds for which $Config{nvtype} reports "__float128") already use Perl_strtod(), with the result that the __float128 values are assigned correctly, in my experience on Ubuntu-16.04. (By "correctly", I mean rounded to nearest, ties to even.)
But when perl's nvtype is "double" or "long double", then values are being assigned using perl's Atof function and there's a fair chance that values are being assigned incorrectly.
The magnitude of Atof's inaccuracies is not particularly large - mostly it's only 1 unit of least precision (ULP). But it can be as large as 7 ULP when nvtype is "double" and and as large as 54 ULP when nvtype is the extended precision "long double".
(The figures of "7" and "54" are the largest I've found, having tested millions of random values - and those 2 numbers turn up often enough.)
The actual likelihood of striking inaccuracies with Atof depends upon the exponent range that you're working in. If the exponent is in the range (say) -10 to 10 the likelihood of an incorrect assignment is about 10%.
But when I randomly select values across the full exponent range, I'm finding that the chances of an incorrect assignment rise to around 97% for "doubles" and 82% for "long doubles".
When I hack the perl source to use Perl_strtod, the chances of an incorrect assignment become 0. (Ok ... I haven't checked
every value ... but I've not yet found a value that has been incorrectly assigned by Perl_strtod on Ubuntu.)
It turns out that using Perl_strtod instead of perl's Atof is very easy to implement. We just need to open up numeric.c in the top level perl source folder, replace (the one occurrence of) "strtoflt128" with "Perl_strtod", replace every occurrence of "USE_QUADMATH" with "Perl_strtod", and rebuild perl.
The actual patch (for perl-5.28.0 source) can be downloaded from
my scratchpad.
UPDATE: Better to grab
this patch because:
a) it's a portable patch for both mingw-w64 built Windows perl && Linux perl;
b) at some time I'll probably clear my scratchpad.
That's about it. If your perl's nvtype is "__float128" or your build of perl doesn't define "Perl_strtod", then applying the patch will not change anything.
Otherwise, however, if you build perl using the patched numeric.c then perl will assign floating point values using Perl_strtod instead of perl's Atof.
It's very much the same story on MS Windows wrt to mingw-w64 builds of perl whose nvtype is "double", where exactly the same patch makes equally dramatic improvements to the assigning of floating point values.
Sadly, however, for "long doubles" on Windows, there's
https://sourceforge.net/p/mingw-w64/bugs/711 and
https://sourceforge.net/p/mingw-w64/bugs/725 that complicate matters.
And there's also an issue wrt to strtold's assigning of some subnormal long double values - for which I've yet to submit a bug report.
(More about Windows at a later date.)
Here's the script I use to check
$ARGV[1] randomly selected values within a specified exponent range
(-$ARGV[0] to +$ARGV[0]).
# atonv.pl
# Test a range of values for
# correctness of assignment
use strict;
use warnings;
use Math::MPFR qw(:mpfr);
die "Upgrade to Math-MPFR-4.03"
unless $Math::MPFR::VERSION >= 4.03;
die "Usage: perl atonv.pl maximum_exponent how_many_values"
unless @ARGV == 2;
$|++;
my $display = 0;
while($display !~ /^y/i && $display !~ /^n/i) {
print("Do you want mismatched values to be displayed ? [y|n]: \n");
$display = <STDIN>;
}
$display = 0 if $display =~ /n/i;
my($mant, $exp, $perl_unpacked, $mpfr_unpacked, $str_value);
my($count, $diff, $max_diff, $min_diff) = (0, 0, 0, 0);
my $max_exp = $ARGV[0];
$max_exp++;
# $workspace is the Math::MPFR object to which
# the value being tested is assigned.
# Here we set the precision of $workspace to the
# same number of bits as perl's NV.
my $workspace = Rmpfr_init2($Math::MPFR::BITS);
my $failed = 0;
my($perl_nv, $mpfr_nv);
for(;;) {
$count++;
$mant = int(rand(10))
. '.'
. int(rand(10))
. int(rand(10))
. int(rand(10))
. int(rand(10))
. int(rand(10))
. int(rand(10))
. int(rand(10))
. int(rand(10))
;
$exp = int(rand($max_exp));
$exp = "-$exp" if $count % 2;
$str_value = $mant . "e$exp";
# Assign $str_value to $mpfr_nv using mpfr
$mpfr_nv = atonv($workspace, $str_value);
# Assign $str_value to $perl_nv using perl
$perl_nv = "$str_value" + 0;
# $mpfr_nv and $perl_nv should be exactly equivalent.
# Else atleast one of mpfr and perl has assigned incorrectly.
# IME, mpfr does not assign incorrectly.
unless($perl_nv == $mpfr_nv) {
$failed++;
$perl_unpacked = scalar reverse unpack "h*", pack "F<", $perl_nv;
$mpfr_unpacked = scalar reverse unpack "h*", pack "F<", $mpfr_nv;
print "$str_value: $mpfr_nv:\n $perl_unpacked vs $mpfr_unpacked\n\
+n" if $display;
$diff = hex(substr($perl_unpacked, -8, 8)) - hex(substr($mpfr_unpa
+cked, -8, 8));
if($diff > $max_diff) {
$max_diff = $diff;
}
elsif($diff < $min_diff) {
$min_diff = $diff;
}
}
last if $count == $ARGV[1];
}
print "Count: $count\n";
print "Failed: $failed\n";
print "Largest differences were $max_diff ULPs and $min_diff ULPs\n";
print "Failed: $failed\n";
print "Largest differences were $max_diff ULPs and $min_diff ULPs\n";
It requires Math-MPFR-4.03. If you want to test values in the subnormal range, you should build Math::MPFR against mpfr-4.0.x as earlier versions of mpfr were buggy in their calculation of subnormals.
As a starter, run
perl atonv.pl 300 100, opting to display mismatches, and see how that fares.
Whenever I run that command against a patched perl-5.28.0, 0 mismatches are detected, irrespective of perl's nvtype.
Whenever I run that command against an unpatched perl-5.28.0, about 80 failures are detected unless, of course, nvtype is "__float128" - in which case no failures still occur.
There's probably not many who would bother, but I certainly intend to continue building perl with this hack in place.
UPDATE: For the record, gcc version on my Ubuntu box is 5.4.0, and libc version is 2.23
Cheers,
Rob