JoelSanchez JoelSanchez - 4 months ago 9
Javascript Question

Implementing JavaScript's left shift operator in Clojure

I need to do a left shift operation that behaves the exact same way as JavaScript's. The problem is that this:

a << 16


behaves like Clojure's "bit-shift-left" only if a <= 32767:

// JS
32767 << 16 // 2147418112
32768 << 16 // -2147483648
567890 << 16 // -1437466624

;; CLJ
(bit-shift-left 32767 16) // 2147418112
(bit-shift-left 32768 16) // 2147483648
(bit-shift-left 567890 16) // 37217239040


I noticed that, when doing "37431 << 16", JS does something completely different from Clojure at a binary level. While Clojure transforms 1001001000110111 into 10010010001101110000000000000000, JS transforms 1001001000110111 into 1101101110010010000000000000000:

// CLJ, then JS
10 01001 00011 01110 00000 00000 00000
1 10110 11100 10010 00000 00000 00000


I notice this is two's complement, and I notice that JS may be doing this because it cannot (for some reason) use more than 32 bits for this (all bit-level operations done on 32 bits, maybe?), so I wonder if I should apply two's complement to the number if it is above 32767. But then again, I'm a Clojure newbie so I'm not very sure on how to do this.

Answer

Firstly, clojure.core/bit-shift-left will treat its left input as a long. You can use clojure.lang.Numbers/shiftLeftInt to shift a number as an int:

(clojure.lang.Numbers/shiftLeftInt 567890 16)
;= -1437466624

This matches the result you get in JavaScript. There's no wrapper around this static method in clojure.core, but you can provide your own.

Secondly, (clojure.lang.Numbers/shiftLeftInt 37431 16) evaluates to -1841889280 in Clojure (1.8.0) and 37431 << 16 evaluates to the same number, -1841889280, in Node (4.4.5), so I don't think there's any problem there. You'll have to apply >>> 0 to your number in JavaScript to get the expected bits in the string representation, though:

// Node 4.4.5
> ((37431 << 16) >>> 0).toString(2)
'10010010001101110000000000000000'

It's good to note that fishing out individual bits with & works fine without the >>> 0 "unsigned cast":

> (37431 << 16) & (1 << 31)
-2147483648
> (37431 << 16) & (1 << 30)
0
> (37431 << 16) & (1 << 29)
0
> (37431 << 16) & (1 << 28)
268435456

And you can compute both string representations in Clojure:

(Integer/toString (clojure.lang.Numbers/shiftLeftInt 37431 16) 2)
;= "-1101101110010010000000000000000"
(Integer/toBinaryString (clojure.lang.Numbers/shiftLeftInt 37431 16))
;= "10010010001101110000000000000000"

Note that in Java bit shift operators take only the rightmost 5 or 6 bits (for ints and longs, respectively) of the right operand into account, so if you try shifting an int or long by more than 31/63 bits, you won't get the result you expect. java.lang.BigInteger has a shiftLeft method that does not have this limitation.

Comments