---
title: "Binary Numbers"
description: "Bits, place value, and carry — how a machine counts in base 2."
source: https://vizlearn.app/cs/architecture/binary-numbers
---

# Binary Numbers

Binary numbers are whole numbers written in base 2, where each position holds a power of two instead of a power of ten. An 8-bit word has eight positions worth 128, 64, 32, 16, 8, 4, 2, and 1, counting from the left, so it can hold exactly the values 0 through 255.

The useful picture is an odometer. A car odometer has wheels with ten digits on them; a byte has wheels with two. Everything strange about binary numbers falls out of that one change: the words get longer, the carries happen constantly, and when the leftmost wheel rolls past its last digit there is no ninth wheel to catch the overflow. The value simply starts again at zero, which is why the deepest lesson in this page is not how to write 77 in bits but why 255 plus 1 is 0.

## How Binary Numbers Work

A position's meaning comes from where it sits, not from what is in it. In base 10 the 4 in 40 means forty because it sits in the tens column. In base 2 the same rule runs with a smaller base: position `k`, counting from 0 at the right, is worth 2^k. Bit 0 is worth 1, bit 3 is worth 8, bit 7 is worth 128. A bit is a yes-or-no answer to "is this position's value included?", and the number is the sum of every yes.

That gives you both directions for free.

To read a word, add up the weights of the positions holding a 1 and ignore the rest. `01001101` has 1s at positions 0, 2, 3, and 6, so it is 1 + 4 + 8 + 64 = 77. Nothing else contributes, because a 0 contributes 0 times its weight.

To write a number, run that sum backwards. You do not need to hunt for the biggest power of two that fits. Ask a cheaper question instead: is the number odd? If it is, bit 0 must be 1, because every other position is worth an even number and only bit 0 can supply the odd part. So `n % 2` reads off the lowest bit exactly. Then `n // 2` discards it and slides every remaining position down by one, which makes the next bit the new lowest bit. Repeat until nothing is left. The remainders come out lowest-position-first, which is the reverse of how you would write them on paper — a detail worth keeping straight, because it is the single most common source of a mirrored answer.

Counting is where the wheels really turn. Adding 1 to a word with 0 in bit 0 flips one bit and stops. Adding 1 to a word ending in 1 cannot stop there: that position is already full, so it resets to 0 and passes a carry to its neighbour. If the neighbour is also 1 it does the same thing, and the carry keeps travelling left through the run of 1s until it finds a 0 to land on. That is the ripple, and it is why `01111111 + 1` rewrites eight positions while `01110000 + 1` rewrites one.

## Binary Numbers Step by Step

The default run converts `n = 77` into an 8-bit word. Every position starts at 0, and the loop fills them one at a time from the right.

1. **77 is odd**, so `77 % 2 = 1` and bit 0 is 1. Then `77 // 2 = 38` is what is left to place.
2. **38 is even**, so `38 % 2 = 0` and bit 1 is 0. Then `38 // 2 = 19`.
3. **19 is odd**, so bit 2 is 1, and 19 becomes 9.
4. **9 is odd**, so bit 3 is 1, and 9 becomes 4.
5. **4 is even**, so bit 4 is 0, and 4 becomes 2.
6. **2 is even**, so bit 5 is 0, and 2 becomes 1.
7. **1 is odd**, so bit 6 is 1, and 1 becomes 0.

With `n` at 0 the loop has nothing left to place and stops. Notice what it never did: it never touched bit 7. That position was set to 0 when the word was created and no iteration reached it, so the word reads `01001101`, with its leading zero inherited rather than computed. Reading the word back the other way confirms the answer: 64 + 8 + 4 + 1 = 77.

Switch the mode selector to **Decode** and the same word runs through the inverse procedure. The scan visits bit 0 (a 1, so add 1), bit 1 (a 0, add nothing), bit 2 (add 4), bit 3 (add 8), bits 4 and 5 (both 0), bit 6 (add 64), and bit 7 (a 0), finishing at 77. Two procedures, one number, and you can always check one against the other.

**Count** mode starts at 13 (`00001101`) and adds 1 four times. The first increment flips bit 0 from 1 to 0 and lands the carry on bit 1, giving `00001110` = 14, two bits changed. The second finds a 0 in bit 0 and sets it in a single write: `00001111` = 15. The third is the interesting one. Bits 0, 1, 2, and 3 are all 1, so all four clear to 0 and the carry only comes to rest on bit 4 — five positions rewritten to move a number by one, ending at `00010000` = 16. The fourth increment is back to a single flip: 17.

On the canvas, the row of cells is the word, the labels above each cell are that position's weight, and the cursor below the row marks the position being written or read. The ledger under the word records every division, weight, or increment as it happens, so the arithmetic and the bits never drift apart.

## Why 8 Bits Stop at 255

Count the patterns. Each of the eight positions is independently 0 or 1, so there are 2 × 2 × 2 × 2 × 2 × 2 × 2 × 2 = 2⁸ = 256 distinct words. They have to represent 256 distinct values, and starting at 0 puts the last one at 255. The largest word, `11111111`, is 128 + 64 + 32 + 16 + 8 + 4 + 2 + 1, and that sum being exactly one less than 256 is not a coincidence: filling every position below 2⁸ always lands one short of it.

Now add 1 to that. Every position is a 1, so every position clears to 0, and the carry walks all the way past bit 7 looking for somewhere to land. There is nowhere. The word is not a number that grew too big; it is eight physical positions, and the ninth does not exist. The carry is dropped and the word reads `00000000`. Try it with the **Odometer wrap** scenario, which starts at 253: two ordinary increments, then the rollover, and it stops on `00000000` rather than counting past it.

This is not a quirk of the animation. It is what fixed-width machine arithmetic does, and it is the mechanism behind a whole family of real bugs: a counter that resets, a timestamp that jumps backwards, a level-99 character whose next kill sends them to level 0. A wider fixed-width counter moves the boundary; it does not remove it. Check the maximum before incrementing or use a checked operation when wrap is not acceptable. The truncated word alone does not report the lost carry, although hardware flags and checked APIs can.

Both conversions cost one pass over the word. Their time complexity is O(W) for a word of W bits, which for a fixed 8-bit word is constant time — you never loop more than eight times. An increment is O(W) in the worst case, when the carry ripples through every position: 127 → 128 and 255 → 0 both rewrite all eight bits, and any word needs its low seven bits all 1s to get there; averaged over all 256 starting values, an increment flips just under two bits.

## Edge Cases

- **`n = 0`** never enters the loop at all. There is no lowest bit to peel off, so the word keeps the zeros it was created with and reads `00000000`.
- **`n = 255`** is the other extreme: eight divisions, every position written, and no room left over.
- **A power of two** such as 64 sets exactly one position, because 64 *is* 2⁶. Any word with a single 1 in it is a power of two, which is the fastest way to recognize one by eye.
- **Decoding `00000000`** produces 0 rather than an error. No position is set, so nothing is added.
- **Counting past 255** wraps to 0 and keeps going. The word never signals that anything went wrong.

## Common Mistakes

**Writing the remainders in the order they come out.** The divisions produce bit 0 first, then bit 1, and so on, but a written word puts bit 7 on the left. Reading the ledger top to bottom gives you the word backwards. Here the mirror image of `01001101` is `10110010`, which is 178, not 77 — a wrong answer that looks completely reasonable.

**Assuming the leading zeros were computed.** For 77 the loop stops after seven divisions, so bit 7 is 0 only because the word was initialized to zeros. In a language where you build up a string of bits instead, forgetting to pad to the full width gives you a 7-character word that no longer lines up with anything else in the byte.

**Reading position numbers from the left.** Bit 7 sits on the left and bit 0 on the right, so the positions count down as you read across. Weighting the leftmost position as 2⁰ produces the mirrored value described above, and it is a stubborn habit because everything else in programming is indexed left to right.

**Expecting 255 + 1 to be 256.** In a fixed-width word it is 0. Nothing raises an exception and nothing announces the loss; the carry leaves and the count restarts.

**Confusing "how many bits flipped" with "how much the value changed".** Every increment adds exactly 1, whether it rewrites one position or eight. The five-bit ripple from 15 to 16 is not a bigger jump than the single flip from 14 to 15.

## A Note on Simplification

This lesson fixes one model — an unsigned 8-bit word — so that place value, conversion, and the carry stay visible in a single row of cells. Real machines work in 16-, 32-, or 64-bit words, and languages differ in what they do at the edges: Python integers grow to whatever size they need and never wrap, while a fixed-width unsigned integer in C wraps modulo its width. Rust checks integer overflow in debug mode and can panic; explicit wrapping operations request the behavior shown here. See the [Rust overflow rules](https://doc.rust-lang.org/reference/expressions/operator-expr.html#overflow). Signed types are a separate question, and not the same one: in C, signed overflow is undefined behaviour rather than a defined wrap. Negative numbers are missing entirely, because they need a second convention layered on top of place value; that is what two's complement adds, and it has its own lesson. This page is an explainer for how a machine writes down a whole number, not a full account of machine arithmetic.
