0FF (cyan) expecting
F00 (red) and of course that's not what I saw, so I've written up (so I remember) how it works.
MY EVENTAttend ffconf.org 2018
The UK's best JS and web development conference. 8 amazing speakers, workshops, socials — find out more & get tickets today.
Everything is represented computationally as a bit. A bit is a
1. Character, for instance, are typically 8 bits. The letter
R has a decimal value of
"R".charCodeAt(0)), which has a hex value of
0x52 and a binary value of
0b1010010. Although the binary shows as 7 bits, it's more useful to think of it as 8 bits AKA 1 byte (or certainly in my case).
Note the preceding
0x means hex, and
0b means binary (off topic, but octal is
So, my (cyan) value of
0x0FF can be represented as 4
0s and two sets of 4
0b000011111111. Let's look at bitwise not'ing the value.
The not operation will invert the binary value (example taken from MDN):
~ 0b1 (where
1 is the binary value I want to invert).
Here's when things get hairy. Running
What is a number?
In the example above, I expected to see
0 as the result, but I see
-2. Why is that?
There's a few things going on that are initially misleading. First of all, the result is a decimal, not binary. In all the REPLs I've come across (devtools' console, jsconsole, etc) show the results as a
If I transform the result to binary, we can get a better idea of what's going on:
A quick breakdown:
n >>> 0is a zero-fill right which will (if I understood correctly) drop the sign on the integer which results in a large decimal value (i.e. -1 in binary is all
1values including the far left sign bit).
Number.toString(2)sets the radix to base 2 (binary) and returns a string
padStart(64, sign)uses the ES6 padStart (aka pad left) with the sign bit (if our number was negative, the sign is
0) and fills the left part of the string until the length is 64 characters (or to us: 64 bits)
Why does not 1 equal minus 2?
Now that we have a better understanding of 64 bit numbers, we can see that inverting
0b1 is actually inverting the first 63 zero bits and then the final one bit. Because the sign bit is also flipped, the result is 63 one bits and one zero bit:
What we really want is some kind of clamping on our bits. Here we can use typed arrays to help.
Typed arrays for unsigned bytes
new Uint8Array will give us an array that holds unsigned bytes in each array element. The unsigned is key getting the not operation to work the way we expect. So
0b1 will look like
00000001 and flipping correctly will be
Keeping things simple, I'll create a new typed array with single element containing the
0b1 value from earlier. Now, with this unsigned byte, running the not operation yields the same result in the console, but the stored result is actually as expected:
My original requirements were also hugely flawed. I had hoped to flip red to cyan with the assumption that
#0FF would flip to
#F00. I spotted my mistake though. The hex number is
0xFF (or leading zeros, as with decimal, aren't required), if we pad left, the value is
That value isn't red at all. In fact, in most browsers (at time of writing) don't support 8 digit hex values (although apparently Firefox does and so does Chrome Canary). This colour (in 8 digit hex) is blue!
The next job would be to put this value into the typed array, but now the
Unit8Array is too small. Each array element in the
Uint8Array can be 8 bits, the value in hex is 32 bits (including the leading zeros). I could switch out to using the
Uint32Array and repeat the process from eariler, and it would work, but it feels…clunky. To the point where I'm realising that the entire premise was flawed.
But now I've learnt all about bitwise not, the answer to my original problem lies in using the bitwise XOR.
XOR…or: what I should have done
This was a fun dive into understanding the not operator, but if I had really just wanted to flip red to cyan, XOR against
0xFFF would have been the way.
a XOR b, XOR yields binary
1 if a and b are different, and binary
0 if they're the same. Thus generating an inverted result. So, a bitwise XOR against
0xFFF will always flip every individual bit, and importantly, it will leave the sign bit alone (i.e. we won't suddenly get negative values).
Well, that was fun. I've crawled out of my rabbit hole, and returning to work. I hope it was fun for you too!