Sometimes you want to perform bit operations in JavaScript, and due to it's mutating nature, it's easy to get in a muddle. I recently wanted to run a bitwise `not`

on `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.

UK EVENT**Attend ffconf.org 2024**

The conference for people who are passionate about the web. 8 amazing speakers with real human interaction and content you can't just read in a blog post or watch on a tiktok!

**£249+VAT - reserve your place today**

## Bits

Everything is represented computationally as a bit. A bit is a `0`

or `1`

. Character, for instance, are typically 8 bits. The letter `R`

has a decimal value of `82`

(via `"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 `0o`

).

So, my (cyan) value of `0x0FF`

can be represented as 4 `0`

s and two sets of 4 `1`

s: `0b000011111111`

. Let's look at bitwise not'ing the value.

## Bitwise not

The not operation will invert the binary value (example taken from MDN):

a | NOT a |
---|---|

0 | 1 |

1 | 0 |

In JavaScript this is represented as `~ 0b1`

(where `1`

is the binary value I want to invert).

Here's when things get hairy. Running `~ 0b1`

in JavaScript yields `-2`

:

## 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 `Number`

type. Numbers are decimal in JavaScript.

Knowing that numbers are decimal isn't quite enough though. Numbers in JavaScript are double-precision floating-point format occupying 64 bits (or 8 bytes).

If I transform the result to binary, we can get a better idea of what's going on:

A quick breakdown:

`n >>> 0`

is 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`1`

values 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`1`

otherwise`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: `-2`

.

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

Creating a `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 `11111110`

.

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:

## Flipping #0FF

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 `0x0000FFFF`

.

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.

For `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!