Attempting to understand the difference in computed result for a Math.imul (javascript) recreation in Elixir

128 Views Asked by At

Currently working on an Elixir project which has a need for the standard javascript method Math.imul functionality. After implementing the method in Elixir, the returned value is vastly different from that of the one produced by Javascript's Math.imul (via the shim here which I'm referencing for the recreation) which is an obvious problem if I'm going for value parity. My goal is for the Elixir function to produce the same value as the Javascript one. It appears that all of the values produced within both methods are identical right up until the final result calculation utilizing the bitwise shift left operator.

imul(-5, 12) produces...

Javascript:

const imul = (x: number, y: number) => {
    const a = ToUint32(x);
    // 4294967291
    const b = ToUint32(y);
    // 12
    const ah = (a >>> 16) & 0xffff;
    // 65535
    const al = a & 0xffff;
    // 65531
    const bh = (b >>> 16) & 0xffff;
    // 0
    const bl = b & 0xffff;
    // 12

    return (al * bl) + (((ah * bl) + (al * bh)) << 16)
    // -60
  }

Elixir:

def imul(x, y) do
    a = toUInt32(x)
    # 4294967291
    b = toUInt32(y)
    # 12
    ah = (a >>> 16) &&& 0xffff
    # 65535
    al = a &&& 0xffff
    # 65531
    bh = (b >>> 16) &&& 0xffff
    # 0
    bl = b &&& 0xffff
    # 12

    (al * bl) + (((ah * bl) + (al * bh)) <<< 16)
    # 51539607492
  end

  def toUInt32(num) do
    <<num :: integer-unsigned-32>>
    |> :binary.decode_unsigned(:big)
  end

I understand somewhat that Elixir operates on integers of arbitrary precision, so you don't need to worry about 32-bit integer limits as in JavaScript. I believe the semantics of bitwise operations are, however, similar in terms of manipulating individual bits so I'm slightly confused at what I'm doing wrong here and what I need to do to arrive at the same value that the canonical (Math.imul) method is arriving at, -60. Help appreciated!

1

There are 1 best solutions below

0
Adam Millerchip On BEST ANSWER

As Elixir doesn't have integer overflows, you don't have to worry about bitshifts and can simply multiply the values, then convert to a signed integer.

def imul(x, y) do
  <<result::signed-32>> = <<x * y::32>>
  result
end

More detailed explanation:

Javascript:

> 1 << 30
1073741824
> 1 << 31
-214748364

Elixir:

iex> 1 <<< 30
1073741824
iex> 1 <<< 31
2147483648

You can see that in Javascript, the result of a bitwise operation is assumed to be a 32-bit signed integer, so bitshifting left causes an integer overflow. In Elixir it is an unlimited-bit signed integer, so bitshifting left does not cause an integer overflow.

If you want to reproduce the same behaviour as Javascript, you have to use the binary special form to tell Elixir to convert the left-shifted number to a 32-bit signed integer:

iex> shifted_left = (ah * bl) + (al * bh) <<< 16
51538821120 # Equivalent to 101111111111111101000000000000000000, 36 bits
iex> <<shifted_left_with_integer_overflow::signed-32>> = <<shifted_left::32>>
<<255, 244, 0, 0>>
iex> (al * bl) + shifted_left_with_integer_overflow
-60

But as shown above, the bit-shifting is not necessary in Elixir in the first place.