sub FMOD { return $_[0] - int($_[0] / $_[1]) * $_[1]; }
That's exactly what I was using - and, for the given example, it returns 859064762.875, whereas the JS implementations return 859064762.882.
In using C's fmod() function, although the input arguments and the output result are standard 53-bit doubles, the calculations are being done to a higher precision (presumably for improved accuracy).
Here's a demo script:
use strict;
use warnings;
use Math::MPFR qw(:mpfr);
print FMOD(900719925474099.7, 2147483647.83), "\n";
print MPFR_FMOD(900719925474099.7, 2147483647.83), "\n";
sub FMOD { return $_[0] - int($_[0] / $_[1]) * $_[1]; }
sub MPFR_FMOD {
# Do exactly what FMOD does - but on Math::MPFR objects, performing
# the calculation at 200-bit precision.
my $arg0 = Math::MPFR->new($_[0]); # 53 bit precision representatio
+n of $_[0]
my $arg1 = Math::MPFR->new($_[1]); # 53 bit precision representatio
+n of $_[1]
# Do the calculation at 200 bit precision for improved accuracy.
Rmpfr_set_default_prec(200);
my $ret = int($arg0 / $arg1);
$ret *= $arg1;
$ret = $arg0 - $ret;
# Convert the 200-bit precision $ret to
# a perl scalar (double), and return it
my $nv = Rmpfr_get_d($ret, MPFR_RNDN);
# Restore default precision back to the
# original value of 53 before returning
Rmpfr_set_default_prec(53);
return $nv;
}
__END__
Outputs:
859064762.875
859064762.882
I just need to use C's fmod function, or any other equivalent thereof.
Cheers, Rob | [reply] [d/l] [select] |
Okay. Let me simplify the problem:
Try this in JavaScript:
var A = 900719066409336.9;
alert(A); // This will display: 900719066409336.9
Try this in Perl:
my $A = 900719066409336.9;
print $A; # This will print: 900719066409337
This is QBASIC 1.1 code:
LET A# = 900719066409336.9#
PRINT A# ' This will print: 900719066409336.9
So, there's the problem. As you can see, at one point, the FMOD() function that we have divides the numbers that you picked in your example, and then it takes the integer part of that. Everything works okay so far. We get the same result in all three languages. But then we multiply this number by 2147483647.83, and that's when things go bad.
In JavaScript and even in ancient QBASIC 1.1, we get the correct result. But in Perl, the double variable loses precision for some reason.
It gets more interesting, because if you use printf instead of print in Perl, you further get different results:
my $A = 900719066409336.9;
print $A; # This will print: 900719066409337
printf('%.1f', $A); # This will print 900719066409336.9
printf('%.2f', $A); # This will print 900719066409336.87
printf('%.3f', $A); # This will print 900719066409336.870
Man, this is weird! I have no idea what's going on.
| [reply] |
Man, this is weird! I have no idea what's going on
There's a lot to digest, but here's a few pointers that might make things a little easier.
Firstly, prior to perl-5.30.0, perl was prone to assign values to NVs incorrectly - so you can eliminate one source of frustration by not using a perl that's any older than 5.30.0.
(This problem did not afflict quadmath builds ($Config{nvtype} is '__float128').
Secondly, perl's print() function often lies about floating point values.
A good example is:
>perl -le "print 1.4/10;"
0.14
You might therefore deduce that 0.14 == 1.4/10, but you'd be wrong - and perl will happily inform you of that:
>perl -le "print 'wtf' if 0.14 != 1.4/10;"
wtf
IOW, perl knowingly lies, whereas reputable languages like JavaScript, Raku, and Python (to name a few) will truthfully report that 1.4/10 is 0.13999999999999999, and perl will happily confirm the fact:
>perl -le "print 'ok' if 0.13999999999999999 == 1.4/10;"
ok
The problem is that perl takes the correct 17-significant-digit representation and rounds it to a 15-bit-significant-digit number. Therefore, whenever 16 or 17 significant digits are required for correctness, perl fails to deliver.
I can't describe how stupid I think this choice was without resorting to expletives ...
The best you can do with perl is to use printf() to output 17 significant digits.
At least then you'll have a value that will survive the round trip. A perl floating point scalar $nv, survives the round trip if and only if the condition ("$nv" == $nv) is true. (Apart from NaNs of course.)
The other sane thing that JavaScript, Raku and Python (to name a few) do is to display the fewest possible digits needed for the round trip to succeed. (For this they utilize the ryu algorithm.
OTOH, if you get perl to printf() 17 significant digits (for correctness), you are often displaying more digits than are necessary.
For example:
>perl -le "printf '%.17g', 1e+23"
9.9999999999999992e+22
Sure - that survives the round trip, but so does "1e+23" - and it's "1e+23" that a smart print() implementation provides.
Perl will tell you that the condition (9.9999999999999992e+22 == 1e+23) is true, so it makes good sense to output the latter (as do JavaScript, Python and Raku).
If you're interested, you can use Math::Ryu's d2s() function to display the value of your perl NVs (doubles) using the minimum number of significant digits that are required for the round trip to succeed. (Just like JavaScript, Raku and Python !!)
HTH.
Cheers, Rob
| [reply] [d/l] [select] |
>
Man, this is weird! I have no idea what's going on.
When using print perl will "smoothen" the last digits of a floating point in order to "hide" potential rounding errors from previous operations.
This has been discussed here many times. Dunno if it's documented.
printf can be used to show the real content, of course only a binary format will be always fully correct.
$a = 1 - 1e-16;
say $a;
printf "%.16f",$a;
__END__
1
0.9999999999999999
| [reply] [d/l] |