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 / 2is 3 (integer division truncates towards zero), and-7 / 2is -3. In Python,7 // 2is 3 but-7 // 2is -4 (floor division rounds down). Neither is wrong; they differ. - Remainder.
-7 % 3is -1 in C (the sign follows the dividend) and 2 in Python (the sign follows the divisor). A wrap-around index like(i − 1) % nis safe in Python and needs(i − 1 + n) % nin 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/intis integer division even if the result goes into adouble: 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).
| float (32-bit) | double (64-bit) | |
|---|---|---|
| Significant bits | 24 (23 stored) | 53 (52 stored) |
| Decimal digits | about 7 | about 15 to 16 |
| Machine epsilon (gap above 1.0) | 2−23 = 1.192e-7 | 2−52 = 2.220e-16 |
| Largest finite value | 3.403e+38 | 1.798e+308 |
| Smallest normal value | 1.175e-38 | 2.225e-308 |
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
doubleis 0.100000000000000005551115123126… and the nearestfloatis 0.100000001490116119384765625000…. - So
0.1 + 0.2is 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
floatnear 16 777 216 (224) has gaps of 2, so 16 777 216 + 1 stays 16 777 216. A running total in afloatstops growing when each addition is smaller than half the gap. - Time stamps. Seconds since 1970 are about 1.79 × 109 now. A
floatthere has a gap of 128 seconds (!), adoublea 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
floatonly 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
doubleaccumulator forfloatdata), sum in pairs (NumPy’ssumdoes pairwise summation), sort small to large, or usemath.fsumin 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
| Value | How it arises | Behavior |
|---|---|---|
| +∞ / −∞ | 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 conversion | Propagates through every calculation; NaN ≠ NaN, so test with isnan() (math.isnan, np.isnan) |
| −0.0 | Negative results that underflow to zero | Equals +0.0 in comparisons but can change the sign of 1 ÷ x and atan2 |
| Subnormal numbers | Values below the smallest normal number | Gradual 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, soround(2.5)is 2 andround(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… | Use | Why |
|---|---|---|
| Counts, indexes, states, IDs | An integer type of the right width (uint32_t, size_t) | Exact; faster than floats; no rounding |
| Bytes and bit fields | uint8_t, uint16_t | Exact width and no sign surprises (see Bits, Bytes and Data Formats) |
| Time stamps and long counters | 64-bit integer (microseconds or nanoseconds) | No precision loss, no early wrap-around |
| Measurements and engineering math on a PC | double | About 16 digits: more than any sensor |
| Large arrays, graphics, machine learning, microcontrollers with an FPU | float (or 16-bit float formats) | Half the memory and bandwidth; check the accuracy |
| Microcontrollers without an FPU | Integers and fixed-point | Float math is slow and large (see Embedded Systems) |
| Money, exact decimals | Integer cents or a decimal type | Binary floats cannot represent 0.1 |
| Exact big numbers | Python int, big-integer libraries | No overflow |
Related
- Programming Languages: the overview of this guide.
- Bits, Bytes and Data Formats: integers as bit patterns.
- Numerical Methods: how these errors affect equation solving, integration and simulation.
- Embedded Systems: Memory and Number Formats: fixed-point math on a microcontroller.
- Data Logging: precision, rounding and time stamps of measured data.
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.
