While testing my new decimal to floating-point converter I discovered a bug in old territory: PHP incorrectly converts the number 2.2250738585072012e-308.
<?php printf("%.17g",2.2250738585072012e-308); ?>
This prints 2.2250738585072009E-308; it should print 2.2250738585072014e-308. (I verified that the internal double value is wrong; the printed value correctly represents it.)
2.2250738585072012e-308 is the number that hung Java. (Not to be confused with 2.2250738585072011e-308, the number that hung PHP.) When the Java bug surfaced, I tested 2.2250738585072012e-308 in PHP to verify that it did not hang — but I guess I never checked that it converted correctly.
It’s That Normal/Subnormal Border Again
2.2250738585072012e-308 is supposed to convert to DBL_MIN, which is 0x1p-1022. PHP converts it to 0x0.fffffffffffffp-1022, the number one ULP below DBL_MIN — the largest subnormal number. In decimal terms, PHP thinks it’s 2.2250738585072009E-308, when it should be 2.2250738585072014e-308. 2.2250738585072014e-308 is the exact decimal value of DBL_MIN rounded to 17 significant digits; DBL_MIN is 2.2250738585072013830…(695 more digits) x 10-308.
(Neighboring numbers 2.2250738585072013e-308, 2.2250738585072014e-308, 2.2250738585072015e-308, and 2.2250738585072016e-308 all convert correctly to DBL_MIN.)
I didn’t try to debug this, but I know that PHP uses an old copy of David Gay’s dtoa.c. There were some changes made to dtoa.c in 1997 that, based on their descriptions, seem to address this bug (see dtoa.c change log):
Wed Feb 12 00:40:01 EST 1997 dtoa.c: strtod: on IEEE systems, scale to avoid intermediate underflows when the result does not underflow; compiling with -DNO_IEEE_Scale restores the old logic. Fix a bug, revealed by input string 2.2250738585072012e-308, in treating input just less than the smallest normalized number. (The bug introduced an extra ULP of error in this special case.)
Fri May 15 07:49:07 EDT 1998 dtoa.c: strtod: fix another glitch with scaling to avoid underflow with IEEE arithmetic, again revealed by the input string 2.2250738585072012e-308, which was rounded to the largest denormal rather than the smallest normal double precision number.
Update: The Bug Was Fixed
This bug was marked fixed on 3/17/15, for PHP version 5.5.20. I did not find an entry in the change log, but I looked at the updated zend_strtod.c. They fixed it by refreshing zend_strtod() with the latest copy of David Gay’s strtod(), the fix I had recommended over four years ago for the “PHP hangs” bug.
Interestingly, they carried over the PHP hang fix (volatile keyword) into the new copy. This was unnecessary since Gay’s code fixes it in another way.
I have not tested the fix in PHP, but a standalone copy of Gay’s vanilla code converts it correctly.