First a binary point: fixed point and why it is not enough
So far every value you have built has been a whole number — an unsigned integer or a two's-complement signed one. But the world is full of fractions: 3.5 volts, 0.1 seconds, half a pixel. The first idea is the simplest: just imagine a binary point sitting at a fixed spot inside the word, exactly as the decimal point sits in 12.75. This is called fixed point.
Just as decimal digits after the point mean tenths, hundredths, thousandths, the binary bits after the point mean halves, quarters, eighths. So 101.011 in binary is 4 plus 1 plus one-quarter plus one-eighth, which is 5.375. Fixed point is fast — it reuses the integer adder you already met, because the points always line up. But it is rigid: if you reserve, say, 8 bits before the point and 8 after, you can never represent a billion (too big) nor a billionth (too small) in the same format. The range and the precision are welded together.
Floating point: a binary point that slides
The fix is to let the point float. This is just scientific notation, which you already use for decimals: instead of writing 0.0000000123 you write 1.23 times 10^-8, splitting the number into a fraction (1.23) and an exponent (minus 8) that says where the point really goes. Floating point does exactly this in base two: every value becomes a sign, a fraction called the significand (or mantissa), and an exponent, all packed into one fixed-size word. The same number of bits can now stretch to describe the mass of a galaxy or the size of an atom, because the exponent does the stretching.
There is a clever space-saving step called normalization. Just as decimal scientific notation insists on exactly one nonzero digit before the point (1.23, never 0.123 or 12.3), binary normalization shifts the significand until it reads 1.something. But in binary that leading digit can only ever be 1 — so why store it? IEEE 754 simply does not. The leading 1 is an implicit (hidden) bit, giving you one extra bit of significand precision for free. The stored fraction holds only the bits after the point.
Inside an IEEE 754 word: sign, exponent, significand
IEEE 754 is the standard nearly every processor obeys, so a float written on one machine reads identically on another. A 32-bit single-precision float splits its bits into three fields: 1 sign bit, 8 exponent bits, and 23 significand bits (24 if you count the hidden leading 1). Double precision uses 64 bits — 1, 11, and 52 — for far more range and precision. The fields are laid out from the top so that, pleasingly, comparing two same-sign floats as if they were integers gives the right ordering.
One subtlety hides in the exponent. It must represent both large positive and small negative powers, yet for that integer-comparison trick to work it cannot use two's complement (whose negative numbers have a leading 1 and would sort wrong). Instead the exponent is stored with a bias: you add a constant (127 for single precision) before storing and subtract it when reading. So a stored exponent of 127 means an actual exponent of 0, and stored 130 means actual 3. Biased exponents are plain unsigned numbers, so they sort in the natural order.
32-bit single precision: value = (-1)^sign x 1.fraction x 2^(exp - 127) +-----+-----------+-------------------------+ | sign| exponent | significand | | 1 | 8 bits | 23 bits | +-----+-----------+-------------------------+ Encode -6.0: 6.0 = 110.0 (binary) = 1.10 x 2^2 (normalized: hidden leading 1) sign = 1 (negative) exponent = 2 + 127 = 129 = 1000 0001 (biased) fraction = 100 0000 ... 0 (the bits after the point) bits = 1 1000 0001 100 0000 0000 0000 0000 0000
Zero, infinity, and NaN: the reserved patterns
The hidden leading 1 has a snag: with an always-present 1, how do you write zero? IEEE 754 carves out the two extreme exponent values for special meanings. An all-zero exponent with an all-zero significand is zero itself (and because the sign bit is separate, there are technically a positive zero and a negative zero, which compare as equal). The all-zero exponent with a nonzero significand encodes subnormal numbers — tiny values below the normal range that drop the hidden 1 to fade smoothly toward zero rather than jumping straight to it.
At the other end, the all-ones exponent is reserved too. With a zero significand it means infinity (positive or negative, by the sign bit) — the honest result of dividing by zero or overflowing the largest representable number. With a nonzero significand it means NaN, 'not a number': the result of a genuinely undefined operation like zero divided by zero, infinity minus infinity, or the square root of a negative. NaN is contagious — almost any arithmetic touching a NaN yields NaN — and, famously, a NaN is not even equal to itself, which is one reliable way to test for it.
The honest part: floating point is not exact
Here is the truth that trips up every beginner: most decimal fractions cannot be stored exactly. Just as one-third is 0.3333... forever in decimal, the innocent-looking 0.1 is a repeating fraction in binary — it never terminates. With only 23 or 52 significand bits, the hardware stores the nearest value it can and rounds the rest away. So 0.1 plus 0.2 does not give exactly 0.3; it gives something like 0.30000000000000004. The bits are doing flawless binary arithmetic on numbers that were already slightly wrong.
Because rounding happens at every step, the rules you trust for real numbers quietly break. Floating-point addition is not associative: (a + b) + c can differ from a + (b + c), because where the rounding lands depends on the order. Adding a tiny number to a huge one can lose the tiny one entirely — it falls below the gap between adjacent representable values. IEEE 754 pins down exactly how rounding works (the default is round-to-nearest, ties-to-even, governed by the rounding mode) so that results are at least predictable and reproducible across machines, even though they are not the exact mathematical answer.
Reading a float, step by step
Pulling the three fields apart uses exactly the shift-and-mask surgery from the previous guide — a float in a register is just bits, and decoding it is field extraction followed by a little arithmetic. Here is the whole recipe for a normal single-precision value, the same dance a floating-point unit performs in hardware.
- Read the top bit as the sign: 0 means positive, 1 means negative.
- Extract the next 8 bits as the biased exponent, then subtract the bias 127 to get the true power of two.
- Take the low 23 bits as the fraction and glue the hidden leading 1 back on the front to rebuild the full significand 1.fraction.
- Combine them: value equals sign times the significand times two to the true exponent — watching for the reserved all-zero and all-ones exponents that mean zero/subnormal or infinity/NaN.
With that, you can represent and decode any real number a computer holds. You have now walked the whole spine of data representation: bits and bases, unsigned and signed integers, the bit tricks that move them around, and floating point for fractions. One question remains before we leave this rung — given a multi-byte number, in what order do its bytes sit in memory, and how do characters and aligned words get laid out? That is endianness, the subject of the final guide.