in reply to Re^2: [OT: JavaScript] JS remainder operation ('%')
in thread [OT: JavaScript] JS remainder operation ('%')

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.

  • Comment on Re^3: [OT: JavaScript] JS remainder operation ('%')

Replies are listed 'Best First'.
Re^4: [OT: JavaScript] JS remainder operation ('%')
by syphilis (Archbishop) on Jan 18, 2024 at 12:59 UTC
    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]
        C++ outputs the same number as Perl

        I think there was, and probably still is to some extent, the view that if a 15-significant-digit representation isn't a good enough approximation, then it's up to the user to provide the code that does what's wanted.
        That might be just using printf "%.17g", ..., or using a certain library of your choice.

        Interestingly, every finite double can be expressed exactly in base 10 in no more than 767 mantissa digits and, if one wants to get anal about it, one can even get to see that exact representation by doing printf("%.767g", $val).
        And that will probably work on most perls (and also C, C++), unless you've got an ancient system:
        D:\>perl -le "printf '%.767g', 0.1;" 0.1000000000000000055511151231257827021181583404541015625 and D:\>perl -le "printf '%.767g', 2 ** -1074;" 4.94065645841246544176568792868221372365059802614324764425585682500675 +50727020875186529 9836361635992379796564695445717730926656710355939796398774796010781878 +12630071319031140 4527845817167848982103688718636056998730723050006387409153564984387312 +47339727316961514 0031715385398074126238565591171026658556686768187039560310624931945271 +59149245532930545 6544401127480129709999541931989409080416563324524757147869014726780159 +35523861155013480 3526493472019379026810710749170333222684475333572083243193609238289345 +83680601060115061 6980975307834227731832924790498252473077637592724787465608477820373446 +96995336470179726 7771758512566055119913150489110145103786273816725095583738973359899366 +48099411642057026 37090279242767544565229087538682506419718265533447265625e-324
        Mind you, I don't think I've ever found a practical use for this capability.

        Cheers,
        Rob
      > 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

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

        That would be better. (Some might even argue that "perl's interpolation" would be the more strictly accurate replacement - since all print() does is to output what perl has interpolated ... but that sort of thinking is way too moot for me to understand ;-)

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

        I don't quite follow that. The only problem is the print function (or perl's interpolation of NV's or whatever we should call it).
        There's no issue with perl's internal representation of doubles, since 5.30.0.

        Cheers,
        Rob
Re^4: [OT: JavaScript] JS remainder operation ('%') (print's float rounding)
by LanX (Saint) on Jan 18, 2024 at 03:40 UTC
    > 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

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

        Sounds like the logical spot for it. (Maybe we're still trying to come up with a justification ;-)

        I recall reading (somewhere) that 15 digits was chosen because that's the value of float.h's DBL_DIG - and DBL_DIG is described as "DBL_DIG specifies the number of digits of precision of a double".
        So you can see that someone could easily get sucked into thinking that 15 digits was the smart thing to do.

        If you go beyond 15 digits, then you hit the case that 2 different 16-digit numbers can have the same value:
        >perl -le "print 'ok' if 9.999999999999999 == 9.999999999999998;" ok
        That's 2 different 16 digit numbers with the same value. Therefore the "number of digits of precision of a double" must be less than 16.
        But you won't get 2 different 15-digit numbers with the same value.

        Cheers,
        Rob