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:
- Mozilla (Firefox and others): js/source/js/src/jsdtoa.c and source/mozilla/js/src/dtoa.c.
- WebKit (Safari): JavaScriptCore/wtf/dtoa.cpp.
- V8 JavaScript Engine (Chrome): dtoa.c.
- NetBSD: src/lib/libc/gdtoa/strtod.c.
- Python: python/trunk/Python/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.
(Cookies must be enabled to leave a comment...it reduces spam.)