This article is based on alightning talk I recently did at dotJS and it was written forMariko’sWeb Advent Calendar. Check all the other interesting articles, speciallyPam’s andRicardo’s!
I’m not entirely sure about how many web developers know about (or even use) it, but JavaScript is capable of binary. 0s and 1s can easily be manipulated with bitwise operators on our favourite language and that’s what I’ll present on this post.
First of all,why? Why would you care about this? In your years of web development, you probably never had the need to use any of these operations, so why are you even reading this? OMG is it one more thing to know and add to myJavaScript fatigue??
Don’t worry, this is just a piece of curiosity. Please keep reading if you love quirks! This article will be a brief introduction to the available bitwise operations, but I can already recommend you agreat post fromDan Prince. In short, he was able to greatly reduce the memory footprint of a game we was developing using bitwise operators. He was working on a 512x512 pixel matrix, using Plain Old JavaScript Objects to represent each pixel. However, using just the strictly necessary bits to save the game’s state, each object was replaced by an integer, reducing the memory consumption four times! You’ll find more info in his blog post.
A few technicalities first
Let me quickly tell you a few important technical details about how JavaScript deals with numbers and binary operators.
Numbers are stored using 64 bits
Basically, all numbers in JavaScript are floating point. A single bit for sign (0 for positive and 1 for negative numbers), 11 bits exponent bits to indicate where the point is, and finally 52 bits representing the actual digits of the number.
sign | exponent | fraction(1 bit) | (11 bit) | (52 bit) 63 | 62 -- 52 | 51 --- 0
Numbers with more than 32 bits get truncated
It means that, from the 64 bits you read on the previous paragraph, we’ll only keep the 32 at the right (i.e. the least significant).
// 15872588537857Before:11100110111110100000000000000110000000000001After:10100000000000000110000000000001// 2684379137vara=(-5>>>0).toString(2);// "11111111111111111111111111111011"parseInt(a,2);// 4294967291
Bitwise operations are performed on pairs of bits
Operations are performed by pairing up each bit in the first operand with the corresponding bit in the second operand. Example:
// Using only eight bits here for illustration purposes:vara=9;// 0000 1001varb=5;// 0000 0101a&b->a// 0000 1001&&&&&&&&b// 0000 0101---------00000001->1(base10)
Bitwise operators
JavaScript has seven bitwise operators, all of them convert their operands to 32-bit numbers.
&
(AND)
| a | b | a & b ||---|---|-------|| 0 | 0 | 0 || 0 | 1 | 0 | | 1 | 0 | 0 || 1 | 1 | 1 |
Simply speaking,&
results in0
if there is at least one0
.
|
(OR)
| a | b | a | b ||---|---|-------|| 0 | 0 | 0 || 0 | 1 | 1 || 1 | 0 | 1 || 1 | 1 | 1 |
In the case of|
, the output will be1
if there is at least one1
.
^
(XOR)
| a | b | a ^ b ||---|---|-------|| 0 | 0 | 0 || 0 | 1 | 1 || 1 | 0 | 1 || 1 | 1 | 0 |
Different bits will result in1
, simply put. I must admit XOR is my favourite, it can be quite baffling. 10 points to whoever knows what the following code does:
vara=1,b=2;a^=b;b^=a;a^=b;// wat?
If you didn’t get it, don’t worry, you are not alone. It’s a very obfuscatedvalue swap without a third variable (only between integers, though). Check this out:
vara=1;// 0001varb=2;// 0010a^=b;// 0001 ^ 0010 = 0011b^=a;// 0010 ^ 0011 = 0001a^=b;// 0011 ^ 0001 = 0010console.log(a);// 2 (0010)console.log(b);// 1 (0001)
~
(NOT)
NOT
operator simply inverts all the bits, including the sign. It’s like inverting the colours of an image.
9 = 00000000000000000000000000001001 --------------------------------~9 = 11111111111111111111111111110110 = -10 (base 10)
Applying~
on any number x results on -(x + 1). In the example above, ~9 yields -10. This is related to the way JavaScript represents 32-bit numbers usingtwo’s complement (something that we will not get into detail here).
<<
(left shift)
<<
pushes 0-bits from the righttowards the left, droping as many from its left as the ones pushed from its right.
9 : 0000 0000 10019 << 2 : 0000 0010 0100 // 36 ^^ new bits
>>
(Sign-propagating) right shift
>>
shifts bits towards the right, but it is not simply calledright shift because unlike the left shift, it doesn’t push always zeros. The bit pushed depends on the sign of the number: if the number is positive, 0-bits will be pushed; if the number is negative, 1-bits will be used instead.
9 : 0000 0000 1001 9 >> 2 : 0000 0000 0010 // 2 ^^ new bits-9 : 1111 1111 0111-9 >> 2 : 1111 1111 1101 ^^ new bits
>>>
(Zero-fill) right shift
>>>
is a specific case of right shift, where the new bits coming from the left towards the right are always 0, independent of the sign of the number. A consequence of it is that it turns any negative number into positive.
9 : 0000 0000 1001 9 >>> 2 : 0000 0000 0010 ^^ new bits-9 : 1111 1111 0111-9 >>> 2 : 0011 1111 1101 ^^ new bits
Fun with bitwise operators
So what can we do with these operators? Given their quirks and behaviour, let’s see some weirdness in action. A lot of these quirks stem from the transformation from 64-bit to 32-bit.
Truncate numbers
vara=3.14;varb=-3.14;console.log(a&a,b&b);// 3, -3console.log(a|0,b|0);// 3, -3console.log(~~a,~~b);// 3, -3
Convert strings to numbers, emulatingparseInt
vara='15'>>>0;varb='15.4'>>>0;console.log(a,b);// 15, 15varc='3.14';vard=c|0;vare=c&c;console.log(d,e);// 3, 3
Multiply a number by multiples of 2
console.log(7<<1);// 7 * 2 * 1 = 14console.log(7<<2);// 7 * 2 * 2 = 28console.log(7<<3);// 7 * 2 * 3 = 56// …
Different sub-string search
varstring='javacript';varsubstr='java';// If the sub-string is found,// appying NOT to the index will return a negative number,// which is a truthy value;// If not found, `indexOf` will return -1,// which in turn ~(-1) == 0, into the `else` case.if(~string.indexOf(substr)){// Found the sub-string!}else{// Nope, no match}
So… should you use this?
Short answer… no.
Long answer… it depends. As you’ve seen, there are a lot of gotchas and quirks people need to be aware of when using this. You need to know the variable types you’re dealing with, and that’s hard(er) to do in a dynamically typed language like JavaScript. You wouldn’t want to accidentally truncate numbers with decimals or make a negative number positive.
Other issue you should have in consideration is the consequent code obfuscation when you decide to writex << 1
instead orx * 2
, for example. However, this might be a compromise you are willing to do, which becomes pretty manageable with wrappers liketiny-binary-format.
Finally, keep in mind thatDouglas Crockford doesn’t like it, considering it one of the bad parts of JavaScript.
However, for side projects or applications where you need to squeeze more out of the hardware you are working on, why not? I write JavaScript for fun on my personal projects, and in those cases I like to do different things than I do on my daily job. If that involves shifting bits left & right, good for you! Keep your code weird and interesting — and learn something along the way.