Recently I came acrosss a LinkedIn post where someone said: in order to improve isEven(n: number) performance, we can use bitwise operator instead of modulo. At a glance I totally agree with that oppinion.

However, when I look closely to the code, I realized something wasn’t right. I can’t find the original post, so this is what I remember. The function is short so I believe I won’t mess anything up.

function isEven(n: number) {
    return (n ^ 1) === (n + 1);
}

In Javascripts, a number can be an integer, decimal, or NaN, or Inf or event BigInt. But let’s assume we only use this for integer.

This piece of code is wrong. If we plug a very “small” negative number in, for example, -9007199249885170 which is greater than Number.MIN_SAFE_INTEGER = -(2^53-1) is a safe integer. We get

const n = -2
isEven(n)
//true

const a = -9007199249885170
isEven(a)
// false

a^1
// 4855823 - WHAT????

We thought we only flipped the last bit (or index 0 bit) of a but somehow everything is messed up. This is so interesting but I think it deserves an other post when I dive deep into Javascripts 54 bits two’s complement integer. But for now, let’s stick with the original problem.

So, normally, we would do n & 1 === 0 to check even number instead.

We can write a simple test to prove our assumption

function realEven(n: number) {
    return (n % 2) === 0;
}
function checkEvenFunction(isEvenFn: (n: number) => boolean) {
    for (let i = Number.MIN_SAFE_INTEGER; i <= Number.MAX_SAFE_INTEGER; i ++) {
        assert.equal(isEvenFn(i), realEven(i), i);
    }
}

function isEvenXor(n: number) {
    return (n ^ 1) === (n + 1);
}

function isEvenAnd(n: number) {
    return (n & 1) === 0;
}

And then call

checkEvenFunction(isEvenXor);

And

checkEvenFunction(isEvenAnd);

😪 I hope you didn’t try this.

Anyway, we have learned how not to check an integer is even in Javascripts. And we’ve done.

Scrolling through comments of the LinkedIn post, there’s a guy who claimed to be a compiler designer. And he said using % or & don’t matter, because compilers are smart enough to optimize it. 🤔 an other “wait a minute” moment.

So he’s saying that I have been writing & the whole time without improving any nanosecond of my Leetcode solutions? IT CAN’T BE!!! But he’s a compiler designer, he ain’t be wrong… Ok, I have to do fact check myself. Can’t let this slip through.

#include <stdio.h>

int main() {
    int a;
    scanf("%d", &a);

    if (a % 2) {
        printf("a is odd\n");
    }
    return 0;
}

This is straight forward check, we need to read a from stdin otherwise compiler would evaluate a % 2 at compile time and eliminate redundant code.

gcc main.c -o main
objdump --disassemble-symbol=main

Let’s checkout the actual assembly code. You can view full assembly code here Online Compiler

// gcc -g -o output.s -masm=intel -fno-verbose-asm -S -fdiagnostics-color=always example.cpp
// ....
        mov     eax, DWORD PTR [rbp-4]
        and     eax, 1
        test    eax, eax
        je      .L2
        mov     edi, OFFSET FLAT:.LC1
        call    puts
.L2:
        mov     eax, 0
        leave
        ret

Without any optimization flag, gcc changes a % 2 to a & 1 .

// clang -O0 ...
// ...
        mov     eax, dword ptr [rbp - 8]
        mov     ecx, 2
        cdq
        idiv    ecx
        cmp     edx, 0
        je      .LBB0_2
        lea     rdi, [rip + .L.str.1]
        mov     al, 0
        call    printf@PLT

No optimization flag -O0 For clang, it still use idiv and get modulo from edx registry. Therefore, without optimization flag, clang keep our code as it is.

// clang -O3
// ...
        test    byte ptr [rsp + 4], 1
        je      .LBB0_2
        lea     rdi, [rip + .Lstr]
        call    puts@PLT

Aggressive optimization flag -O3 Trying optimization flag from -O1 to -O3 give the same result. We see clang use test instruction to compare bits of a with number 1. It is equivalent of a & 1. So we can conclude that clang also optimize modulo check if we ask for optimization.

Note that this only work with if (n % 2) check, not assignment expression like int x = n % 2. Because the result would be assigned to x and negative numbers have negative modulo. Therefore, compiler can’t replace it with & 1 always produce either 0 or 1.

And according to this article from Leetcode, they compile C/C++ with -O2 flag. So my n & 1 manual optimization is wasted 🤣

Now I wonder how V8 optimize this kind of logic with integer number 🤔 . I don’t have d8 built on my computer now, so this has to be delayed a bit.