Numbers and Floating Point

Integers and overflow, IEEE 754 floats, rounding error, cancellation, summing, comparing, units and significant figures.

Integers

Computers store whole numbers exactly, in a fixed number of bits, so they overflow. An n-bit unsigned integer holds 0 to 2n − 1; a signed integer (two’s complement, see Bits, Bytes and Data Formats) holds −2n−1 to 2n−1 − 1. Python integers grow without limit; C, Rust (in release builds), Java and most hardware wrap or are undefined.

Worked example: division, remainder and overflow

  • Truncation. In C, 7 / 2 is 3 (integer division truncates towards zero), and -7 / 2 is -3. In Python, 7 // 2 is 3 but -7 // 2 is -4 (floor division rounds down). Neither is wrong; they differ.
  • Remainder. -7 % 3 is -1 in C (the sign follows the dividend) and 2 in Python (the sign follows the divisor). A wrap-around index like (i − 1) % n is safe in Python and needs (i − 1 + n) % n in C.
  • Overflow. An unsigned 8-bit counter goes 255 + 1 → 0; a signed 16-bit goes 32 767 + 1 → −32 768 (undefined in C). A 32-bit millisecond counter wraps after 232 ms = 49.7 days; a 32-bit seconds counter (Unix time) after 2038-01-19.
  • Mixed types. int / int is integer division even if the result goes into a double: write (double)a / b.

Floating-point numbers

Most real numbers are stored in IEEE 754 floating-point format: a sign, an exponent and a mantissa, so that value = (−1)sign × 1.mantissa × 2exponent. A 32-bit float has 24 significant bits (about 7 decimal digits); a 64-bit double has 53 (about 16 digits).

signexponent (8 bits)mantissa / fraction (23 bits)00111110001000000000000000000000310exponent = 01111100 = 124124 − 127 (bias) = −3mantissa = .01 = 0.251 + 0.25 = 1.25 (hidden leading 1)value = (−1)^sign × 1.mantissa × 2^(exponent − 127) = 1.25 × 2⁻³ = 0.15625In hexadecimal this is 0x3E200000.
How a 32-bit float stores 0.15625. The exponent is stored with a bias of 127, and the mantissa has an implied leading 1.
float (32-bit)double (64-bit)
Significant bits24 (23 stored)53 (52 stored)
Decimal digitsabout 7about 15 to 16
Machine epsilon (gap above 1.0)2−23 = 1.192e-72−52 = 2.220e-16
Largest finite value3.403e+381.798e+308
Smallest normal value1.175e-382.225e-308
Gaps between floating-point numbers grow with the number1e-321e-281e-241e-201e-161e-121e-81e-41e01e4size of the numbergap to the next float1e-31e01e31e61e91e1232-bit float (float)64-bit float (double)16 777 216: gap of 2Each line steps up at every power of two. Relative precision stays near 10⁻⁷ (float) and 10⁻¹⁶ (double).
The gap between neighboring floats grows with the size of the number, so absolute precision falls as numbers get large.

Worked example: why 0.1 is not 0.1

  • 0.1 is a repeating fraction in binary (0.000110011001100…), just as 1 ÷ 3 repeats in decimal, so it must be rounded. The nearest double is 0.100000000000000005551115123126… and the nearest float is 0.100000001490116119384765625000….
  • So 0.1 + 0.2 is 0.30000000000000004, not 0.3, and adding 0.1 ten times gives 0.99999999999999989, not 1. These are not bugs; they are the rounding of the stored values.
  • Large and small do not mix. A float near 16 777 216 (224) has gaps of 2, so 16 777 216 + 1 stays 16 777 216. A running total in a float stops growing when each addition is smaller than half the gap.
  • Time stamps. Seconds since 1970 are about 1.79 × 109 now. A float there has a gap of 128 seconds (!), a double a gap of 2.38e-7 s (about 0.24 µs). Never store a time stamp in a float; use a 64-bit integer (microseconds or nanoseconds) or a double with care.
  • π in a float is 3.1415927410, correct to about seven digits (the true value is 3.1415926536).

Rounding error and what it does

Each floating-point operation rounds its result, so it has a relative error of at most ε ÷ 2 (about 1.1 × 10−16 for a double). One operation is harmless; the trouble is that errors accumulate through millions of operations, and that some operations amplify them. The rules of thumb:

  • Use double by default on a PC; use float only when memory or speed demand it, and then check the accuracy.
  • Errors usually grow like √n for random rounding and like n for a steady bias, so a long sum or a long simulation needs care.
  • Algorithms matter more than precision: a stable formula in single precision beats a naive one in double.
  • Test with known answers and with different precisions: if the result changes in the third digit when you switch from float to double, it is not trustworthy to more than three.
  • Floating-point addition is not associative: (a + b) + c may differ from a + (b + c), so the order of a sum (and parallel or vectorized sums) changes the last digits.

Adding many numbers

Worked example: a million additions of 0.1

  • The exact total of 1 000 000 additions of 0.1 is 100 000. In double precision the plain running total gives 100000.000001333, an error of 1.33e-6.
  • In single precision the same loop gives 100958.34, an error of 958 (0.96 %): as the total grows, the gap grows, and many additions of 0.1 are partly lost.
  • Kahan (compensated) summation keeps the lost low-order part in a second variable and adds it back: in double it returns 100000.000000000, an error of 0.00e+0 (essentially exact).
  • Alternatives: sum in a wider type (a double accumulator for float data), sum in pairs (NumPy’s sum does pairwise summation), sort small to large, or use math.fsum in Python, which is exact.
def kahan_sum(values):
    total = 0.0
    c = 0.0                       # running compensation for lost low-order bits
    for v in values:
        y = v - c
        t = total + y
        c = (t - total) - y       # what was lost when adding y to total
        total = t
    return total

import math
math.fsum([0.1] * 10)             # 1.0 exactly; sum([0.1] * 10) gives 0.9999999999999999

Cancellation: subtracting nearly equal numbers

Subtracting two numbers that are almost equal cancels their leading digits and leaves the rounding error of the inputs as the leading digits of the answer: catastrophic cancellation. The cure is usually to rearrange the formula so that the subtraction does not happen.

Worked examples: two formulas, rewritten

  • (1 − cos x) ÷ x² for small x. The true limit is 0.5. At x = 10−8 the direct formula gives 0.0000 (cos x rounds to exactly 1, so the numerator is 0). The identity 1 − cos x = 2 sin²(x ÷ 2) gives 0.5000.
  • The small root of x² + 108 x + 1 = 0. The exact small root is −1.0000000000000000 × 10−8. The textbook formula (−b + √(b² − 4)) ÷ 2 subtracts two numbers near 108 and gives -7.451e-9. Computing the large root first and using the product of the roots (c ÷ a = 1) gives -1.0000000000000000e-8.
  • Differences of large measured numbers: the difference of two 10-digit readings that agree to eight digits has only two good digits. Measure the difference directly when you can.
  • Numerical derivatives divide a small difference by a small step: too large a step is inaccurate, too small a step is dominated by rounding, and the best step is about √ε × x ≈ 10−8 x in double (see Numerical Methods).

Comparing numbers

Never test floating-point numbers for equality. Test whether they are close, using a relative tolerance for large numbers and an absolute one near zero.

Worked example: absolute against relative

Two readings: 1000000.1000000000 and 1000000.1000000001. Their difference is 1.16e-10, which is one rounding gap apart (the gap of a double near a million is about 1.2 × 10−10). An absolute tolerance of 10−12 would call them different, but the difference is only 1.16e-16 of the value: close enough for any practical purpose. A relative tolerance of 10−9 accepts it, and Python’s math.isclose(a, b, rel_tol=1e-9, abs_tol=1e-12) does exactly this check (the absolute term handles comparisons with zero).

#include <math.h>
#include <float.h>
int nearly_equal(double a, double b, double rel, double abs_tol) {
    double diff = fabs(a - b);
    return diff <= abs_tol || diff <= rel * fmax(fabs(a), fabs(b));
}
/* use rel about 1e-9 for doubles, 1e-5 for floats; abs_tol a few times the noise of the data */
  • Compare with a tolerance that means something: the noise of the measurement, not a magic 1e-6.
  • Use < and > freely; they are safe, it is == that is not.
  • Do not use a float as a loop counter: for (x = 0; x != 1.0; x += 0.1) never ends. Count with an integer and compute x from it.
  • Test exact values only when they are exactly representable (powers of two, small integers).

Special values: NaN, infinity and negative zero

ValueHow it arisesBehavior
+∞ / −∞1.0 ÷ 0.0 (in IEEE arithmetic), overflow past the largest value∞ + 1 = ∞; 1 ÷ ∞ = 0; ∞ − ∞ is NaN. (Python raises ZeroDivisionError for 1.0 / 0.0 instead.)
NaN (not a number)0.0 ÷ 0.0, √(−1), ∞ − ∞, a failed conversionPropagates through every calculation; NaN ≠ NaN, so test with isnan() (math.isnan, np.isnan)
−0.0Negative results that underflow to zeroEquals +0.0 in comparisons but can change the sign of 1 ÷ x and atan2
Subnormal numbersValues below the smallest normal numberGradual underflow: precision falls; on some CPUs they are very slow
  • Check inputs and intermediate results for NaN and infinity in data pipelines: one NaN in a sum makes the whole sum NaN, which is often how you find a bad sensor value.
  • Missing data is not zero: represent it as NaN (or a masked value), not 0 or −999.
  • On microcontrollers the behavior of division by zero, NaN and overflow depends on the chip and the compiler options: avoid relying on it.

Rounding and significant figures

  • Round only for display, not in the middle of a calculation: rounding early adds errors that accumulate.
  • Rounding rules: Python’s round() and the IEEE default round half to even, so round(2.5) is 2 and round(3.5) is 4 (and round-half-up would give 3 for 2.5). This removes a bias when many numbers are rounded; spreadsheets and some languages round half up, so results can differ in the last digit.
  • Significant figures: a result cannot be more precise than its least precise input. A length of 12.3 mm (three significant figures) times a width of 4.56 mm gives 56.088 mm², which you report as 56.1 mm², not 56.088.
  • Display with formats, not by converting to text yourself: f"{x:.3g}" (three significant figures), f"{x:.2f}" (two decimals), "%.3g" in C.
  • Money and exact decimals: use integers (cents: 10 + 20 = 30 exactly) or a decimal type (Python decimal), never binary floats.

Units and uncertainty

A number in a program has no unit, so keep track of it yourself: put it in the name (length_mm), convert at the edges of the program, and use one system inside. Mixing units has destroyed spacecraft (NASA’s Mars Climate Orbiter, 1999, was lost because one program gave thrust in pound-force seconds and another expected newton seconds). A units library such as pint (Python) catches the mistakes automatically.

Worked example: carrying uncertainty

A power is computed from a voltage V = 12.00 ± 0.05 V and a current I = 2.35 ± 0.02 A. P = V × I = 12.00 × 2.35 = 28.20 W. For a product the relative uncertainties combine in quadrature: √((0.05 ÷ 12.00)² + (0.02 ÷ 2.35)²) = √(1.74e-5 + 7.24e-5) = 0.95 %, so ΔP = 0.27 W. Report P = 28.2 ± 0.3 W: the extra digits that the computer prints are not information.

Choosing a number type

For…UseWhy
Counts, indexes, states, IDsAn integer type of the right width (uint32_t, size_t)Exact; faster than floats; no rounding
Bytes and bit fieldsuint8_t, uint16_tExact width and no sign surprises (see Bits, Bytes and Data Formats)
Time stamps and long counters64-bit integer (microseconds or nanoseconds)No precision loss, no early wrap-around
Measurements and engineering math on a PCdoubleAbout 16 digits: more than any sensor
Large arrays, graphics, machine learning, microcontrollers with an FPUfloat (or 16-bit float formats)Half the memory and bandwidth; check the accuracy
Microcontrollers without an FPUIntegers and fixed-pointFloat math is slow and large (see Embedded Systems)
Money, exact decimalsInteger cents or a decimal typeBinary floats cannot represent 0.1
Exact big numbersPython int, big-integer librariesNo overflow

Related

FAQ

Why is 0.1 + 0.2 not 0.3?

Neither 0.1 nor 0.2 can be stored exactly in binary; each is rounded to the nearest representable value, and their sum rounds again to 0.30000000000000004. Compare with a tolerance, or use integers (cents) or a decimal type for exact decimals.

Should I use float or double?

double unless memory or speed force you otherwise. The extra precision is cheap on a PC, and a float’s seven digits are quickly used up by sums and small differences.

What is NaN and why does it spread?

Not-a-number is the result of an undefined operation (0 ÷ 0, √−1). Any calculation that uses it also gives NaN, and it compares unequal to everything, even itself, so use isnan(). One bad sensor value can turn a whole average into NaN.

How many digits should I show?

No more than the data support: the number of significant digits of the least precise input, or one more for intermediate values. Compute the uncertainty, and round the result to match it.

References

  • IEEE 754-2019, Standard for Floating-Point Arithmetic.
  • Goldberg, D., “What every computer scientist should know about floating-point arithmetic”, ACM Computing Surveys, 1991.
  • Kahan, W., “Pracniques: further remarks on reducing truncation errors”, Communications of the ACM, 1965 (compensated summation); Higham, N., Accuracy and Stability of Numerical Algorithms.

Last reviewed: October 2026. Digits shown come from IEEE doubles computed in the page builder: they are reproducible on any IEEE 754 machine.