A reader of my blog, Water Qian, reported a bug to me after reading my article “How GLIBC’s strtod() Works”. I recently tested strtod(), which was was fixed to do correct rounding in glibc 2.17; I had found no incorrect conversions.
Water tested the conversion of 2-1075 — in retrospect an obvious corner case I should have tried — and found that it converted incorrectly to 0x0.0000000000001p-1022. That’s 2-1074, the smallest double-precision value. It should have converted to 0, under round-to-nearest/ties-to-even rounding.
(Update 11/13/13: This bug has been fixed for version 2.19.)
I wrote the following program to demonstrate the problem:
That 1075 fractional digit string is 2-1075, which I generated with PARI/GP using the expression 2.^-1075
In binary, 2-1075 is 1074 leading zeros followed by a single 1 bit. The bit position at 2-1075 is the rounding bit for subnormal numbers, which have a significand of 52 bits. For input 2-1075, the double-precision significand is 0, the rounding bit is 1, and all bits beyond are 0s. This makes it a halfway case, and in this case should round to 0. strtod() thinks there are more bits beyond the rounding bit, and thus rounds up.
In the code, this is what happens: after strtod() does the division, it calls round_and_return(), which calls round_away(). The parameters to round_away() are last_digit_odd == false, half_bit == true, and more_bits == true. This causes it to round up (case FE_TONEAREST). more_bits should be false, which would make it round correctly to even.
I wrote this bug report.
Update 11/13/13: Fixed
This bug was minor and has been quickly fixed. In round_and_return(), the computation of more_bits was changed:
for (i = 0; i < RETURN_LIMB_SIZE; ++i) more_bits |= retval[i] != 0;
for (i = 0; i < RETURN_LIMB_SIZE - 1; ++i) more_bits |= retval[i] != 0;
I tested this fix and it works.
glibc added this input to their list of testcases, but in the much more manageable hexadecimal floating-point constant form: 0x1p-1075. This test is valid because the bug is not in the processing of the decimal string, but in the rounding of the converted result.