root.system / 0x02 / binary

Two symbols.
Everything else.

Every photo, song, message and program on every device you've ever touched is, at the lowest level, a sequence of 0s and 1s. This page walks you from "what does that even mean" all the way down to two's complement and IEEE 754, which is the machine's actual view of a number.

Beginner// level 01

What is binary, really?

In 1947, a physicist at Bell Labs made electricity change direction. He called it a transistor.

A transistor is just a switch. It has two states. High voltage or low voltage. On or off. 1 or 0.

That is the entire foundation of every computer ever built. Not because someone chose binary arbitrarily; because physics made it inevitable. A switch with two states is the simplest reliable building block that exists. Everything else is just what you can build when you wire enough of them together.

You already know how to count. When you write 237, you don't think about it, but you're using a system called base-10: ten symbols (0 through 9), and each position is worth ten times more than the one to its right.

237 means 2×100 + 3×10 + 7×1.

Binary is the same idea, but with only two symbols: 0 and 1. Each position is worth twice as much as the one to its right. That's it. That's the whole thing.

Counting the binary way

positionvalueexample bitcontributes
2⁰111
200
414
818

1101 in binary  =  8 + 4 + 0 + 1  =  13 in decimal

Why two symbols?

Computers are built from billions of tiny switches. A switch has two natural states: off (low voltage) and on (high voltage). Map 0 to off, 1 to on, and suddenly numbers, letters, images, and code are all just patterns of switch-states. The switch itself, the transistor, gets its own page later on.

Try it: toggle eight switches

// the bit toggle — click any bit
64 + 8 = 72
decimal72
binary01001000
hex0x48
This is the letter H in ASCII

click any bit to toggle it. the binary, decimal, and hex below all update in lockstep. they are three notations for the same eight switch-states.

Every transistor in your CPU is one of these switches. Your CPU has about 100 billion of them. ← see: logic gates

Your first program: print a number in binary

Rust• • •
fn main() {
    let n: u8 = 13;
    // {:08b} = 8-digit binary, zero-padded
    println!("{} → {:08b}", n, n);
    // prints: 13 → 00001101
}
C• • •
#include <stdio.h>

int main(void) {
    unsigned char n = 13;
    // C has no %b, so print bit by bit
    printf("%d → ", n);
    for (int i = 7; i >= 0; i--)
        putchar((n >> i) & 1 ? '1' : '0');
    putchar('\n');
    return 0;
}
// takeaway
A bit is one switch. Eight bits make a byte. Everything bigger is just more bytes.
Intermediate// level 02

Bitwise operations & signed numbers

Once numbers live as bits, you can do something you can't do as easily in decimal: operate on each bit independently. These are the bitwise operators, and they're shockingly fast because the CPU can do them in a single cycle.

opnamedoesexample
&ANDboth bits 10b1100 & 0b1010 = 0b1000
|OReither bit 10b1100 | 0b1010 = 0b1110
^XORbits differ0b1100 ^ 0b1010 = 0b0110
! / ~NOTflip every bit~0b1100 = 0b0011 (in 4 bits)
<<shift leftmultiply by 20b0011 << 1 = 0b0110
>>shift rightdivide by 20b0110 >> 1 = 0b0011

Negative numbers: two's complement

Computers don't have a "minus sign" wire. So how do they store −5? They use a clever convention called two's complement: take the positive number, flip every bit, then add 1. The leftmost bit becomes a sign bit: 0 for positive, 1 for negative.

 5 (8-bit) = 00000101
flip = 11111010
+ 1 = 11111011  ←  this is −5

The genius: addition just works. 5 + (−5) as bits is 00000101 + 11111011 = 100000000. The 9th bit overflows out, and you're left with 00000000 = 0. The hardware doesn't need separate adders for signed and unsigned numbers.

SHA-256, the algorithm that secures Bitcoin, is 64 rounds of AND, OR, XOR, NOT, bit rotations and bit shifts. The same six operations in the table above. Logic gates doing mathematics at billions of cycles per second. ← see: hashing

Bit tricks you'll actually use

Rust• • •
fn main() {
    let x: u8 = 0b0010_1100;

    // is bit 3 set?
    let bit3 = (x >> 3) & 1;       // 1

    // set bit 0
    let y = x | 1;                  // 0010_1101

    // clear bit 2
    let z = x & !(1 << 2);          // 0010_1000

    // toggle bit 5
    let w = x ^ (1 << 5);           // 0000_1100

    // count ones (population count)
    let ones = x.count_ones();      // 3

    println!("{} {} {} {} {}", bit3, y, z, w, ones);
}
C• • •
#include <stdio.h>

int main(void) {
    unsigned char x = 0b00101100;

    // is bit 3 set?
    int bit3 = (x >> 3) & 1;       // 1

    // set bit 0
    unsigned char y = x | 1;        // 0010_1101

    // clear bit 2
    unsigned char z = x & ~(1 << 2); // 0010_1000

    // toggle bit 5
    unsigned char w = x ^ (1 << 5); // 0000_1100

    // count ones (gcc/clang builtin)
    int ones = __builtin_popcount(x); // 3

    printf("%d %u %u %u %d\n", bit3, y, z, w, ones);
    return 0;
}
// the pattern
Test a bit with &, set with |, clear with & ~, toggle with ^. Memorise this and bit manipulation becomes muscle memory.

When the OS marks a file as readable, writable, or executable it sets three bits in a permission byte. chmod 755 is just three octal digits. Each one is three bits. Your entire filesystem security model is bitwise flags. ← see: operating system

Advanced// level 03

Floats, endianness & why 0.1 + 0.2 ≠ 0.3

IEEE 754: how computers store decimals

Integers are easy: a fixed pattern of bits, one fixed value. Decimals are a different story. Computers use a binary version of scientific notation called IEEE 754. A 32-bit float splits into three fields:

sign
1 bit
0 = positive, 1 = negative.
exponent
8 bits
Biased by 127. Tells you which power of 2 to multiply by.
mantissa
23 bits
The significant digits, in binary, with an implicit leading 1.

The number is reconstructed as (−1)sign × 1.mantissa × 2exp − 127. The catch: most decimal fractions aren't exactly representable in binary. 0.1 in binary is a repeating fraction, just like 1/3 is in decimal. So 0.1 + 0.2 stores as 0.30000000000000004 on virtually every machine.

Rust• • •
fn main() {
    let a: f32 = 0.1;
    let b: f32 = 0.2;

    // raw 32-bit pattern of 0.1
    println!("0.1 bits = {:032b}", a.to_bits());
    println!("0.1+0.2 = {}", a + b);
    // 0.30000001
    println!("equal?  = {}", a + b == 0.3);
    // false
}
C• • •
#include <stdio.h>
#include <stdint.h>
#include <string.h>

int main(void) {
    float a = 0.1f, b = 0.2f;

    // reinterpret bits without UB
    uint32_t bits;
    memcpy(&bits, &a, sizeof(bits));

    printf("0.1 bits = 0x%08x\n", bits);
    printf("0.1+0.2 = %.10f\n", a + b);
    printf("equal?  = %d\n", (a + b) == 0.3f);
    return 0;
}

Endianness: how bytes line up in memory

A 32-bit integer is four bytes. But in what order are those bytes laid out in memory? Two conventions exist: little-endian (least significant byte first, used by x86 and ARM by default) and big-endian (most significant byte first, used by network protocols and older CPUs).

0xDEADBEEF stored in memory:
little-endian: EF BE AD DE  ←  what your laptop does
big-endian:    DE AD BE EF  ←  what TCP/IP uses

This matters when you read raw bytes from disk, the network, or shared memory between architectures. Forget about it and you get silently corrupted data.

// gotcha
Never compare floats with ==. Compare with an epsilon: (a − b).abs() < 1e-6. Use integer or fixed-point math when correctness matters (currency, accounting, blockchain consensus).

Binary in blockchain

Every concept on this page appears inside a Bitcoin node.

SHA-256 uses bitwise AND, XOR, NOT and bit rotations: the exact operations from the intermediate section above. 64 rounds, billions of times per second.

Bitcoin transaction amounts are 64-bit unsigned integers (u64 in Rust). Stored in little-endian byte order. The same endianness your x86 CPU uses.

The 0.1 + 0.2 problem is why blockchain ledgers never use floats. Every balance is stored as an integer. In Bitcoin, the unit is satoshis. 1 BTC = 100,000,000 satoshis. Integer math, exact, always.

A blockchain private key is 256 bits of random data. 32 bytes. The same bit patterns this page is about, just 256 of them. Chosen once, never shared, never lost.

Rust• • •
fn satoshis_to_btc(satoshis: u64) -> f64 {
    satoshis as f64 / 100_000_000.0
    // note: display only; never use floats
    // for actual Bitcoin arithmetic
}

fn main() {
    let balance: u64 = 100_000_000; // 1 BTC in satoshis
    println!("Balance: {} BTC", satoshis_to_btc(balance));

    // Bitcoin amounts as integer bits
    println!("As bits: {:064b}", balance);
    println!("As hex:  {:#018x}", balance);
    // 0x0000000005f5e100
}
C• • •
#include <stdio.h>
#include <stdint.h>

int main(void) {
    uint64_t balance = 100000000; // 1 BTC in satoshis
    printf("Balance: %.8f BTC\n", balance / 100000000.0);
    printf("As hex: 0x%016lx\n", balance);
    // 0x0000000005f5e100
    return 0;
}

Where binary appears in BitRoot

This page is the substrate; every other topic on the site rests on it somewhere. The shortest path from any of them back to here:

Where this lands you

You now have the substrate. You know what a bit is, how integers and decimals are encoded, and how the CPU manipulates them. Next: how those bits become letters.

next up / 0x03
Bytes become letters: ASCII & Unicode
ascii