Fractions in Binary: Fixed and Floating Point
Bits after a binary point are worth 1/2, 1/4, 1/8 and so on. Fixed point puts the point in one spot for good, so every step is the same size. Floating point is scientific notation in base 2: the gaps grow with the number, but the error as a fraction of the number stays about the same.
What the symbols mean
- Stored integer
The bits read as an ordinary integer.
- Fraction bits
How many bits sit after the binary point.
What the symbols mean
- Sign bit
0 for positive, 1 for negative.
- Fraction
The 23 bits after the leading 1, which itself isn't stored.
- Stored exponent
8 bits; subtracting the bias 127 gives the true exponent.
Explanation
Fixed point and floating point are the two ways a computer stores numbers with a fractional part, like 5.75. The names say where the binary point goes: in fixed point it sits in one agreed place for good, while in floating point it floats, moving to just after the number's leading 1, as in scientific notation. Fixed point is fast and predictable on small chips, and floating point handles huge and tiny numbers alike. The float formula looks scary, but it's three pieces: (the sign, plus or minus), (the significant digits) and (how far to slide the point). It's just scientific notation in binary; bear with it.
Binary fractions. Place value doesn't stop at the point. In decimal, the digits after it are worth tenths, hundredths and so on, and in binary the bits after the point are worth :
To convert a decimal fraction, double it again and again and peel off the whole-number part each time. (Doubling slides every bit one place left, so the bit that crosses the point is the next one.) (bit 0), (bit 1), then (bit 1), so . A fraction ends in binary only when its denominator is a power of 2, just as never ends in decimal. has a factor of 5 below, so it repeats forever:
Any finite number of bits has to round it, which is why 0.1 + 0.2 == 0.3 is false in nearly every programming language: the stored sum is 0.30000000000000004.
Fixed point keeps the binary point in one agreed place. In Q format, an integer with fraction bits stands for . Q15 packs to into a 16-bit integer, in steps of . Adding is plain integer addition. Multiplying two Q15 numbers gives times the product, so you shift right by 15 to get back to Q15. Microcontrollers without a floating-point unit run control loops and audio filters this way, because integer math is fast and exactly predictable. The catch is that the step is the same everywhere, so small values get few significant bits and big ones overflow.
int16_t a = 16384; // 0.5 in Q15
int16_t b = 8192; // 0.25
int16_t p = ((int32_t)a * b) >> 15; // 4096, which is 0.125
Floating point fixes that catch the same way scientists write : let the point move. It's scientific notation in base 2, . Normalizing so exactly one 1 sits before the point means that 1 never needs storing. IEEE 754 single precision (float) uses 32 bits: 1 sign bit, 8 exponent bits holding , and 23 fraction bits .
| sign | exponent (bias) | fraction | precision | |
|---|---|---|---|---|
| float32 | 1 | 8 (127) | 23 | about 7 digits |
| float64 | 1 | 11 (1023) | 52 | about 16 digits |
Exponent fields of all 0s and all 1s are reserved for zero, tiny "subnormal" numbers, infinity and NaN.
The gaps double. Between and there are evenly spaced floats, so they're apart. Just above 1 the gap is (machine epsilon). Between and it's 2, so the integer 16,777,217 has no float32 at all. Rounding to the nearest float makes a relative error of at most , the same everywhere: floats trade fixed absolute precision for fixed relative precision.
A common mistake. It's tempting to test floats with ==, since on paper is . But each value was rounded on the way in, so the two sides can differ in the last bit. Compare against a tolerance instead. For the same reason, don't add tiny amounts to a huge running total: once the gap between floats is bigger than twice the amount, each addition rounds away to nothing.
See it
Worked examples
Write 5.75 in binary and store it in unsigned Q4.4 (8 bits, 4 of them after the point).
- 1
and , so .
Encode as an IEEE 754 single-precision float.
- 1
.
Practice
Type your answer and check it. Solve them all to complete the lesson.
What is in decimal?
Write . Enter the first three bits .
In Q15 fixed point, a 16-bit integer stands for . What value does = 0x4000 stand for?
In signed Q1.7 format (an 8-bit two's complement integer with 7 bits after the binary point), what value is 0xC0?
A float32 has 23 fraction bits. The gap between 1 and the next larger float is . What is ?
Encode 0.75 as an IEEE 754 single-precision float. Give the 32 bits in hex.
A missile-defense computer counted time in tenths of a second and converted it to seconds by multiplying by . It stored in binary with 23 bits after the point and chopped off the rest, so it used . After 100 hours of running, how far off was its time, in seconds?
Why does 0.1 + 0.2 == 0.3 come out false in most programming languages?
The hardest one here. Get it, and you've got this lesson.
A game keeps time in a float32 and adds 0.1 every tick, rounding each sum to the nearest float32. At what time, in seconds, does the timer stop advancing for good?
Where it's used
DSP chips and small microcontrollers run filters and control loops in fixed point; phones, laptops and GPUs use IEEE 754 floats. Knowing where the gaps between floats are is how engineers avoid clocks that drift or freeze after a few weeks of uptime.