Theory: Floating-Point Arithmetic & Errors
Section 2.4 showed how floating-point numbers are stored. Here we compute with them: addition, subtraction, multiplication and division, all worked in the lecture’s 14-bit model (1 sign bit, 5-bit excess-16 exponent, 8 fraction bits with an implicit leading 1). Then we look at what can go wrong: limited range (overflow/underflow) and limited precision (rounding errors), and the programming habits that follow (Null & Lobur, Ch. 2).
Addition and Subtraction
You cannot add significands until they refer to the same power of the base. Decimal analogy:
1.5×102+3.6×103=0.15×103+3.6×103=3.75×103
In general, for X=XS×BXE and Y=YS×BYE with XE≤YE:
X±Y=(XS×BXE−YE±YS)×BYE
Algorithm (FP add/subtract)
- Check for zeros. If either operand is 0, the result is the other operand (negated if it is the subtrahend).
- Align the significands. Shift the significand of the number with the smaller exponent to the right, adding 1 to its exponent for each place, until both exponents are equal.
- Add or subtract the significands (taking signs into account).
- Normalize the result back to 1.xxx×2e: if it became ≥2 (e.g. 10.xxx), shift right and increase the exponent; if it has leading 0s (e.g. 0.0xxx, possible after subtraction), shift left and decrease the exponent.
- Round/truncate to the available fraction bits. Check the exponent for overflow/underflow.
Always shift the smaller number. Shifting it right only loses its least significant bits, which matter least. Shifting the larger number left would push off its most significant bits.
Mini-example
1.1×21+1.0×20 (i.e. 3+1): align 1.0×20=0.1×21; add 1.1+0.1=10.0; normalize 10.0×21=1.0×22=4 ✓.
Multiplication and Division
Multiplication does not need alignment:
X×Y=(XS×YS)×BXE+YE,YX=(YSXS)×BXE−YE
Algorithm (FP multiply)
- Check for zeros (if either is 0, the product is 0).
- Sign = SX⊕SY (XOR: different signs → negative).
- Add the exponents. With biased exponents, add the stored fields and subtract one bias: E=EX+EY−16 (otherwise the bias would be counted twice).
- Multiply the significands (an unsigned integer-style multiplication).
- Normalize (the product of two values in [1,2) lies in [1,4), so at most one right shift is needed) and round/truncate.
Division is analogous: subtract the exponents (and add the bias back: E=EX−EY+16) and divide the significands, then normalize.
Summary of floating-point arithmetic
| Operation | Significands | Exponents | Extra step |
|---|
| X+Y, X−Y | add / subtract after alignment | make equal (the larger one) | align first, normalize after |
| X×Y | multiply | add | normalize; sign = XOR |
| X/Y | divide | subtract | normalize; sign = XOR |
Range, Precision, Overflow and Underflow
- The exponent field decides the range (how large/small magnitudes can get).
- The significand field decides the precision (how many significant bits; how finely values are spaced).
Overflow occurs when a result’s exponent is too large to store (there is no room for the high-order bits of the exponent). Underflow occurs when a nonzero result’s exponent is too small, i.e. the value is too close to zero to represent.
The real line splits into regions (shown for IEEE double precision, whose limits are about 10±308):
negative | expressible | negative | positive | expressible | positive
overflow | negative numbers | underflow| underflow| positive numbers | overflow
--------------+-------------------+----------0----------+-------------------+-----------
-1.0e308 -1.0e-308 1.0e-308 1.0e308
For the 14-bit model, true exponents run from −16 to +15, so the largest magnitude is 1.111111112×215=65408, and anything requiring an exponent below −16 underflows.
Precision limits mean spacing: with 8 fraction bits, neighbouring values near 2e are 2e−8 apart. Near 128=27 the spacing is 2−1=0.5, so only 128,128.5,129,… exist.
Floating-Point Errors
The real numbers are infinite and dense (between any two there is always another), but a computer’s arithmetic is finite: it can only approximate real arithmetic. More bits give a better approximation, but there is always some error, no matter how many bits we use.
| Kind | What happens | Example (14-bit model) |
|---|
| Blatant error | Obvious failure: the program crashes or produces something clearly wrong, typically overflow/underflow. | 2−18 needs exponent field 16−18=−2<0: cannot be stored (underflow). |
| Subtle error | A plausible but slightly wrong result that is hard to detect. | 128.75 is stored as 128.5 (truncation). |
Experienced programmers know that it is better for a program to crash than to produce incorrect but plausible results. Blatant errors get noticed and fixed; subtle errors silently propagate through calculations.
Never test floating-point values for exact equality, and in particular never test x == 0.0. Values that “should” be equal often differ in the last bit (and zero even has two encodings, +0 and −0). Instead compare with a tolerance: treat x and y as equal when abs(x - y) < epsilon for some small epsilon suited to the problem.
Mini-example
In IEEE double precision, adding 0.1 to itself ten times gives 0.9999999999999999, not 1.0, because 0.1 is a non-terminating binary fraction. So sum == 1.0 is false, while abs(sum - 1.0) < 1e-9 is true.
Worked Examples
Exercise 1
Add 1.110012×22+1.100112×20. Normalize the result, store it in the 14-bit model, and show what happens if only 5 fraction bits are kept. Verify in decimal.
Show solution ↓Hide solution ↑
Solution
Step 1, zeros: neither operand is 0.
Step 2, align: the smaller exponent is 0; shift 1.10011 right by 2−0=2 places:
1.10011×20=0.0110011×22Step 3, add:
1.1100100
+ 0.0110011
-----------
10.0010111 (x 2^2)
Step 4, normalize: 10.0010111×22=1.00010111×23.
Step 5, store (8 fraction bits): the fraction 00010111 fits exactly.
- Sign 0, exponent 3+16=19=10011, fraction 00010111
- 14-bit result: 0 10011 00010111
With only 5 fraction bits, truncation drops the last three bits: 1.00010×23=1000.102=8.5.
Decimal check:
- 1.110012=1+21+41+321=1.78125, times 4=7.125.
- 1.100112=1+21+161+321=1.59375, times 1=1.59375.
- Sum =8.71875. And 1.000101112×8=1000.101112=8+0.5+0.125+0.0625+0.03125=8.71875 ✓.
The 5-bit version (8.5) shows how truncating the significand introduces an error of 0.21875.
Exercise 2
In the 14-bit model, add A=0 10010 10000000 and B=0 10000 01000000. Give the result as a 14-bit pattern and in decimal.
Show solution ↓Hide solution ↑
Solution
Decode:
- A: exponent 10010=18, true 2; significand 1.1; so A=1.1×22 (=6).
- B: exponent 10000=16, true 0; significand 1.01; so B=1.01×20 (=1.25).
Align: B has the smaller exponent; shift right 2−0=2 places: 1.01×20=0.0101×22.
Add:
1.1000
+ 0.0101
--------
1.1101 (x 2^2)
Normalize: already of the form 1.xxx. Result 1.1101×22.
Store: sign 0, exponent 2+16=18=10010, fraction 11010000:
A+B=0 10010 11010000Check: 1.1101×22=111.012=7.25=6+1.25 ✓.
Exercise 3
Compute 12−1.25 using floating-point subtraction and store the result in the 14-bit model.
Show solution ↓Hide solution ↑
Solution
Convert and normalize: 12=11002=1.1×23; 1.25=1.012=1.01×20.
Align: shift the smaller (1.01×20) right by 3 places: 0.00101×23.
Subtract:
1.10000
- 0.00101
---------
1.01011 (x 2^3)
(Bit by bit from the right: 0−1 borrow → 1; 0−0−1 borrow → 1; 0−1−1 borrow → 0; 0−0−1 borrow → 1; 1−0−1=0; integer 1−0=1.)
Normalize: the leading bit is still 1, so no shift needed: 1.01011×23.
Store: sign 0, exponent 3+16=19=10011, fraction 01011000:
12−1.25=0 10011 01011000Check: 1.01011×23=1010.112=8+2+0.5+0.25=10.75 ✓.
Exercise 4
Find the product of 1210 and 1.2510 using the 14-bit floating-point model. Show the exponent calculation using the biased fields.
Show solution ↓Hide solution ↑
Solution
Convert: 12=1.1×23 → 0 10011 10000000; 1.25=1.01×20 → 0 10000 01000000.
Sign: 0⊕0=0 (positive).
Exponent: true exponents add: 3+0=3. With the biased fields: 19+16−16=19=10011 (subtract one bias, otherwise it is counted twice).
Significands: 1.1×1.01:
1.01
x 1.1
------
101 (1.01 x 0.1, shifted)
101 (1.01 x 1)
------
1.111
(As integers: 101×11=1111, i.e. 5×3=15; three fraction places in total give 1.111.)
Normalize: 1.111×23, already normalized.
Store: 0 10011 11100000.
Check: 1.111×23=11112=15=12×1.25 ✓.
Exercise 5
In the 14-bit model compute (a) (−6)×3 and (b) 18÷6. Show the normalization step and the 14-bit result.
Show solution ↓Hide solution ↑
Solution
6=1102=1.1×22; 3=112=1.1×21.
(a) (−6)×3
- Sign: 1⊕0=1 (negative).
- Exponent: 2+1=3.
- Significands: 1.1×1.1=10.01 (as integers 11×11=1001, i.e. 3×3=9, with two fraction places).
- Normalize: 10.01×23=1.001×24 (shift right 1, exponent +1).
- Store: exponent 4+16=20=10100, fraction 00100000.
(−6)×3=1 10100 00100000Check: −1.0012×24=−100102=−18 ✓.
(b) 18÷6
18=1.001×24 and 6=1.1×22.
- Sign: 0⊕0=0.
- Exponent: 4−2=2.
- Significands: 1.001÷1.1=0.11 (decimal: 1.125/1.5=0.75=0.112).
- Normalize: 0.11×22=1.1×21 (shift left 1, exponent −1).
- Store: exponent 1+16=17=10001, fraction 10000000.
18÷6=0 10001 10000000Check: 1.12×2=112=3 ✓.
Exercise 6
Using the 14-bit excess-16 model:
(a) Store 128.75. What value is actually stored, and what is the error?
(b) Compute 128+0.75 with the FP addition algorithm. What happens?
(c) Can 2−18 be stored? Can the product 210×28?
(d) Classify each problem as a blatant or subtle error.
Show solution ↓Hide solution ↑
Solution
(a) 128.75=10000000.112=1.000000011×27. The fraction has 9 bits (000000011) but only 8 fit, so truncate to 00000001:
- Sign 0, exponent 7+16=23=10111, fraction 00000001 → 0 10111 00000001
- Stored value: 1.000000012×27=10000000.12=128.5
Error =128.75−128.5=0.25; relative error =0.25/128.75≈0.0019≈0.2%.
The reason: near 27 the 8 fraction bits give a spacing of 27−8=0.5, so only 128, 128.5, 129, … exist.
(b) 128=1.0×27, 0.75=0.112=1.1×2−1.
Align: shift 1.1×2−1 right by 7−(−1)=8 places: 0.000000011×27.
Add: 1.000000000+0.000000011=1.000000011×27.
Truncate to 8 fraction bits: 1.00000001×27=128.5. The lowest bit of 0.75 was shifted off the end during alignment, so the arithmetic itself loses 0.25: same subtle error as in (a).
(c) 2−18=1.0×2−18 needs exponent field −18+16=−2, but the field cannot be negative (minimum 00000, true exponent −16). It cannot be stored: exponent underflow.
210×28=218 needs exponent field 18+16=34>31. It cannot be stored: exponent overflow (the largest representable value is 65408<218=262144).
(d)
- 2−18 (underflow) and 218 (overflow): blatant errors. The value simply cannot be represented; the program fails or flags it.
- 128.75→128.5: a subtle error. A plausible number is produced that is wrong by about 0.2%, and nothing signals the problem.
Exercise 7
(a) Store 0.1 in the 14-bit model. What value is actually stored?
(b) A program sums 0.1 ten times in IEEE double precision and then tests if (sum == 1.0). What happens, and how should the test be written? Why is if (x == 0.0) also dangerous?
Show solution ↓Hide solution ↑
Solution
(a) 0.1=0.000110011001100…2 (non-terminating; the block 0011 repeats).
Normalize: 0.0001100110011…=1.100110011…×2−4.
- Sign 0; exponent −4+16=12=01100
- First 8 fraction bits: 10011001 (the rest are truncated)
- Pattern: 0 01100 10011001
Stored value: 1.100110012×2−4=(1+256153)/16=0.099853515625. Error ≈0.000146 (about 0.15%).
(b) Even with 52 fraction bits, 0.1 is stored only approximately (in single precision it is 3DCCCCCD16, already rounded). Each addition also rounds. After ten additions the double-precision sum is 0.9999999999999999, so sum == 1.0 is false and the branch is wrongly skipped. This is a subtle error: no crash, just wrong behaviour.
Correct approach: compare against a tolerance,
if (abs(sum - 1.0) < 1e-9) ...
Testing x == 0.0 is dangerous for the same reason: a result that “should” be zero is often a tiny nonzero value like 10−17 after rounding. (IEEE also has two zero encodings, +0 and −0.) Use abs(x) < epsilon instead.