Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Bitwise operations with big integers

I am implementing decoding of BER-compressed integers and recently I've found a weird JavaScript behavior related to bitwise operations with big integers.

E.g.:

var a = 17516032;          // has 25 bits
alert(a << 7)              // outputs -2052915200
alert(a * 128)             // outputs  2242052096
alert(2242052096 >> 16)    // outputs -31325
alert(2242052096 / 65536)  // outputs  34211

While the first workaround (multiplication instead of left shift) is acceptable, the second isn't.

Why it happens? How to bear with it?

like image 693
Roman Bodnarchuk Avatar asked Jul 27 '11 16:07

Roman Bodnarchuk


4 Answers

17516032 in binary is 00000001000010110100011000000000. Shifting to the left by 7 gives you 10000101101000110000000000000000. This is equal to -2052915200 in two's complement (which is how almost all computers represent negative numbers).

>> is a signed right shift. That means that the leftmost bit (which determines the sign of a number) will be shifted into the left side.

e.g.

1100 >> 2 == 1111
0111 >> 2 == 0001

If you want to do an unsigned shift (which ignores the sign bit), use >>> which will zero-fill the left end of the bitstring.

like image 70
tskuzzy Avatar answered Oct 29 '22 19:10

tskuzzy


Bitwise operators work on 32 bit integers, while multiplication and division works on floating point numbers.

When you shift a number, it's converted from a floating point number to a 32 bit integer before the operations, and converted back to a floating point number after the operation. The number 2242052096 has the 32nd bit set, so it is a negative number when converted to and from a 32 bit integer.

The >> right shift operator doesn't change the sign of the value, i.e. the bits that are shifted in from the left have the same value as the sign bit. Use the >>> right shift operator to shift in zero bits instead.

Reference: MDN: Bitwise operators

like image 31
Guffa Avatar answered Oct 29 '22 19:10

Guffa


(2242052096 / 65536) == (2242052096 >>> 16)

Note the different shift.

like image 40
Griffin Avatar answered Oct 29 '22 19:10

Griffin


Javascript normally represents numbers as (double-precision) floating point.

Almost all bitwise operations convert to a signed 32-bit integer, do whatever they're going to do, then treat the result as a signed 32-bit integer when converting back.

The exception is >>> which treats the result as an unsigned 32-bit integer when converting back.

So:

  • right shifts can be made to work simply by using >>> instead of >> ;
  • a * 128 gives the expected answer because it's never converted to a signed 32-bit integer in the first place - it's just a floating-point multiplication;
  • a << 7 gives an unexpected answer because it's converted to a signed 32-bit integer, and then you shift a 1 into the sign bit, resulting in a negative signed 32-bit value.

There isn't a <<<, but if you want to write your left shift as a shift, you can use

(a << 7) >>> 0

to get the expected answer (the >>> 0 effectively casts the signed 32-bit value to an unsigned 32-bit value).

like image 33
Matthew Slattery Avatar answered Oct 29 '22 17:10

Matthew Slattery