5

I've found that working with integers in Ruby causes them to act different than JS when their binary representation is larger than 32 bits.

a = 144419633058839139324
b = 3903086624

JS:

a >> 0; => 1482555392
b >> 0; => -391880672

Ruby:

a >> 0 => 144419633058839139324
[a].pack('V').unpack('V').first => 1482560508
[b].pack('V').unpack('V').first => 3903086624

My question is how can I convert my Ruby code to give the same return values JS?

Travis
  • 13,311
  • 4
  • 26
  • 40
  • I'm not completely sure how to do this, but this might help: http://stackoverflow.com/questions/12125421/why-does-a-shift-by-0-truncate-the-decimal – itdoesntwork Oct 06 '14 at 02:19
  • what do you mean by truncate ? why would you do that to Integer ? – Siva Oct 07 '14 at 04:45
  • Truncate meaning removing part of the binary representation of the integer and returning the truncated portion. Essentially in Ruby: bin = `a.to_s(2) => "1111101010000111001100110110001100101011000010111100001001111111100"` `bin.to_i(2) => a` `bin[-32..-1].to_i(2) => 1482560508` Ruby correctly truncates the value, however the issue is with JS rounding the value. A program I'm implementing in Ruby from JS relies on returning the same JS values. – Travis Oct 07 '14 at 15:34

1 Answers1

6

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:

  1. Let number be the result of calling ToNumber on the input argument. [This just converts non-numeric values like booleans and strings into numbers.]
  2. If number is NaN, +0, −0, +∞, or −∞, return +0.
  3. Let posInt be sign(number) * floor(abs(number)). [sign returns -1 if number is negative or 1 if it's positive.]
  4. 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.
  5. 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.

Jordan Running
  • 102,619
  • 17
  • 182
  • 182