Let’s take a look at a single line of code from splitmix32, a pseudo-random number generator that operates on 32 bit values:
z = (z ^ (z >> 16)) * 0x85ebca6b;
Ignoring the number of theoretical aspects of why the algorithm is performing these operations — this is a fairly straightforward line of code in C, and maybe other languages that have adopted C semantics, like Rust. This code takes our variable z
- in this case a 32 bit unsigned integer - and xors it with the value of z
shifted right by 16 bits. We then multiply that by the value 0x85ebca6b
.
In this example, let’s start with a value of z
of 0xc0ffee51
- this line of code will set z to 0x4537ceba
. This same line of code happens to be valid JavaScript - but running it against our initial value of 0xc0ffee51
gives us 0x20f51f3bb9a2ce00
... a very different result.
There are several problems here: first, the right shift operator in JavaScript (>>
) is an arithmetic right shift — or a signed right shift, which treats z
as a two’s complement signed integer. What we want is the unsigned right shift operator (>>>
). Next, the multiplication operator in JavaScript (*
) operates on Number
s ,which are 64-bit IEEE 754 floating-point numbers. So multiplying two large 32-bit numbers can give you a number that is larger than 32 bits. Instead, we can look to [Math.imul](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/imul)
, which gives 32-bit multiplication with C-like semantics.
With those two corrections in mind, the same line of code in JavaScript becomes:
z = Math.imul((z ^ (z >>> 16)), 0x85ebca6b);
This is not a particularly complicated change, but it is a change, and a rather subtle one. If you’re accustomed to thinking about bitwise manipulation in terms of C semantics, it’s easy to get this wrong and when you do, it’s hard to find the problem.