This is a great question. I did some experimentation trying to figure out exactly what operations JavaScript is doing, but then I thought, "I bet the spec says." Sure enough, it did!
First I checked out the Bitwise Shift Operators section, and what I learned from that is what you already knew: Before doing bitwise operations, JavaScript converts its operands to 32-bit integers. For that, it links to the definition of an "abstract operation" (i.e. an algorithm to be implemented by JavaScript engines) called ToInt32. Happily, it's really easy to follow:
ToInt32: (Signed 32 Bit Integer)
The abstract operation ToInt32 converts its argument to one of 232 integer values in the range -231 through 231−1, inclusive. This abstract operation functions as follows:
- Let number be the result of calling ToNumber on the input argument. [This just converts non-numeric values like booleans and strings into numbers.]
- If number is NaN, +0, −0, +∞, or −∞, return +0.
- Let posInt be
sign(
number) * floor(abs(
number))
. [sign
returns -1 if number is negative or 1 if it's positive.]
- Let int32bit be posInt modulo 232; that is, a finite integer value k of Number type with positive sign and less than 232 in magnitude such that the mathematical difference of posInt and k is mathematically an integer multiple of 232.
- If int32bit is greater than or equal to 231, return int32bit − 232, otherwise return int32bit.
We can translate this directly into Ruby (I've numbered the steps 1–5 as comments):
def to_int32(number)
# (1)(2)
begin
sign = number < 0 ? -1 : 1
abs = number.abs
return 0 if abs == 0 || abs == Float::INFINITY
rescue
return 0
end
pos_int = sign * abs.floor # (3)
int_32bit = pos_int % 2**32 # (4)
# (5)
return int_32bit - 2**32 if int_32bit >= 2**31
int_32bit
end
So, does it work?
a = 144419633058839130000
puts to_int32(a)
# => 1482551184
b = 3903086624
puts to_int32(b)
# => -391880672
Seems legit!
Now, I'm sure there are more concise and probably faster ways to do this, but this should get you started.