PHP Converts 2.2250738585072012e-308 Incorrectly

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.)

The Bug

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.

I reported this to PHP.

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.

RSS feed icon
RSS e-mail icon

Leave a Reply

Your email address will not be published. Required fields are marked *

(Cookies must be enabled to leave a reduces spam.)