When working with IEEE 754 floating-point numbers in C, C++, or any modern language, subnormal (or denormal) numbers often lead to surprising results. A common point of confusion arises when dividing a subnormal number by 2: a trailing 1 bit is shifted off the right edge, yet the result appears to truncate rather than round up.

The Phenomenon

Consider the following C snippet:

double d = 5.4744067603836093e-309;
printf("%.17g %a\n", d, d);
d /= 2.0;
printf("%.17g %a\n", d, d);

In binary, the 52-bit fractional significands look like this:

Original:  0.0011111011111011111111011101011111000001110001010001
After /2:  0.0001111101111101111111101110101111100000111000101000

Notice that dividing by 2 shifted everything right by one bit. The trailing 1 bit fell off the 52-bit precision limit, but the result ends in 0 instead of rounding up to 1. Why didn't rounding occur?

The Core Reason: IEEE 754 "Round to Nearest, Ties to Even"

The operation did round, but it followed the default IEEE 754 rounding mode: Round to Nearest, Ties to Even (also known as banker's rounding).

When you divide the original number by 2 mathematically, the exact infinite-precision result is:

0.0001111101111101111111101110101111100000111000101000 1 (exact)

The value falls exactly halfway between two representable 52-bit double-precision values:

  • Option A (round down): ...1000 (least significant bit is 0, which is even)
  • Option B (round up): ...1001 (least significant bit is 1, which is odd)

Because the discarded remainder is exactly .1 in binary (a tie), the rule dictates choosing the value with the even least significant bit (0). Thus, the floating-point unit selects Option A.

Why Multiplication Becomes Different (x * y vs x * 2 * y / 2)

In the question, another comparison was made:

double x = 1.3877787394217764e-17;
double y = 1.9723629584729566e-292;
double d2 = x * y;
double d3 = x * 2 * y / 2;
if (d2 != d3) printf("unexpected!\n");

Here is why d2 != d3 evaluates to true:

1. Direct Multiplication (d2 = x * y)

The exact mathematical product of x and y produces infinitely more bits past the 52nd fraction bit:

Exact: 0.001111101111101111111101110101111100000111000101000100111110...

Because the extra bits (00111110...) are strictly greater than zero, the exact value is strictly greater than the midpoint for the rounding threshold at that position. It is not an exact tie, so standard nearest-neighbor rounding pushes it up to ...1001.

2. Scaled Multiplication and Division (d3 = (x * 2 * y) / 2)

When evaluating x * 2 * y first:

  • Because of the factor of 2, the intermediate product is shifted left by 1 bit, retaining one extra bit of precision in the 52-bit significand.
  • The intermediate result rounds to end in ...0001.
  • Then, dividing this intermediate result by 2 shifts that 1 into the tie position (...0000.1).
  • As explained above, applying "Ties to Even" on ...0000.1 causes it to round down to ...0000.

Summary

Subnormal floating-point operations follow the exact same mathematical rounding rules as normal numbers:

  • If the discarded fraction is greater than 0.5 ULP, the number rounds up.
  • If the discarded fraction is less than 0.5 ULP, the number rounds down.
  • If the discarded fraction is exactly 0.5 ULP (a tie), the result rounds to the value with a 0 in the least significant bit (even).