go ahead... be a heretic PerlMonks

### Re^2: [OT: JavaScript] JS remainder operation ('%')

by syphilis (Archbishop)
 on Jan 18, 2024 at 01:17 UTC Need Help??

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

Replies are listed 'Best First'.
Re^3: [OT: JavaScript] JS remainder operation ('%')
by harangzsolt33 (Chaplain) on Jan 18, 2024 at 02:46 UTC
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.

> 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

Cheers Rolf
(addicted to the Perl Programming Language :)
see Wikisyntax for the Monastery

> When using print perl will "smoothen" the last digits of a floating point

> Dunno if it's documented.

Can't find it, shouldn't it be mentioned in print ?

Cheers Rolf
(addicted to the Perl Programming Language :)
see Wikisyntax for the Monastery

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
> whereas reputable languages like JavaScript, Raku, and Python

Perl was born before these reputable languages existed. C++ outputs the same number as Perl:

```#include<iostream>
int main(int argc, char** argv) {
std::cout << 1.4/10 << std::endl;
}
Output:
```0.14

map{substr\$_->[0],\$_->[1]||0,1}[\*||{},3],[[]],[ref qr-1,-,-1],[{}],[sub{}^*ARGV,3]
> 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.

Wouldn't it be better to s/perl/print/g here?

I agree that print is kind of "lying", and I would love to see this unexpected behavior at least clearly documented in the perldocs.

IMHO your statement is misleading as it is, because it's limited to output by print and doesn't effect internal representation.

##### Edit

Since print only affects the output one might consider adding something like print_truely feature, for future versions.

Best with accompanying flags in printf for compatibility.

Cheers Rolf
(addicted to the Perl Programming Language :)
see Wikisyntax for the Monastery

Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: note [id://11157068]
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others examining the Monastery: (1)
As of 2024-05-23 15:27 GMT
Sections?
Information?
Find Nodes?
Leftovers?
Voting Booth?

No recent polls found