in reply to Trying to translate overflowing JS code to Perl

Based on Corion's unpack( "l", pack "L", ($value & 0xffffffff))) I have put together a package which overloads basic arithmetic operators in an attempt to do these operations with 32bit integers thus simulating JS's 32bit integers. It succeeds in 1169367104<<5 = -1234958336 but fails for 1073741824+1073741824 (that's 1<<30 + 1<<30) (Perl result overflows: -2147483648 but it does not in JS: 2147483648). I have also tried NERDVANA's method of converting to 32bit in C and also with doing the addition above in C. These fail as well for said test case (1073741824+1073741824)

5 mins edit - sorry: Well 1073741824 = 1000000000000000000000000000000 = 1<<30 (that's 31 digits leaving the 32nd for sign), so 1073741824+1073741824=1000000000000000000000000000000+1000000000000000000000000000000 should overflow into the sign bit. Producing negative zero (so to speak). But Perl with 32bit integers (and Corion's hack) produces -2147483648 (ok perhaps i am missing the details on what happens with the value-part in overflowing situations) and (node.js) JS produces 2147483648 as if no overflow occured. Perhaps I have got JS's default integer size wrong? Nope! results are for node.js!!! when I enter 1<<30 + 1<<30 into firefox JS console, I get 0. Which is the negative zero. Why Perl produces -2147483648. Which takes the single bit which is ON to be both value and sign?

2hour Edit: I have changed the package code slightly to use Corion's method by default.

Here is Math::AnyInt:

package Math::AnyInt; #use 5.006; use v5.36; use strict; use warnings; our $VERSION = '0.01'; use Exporter qw(import); our @EXPORT = qw( calcop VERBOSE_AnyInt ); # caller can set this to 0 or 1 our $VERBOSE_AnyInt = 0; # overload basic arithmetic operators use overload '+' => sub { # we get 3 params: 1+2 are objects of this class, swap is ... well # get the value of the objects, do the operation # and then convert to 32bit my ($self, $other, $swap) = @_; if( $VERBOSE_AnyInt > 0 ){ print STDOUT "+ : operating on ".(defin +ed($self)&&defined($$self)?$$self:'<undef>')." and ".(defined($other) +?$other:'<undef>')." (swap is ".(defined($swap)?($swap?'yes':'no'):'< +undef>').")\n" } Math::AnyInt->new( calc_32bit_signed($self->value() + $other->valu +e()) ); }, '-' => sub { my ($self, $other, $swap) = @_; if( $VERBOSE_AnyInt > 0 ){ print STDOUT "- : operating on ".(defin +ed($self)&&defined($$self)?$$self:'<undef>')." and ".(defined($other) +?$other:'<undef>')." (swap is ".(defined($swap)?($swap?'yes':'no'):'< +undef>').")\n" } return $swap ? Math::AnyInt->new( calc_32bit_signed($self->value() - $other-> +value()) ) : Math::AnyInt->new( calc_32bit_signed($other->value() - $self-> +value()) ) ; }, '*' => sub { my ($self, $other, $swap) = @_; if( $VERBOSE_AnyInt > 0 ){ print STDOUT "* : operating on ".(defin +ed($self)&&defined($$self)?$$self:'<undef>')." and ".(defined($other) +?$other:'<undef>')." (swap is ".(defined($swap)?($swap?'yes':'no'):'< +undef>').")\n" } Math::AnyInt->new( calc_32bit_signed($self->value() + $other->valu +e()) ); }, '/' => sub { my ($self, $other, $swap) = @_; if( $VERBOSE_AnyInt > 0 ){ print STDOUT "/ : operating on ".(defin +ed($self)&&defined($$self)?$$self:'<undef>')." and ".(defined($other) +?$other:'<undef>')." (swap is ".(defined($swap)?($swap?'yes':'no'):'< +undef>').")\n" } return $swap ? Math::AnyInt->new( calc_32bit_signed($self->value() / $other-> +value()) ) : Math::AnyInt->new( calc_32bit_signed($other->value() / $self-> +value()) ) ; }, '<<' => sub { my ($self, $other, $swap) = @_; if( $VERBOSE_AnyInt > 0 ){ print STDOUT "<< : operating on ".(defi +ned($self)&&defined($$self)?$$self:'<undef>')." and ".(defined($other +)?$other:'<undef>')." (swap is ".(defined($swap)?($swap?'yes':'no'):' +<undef>').")\n" } # if operands were swapped, it does matter in this case if( $swap ){ die "<< : order is important why is swap on?" } Math::AnyInt->new( calc_32bit_signed($self->value() << $other->val +ue()) ); }, '>>' => sub { my ($self, $other, $swap) = @_; if( $VERBOSE_AnyInt > 0 ){ print STDOUT ">> : operating on ".(defi +ned($self)&&defined($$self)?$$self:'<undef>')." and ".(defined($other +)?$other:'<undef>')." (swap is ".(defined($swap)?($swap?'yes':'no'):' +<undef>').")\n" } if( $swap ){ die "<< : order is important why is swap on?" } Math::AnyInt->new( calc_32bit_signed($self->value() >> $other->val +ue()) ); }, '**' => sub { my ($self, $other, $swap) = @_; if( $VERBOSE_AnyInt > 0 ){ print STDOUT "** : operating on ".(defi +ned($self)&&defined($$self)?$$self:'<undef>')." and ".(defined($other +)?$other:'<undef>')." (swap is ".(defined($swap)?($swap?'yes':'no'):' +<undef>').")\n" } if( $swap ){ die "<< : order is important why is swap on?" } Math::AnyInt->new( calc_32bit_signed($self->value() ** $other->val +ue()) ); }, '""' => sub { ${$_[0]} } ; sub new { my ($class, $val) = @_; return bless \$val } # Corion's converting to 32bit signed int sub calc_32bit_signed { return unpack("l", pack("L", $_[0])) } ##sub calc_32bit_signed { unpack( "l", pack "L", ($_[0] & 0xffffffff)) + } # NERDVANA's converting to 32bit signed int #sub calc_32bit_signed { return calc_32bit_signed_in_C($_[0]) } use Inline 'C', <<~'END'; #include <stdint.h> long calc_32bit_signed_in_C(long input) { return (int32_t)((int32_t)input); } long test_addition_in_C(long l, long r) { return (int32_t)((int32_t)l + (int32_t)r); } END # return the integer value contain in this object # which is just a ref to the value (see new()) sub value { return ${ $_[0] } } # this does the arithmetic when the operator is in string format, usef +ul for testing sub calcop { my ($lop, $op, $rop) = @_; if( ref($lop) ne __PACKAGE__ ){ die 'calcop()'." : left-operant mu +st be a ".__PACKAGE__." object but it is just '".ref($lop)."'."; } if( ref($rop) ne __PACKAGE__ ){ die 'calcop()'." : right-operant m +ust be a ".__PACKAGE__." object but it is just '".ref($rop)."'."; } if( $VERBOSE_AnyInt > 0 ){ print STDOUT 'calcop()'." : called for +'$$lop' '$op' '$$rop' ...\n" } if( $op eq '+' ){ return $lop + $rop; } elsif( $op eq '-' ){ return $lop - $rop } elsif( $op eq '*' ){ return $lop * $rop } elsif( $op eq '/' ){ return $lop / $rop } elsif( $op eq '<<' ){ return $lop << $rop } elsif( $op eq '>>' ){ return $lop >> $rop } elsif( $op eq '**' ){ return $lop ** $rop } die 'calcop()'." : unknown operator '$op'."; } 1;

And here is a basic test harness:

#!/usr/bin/env perl use strict; use warnings; use lib 'blib/lib'; our $VERSION = '0.01'; use Test::More; use Math::AnyInt; $Math::AnyInt::VERBOSE_AnyInt = 1; my @tests = ( # left-operand, operator, right-operand, expected result in JS [1, '+', 2, 3], [1169367104, '<<', 5, -1234958336], [1073741824, '+', 1073741824, 2147483648], ); for my $at (@tests){ my ($lop, $op, $rop, $expected) = @$at; my $LOP = Math::AnyInt->new($lop); is($$LOP, $lop, 'Math::AnyInt->new()'." : called and contains valu +e '$lop'."); my $ROP = Math::AnyInt->new($rop); is($$ROP, $rop, 'Math::AnyInt->new()'." : called and contains valu +e '$rop'."); my $result = Math::AnyInt::calcop($LOP, $op, $ROP); is($$result, $expected, "$lop $op $rop : got $result, expected $ex +pected."); } # test addition of two integers by taking it in C via Inline::C # and using C's int32_t (NERDVANA) is(Math::AnyInt::test_addition_in_C(1073741824, 1073741824), 214748364 +8, "Testing NERDVANA's C additon calc()"); # END done_testing();

bw, bliako

Replies are listed 'Best First'.
Re^2: Trying to translate overflowing JS code to Perl
by syphilis (Archbishop) on Dec 01, 2023 at 08:10 UTC
    And here is a basic test harness:

    I think your implementation of NERDVANA's code is flawed.
    In specifying a return type of "long", the returned "int32_t" is being cast to a 64-bit integer on most 64-bit systems.
    Instead of "long", you should specify a return type of "int32_t" - though I think you'll get away with specifying "int" (thereby avoiding the typemap issue that NERDVANA mentioned), which is the same as "int32_t" everywhere.
    (By "everywhere", I mean "everywhere that I've come across", though I believe the standards actually allow that "int" can be the same as "int64_t".)

    With that change in place, you should see the last 2 tests fail, as they ought (in their present form).

    Thanks for the fun - and for going to the trouble of presenting Math::AnyInt.

    Cheers,
    Rob
      Instead of "long", you should specify a return type of "int32_t" - tho +ugh I think you'll get away with specifying "int" (thereby avoiding t +he typemap issue that NERDVANA mentioned), which is the same as "int3 +2_t" everywhere.

      Thanks for the suggestion (and in the other threads too!). But when I specify a int32_t as return type to NERDVANA's function via Inline::C it compiles fine but Perl complains that it can not find that function. I think it checks the signatures and does not consider a function with return as int32_t as a proper candidate. It works fine for int (and long) return type.

        It works fine for int (and long) return type

        For me, on Windows 11 and Ubuntu-22.04, both of which are running perl-5.38.0, the last 2 tests of the Math::AnyInt test suite are failing because:
        not ok 9 - 1073741824 + 1073741824 : got -2147483648, expected 2147483 +648. # Failed test '1073741824 + 1073741824 : got -2147483648, expected 2 +147483648.' # at ../math-anyint/test.pl line 28. # got: '-2147483648' # expected: '2147483648' not ok 10 - Testing NERDVANA's C additon calc() # Failed test 'Testing NERDVANA's C additon calc()' # at ../math-anyint/test.pl line 34. # got: '-2147483648' # expected: '2147483648' 1..10 # Looks like you failed 2 tests of 10.
        I'm wondering why that is.
        Looks like you're right - seems it not the reason I tendered. I was too lazy to start up the Ubuntu box, and on Windows both "int" and "long" are always 32 bits so the result will always be the same no matter which type is picked. So I took a (bad) guess.

        Do you know why you and I are getting different results ?
        Are we hitting undefined (or implementation defined) behaviour ?

        Here's a simple Inline::C script that tests for both "int" and "long" inputs and outputs:
        use strict; use warnings; use Inline C => <<'EOC'; /* As the function appears in AnyInt.pm */ long calc_32bit_signed_in_C(long input) { return (int32_t)((int32_t)input); } /* The same function, renamed to begin with an underscore, and return type altered to int */ int _calc_32bit_signed_in_C(int input) { return (int32_t)((int32_t)input); } EOC print calc_32bit_signed_in_C(1073741824 + 1073741824), "\n"; print _calc_32bit_signed_in_C(1073741824 + 1073741824), "\n"; __END__ Outputs: -2147483648 -2147483648
        But you're Math::IntAny test script implies that you are seeing:
        2147483648 2147483648
        Cheers,
        Rob