Scientific notation, in binary
In science we write 6 020 000 as 6.02 × 10⁶: a sign, a number with one digit before the point, and an exponent. IEEE 754 — the standard used by almost every computer — does the same in base 2:
value = (−1)^sign × 1.mantissa × 2^(exponent − 127)
A 32-bit float has three fields:
| Field | Bits | Meaning |
|---|---|---|
| Sign | 1 | 0 = positive, 1 = negative |
| Exponent | 8 | the power of 2, stored with a bias of 127 |
| Mantissa (fraction) | 23 | the bits after the leading 1 |
Converting −6.75 step by step
- Sign: negative → 1.
- Integer part: 6 = 110₂ (divide by 2: remainders 0, 1, 1, read upwards).
- Fraction part: multiply by 2 repeatedly: 0.75 × 2 = 1.5 → 1, 0.5 × 2 = 1.0 → 1. So 0.75 = .11₂.
- So 6.75 = 110.11₂. Normalise by moving the point 2 places left: 1.1011 × 2².
- Exponent: 2 + 127 = 129 = 10000001₂.
- Mantissa: the bits after the leading 1, padded to 23 bits: 10110000000000000000000.
Result: 1 10000001 10110000000000000000000 = 0xC0D80000.
The hidden bit
After normalising, the first bit is always 1 — so IEEE 754 doesn’t store it. That free “hidden bit” gives 24 bits of precision from 23 stored bits.
Why 0.1 can’t be stored exactly
0.1 × 2 = 0.2 → 0, 0.4 → 0, 0.8 → 0, 1.6 → 1, 1.2 → 1, then 0.4 again… In binary 0.1 = 0.000110011001100… forever. It must be rounded to 23 bits, so the stored float is 0.100000001490116… That’s why:
>>> 0.1 + 0.2
0.30000000000000004
>>> 0.1 + 0.2 == 0.3
False
>>> abs((0.1 + 0.2) - 0.3) < 1e-9 # compare floats with a tolerance
True
Never use floats for money — use integers (paise or cents) or a decimal type.
Special values
| Exponent bits | Mantissa | Meaning |
|---|---|---|
| all 0 | 0 | ±0 |
| all 0 | not 0 | subnormal (tiny) numbers, no hidden 1 |
| all 1 | 0 | ±infinity (e.g. 1/0) |
| all 1 | not 0 | NaN — “not a number” (e.g. 0/0) |
So normal exponents run from 1 to 254, meaning 2⁻¹²⁶ to 2¹²⁷.
Code: see the bits yourself
import struct
def float_bits(x):
[u] = struct.unpack(">I", struct.pack(">f", x)) # the raw 32 bits
b = f"{u:032b}"
return b[0], b[1:9], b[9:], hex(u)
print(float_bits(-6.75))
# ('1', '10000001', '10110000000000000000000', '0xc0d80000')
print(float_bits(0.1))
# ('0', '01111011', '10011001100110011001101', '0x3dcccccd')
Common mistakes
- Forgetting the bias: the stored exponent is e + 127, not e.
- Including the leading 1 in the 23 mantissa bits.
- Moving the binary point the wrong way: if the point moves left, the exponent is positive.
Complexity at a glance
| Case / operation | Time | Why |
|---|---|---|
| Single precision (float) | 1 + 8 + 23 = 32 bits | About 7 significant decimal digits. |
| Double precision (double) | 1 + 11 + 52 = 64 bits | About 15–16 significant decimal digits. |
Quick check
Test yourself — pick an answer to see if you got it.
1. What is the exponent bias in IEEE 754 single precision?
Stored exponent = actual exponent + 127. (Double precision uses 1023.)
2. Why is the leading 1 of the mantissa not stored?
The "hidden bit" gives 24 bits of precision from 23 stored bits.
3. What is the sign bit of -0.15625?
1 means negative.
4. Why does 0.1 + 0.2 == 0.3 give False in most languages?
Like 1/3 in decimal, 1/10 never ends in binary, so tiny rounding errors appear.