About

2.5 Floating-Point Arithmetic & Errors

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×1031.5 \times 10^2 + 3.6 \times 10^3 = 0.15 \times 10^3 + 3.6 \times 10^3 = 3.75 \times 10^3

In general, for X=XS×BXEX = X_S \times B^{X_E} and Y=YS×BYEY = Y_S \times B^{Y_E} with XE≤YEX_E \le Y_E:

X±Y=(XS×BXE−YE±YS)×BYEX \pm Y = \left(X_S \times B^{X_E - Y_E} \pm Y_S\right) \times B^{Y_E}

Algorithm (FP add/subtract)

  1. Check for zeros. If either operand is 0, the result is the other operand (negated if it is the subtrahend).
  2. 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.
  3. Add or subtract the significands (taking signs into account).
  4. Normalize the result back to 1.xxx×2e1.xxx \times 2^e: if it became ≥2\ge 2 (e.g. 10.xxx10.xxx), shift right and increase the exponent; if it has leading 0s (e.g. 0.0xxx0.0xxx, possible after subtraction), shift left and decrease the exponent.
  5. 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×201.1 \times 2^1 + 1.0 \times 2^0 (i.e. 3+13 + 1): align 1.0×20=0.1×211.0 \times 2^0 = 0.1 \times 2^1; add 1.1+0.1=10.01.1 + 0.1 = 10.0; normalize 10.0×21=1.0×22=410.0 \times 2^1 = 1.0 \times 2^2 = 4 ✓.

Multiplication and Division

Multiplication does not need alignment:

X×Y=(XS×YS)×BXE+YE,XY=(XSYS)×BXE−YEX \times Y = (X_S \times Y_S) \times B^{X_E + Y_E}, \qquad \frac{X}{Y} = \left(\frac{X_S}{Y_S}\right) \times B^{X_E - Y_E}

Algorithm (FP multiply)

  1. Check for zeros (if either is 0, the product is 0).
  2. Sign = SX⊕SYS_X \oplus S_Y (XOR: different signs → negative).
  3. Add the exponents. With biased exponents, add the stored fields and subtract one bias: E=EX+EY−16E = E_X + E_Y - 16 (otherwise the bias would be counted twice).
  4. Multiply the significands (an unsigned integer-style multiplication).
  5. Normalize (the product of two values in [1,2)[1, 2) lies in [1,4)[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+16E = E_X - E_Y + 16) and divide the significands, then normalize.

Summary of floating-point arithmetic

OperationSignificandsExponentsExtra step
X+YX + Y, X−YX - Yadd / subtract after alignmentmake equal (the larger one)align first, normalize after
X×YX \times Ymultiplyaddnormalize; sign = XOR
X/YX / Ydividesubtractnormalize; sign = XOR

Range, Precision, Overflow and Underflow

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±30810^{\pm 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-16 to +15+15, so the largest magnitude is 1.111111112×215=654081.11111111_2 \times 2^{15} = 65408, and anything requiring an exponent below −16-16 underflows.

Precision limits mean spacing: with 8 fraction bits, neighbouring values near 2e2^e are 2e−82^{e-8} apart. Near 128=27128 = 2^7 the spacing is 2−1=0.52^{-1} = 0.5, so only 128,128.5,129,…128, 128.5, 129, \dots 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.

KindWhat happensExample (14-bit model)
Blatant errorObvious failure: the program crashes or produces something clearly wrong, typically overflow/underflow.2−182^{-18} needs exponent field 16−18=−2<016 - 18 = -2 \lt 0: cannot be stored (underflow).
Subtle errorA plausible but slightly wrong result that is hard to detect.128.75128.75 is stored as 128.5128.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+0 and −0-0). Instead compare with a tolerance: treat xx and yy as equal when abs(x - y) < epsilon for some small epsilon suited to the problem.

Mini-example

In IEEE double precision, adding 0.10.1 to itself ten times gives 0.9999999999999999, not 1.0, because 0.10.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×201.11001_2 \times 2^2 + 1.10011_2 \times 2^0. 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 ↓
Solution

Step 1, zeros: neither operand is 0.

Step 2, align: the smaller exponent is 00; shift 1.100111.10011 right by 2−0=22 - 0 = 2 places:

1.10011×20=0.0110011×221.10011 \times 2^0 = 0.0110011 \times 2^2

Step 3, add:

   1.1100100
 + 0.0110011
 -----------
  10.0010111      (x 2^2)

Step 4, normalize: 10.0010111×22=1.00010111×2310.0010111 \times 2^2 = 1.00010111 \times 2^3.

Step 5, store (8 fraction bits): the fraction 0001011100010111 fits exactly.

  • Sign 00, exponent 3+16=19=100113 + 16 = 19 = 10011, fraction 0001011100010111
  • 14-bit result: 0 10011 000101110\ 10011\ 00010111

With only 5 fraction bits, truncation drops the last three bits: 1.00010×23=1000.102=8.51.00010 \times 2^3 = 1000.10_2 = 8.5.

Decimal check:

  • 1.110012=1+12+14+132=1.781251.11001_2 = 1 + \tfrac12 + \tfrac14 + \tfrac1{32} = 1.78125, times 4=7.1254 = 7.125.
  • 1.100112=1+12+116+132=1.593751.10011_2 = 1 + \tfrac12 + \tfrac1{16} + \tfrac1{32} = 1.59375, times 1=1.593751 = 1.59375.
  • Sum =8.71875= 8.71875. And 1.000101112×8=1000.101112=8+0.5+0.125+0.0625+0.03125=8.718751.00010111_2 \times 8 = 1000.10111_2 = 8 + 0.5 + 0.125 + 0.0625 + 0.03125 = 8.71875 ✓.

The 5-bit version (8.58.5) shows how truncating the significand introduces an error of 0.218750.21875.

Exercise 2

In the 14-bit model, add A=0 10010 10000000A = 0\ 10010\ 10000000 and B=0 10000 01000000B = 0\ 10000\ 01000000. Give the result as a 14-bit pattern and in decimal.

Show solution ↓
Solution

Decode:

  • AA: exponent 10010=1810010 = 18, true 22; significand 1.11.1; so A=1.1×22A = 1.1 \times 2^2 (=6= 6).
  • BB: exponent 10000=1610000 = 16, true 00; significand 1.011.01; so B=1.01×20B = 1.01 \times 2^0 (=1.25= 1.25).

Align: BB has the smaller exponent; shift right 2−0=22 - 0 = 2 places: 1.01×20=0.0101×221.01 \times 2^0 = 0.0101 \times 2^2.

Add:

   1.1000
 + 0.0101
 --------
   1.1101     (x 2^2)

Normalize: already of the form 1.xxx1.xxx. Result 1.1101×221.1101 \times 2^2.

Store: sign 00, exponent 2+16=18=100102 + 16 = 18 = 10010, fraction 1101000011010000:

A+B=0  10010  11010000A + B = 0\ \ 10010\ \ 11010000

Check: 1.1101×22=111.012=7.25=6+1.251.1101 \times 2^2 = 111.01_2 = 7.25 = 6 + 1.25 ✓.

Exercise 3

Compute 12−1.2512 - 1.25 using floating-point subtraction and store the result in the 14-bit model.

Show solution ↓
Solution

Convert and normalize: 12=11002=1.1×2312 = 1100_2 = 1.1 \times 2^3; 1.25=1.012=1.01×201.25 = 1.01_2 = 1.01 \times 2^0.

Align: shift the smaller (1.01×201.01 \times 2^0) right by 33 places: 0.00101×230.00101 \times 2^3.

Subtract:

   1.10000
 - 0.00101
 ---------
   1.01011     (x 2^3)

(Bit by bit from the right: 0−10 - 1 borrow → 1; 0−0−10 - 0 - 1 borrow → 1; 0−1−10 - 1 - 1 borrow → 0; 0−0−10 - 0 - 1 borrow → 1; 1−0−1=01 - 0 - 1 = 0; integer 1−0=11 - 0 = 1.)

Normalize: the leading bit is still 1, so no shift needed: 1.01011×231.01011 \times 2^3.

Store: sign 00, exponent 3+16=19=100113 + 16 = 19 = 10011, fraction 0101100001011000:

12−1.25=0  10011  0101100012 - 1.25 = 0\ \ 10011\ \ 01011000

Check: 1.01011×23=1010.112=8+2+0.5+0.25=10.751.01011 \times 2^3 = 1010.11_2 = 8 + 2 + 0.5 + 0.25 = 10.75 ✓.

Exercise 4

Find the product of 121012_{10} and 1.25101.25_{10} using the 14-bit floating-point model. Show the exponent calculation using the biased fields.

Show solution ↓
Solution

Convert: 12=1.1×2312 = 1.1 \times 2^3 → 0 10011 100000000\ 10011\ 10000000; 1.25=1.01×201.25 = 1.01 \times 2^0 → 0 10000 010000000\ 10000\ 01000000.

Sign: 0⊕0=00 \oplus 0 = 0 (positive).

Exponent: true exponents add: 3+0=33 + 0 = 3. With the biased fields: 19+16−16=19=1001119 + 16 - 16 = 19 = 10011 (subtract one bias, otherwise it is counted twice).

Significands: 1.1×1.011.1 \times 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=1111101 \times 11 = 1111, i.e. 5×3=155 \times 3 = 15; three fraction places in total give 1.1111.111.)

Normalize: 1.111×231.111 \times 2^3, already normalized.

Store: 0  10011  111000000\ \ 10011\ \ 11100000.

Check: 1.111×23=11112=15=12×1.251.111 \times 2^3 = 1111_2 = 15 = 12 \times 1.25 ✓.

Exercise 5

In the 14-bit model compute (a) (−6)×3(-6) \times 3 and (b) 18÷618 \div 6. Show the normalization step and the 14-bit result.

Show solution ↓
Solution

6=1102=1.1×226 = 110_2 = 1.1 \times 2^2; 3=112=1.1×213 = 11_2 = 1.1 \times 2^1.

(a) (−6)×3(-6) \times 3

  • Sign: 1⊕0=11 \oplus 0 = 1 (negative).
  • Exponent: 2+1=32 + 1 = 3.
  • Significands: 1.1×1.1=10.011.1 \times 1.1 = 10.01 (as integers 11×11=100111 \times 11 = 1001, i.e. 3×3=93 \times 3 = 9, with two fraction places).
  • Normalize: 10.01×23=1.001×2410.01 \times 2^3 = 1.001 \times 2^4 (shift right 1, exponent +1+1).
  • Store: exponent 4+16=20=101004 + 16 = 20 = 10100, fraction 0010000000100000.
(−6)×3=1  10100  00100000(-6) \times 3 = 1\ \ 10100\ \ 00100000

Check: −1.0012×24=−100102=−18-1.001_2 \times 2^4 = -10010_2 = -18 ✓.

(b) 18÷618 \div 6

18=1.001×2418 = 1.001 \times 2^4 and 6=1.1×226 = 1.1 \times 2^2.

  • Sign: 0⊕0=00 \oplus 0 = 0.
  • Exponent: 4−2=24 - 2 = 2.
  • Significands: 1.001÷1.1=0.111.001 \div 1.1 = 0.11 (decimal: 1.125/1.5=0.75=0.1121.125 / 1.5 = 0.75 = 0.11_2).
  • Normalize: 0.11×22=1.1×210.11 \times 2^2 = 1.1 \times 2^1 (shift left 1, exponent −1-1).
  • Store: exponent 1+16=17=100011 + 16 = 17 = 10001, fraction 1000000010000000.
18÷6=0  10001  1000000018 \div 6 = 0\ \ 10001\ \ 10000000

Check: 1.12×2=112=31.1_2 \times 2 = 11_2 = 3 ✓.

Exercise 6

Using the 14-bit excess-16 model:

(a) Store 128.75128.75. What value is actually stored, and what is the error?

(b) Compute 128+0.75128 + 0.75 with the FP addition algorithm. What happens?

(c) Can 2−182^{-18} be stored? Can the product 210×282^{10} \times 2^{8}?

(d) Classify each problem as a blatant or subtle error.

Show solution ↓
Solution

(a) 128.75=1000 0000.112=1.000000011‾×27128.75 = 1000\,0000.11_2 = 1.00000001\underline{1} \times 2^7. The fraction has 9 bits (000000011000000011) but only 8 fit, so truncate to 0000000100000001:

  • Sign 00, exponent 7+16=23=101117 + 16 = 23 = 10111, fraction 0000000100000001 → 0 10111 000000010\ 10111\ 00000001
  • Stored value: 1.000000012×27=1000 0000.12=128.51.00000001_2 \times 2^7 = 1000\,0000.1_2 = 128.5

Error =128.75−128.5=0.25= 128.75 - 128.5 = 0.25; relative error =0.25/128.75≈0.0019≈0.2%= 0.25 / 128.75 \approx 0.0019 \approx 0.2\%.

The reason: near 272^7 the 8 fraction bits give a spacing of 27−8=0.52^{7-8} = 0.5, so only 128128, 128.5128.5, 129129, … exist.

(b) 128=1.0×27128 = 1.0 \times 2^7, 0.75=0.112=1.1×2−10.75 = 0.11_2 = 1.1 \times 2^{-1}.

Align: shift 1.1×2−11.1 \times 2^{-1} right by 7−(−1)=87 - (-1) = 8 places: 0.000000011×270.000000011 \times 2^7.

Add: 1.000000000+0.000000011=1.000000011×271.000000000 + 0.000000011 = 1.000000011 \times 2^7.

Truncate to 8 fraction bits: 1.00000001×27=128.51.00000001 \times 2^7 = 128.5. The lowest bit of 0.750.75 was shifted off the end during alignment, so the arithmetic itself loses 0.250.25: same subtle error as in (a).

(c) 2−18=1.0×2−182^{-18} = 1.0 \times 2^{-18} needs exponent field −18+16=−2-18 + 16 = -2, but the field cannot be negative (minimum 0000000000, true exponent −16-16). It cannot be stored: exponent underflow.

210×28=2182^{10} \times 2^{8} = 2^{18} needs exponent field 18+16=34>3118 + 16 = 34 \gt 31. It cannot be stored: exponent overflow (the largest representable value is 65408<218=26214465408 \lt 2^{18} = 262144).

(d)

  • 2−182^{-18} (underflow) and 2182^{18} (overflow): blatant errors. The value simply cannot be represented; the program fails or flags it.
  • 128.75→128.5128.75 \to 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.10.1 in the 14-bit model. What value is actually stored?

(b) A program sums 0.10.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 ↓
Solution

(a) 0.1=0.000110011001100…20.1 = 0.000110011001100\ldots_2 (non-terminating; the block 00110011 repeats).

Normalize: 0.0001100110011…=1.100110011…×2−40.0001100110011\ldots = 1.100110011\ldots \times 2^{-4}.

  • Sign 00; exponent −4+16=12=01100-4 + 16 = 12 = 01100
  • First 8 fraction bits: 1001100110011001 (the rest are truncated)
  • Pattern: 0 01100 100110010\ 01100\ 10011001

Stored value: 1.100110012×2−4=(1+153256)/16=0.0998535156251.10011001_2 \times 2^{-4} = \left(1 + \tfrac{153}{256}\right) / 16 = 0.099853515625. Error ≈0.000146\approx 0.000146 (about 0.15%0.15\%).

(b) Even with 52 fraction bits, 0.10.1 is stored only approximately (in single precision it is 3DCCCCCD16\text{3DCCCCCD}_{16}, 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−1710^{-17} after rounding. (IEEE also has two zero encodings, +0+0 and −0-0.) Use abs(x) < epsilon instead.