A Better Fix for the PHP 2.2250738585072011e-308 Bug

Recently I discovered a bug in PHP’s decimal to floating-point conversion routine, zend_strtod(): it went into an infinite loop trying to convert the decimal string 2.2250738585072011e-308 to floating-point. zend_strtod() is based on David Gay’s strtod() function in dtoa.c, as are the decimal to floating-point conversion routines of many other open source projects. So why hasn’t this bug affected these other projects?

zend_strtod() is based on a very old copy of dtoa.c. The current version of dtoa.c is immune to the 2.2250738585072011e-308 bug — and has been since 1997 by my reckoning. So while the ‘volatile’ keyword fixes the PHP problem, I think there’s a better solution: upgrade zend_strtod() to the latest dtoa.c.

Volatile Fixes It, But It Shouldn’t Have Executed In the First Place

As I showed, this section of code in zend_strtod() caused the problem:

/* Compute adj so that the IEEE rounding rules will
 * correctly round rv + adj in some half-way cases.
 * If rv * ulp(rv) is denormalized (i.e.,
 * y <= (P-1)*Exp_msk1), we must adjust aadj to avoid
 * trouble from bits lost to denormalization;
 * example: 1.2e-307 .
 */
if (y <= (P-1)*Exp_msk1 && aadj >= 1.) {
	aadj1 = (double)(int)(aadj + 0.5);
	if (!dsign)
		aadj1 = -aadj1;
}
adj = aadj1 * ulp(value(rv));
value(rv) += adj;

In the current version of dtoa.c on ampl.com, that section of code now appears in the #else of a “new” (circa 1997) #ifdef:

#ifdef Avoid_Underflow
...
#else
... the above zend_strtod() code that hits is now here ...
#endif /*Avoid_Underflow*/

In the ampl.com copy, Avoid_Underflow is defined by default (indirectly by not defining another flag, NO_IEEE_Scale). In other words, this code — the cause of the 2.2250738585072011e-308 bug — is no longer supposed to be executed!

Some Other Projects That Use dtoa.c

There are many projects that use dtoa.c; here are some of them, with links to their copies of dtoa.c:

The NetBSD, WebKit, V8, and Mozilla “js/src/dtoa.c” copies of dtoa.c are relatively current; they contain the #ifdefs that exclude the problematic section of code. Python’s copy has removed most of the #ifdefs, and with them, that section of code.

The Mozilla “js/src/jsdtoa.c” copy of dtoa.c is a little different. The problem section of code is not #ifdefed out, although there are Avoid_Underflow #ifdefs elsewhere. I took a copy of jsdtoa.c and modified it slightly so I could run it standalone in Visual C++. Even with full optimization, I couldn’t get that section of code to execute — at least not for input 2.2250738585072011e-308 or nearby inputs. I do not understand the deltas in that code enough to say why.

Dingbat
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 comment...it reduces spam.)