Bit operation is not often used in JavaScript/TypeScript but there are some cases where we need to use it. I needed the bit operation because the application needs to work with another machine. If the first bit is on, the corresponding option is enabled for example. If the option is enabled, do this process, otherwise a different process.
Let’s learn about bit operations in JavaScript/TypeScript.
To show the bit, we’ll use the following function in this article.
function toBits(num: number, length = 8): string {
return num + ": " + Math.abs(num).toString(2).padStart(length, "0");
}
Shift operation Left << and Right >>
This is very basic. “>>” shifts the bits to right, while “<<” shifts it to left.
console.log(toBits(1 << 1)); // 2: 00000010
console.log(toBits(2 >> 1)); // 1: 00000001
console.log(toBits(2 >> 5)); // 0: 00000000
console.log(toBits(1 << 4)); // 16: 00010000
console.log(toBits(2 ** 4)); // 16: 00010000
console.log(toBits(Math.pow(2, 4))); // 16: 00010000
As you can see, this can be used when we want (2 ^ x ). It looks simple and short.
This bit operation has a pitfall. It can’t handle a big number.
console.log(toBits(2147483647 >> 0, 32)); // 2147483647: 01111111111111111111111111111111
console.log(toBits(2147483648 >> 0, 32)); // -2147483648: 10000000000000000000000000000000
console.log(toBits(2147483649 >> 0, 32)); // -2147483647: 10000000000000000000000000000001
console.log(toBits(2147483648 >>> 0, 32)); // 2147483648: 10000000000000000000000000000000
console.log(toBits(2147483649 >>> 0, 32)); // 2147483649: 10000000000000000000000000000001
console.log(toBits(4294967295 >> 0, 32)); // -1: 11111111111111111111111111111111
console.log(toBits(4294967295 >>> 0, 32)); // 4294967295: 11111111111111111111111111111111
2147483647 is the max number of signed 32 bits. 4294967295 is the unsigned max value. The shift operations convert the original number to 32 bits signed number. We should be careful of it.
If we want to keep it unsigned, we should use “>>>” instead which is used on lines 4, 5, and 7 in the example above.
OR operator |
If at least one of the two bits is 1, the bit becomes 1, otherwise, 0.
console.log(toBits(3)); // 3: 00000011
console.log(toBits(13)); // 13: 00001101
console.log(toBits(3 | 13)); // 15: 00001111
AND operator &
If both of the two bits are 1, the bit becomes 1, otherwise, 0.
console.log(toBits(3)); // 3: 00000011
console.log(toBits(13)); // 13: 00001101
console.log(toBits(3 & 13)); // 1: 00000001
XOR operator ^
If one of the two bits is 1, the bit becomes 1, otherwise 0.
console.log(toBits(3)); // 3: 00000011
console.log(toBits(13)); // 13: 00001101
console.log(toBits(3 ^ 13)); // 14: 00001110
NOT operator ~
This operator inverts the bits.
console.log(toBits(3, 32)); // 3: 00000000000000000000000000000011
console.log(toBits(~3, 32)); // -4: 11111111111111111111111111111100
console.log(toBits(13, 32)); // 13: 00000000000000000000000000001101
console.log(toBits(~13, 32)); // -14: 11111111111111111111111111110010
console.log(toBits(~0xFFFFFFFF, 32)); // 0: 00000000000000000000000000000000
One point that we should be aware of is the following example.
console.log(toBits(~0, 32)); // -1: 00000000000000000000000000000001
console.log(toBits(~0 >>> 0, 32)); // 4294967295: 11111111111111111111111111111111
I expected that ~0 became all bits on but actually not. If we want to turn all bits on, we need to make it an unsigned value by using “>>>”.
Bit operation handles up to 32 bits number
All bit operations can handle up to 32 bits. It means that the result becomes different from what we expect if the number is bigger than 32 bits or an unsigned 32 bits number.
console.log(toBits(2147483649, 32)); // 2147483649: 10000000000000000000000000000001
console.log(toBits(2147483649 | 0, 32)); // -2147483647: 10000000000000000000000000000001
console.log(toBits(2147483649 & 2147483649, 32)); // -2147483647: 10000000000000000000000000000001
console.log(toBits(2147483649 ^ 2, 32)); // -2147483645: 10000000000000000000000000000011
// this is one bit longer
console.log(toBits(4294967296, 33)); // 4294967296: 100000000000000000000000000000000
console.log(toBits(~4294967296, 32)); // -1: 00000000000000000000000000000001
Be aware of this fact if using bit operations.
Check if the target bit is on
If we want to check if the target bit is on, we can use the following function.
function isBitOn(value: number, bit: number): boolean {
const bitValue = 1 << bit;
return (value & bitValue) > 0;
}
console.log(toBits(4)); // 4: 00000100
console.log(isBitOn(4, 0)); // false
console.log(isBitOn(4, 1)); // false
console.log(isBitOn(4, 2)); // true
console.log(isBitOn(4, 3)); // false
Note that the argument “bit” starts with 0. It might be better to check if the specified “bit” value is not a negative value.
Turn the bit on/off
The next step is to turn a bit on/off.
There are two easy ways for turning a bit on. The first one adds the value corresponding to the bit if the bit is off. The second one is I feel easier if we know the bit operation.
function turnOn1(value: number, bit: number): number {
if (isBitOn(value, bit)) { return value; }
const bitValue = 1 << bit;
return value + bitValue;
}
function turnOn2(value: number, bit: number): number {
const bitValue = 1 << bit;
return value | bitValue;
}
console.log("--- turn a bit on ---");
console.log(toBits(4)); // 4: 00000100
const result1 = turnOn1(4, 5);
const result2 = turnOn2(4, 5);
console.log(toBits(result1)); // 36: 00100100
console.log(toBits(result2)); // 36: 00100100
To turn a bit off, we need to do the opposite thing. There are three ways for it.
- Subtract the corresponding value to the bit from the original value if the bit is on.
- Make all bits on except for the target bit by using XOR operation, then, use AND operator.
- Make all bits on except for the target bit by using NOT operation, then, use AND operator.
function turnOff1(value: number, bit: number): number {
if (!isBitOn(value, bit)) {
return value;
}
const bitValue = 1 << bit;
return value - bitValue;
}
function turnOff2(value: number, bit: number): number {
const bitValue = 1 << bit;
// 0xFFFFFFFF -> ~0 >>> 0
const reversed = 0xFFFFFFFF ^ bitValue;
return value & reversed;
}
function turnOff3(value: number, bit: number): number {
const bitValue = 1 << bit;
return value & ~bitValue;
}
console.log("--- turn a bit off ---");
console.log(toBits(36)); // 36: 00100100
const result1 = turnOff1(36, 5);
const result2 = turnOff2(36, 5);
const result3 = turnOff3(36, 5);
console.log(toBits(result1)); // 4: 00000100
console.log(toBits(result2)); // 4: 00000100
console.log(toBits(result3)); // 4: 00000100
End
Bit operations are useful in some cases but we should not use them for a different purpose. For example, when we want to get an integer value from a float value.
console.log(123.111 | 0) // 123
console.log(123.111 >> 0) // 123
console.log(4294967295.111 | 0) // -1
The intention is not clear and it can be error-prone, as you can see in the last line. We expect 4294967295 but the result is -1. We should use Math.trunc
in this case. The intention is clear.
console.log(Math.trunc(4294967295.111)) // 4294967295
Comments