Search code examples
javascriptcperloverflowinteger-arithmetic

Emulate Javascript's 32bit signed integer arithmetic to C (or Perl) - some discrepancies


I am trying to translate simple JS code to C and/or Perl but I found a discrepancy in behaviour when doing arithmetic operations (+ - * / << >>) on integers and the result overflows. I need to simulate JS exactly, even the overflows! JS variables are not explicitly BigInt but just JS var.

JS (via node.js or Firefox's developer tools' console):

function calc(a, b){
 return (a<<b) + (a<<b);
}
var x = calc(1, 30);
result:
2147483648

C:

#include <stdio.h>
#include <stdint.h>
int main(void){
  int32_t lop = 1<<30; //1073741824;
  int32_t rop = 1<<30; //1073741824;
  int32_t res = lop + rop;
  printf("1<<30 + 1<<30 : %d\n", res);
}
result:
1<<30 + 1<<30 : -2147483648

Perl:

sub to32bit { unpack("l", pack("L", $_[0])) } # Corion @ PerlMonks
print to32bit(to32bit(1<<30)+to32bit(1<<30));
result:
-2147483648

Am I right that there is a discrepancy or am I doing things wrong?

How to get this right?

As I said I need to emulate JS's exact behaviour in C/Perl so I don't want to fix JS code but adjust C/Perl code to do the same as JS.

See also related discussion at https://perlmonks.org/?node_id=11155911

Edit Feb/2024: There is now CPAN module Math::JS (at https://metacpan.org/pod/Math::JS) by Sisyphus.


Solution

  • JavaScript is an implementation of ECMAScript, and ECMAScript specifies that a left-shift operation is performed as if the left operand were converted to a 32-bit two’s complement integer before the operation were performed and the right operand were converted to 32-bit unsigned integer. It also specifies that addition is performed using numbers in the IEEE-754 binary64 (“double precision”) form.

    Assuming your C implementation uses binary64 for double, which is very common, and that int32_t and uint32_t are available (in the <stdint.h> header), then JavaScript (a<<b) + (a<<b) is largely equivalent to:

    (double) ((int32_t) a << (uint32_t) b) + (double) ((int32_t) a << (uint32_t) b)
    

    I say “largely equivalent” because details of operations involving conversion between types or handling of infinities, NaNs, and other exceptional cases may vary between ECMAScript and C.

    The ECMAScript specification is explicit about the semantics of operations. If you are converting JavaScript to another language, you should get a copy of the ECMAScript Language Specification and use it.