If you use the upcoming UInteger32.isEven or isOdd methods, you’ll notice that it uses a bitwise AND operation. The reason, as described in a previous post, is because it improves performance.
However, while this is straightforward for all other integer wrapper classes, UInteger32
is an exception. According to ECMAScript 3 11.10:
The production A : A @B, where @ is one of the bitwise operators in the productions above, is evaluated as follows:
- Evaluate A.
- Call GetValue(Result(1)).
- Evaluate B.
- Call GetValue(Result(3)).
- Call ToInt32(Result(2)).
- Call ToInt32(Result(4)).
- Apply the bitwise operator @ to Result(5) and Result(6). The result is a signed 32 bit integer.
- Return Result(7).
The operands (and, thus, the result type) for the bitwise AND operation in ECMAScript are converted to 32-bit signed integers. System.UInteger32 represents an unsigned 32-bit integer.
This is inconvenient because we’d obviously have to fall back to the slower modulus operation for isEven/isOdd on UInteger32. Unless…
((Math.pow(2, 32) + 1) >>> 0 | 0) & 1 // true ((Math.pow(2, 32) + 2) >>> 0 | 0) & 1 // false
We can take advantage of overflow behavior. (Note: Since we’re able to get the correct result by leveraging overflow behavior, we actually don’t perform the extraneous zero-fill right shift as illustrated in the example.)
This is completely safe because:
A) Mathematically, in base 2, the last bit will always be 1 for odd numbers and 0 for even numbers… no matter how big the number is.
B) Bitwise AND will compare both bits in equal-length binary forms. Thus, no matter how big the number is, when you AND against 1, it will always be 0000001 (or zero-padded to whatever length is needed). Therefore, all the preceding bits don’t matter because they will always be ANDed against a zero bit. The only bit that matters is the trailing bit; see A for why this will always work.