Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

 Open In Colab

Introduction to finite precision

Round-off errors are an unavoidable consequence of representing numbers with a finite number of digits. This affects both irrational numbers and rational numbers that cannot be perfectly represented in a given base.

Because memory is finite, computers must round or truncate numbers. While a single rounding error is tiny, these small errors can accumulate over millions of computations and lead to massive discrepancies.

The average human is capable of around one mistake per second, but computers can make millions of mistakes a second!

The magnitude of round-off error is influenced by the precision (the number of digits used) and the base of the number system. The combination of precision and base sets the finite bounds of numbers we are able to represent.

Binary Representation (Base-2)

Humans typically use a base-10 (decimal) numbering system. Computers use a base-2 (binary) system, where each bit is either 0 or 1.

An exception to this is the Mayans, who used a base-20 (vigesimal) system.

Binary integers

In the decimal system, 1305 is expressed as:

130510=5×100+0×101+3×102+1×1031305_{10} = 5 \times 10^0 + 0 \times 10^1 + 3 \times 10^2 + 1 \times 10^3

In binary, each digit’s position corresponds to a power of 2:

101000110012=1×20+0×21+0×22+1×23+1×24+0×25+0×26+0×27+1×28+0×29+1×210=13051010100011001_2 = 1 \times 2^0 + 0 \times 2^1 + 0 \times 2^2 + 1 \times 2^3 + 1 \times 2^4 + 0 \times 2^5 + 0 \times 2^6 + 0 \times 2^7 + 1 \times 2^8 + 0 \times 2^9 + 1 \times 2^{10} = 1305_{10}

Binary fractions

Binary representation can also include fractional parts using powers of 2 (e.g. 2-1, 2-2). For example, 54.7510 can be written in binary.

110110.112=(1×25+1×24+0×23+1×22+1×21+0×20)+(1×2−1+1×2−2)=54.7510110110.11_2 = (1 \times 2^5 + 1 \times 2^4 + 0 \times 2^3 + 1 \times 2^2 + 1 \times 2^1 + 0 \times 2^0) + (1 \times 2^{-1} + 1 \times 2^{-2}) = 54.75_{10}

Unexpectedly non-terminating fractions

Some numbers that are terminating decimals in one base aren’t in binary!

Why is this important?

Integer Representation (Exact but Bounded)

Integers are used for signed numbers and do not suffer from round-off error. However, they have a limited absolute range bounded by the number of bits used to store them.

The range is bounded by the maximum value representable: baseprecision\text{base}^{\text{precision}} and split between positive and negative values. Due to the representation of zero, the negative range is slightly larger.

Negatives can be generally be represented in different ways:

  1. Use a ‘sign bit’ (e.g.: 0=positive, 1 = negative)

  2. Apply a bias (half the full precision range)

  3. The Two’s Complement method which works specifically in binary arithmatic.

Integer Bounds

The minimum and maximum values for a signed integers in binary are:

min=−2bits−1min = -2^{\text{bits}-1}

max=2bits−1−1max = 2^{\text{bits}-1} - 1

What is the largest integer a double-precision (64-bit) variable can store? We can check with the built-in numpy examiner:

Overflow error

What happens when you store a number too large? It will cause an overflow error. Let’s try some:

Integer Operations

Some operations between data types can give unintuitive answers. What do you think the answers to these operations are?

Surprised?

Were you expecting a/ba/b to result in 1? This is a case of package tools hiding numerical complications. In this case, python is sophisticated enough to know that division of integers aren’t always integers, but other languages optimized for speed might truncate it to 1. Beware!

Floating-Point Numbers (Approximate but Wide Range)

Writing out very large or small numbers is impractical. It is much more efficient to use scientific notation to represent the magnitude as an exponent: 10,000,000,000,000,000,000=101910,000,000,000,000,000,000 = 10^{19}

Scientific Notation (Base-10)

We can remove placeholder zeros by using a floating point to separate the fractional part (mantissa) from the order of magnitude (exponent).

Scientific Notation: mantissa×10exponentmantissa \times 10^{exponent}

DecimalScientific NotationMantissaExponent
265.732.6573×1022.6573 \times 10^22.65732
0.00011×10−41 \times 10^{-4}1-4
-0.0034123−3.4123×10−3-3.4123 \times 10^{-3}-3.4123-3
1500∗1500^*1.5×1031.5 \times 10^31.53

*Assuming the trailing zeros are not significant.

Note:

  1. The mantissa is a fraction. If we could assume the decimal point, it becomes an integer. (Why is this important?)

  2. The exponent is the power of the number system’s base (in this case, 10).

Binary floating-point numbers

The same floating-point concept can be applied to binary numbers using base 2: mantissa×2exponentmantissa \times 2^{exponent}

To normalize 54.7510=110110.11254.75_{10} = 110110.11_2, we move the binary point so that it is after the first non-zero digit: 1.10110112×251.1011011_2 \times 2^5

The exponent is 5, which in binary is 1012. So the full floating-point representation is: 1.10110112×210121.1011011_2 \times 2^{101_2}

In binary, the mantissa always starts with a 1. Since this is true we can drop it from the representation entirely and represent the fractional part as an integer (why would we do this?)

IEEE 754 Standard and Precision for binary floating point

Since we are limited by a finite number of bits, floating-point numbers are divided into precision parts according to the IEEE 754 standard:

PrecisionTotal BitsSignExponentMantissa
Single321823
Double6411152
Quad128115112

The sign bit determines if the number is positive or negative. The exponent bits store the magnitude (shifted by the bias to account for negatives), and the mantissa stores the fractional part (following the leading 1).

Python by default using double precision floats.

Sources of Round-off Error

Roundoff error is the difference between the true number and the finite-precision representation. This error can be mitigated systematically by using higher precision at the cost of computational speed and memory.

Although individual errors are small, algorithms involve many steps and errors can easily accumulate. Compilers and simplification steps may mitigate these errors, but it is often best to rely on packaged subroutines that are numerically stabilized - specifically to avoid such problems.

NB: “Numerical stability” has two meanings in numerical methods. The first is robustness to roundoff error, the second (discussed with PDEs) is concerned with propogation of errors.

Unrepresentable Numbers

Some numbers cannot be represented cleanly in base-2. The binary representation of 0.1 is actually a repeating fraction, which means it cannot be perfectly represented with a finite number of bits.

Disparate Magnitudes (Loss of Associativity)

Finite precision means small numbers can be completely lost when added to very large numbers. Because of this, we cannot always rely on the associative property of addition.

Subtractive Cancellation

Subtractive cancellation happens when two nearly equal numbers are subtracted, causing a dramatic loss of significant digits.

A common task is to find the rate of change of a measurement. This is discussed later in finite difference but naively one would expect: ΔT=Tfinal−Tinitial\Delta T = T_{final} - T_{initial} which generates spurious results when Tfinal≈TinitialT_{final} \approx T_{initial}.

What does this mean for data collection and analysis?

Machine epsilon

Whereas subtractive cancellation exposes the binary ‘noise’, at some point a computer just isn’t able to distinguish between numbers with too many significant digits. Machine epsilon is defined as the smallest increment you can add to 1.0 that results in a number that is different from 1.0.

It is closely related to the mantissa (which captures the significant digits in the binary representation). For double precision, this is approximately 252=10−162^{52} = 10^{-16} (not exactly since the mantissa is in binary with teh leading 1 dropped.)

Example: The Quadratic Formula

Recall the quadratic formula: x=−b±b2−4ac2ax = \frac{-b\pm \sqrt{b^2-4ac}}{2a}

If 4ac≪b24ac \ll b^2 this will result in subtractive cancellation in the numerator. Let’s solve the roots of: x2+108x+1x^2 + 10^8x + 1

Numerically Stable Implementation

The problem occurs when b≫acb \gg ac. The discriminant becomes ≈b\approx b and the numerator can suffer subtractive cancellation on either the plus or negative, depending on the sign of bb.

A numerically stabilized method accounts for the sign of bb; sign(b)={1,−1}sign(b)= \{1, -1\}, if bb is positive or negative, and uses the relationship x1⋅x2=c/ax_1 \cdot x_2 = c/a.