Beefy Boxes and Bandwidth Generously Provided by pair Networks
We don't bite newbies here... much
 
PerlMonks  

[OT: JavaScript] JS remainder operation ('%')

by syphilis (Archbishop)
on Jan 17, 2024 at 03:47 UTC ( [id://11157037]=perlquestion: print w/replies, xml ) Need Help??

syphilis has asked for the wisdom of the Perl Monks concerning the following question:

Hi,

11155911 aroused my curiosity about JS arithmetic, and I've lately been working on emulating that arithmetic in perl. (By "working, I mean "playing" ;-)
One thing that's puzzling me is the JS calculation of the expression 900719925474099.7 % 2147483647.83.
According to 2 online JS calculators (https://playcode.io/new and https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Remainder) the result is 859064762.882.
But my (double precision) calculation results in the slightly different 859064762.875:
>perl -le "$x = 900719925474099.7 - (2147483647.83 * 419430); print $x +;" 859064762.875
(The value of 419430 is arrived at by evaluating int(900719925474099.7 / 2147483647.83).)
The online JS calculators agree that 900719925474099.7 - (2147483647.83 * 419430) results in 859064762.875, so apparently their % operation is doing something slightly different.
What am I overlooking ?

The "JS remainder documentation" that I'm looking at is also the second of the 2 links given above.
As regards calculation of r = n % d, it contains:

<quote>
When both operands are non-zero and finite, the remainder r is calculated as r := n - d * q where q is the integer such that r has the same sign as the dividend n while being as close to 0 as possible.
</quote>

Have I misunderstood that documentation ? (I don't know what the := symbol signifies ... I assume it's meant to be taken as the assignment operator.)
Any enlightenment appreciated.

Cheers,
Rob

PS - this is part of a not-particularly-useful-work-in-progress at https://github.com/sisyphus/math-js

Replies are listed 'Best First'.
Re: [OT: JavaScript] JS remainder operation ('%')
by soonix (Canon) on Jan 17, 2024 at 09:55 UTC
    Up to now it was my understanding that the "remainder" operation is defined for integer operands only. With Floats, you have to deal with rounding errors.
      Up to now it was my understanding that the "remainder" operation is defined for integer operands only.

      That sounds sane to me - but why would these online calculators then provide a value for the remainder when one or both inputs is non-integer ?
      Surely, the sane thing to do would be to throw an exception.

      With Floats, you have to deal with rounding errors

      That's not really a problem if you're prepared to apply (and adhere to) consistent rounding practice.
      Sure, that practice doesn't avoid rounding, but it does mean that there's only one correct result.
      Also, for the particular example I've given, the difference between the 2 results (0x1.99a24dd70e56p+29 versus 0x1.99a24dd7p+29) appears to be more than a rounding discrepancy.
      (To my eyes, it looks more like a precision discrepancy.)

      Sorry- it might seem that I'm "taking you to task" over your response, and that's not my intention.
      I'm quite happy to accept that it's silly to be thinking about this, but I just wanted to present those counter-arguments in case they have some validity.

      Thanks for the reply.

      Cheers,
      Rob
        Strictly speaking is Modulo only defined for positive integers, excluding 0 for the dividend.

        Many languages take incompatible liberties in extending this definition by relying on the same implementation/algorithm, but also allowing floats as input.

        The rounding errors theory in your case is very convincing. But you should also check results with negative values and error cases

        > Surely, the sane thing to do would be to throw an exception.

        NB: JS is particularly reluctant to throw exceptions, it rather follows the "quiet" NaN (= not a number) approach, to silently fail.

        IMHO it's impossible to translate this behavior to efficient pure Perl.

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

        Update

        I have problems imagining a use case for float modulo operations, except reimplementing division.

        Any ideas?

Re: [OT: JavaScript] JS remainder operation ('%')
by ikegami (Patriarch) on Jan 17, 2024 at 17:45 UTC

    According to 2 online JS calculators (https://playcode.io/new and https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Remainder) the result is 859064762.882.

    I get 859064762.875 from both of those when using FireFox, and from the FireFox console. (It's not surprising they all give the same result since they all use the JS engine in my browser.)

    I get 859064762.875 from both of those when using Chrome, and from the Chrome console. (Again, the consistency is to be expected.)

    What kind of machine are you using?


    Note that they are are two common modulo operators in CS ("floored" and "truncated"), and it varies a lot by language.

    Perl%Floored
    POSIX::fmodTruncated
    JavaScript%Truncated

    This only matters if an operands is negative, though.

      Keep in mind that there is a IEEE rounding setting that on x86 usually defaults to "round toward even", but could also be set to round down or round up. This could be relevant since the implementation of fmod involves a float-to-int conversion.
      Copying this into the address bar
      • javascript:document.writeln(900719925474099.7 % 2147483647.83)
      is producing 859064762.882 on my android's browsers

      • Chrome
      • Duckduckgo
      • Opera
      I suppose you are on windows?

      update

      it's also producing 859064762.882 on my Ubuntu18's

      • Chromium
      • Firefox

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

      What kind of machine are you using?

      Windows 11, with Google Chrome.
      What kind are you using ?
      Apparently yours uses the sub FMOD { return $_[0] - int($_[0] / $_[1]) * $_[1]; } that harangzsolt33 posted, not the fmod() implementation that most(?) of us are seeing.

      Cheers,
      Rob

        Nevermind, I just noticed that you changed the code before running it in Perl.

Re: [OT: JavaScript] JS remainder operation ('%')
by Anonymous Monk on Jan 17, 2024 at 12:51 UTC

    Also: The spec, Java at TIO

    https://t.ly/EHF3Z
    https://t.ly/m7AYI
    
      https://t.ly/EHF3Z

      Thank You !!!
      From that link: this may be compared with the C library function fmod.
      They're just using the fmod() function from the standard C math.h:
      ## try.pl ## use strict; use warnings; use Inline C => <<'EOC'; double foo(double x, double y) { return fmod(x, y); } EOC print foo(900719925474099.7, 2147483647.83); __END__ Outputs: 859064762.882
      How is it possible that the XP of Anonymous Monk is less than that of Vroom ???
      (Defies all logic ;-)

      Cheers,
      Rob
        Alternatively
        #! /usr/bin/perl use warnings; use strict; use feature qw{ say }; use POSIX qw{ fmod }; say fmod(900719925474099.7, 2147483647.83);

        map{substr$_->[0],$_->[1]||0,1}[\*||{},3],[[]],[ref qr-1,-,-1],[{}],[sub{}^*ARGV,3]
Re: [OT: JavaScript] JS remainder operation ('%')
by syphilis (Archbishop) on Jan 19, 2024 at 23:37 UTC
    Thanks for all the replies.
    Math-JS-0.03 is now on CPAN, for anyone interested.
    (Duh ... I've already noticed one typo in the POD ...)

    Cheers,
    Rob
      I've already noticed one typo in the POD

      The best time for spotting typos is immediately after hitting 'GO'...

      Perhaps that's because we all need a few finctions in our lives 😜

        So true. IIRC one of the major email players (probably a webmail one but am hazy on the details) created an option to hold a message for 30 seconds or so after the user pressed "Send" for just such occurrences. Seemed like a really good idea for us fat-fingered hippos.

        Update: It was gmail.


        🦛

Re: [OT: JavaScript] JS remainder operation ('%')
by Bod (Parson) on Jan 19, 2024 at 22:21 UTC
    The online JS calculators agree...

    I've just built a quick and dirty JS calculator to test the result. My browser, Chrome on Windows 10, agrees with the online calculators you have tried...

    If you suspect it is a browser/OS variance then other Monks with different configurations might like to visit:
    https://www.boddison.com/temp/PMJStest.html

Re: [OT: JavaScript] JS remainder operation ('%')
by harangzsolt33 (Chaplain) on Jan 17, 2024 at 17:31 UTC
    JavaScript's % operator is different. The Perl equivalent would be this :

    sub FMOD { return $_[0] - int($_[0] / $_[1]) * $_[1]; }

    Perl's % operator behaves the same as the MOD operator in QBASIC.

      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
        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.

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlquestion [id://11157037]
Approved by GrandFather
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others perusing the Monastery: (2)
As of 2024-06-23 07:39 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found

    Notices?
    erzuuli‥ 🛈The London Perl and Raku Workshop takes place on 26th Oct 2024. If your company depends on Perl, please consider sponsoring and/or attending.