G27
Self-checkedTier 2 14 min readdl.floating-point-arithmetic

Floating-point arithmetic and precision

Adding, subtracting and multiplying IEEE 754 numbers: the result is the exact answer rounded to the nearest float (ties to even), obtained in hardware by aligning the exponents, adding with guard, round and sticky bits, normalising and rounding; special values, the spacing of floats and when a small number is lost, cancellation, and why floating-point addition is not associative.

Why it exists

The page on IEEE 754 tells how a number is stored. Doing arithmetic with the stored numbers is a different matter: the sum of two floats is usually not a float. 224+12^{24} + 1 needs 25 significant bits and a single-precision number has 24. The hardware must therefore produce a nearby float, and the rules for which one are fixed by the standard so that every machine gives the same answer. Exam questions ask for the bit pattern of a sum, the number of bits needed, or why a program prints a surprising result; the official syllabus asks for "number representation and arithmetic (fixed and floating point)".

Prerequisites

You need the fields of a single-precision number, the hidden bit, the bias, the special values and denormals from IEEE 754, and binary addition from adders and subtractors.

Precise definitions

The rule of the standard. For each of +,−,×,÷+, -, \times, \div and the square root, the computed result must be the exact mathematical result rounded to a float. Rounding means choosing the nearest float; if the exact result is exactly halfway between two floats, the one whose last fraction bit is 0 (the even one) is chosen: round to nearest, ties to even, the default mode. The other IEEE modes (toward zero, toward +∞+\infty, toward −∞-\infty) are not computed here. A result too large for the format becomes ±∞\pm\infty; one below the smallest normal number becomes a denormal (gradual underflow) or zero.

Spacing. In [2k,2k+1)[2^k, 2^{k+1}) the floats are 2k−232^{k-23} apart in single precision (one ulp, unit in the last place). The relative gap is between 2−242^{-24} and 2−232^{-23}. 2−232^{-23} is the machine epsilon (the gap just above 1). The relative error of one rounded operation (normal results) is at most half a gap divided by the number, 2−242^{-24}.

Guard, round and sticky bits. To obtain the correctly rounded result the adder keeps three extra bits to the right of the significand: G (guard, the first bit beyond), R (round, the next), and S (sticky, the OR of all bits beyond those). Three bits are enough to round correctly; the sticky bit is what makes "exactly halfway" different from "just above halfway".

Special values in arithmetic. Any operation with a NaN gives NaN. ∞+∞=∞\infty + \infty = \infty, ∞+(−∞)=\infty + (-\infty) = NaN, ∞×0=\infty \times 0 = NaN, x+∞=∞x + \infty = \infty, x×∞=±∞x \times \infty = \pm\infty for x≠0x \ne 0. x+(−x)=+0x + (-x) = +0 (in the default mode). A comparison with NaN is false, even NaN=NaN\text{NaN} = \text{NaN}.

How it works

Addition and subtraction

  1. Unpack both numbers: sign, exponent, significand with the hidden bit.
  2. Compare exponents. The number with the smaller exponent is the one that is shifted. The difference dd is the shift.
  3. Align: shift the smaller significand right by dd places, keeping G, R and S for what is shifted out.
  4. Add the significands if the signs are equal, subtract if they differ.
  5. Normalise: a carry out needs a shift right by 1 (exponent +1+1); cancelled leading bits need a shift left (exponent decreased by the same amount).
  6. Round to nearest even using the last kept bit, G, R and S.
  7. Pack (and handle overflow, a rounding carry that needs another normalisation, and underflow).

Floating-point addition: 12.5 + 0.1875: start

Computed by a script, not typed
stepLarger operandSmaller operandWorking
1. Unpack
2. Exponent difference
3. Align
4. Add or subtract
5. Normalise
6. Round
7. Pack

Step 0

x = 12.5 = 1.1001 x 2^3 and y = 0.1875 = 1.1 x 2^-3. The steps of a hardware adder: unpack, align the exponents, add, normalise, round, pack.

0 / 7
computed by script: the hardware algorithm of scripts/fp_lib.py applied to single-precision words. python3 scripts/gen-dl-fp-traces.py

12.5+0.187512.5 + 0.1875: the exponents differ by 6, the smaller number's significand slides six places to the right, the sum fits in the 24 bits, nothing needs to be rounded. The result 12.6875 is exact.

Rounding to nearest even

Rounding to nearest even: start

Computed by a script, not typed
caseLGRSActionWhy
L G R S = 0 0 0 0
L G R S = 0 0 0 1
L G R S = 0 0 1 0
L G R S = 0 0 1 1
L G R S = 0 1 0 0
L G R S = 0 1 0 1
L G R S = 0 1 1 0
L G R S = 0 1 1 1
L G R S = 1 0 0 0
L G R S = 1 0 0 1
L G R S = 1 0 1 0
L G R S = 1 0 1 1
L G R S = 1 1 0 0
L G R S = 1 1 0 1
L G R S = 1 1 1 0
L G R S = 1 1 1 1

Step 0

Round to nearest, ties to even. L is the last bit that is kept, G the first bit that is thrown away, R the next, S the OR of all the others. Sixteen combinations; only the two with G=1, R=0, S=0 are ties.

0 / 16
computed by script: the hardware algorithm of scripts/fp_lib.py applied to single-precision words. python3 scripts/gen-dl-fp-traces.py

If the guard bit is 0 the discarded part is below half a place: truncate. If G is 1 and anything else beyond it is 1, it is above half: round up. Only G=1 with R=S=0 is a tie; then the kept bits are rounded to the even neighbour, up if the last kept bit is 1, unchanged if it is 0. Over many operations this does not drift in one direction, which is why it is the default and why it is not "round half up".

Floating-point addition with a tie: 16777216 + 3: start

Computed by a script, not typed
stepLarger operandSmaller operandWorking
1. Unpack
2. Exponent difference
3. Align
4. Add or subtract
5. Normalise
6. Round
7. Pack

Step 0

x = 2^24 and y = 3. Near 2^24 the floats are 2 apart, so the exact sum 2^24 + 3 lies exactly between two floats. Ties are broken towards the neighbour whose fraction ends in 0.

0 / 7
computed by script: the hardware algorithm of scripts/fp_lib.py applied to single-precision words. python3 scripts/gen-dl-fp-traces.py

224+32^{24} + 3: near 2242^{24} the floats are 2 apart, so 224+32^{24} + 3 lies exactly halfway between 224+22^{24} + 2 and 224+42^{24} + 4. The significand after the alignment is …0001∣100\dots 0001 | 100: G=1, R=S=0, last kept bit 1 (odd): round up to the even neighbour, 224+42^{24} + 4. By the same rule 224+12^{24} + 1 (halfway between 2242^{24} and 224+22^{24} + 2, last kept bit 0) rounds down to 2242^{24}: adding 1 to 2242^{24} changes nothing, and 2242^{24} is the first integer whose successor is not a single-precision float.

Cancellation

Subtraction with cancellation: start

Computed by a script, not typed
stepLarger operandSmaller operandWorking
1. Unpack
2. Exponent difference
3. Align
4. Add or subtract
5. Normalise
6. Round
7. Pack

Step 0

x = 1 + 2^-22 and y = -(1 + 2^-23): the numbers are nearly equal and the result is small. The leading bits cancel and have to be shifted out.

0 / 7
computed by script: the hardware algorithm of scripts/fp_lib.py applied to single-precision words. python3 scripts/gen-dl-fp-traces.py

When two nearly equal numbers are subtracted, the leading bits cancel; the result is computed exactly (nothing is rounded: the exponent difference is 0) but the normalisation shifts left by 23 places and the result has only one significant bit. If xx and yy themselves carried rounding errors in their last bits, those bits are now the whole result: the relative error is huge. This is catastrophic cancellation, the reason numerical code avoids subtracting nearly equal numbers.

Multiplication

Multiplication needs no alignment. Exponents add (and one bias is subtracted, because both fields contain it), significands multiply, the signs xor. The product of two numbers in [1,2)[1, 2) is in [1,4)[1, 4): if it is at least 2, shift right by one and add one to the exponent. Then round.

Floating-point multiplication: 14 x 0.34375: start

Computed by a script, not typed
stepFirst factorSecond factorWorking
1. Unpack
2. Add the exponents
3. Multiply the significands
4. Normalise
5. Round
6. Pack

Step 0

x = 14 = 1.75 x 2^3 and y = 0.34375 = 1.375 x 2^-2. Multiplication needs no alignment: the exponents add and the significands multiply.

0 / 6
computed by script: the hardware algorithm of scripts/fp_lib.py applied to single-precision words. python3 scripts/gen-dl-fp-traces.py

How big are the gaps?

Spacing of single-precision floats: start

Computed by a script, not typed
numberSpacing (1 ulp)Half a spacingDoes x + 1 differ from x?Exponent of the spacing
x = 2^0
x = 2^10
x = 2^20
x = 2^23
x = 2^24
x = 2^30
x = 2^100

Step 0

In single precision a number 1.f x 2^k has 23 fraction bits, so the gap to the next float is 2^(k-23): the gaps grow with the numbers. Adding a smaller number than half a gap changes nothing.

0 / 7
computed by script: the hardware algorithm of scripts/fp_lib.py applied to single-precision words. python3 scripts/gen-dl-fp-traces.py

The gap is 2k−232^{k-23} for a number in [2k,2k+1)[2^k, 2^{k+1}): it doubles every time the exponent grows by one. A number smaller than half the gap, added to xx, is rounded away completely: this is absorption. All integers up to 224=16 777 2162^{24} = 16\,777\,216 are single-precision numbers (the gap is 1 or less); above it, only even numbers (up to 2252^{25}), then multiples of 4, and so on. There are 223=8 388 6082^{23} = 8\,388\,608 floats in [1,2)[1, 2), and the same number in every other interval [2k,2k+1)[2^k, 2^{k+1}).

Addition is not associative

Addition is not associative: start

Computed by a script, not typed
numbers(a + b) + ca + (b + c)Comparison
a = 2^24, b = 1, c = 1
a = 1, b = 2^-24, c = 2^-24
a = 0.1, b = 0.3, c = 0.4
a = 10^8, b = -10^8, c = 1
20000 random triples in [0, 1)

Step 0

Floating-point addition is commutative but not associative. Each row adds the same three numbers in two orders, each addition rounded to single precision.

0 / 5
computed by script: the hardware algorithm of scripts/fp_lib.py applied to single-precision words. python3 scripts/gen-dl-fp-traces.py

Each addition is rounded, so what is lost depends on the order. (224+1)+1=224(2^{24} + 1) + 1 = 2^{24} (each 1 is absorbed), but 224+(1+1)=224+22^{24} + (1 + 1) = 2^{24} + 2. In the second row each 2−242^{-24} is exactly half a gap below 1, a tie that rounds back to 1 twice, while 2−24+2−24=2−232^{-24} + 2^{-24} = 2^{-23} is a whole gap and survives. In the last row (108−108)+1=1(10^8 - 10^8) + 1 = 1 but 108+(−108+1)=010^8 + (-10^8 + 1) = 0, because −108+1-10^8 + 1 rounds to −108-10^8. Even for random numbers in [0,1)[0, 1) about a quarter of all triples give different results in the two orders. Addition and multiplication are commutative; they are not associative or distributive, and so a compiler may not reorder floating-point sums unless it is told that exactness does not matter.

A real program

The program is compiled with plain float arithmetic and prints what the hardware does. volatile stops the compiler from computing the sums at compile time.

#include <float.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
 
static uint32_t bits(float x) {
    uint32_t u;
    memcpy(&u, &x, sizeof u);
    return u;
}
 
int main(void) {
    volatile float big = 16777216.0f;          /* 2^24 */
    volatile float one = 1.0f, three = 3.0f, tiny = 5.9604645e-8f;   /* 2^-24 */
    float s1 = big + one, s3 = big + three;
    printf("2^24 + 1 = %.1f   (bits %08X)\n", s1, bits(s1));
    printf("2^24 + 3 = %.1f   (bits %08X)\n", s3, bits(s3));
    float left = (one + tiny) + tiny, right = one + (tiny + tiny);
    printf("(1 + 2^-24) + 2^-24 = %.9g   1 + (2^-24 + 2^-24) = %.9g\n", left, right);
    volatile float a = 100000000.0f, b = -100000000.0f;
    float l2 = (a + b) + one, r2 = a + (b + one);
    printf("(1e8 - 1e8) + 1 = %.1f   1e8 + (-1e8 + 1) = %.1f\n", l2, r2);
    printf("FLT_EPSILON = %.9g (this is 2^-23: %s)\n", FLT_EPSILON, FLT_EPSILON == 1.1920928955078125e-07f ? "yes" : "no");
    volatile double d1 = 0.1, d2 = 0.2, d3 = 0.3;
    printf("double: 0.1 + 0.2 == 0.3 is %s, 0.1 + 0.2 = %.17g\n", d1 + d2 == d3 ? "true" : "false", d1 + d2);
    return 0;
}
2^24 + 1 = 16777216.0   (bits 4B800000)
2^24 + 3 = 16777220.0   (bits 4B800002)
(1 + 2^-24) + 2^-24 = 1   1 + (2^-24 + 2^-24) = 1.00000012
(1e8 - 1e8) + 1 = 1.0   1e8 + (-1e8 + 1) = 0.0
FLT_EPSILON = 1.1920929e-07 (this is 2^-23: yes)
double: 0.1 + 0.2 == 0.3 is false, 0.1 + 0.2 = 0.30000000000000004

Worked examples

EX 15.5 + 0.75 by hand

Easy

5.5=101.12=1.011×225.5 = 101.1_2 = 1.011 \times 2^2 and 0.75=0.112=1.1×2−10.75 = 0.11_2 = 1.1 \times 2^{-1}. The exponents differ by d=3d = 3: shift the significand of 0.75 right by 3: 1.1→0.00111.1 \to 0.0011. Add (aligned at the exponent 2): 1.011+0.0011=1.10011.011 + 0.0011 = 1.1001, so the sum is 1.1001×22=110.012=6.251.1001 \times 2^2 = 110.01_2 = 6.25. Nothing was shifted out of the significand, so there is nothing to round. As a pattern: sign 0, exponent field 2+127=1292 + 127 = 129, fraction 10011001 followed by zeros.

EX 2A sum that must be rounded (8-bit format: 1 sign, 4 exponent bits with bias 7, 3 fraction bits)

Medium

Add a=13=1.101×23a = 13 = 1.101 \times 2^3 (pattern 0 1010 101) and b=0.5625=1.001×2−1b = 0.5625 = 1.001 \times 2^{-1} (pattern 0 0110 001). The exponent difference is 3−(−1)=43 - (-1) = 4. Shift the significand of bb right by 4: 1.001→0.0001 ∣ 0011.001 \to 0.0001\,|\,001 with G, R and S taking the bits that fall off: after 3 fraction bits the shifted number is 0.000 1 0 0 10.000\,1\,0\,0\,1, so G=1, R=0, and S=1 (the last 1 is beyond R). Add to 1.1011.101: 1.101+0.000=1.1011.101 + 0.000 = 1.101 with G R S = 1 0 1. G=1 and S=1: more than half, round up: 1.101+0.001=1.1101.101 + 0.001 = 1.110. The result is 1.110×23=141.110 \times 2^3 = 14, pattern 0 1010 110. The exact sum is 13.5625; the neighbours are 13 and 14, and 13.5625 is nearer to 14.

EX 3How many numbers, and which integers?

Medium

How many single-precision numbers are in [1,2)[1, 2)? The fraction has 23 bits, so 223=8 388 6082^{23} = 8\,388\,608. What is the largest nn such that every integer from 0 to nn is representable? n=224=16 777 216n = 2^{24} = 16\,777\,216: the integer 224+12^{24} + 1 needs 25 significant bits. What is the gap between consecutive floats near 10910^9? 229<109<2302^{29} < 10^9 < 2^{30}, so the gap is 229−23=642^{29-23} = 64: adding 20 to 10910^9 leaves it unchanged (20 is less than 32, half a gap).

EX 4Typical questions

GATE level

(a) "X is 0x41000000 and Y is 0x3F800000. What is the pattern of X + Y?" X=8X = 8 (exponent field 130130, fraction 0), Y=1Y = 1 (exponent field 127127). Difference 3: YY's significand becomes 0.0010.001; 1.000+0.001=1.0011.000 + 0.001 = 1.001 times 232^3 is 9: pattern 0x41100000. (b) "The exponents differ by 30. What is left of the smaller operand after the alignment?" Only the sticky bit: every bit of its significand falls beyond the round bit, and the sticky bit is 1 because the significand is not zero. The sum is then the larger number, rounded. (c) "Does (x + y) + z always equal x + (y + z) in floating point?" No (examples above). (d) "Why does 0.1 + 0.2 == 0.3 fail in double precision?" Neither 0.1 nor 0.2 nor 0.3 is stored exactly; the stored sum is 0.30000000000000004, not the stored 0.3. (e) "In a floating-point multiplication, how are the exponent fields combined?" Added, minus one bias. (f) "Order the steps of floating-point addition." Unpack, compare exponents and shift the smaller, add, normalise, round, pack. (g) "Why is x − y = 0 exactly when x = y?" With denormals no pair of different floats has a difference that rounds to zero (gradual underflow).

Cheat box

Cheat box

  • The result of +,−,×+, -, \times is the exact result rounded to nearest, ties to even.
  • Addition: unpack, compare exponents, shift the smaller significand right by dd (keep G, R, S), add or subtract, normalise (carry: right 1; cancellation: left), round, pack.
  • Round: G=0 truncate; G=1 and (R or S) up; G=1, R=S=0 a tie: up only if the last kept bit is 1.
  • Multiplication: signs xor, exponent fields add minus bias, significands multiply, normalise (product 2 or more: right 1, exponent + 1), round.
  • Gap above 2k2^k is 2k−232^{k-23}; a number below half a gap is absorbed; integers are exact up to 2242^{24}; relative error at most 2−242^{-24}; epsilon =2−23= 2^{-23}.
  • Not associative; subtracting nearly equal numbers loses all significant bits (cancellation).
  • NaN in, NaN out; ∞−∞\infty - \infty and 0×∞0 \times \infty are NaN; x+(−x)=+0x + (-x) = +0.

Traps and GATE patterns

Connections

The adder of the significands is the binary adder of the digital design pages, with a shifter in front and a normaliser after it. Fixed-point arithmetic has no alignment step but a fixed range; floating point trades that for a range of about 10±3810^{\pm 38} and a relative accuracy of 10−710^{-7}. The C types float and double of C types and operators use these formats.

PYQs

1 previous year question from your book. I re-solved each one myself (1 by running or simulating, 0 by reasoning only); the book's key is shown next to my answer. All PYQs

GATE 2026 · Set 1 · MCQ · 1 markQ1 · page 153 of the book
Consider the real valued variables X, Y and Z
represented using the IEEE -754 single precision
floating-point format. The binary representations
of X and Y in hexadecimal notation are as
follows:

X: 35C00000
Y: 34A00000

Let Z = X + Y.

Which one of the following is the binary
representation of Z, in hexadecimal notation?
  • (a)35C80000
  • (b)35CD0000
  • (c)35E80000
  • (d)35EC0000

Self-check

Question 1

In single precision, is 225+12^{25} + 1 representable? What does 225+12^{25} + 1 become, and what does 225+22^{25} + 2 become?

Show answer

Above 2242^{24} the gap is 2, above 2252^{25} it is 4. 225+12^{25} + 1 is not representable; the neighbours are 2252^{25} and 225+42^{25} + 4, and it is nearer to 2252^{25}, so it becomes 2252^{25}. 225+22^{25} + 2 is exactly halfway between them: a tie, rounded to the even neighbour, which is 2252^{25} (fraction 0).

Question 2

Two floats have exponent fields 140 and 120. How far is the smaller significand shifted? Can the smaller number change the larger one?

Show answer

By d=20d = 20 places. A number 2202^{20} times smaller than the larger one is still above half a gap (relative to the larger, half a gap is at most 2−242^{-24}), so it can change the last bits. With dd above 25 it is absorbed in an addition.

Question 3

Why does the hardware need the sticky bit if it already has the guard and round bits?

Show answer

To tell an exact tie (G=1 and everything beyond it 0) from a number slightly above the tie (G=1 and some later bit 1). With only G and R, a 1 further to the right would be lost, and the tie rule (round to even) would be applied to numbers that should simply round up.

Revision card

One-screen revision

  • Result = exact result rounded to nearest, ties to even. Three extra bits G, R, S suffice.
  • Add: shift the smaller exponent's significand right by dd, add or subtract, normalise, round, pack. Multiply: xor signs, add exponents minus bias, multiply significands, normalise, round.
  • Gap 2k−232^{k-23} above 2k2^k; integers exact to 2242^{24}; epsilon 2−232^{-23}; relative error ≤2−24\le 2^{-24}.
  • Not associative; cancellation; NaN rules; x+(−x)=+0x + (-x) = +0.

References

  • ·Patterson, Hennessy, Computer Organization and Design, Chapter 3 Arithmetic for Computers (floating-point addition and multiplication, guard and round bits, accurate arithmetic)
  • ·Hamacher, Vranesic, Zaky, Computer Organization, 5th ed., Chapter 6 Arithmetic (floating-point numbers and operations, guard bits and truncation)
  • ·Goldberg, What Every Computer Scientist Should Know About Floating-Point Arithmetic, ACM Computing Surveys 23(1), 1991, rounding error, guard digits, cancellation

Verification

scripts/enum-checks/dl_fparith_check.py: (1) the definition 'exact result rounded to the nearest float, ties to even' is compared with a brute-force nearest-value search over all finite words of four 8-bit formats (4 exponent and 3 fraction bits, 3 and 4, 5 and 2, 2 and 5) for the exact sum and product of ALL pairs (72770 results), overflow included; (2) the hardware algorithm of scripts/fp_lib.py (align with guard, round and sticky bits, add or subtract, normalise, round, pack; three extra bits) equals the definition on ALL 65536 pairs of each of those formats, for addition and multiplication, subnormals, infinities and NaN included; (3) in single precision 150000 random pairs (random words, nearby exponents, cancellation, subnormals, special values) give the same word from the algorithm, from the definition and from the C compiler's float arithmetic (a helper program compiled and run, the hardware's own result), and 95914 sums also equal Python's struct rounding; (4) properties: commutative, x + (-x) = +0, x + 0 = x, x * 1 = x, the gap above 2^k is 2^(k-23), the relative error of one operation is at most 2^-24, x - y = 0 exactly when x = y; the numbers of the page and its tables. tests/dl-fparith-traces.test.ts recomputes the tables (addition steps, products, spacing, associativity) in TypeScript with BigInt. CONVENTIONS: round to nearest, ties to even (the default mode of IEEE 754); single precision unless stated; the 8-bit example format has 1 sign, 4 exponent (bias 7) and 3 fraction bits; the hardware model keeps three extra bits (guard, round, sticky) and handles the specials by their rules; a result below the smallest normal number becomes a subnormal (gradual underflow). A textbook (Hamacher et al.) may describe a simpler adder with truncation or one guard bit, which gives different last bits: read the question's rule. NOT COMPUTED, quoted: the other three IEEE rounding modes, division and square root, the Pentium division bug remark. Chapter references are from memory and need verification. SECOND PASS (2026-11-02, done by the assistant, not a human check): every Self-check question was solved before the printed answer was read and all agreed; the definitions, worked examples, cheat box and traps were read claim by claim and the numbers re-derived by hand or by a small script (conversions, signed ranges and flags, IEEE 754 fields and custom formats, Boolean identities and counts, K-map groups, adder carries, BCD correction, flip-flop conversions, counter sequences and T equations, state reduction, Quine-McCluskey rounds and covers, floating-point alignment and rounding); long C programs were not re-read line by line (they are compiled and compared by npm run verify:c, and their printed outputs were checked against the prose); tables and animations are machine output and were read, not re-run; No other defect found. Status raised to self-checked; verified needs a textbook or human check.

last verified: never

My notes (saved in this browser)