Floating-point arithmetic (source code)

= Floating-point arithmetic
{title2=$\pm(d_0.d_1\cdots d_{p-1})_\beta\,\beta^e$}
{wiki}

<Floating-point arithmetic> represents a <real number> by a sign, a fixed number of significant digits, and a scale. It trades exactness for a compact representation spanning many orders of magnitude. The picture shows the essential compromise: with the same precision, large numbers have wider gaps between representable neighbors. Thus accuracy is naturally relative to magnitude, rather than a fixed distance along the <number line>.

\Image[/floating-point-spacing.png]
{title=Eight normalized four-bit binary values in each of three intervals, with gaps doubling from one eighth to one half}

In radix $\beta$, an <integer> at least two, a nonzero normalized value has the <positional notation>
$$
y=\pm\left(d_0+\frac{d_1}{\beta}+\cdots+\frac{d_{p-1}}{\beta^{p-1}}\right)\beta^e,\qquad 1\leq d_0<\beta,\quad 0\leq d_j<\beta\ (j\geq1),
$$
where the digits and exponent $e$ are <integers>. The parenthesized factor is the significand; $p$ counts all its significant digits, including the leading digit. Normalization means $d_0\ne0$, so the same value has one such representation. The allowed exponents satisfy $e_{\min}\leq e\leq e_{\max}$. Finite precision restricts the available digits, while the exponent bounds restrict the range. Zero needs a separate representation. This scales a digit string in <positional notation> by a power of its base.https://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html[David Goldberg, What Every Computer Scientist Should Know About Floating-Point Arithmetic, §Floating-point Formats, 1991; Oracle's authorized edited reprint]{ref}

For positive normalized values in $[\beta^e,\beta^{e+1})$, increasing the last digit by one gives the constant neighbor spacing
$$
\Delta_e=\beta^{e-p+1}.
$$
At the next scale, the spacing is multiplied by $\beta$. In the <binary numeral system> with $p=4$, the values in $[1,2)$ are $1,1.125,1.25,\ldots,1.875$; those in $[2,4)$ are $2,2.25,\ldots,3.75$; those in $[4,8)$ are $4,4.5,\ldots,7.5$. The respective gaps are $1/8$, $1/4$, and $1/2$. Although absolute resolution deteriorates with magnitude, $\Delta_e/y$ stays between $\beta^{-p}$ and $\beta^{1-p}$. This scale invariance is why <floating-point arithmetic> suits both microscopic and astronomical calculations.

The IEEE 754 binary64 format uses the <binary numeral system> with $p=53$, including an implicit leading one for normalized values, and normal exponents $-1022\leq e\leq1023$. Its 64 stored <binary digits> comprise one sign digit, 11 exponent digits and 52 trailing significand digits. <Subnormal numbers> extend the range toward zero with a different leading-digit rule. In rounding to nearest, an operation returns the representable value closest to its exact result; a tie goes to the value whose last significant <binary digit> is even. <Machine epsilon> describes the gap just above one, whereas <unit roundoff> bounds a single relative rounding error in the normal range. These format and rounding rules are presented in MIT's floating-point lecture.https://ocw.mit.edu/courses/18-335j-introduction-to-numerical-methods-spring-2019/2f313023ae3404bc217a81a31b227170_MIT18_335JS19_lec2.pdf[Per-Olof Persson, MIT 18.335J, Lecture 2: Floating-Point Arithmetic, the IEEE Standard, slides 6–9, 2019]{ref}

Even an ordinary terminating decimal can require rounding in the <binary numeral system>. The <rational number> $0.1=1/10$ has the repeating <positional notation> $0.0001100110011\ldots{}_2$. Every finite binary <fraction> has a reduced <denominator> that is a power of two, so the factor five in $10$ prevents an exact finite binary representation. In binary64, the exact stored values of three decimal literals and their computed <sum> are
$$
\begin{array}{c|c}
\text{Python expression}&\text{exact stored rational number}\\\hline
0.1&3602879701896397/36028797018963968\\
0.2&3602879701896397/18014398509481984\\
0.3&5404319552844595/18014398509481984\\
0.1+0.2&1351079888211149/4503599627370496
\end{array}
$$
The exact <sum> of the first two stored <rational numbers> is $10808639105689191/36028797018963968$, halfway between two binary64 values. The even-last-digit tie rule rounds it upward. The printed result is `0.30000000000000004`, one step $2^{-54}$ above stored `0.3`, so `0.1 + 0.2 == 0.3` is false. Both conversion of the inputs and rounding of the <addition> matter here.

To reproduce the table on a Python installation using binary64, inspect exact <fractions> rather than only their printed decimal representations:
``
import sys
assert (sys.float_info.radix, sys.float_info.mant_dig) == (2, 53)
for value in (0.1, 0.2, 0.3, 0.1 + 0.2):
    print(repr(value), value.as_integer_ratio())
print(0.1 + 0.2 == 0.3)  # False
``
Python's `as_integer_ratio()` returns a pair of <integers> whose quotient equals the stored finite value exactly.https://docs.python.org/3.14/library/stdtypes.html#float.as_integer_ratio[Python 3.14 documentation, float.as_integer_ratio]{ref} Exact <rational numbers> are also useful for checking <floating-point arithmetic>: a short decimal printout can hide the approximation being made.

Historically, David Goldberg's March 1991 ACM Computing Surveys paper explained representation, rounding and <catastrophic cancellation> together with the IEEE standard's design choices. Oracle provides an authorized edited reprint. Its enduring lesson is practical: use the spacing to understand <roundoff error>, and inspect intermediate values when mathematically equivalent <algorithms> behave differently.https://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html[Goldberg, 1991, reprint notice and §§Rounding Error, Cancellation]{ref}