This is the mail archive of the
newlib@sourceware.org
mailing list for the newlib project.
Re: RFC/RFA: strtod() magnitude problem
- From: Corinna Vinschen <vinschen at redhat dot com>
- To: newlib at sourceware dot org
- Cc: Christian Bruel <christian dot bruel at st dot com>
- Date: Tue, 23 Apr 2013 14:09:46 +0200
- Subject: Re: RFC/RFA: strtod() magnitude problem
- References: <87vc7d3a4g dot fsf at redhat dot com>
- Reply-to: newlib at sourceware dot org
Hi Nick,
On Apr 23 09:49, Nick Clifton wrote:
> Hi Guys,
>
> The strtod() function appears to misbehave on targets where
> DOUBLE_IS_32_BITS is true. For example:
>
> strtod ("123456789.10", &endptr)
>
> return will return a value or 1234567.125 rather than 123456792.0, ie
> two orders of magnitude too small.
There's also another problem with strtod in conjunction with 32 bit
doubles, see, for instance the thread starting at
http://sourceware.org/ml/newlib/2011/msg00178.html and the followup
reversion of the patch here:
http://sourceware.org/ml/newlib/2012/msg00576.html
> Unfortunately endptr will not be left pointing at "89.10", indicating
> that the remaining digits had not been consumed, but instead it will
> be point at the NUL following ".10". Thus you cannot examine endptr
> to determine that the conversion has failed.
>
> The problem happens because the preprocessor constant DBL_DIG is set
> to 6, (for these small sized doubles targets), so only the first 7
> digits are considered for conversion. Presumably the idea is that any
> value returned by strtod() must be able to be converted back into a
> similar string by atod().
>
> This problem is made worse by the fact that scanf() uses strtod() to
> perform ascii to floating point conversions so for example
> sscanf ("123456789.10") fails as well.
>
> I am not sure if the behaviour of strtod() is considered to be correct
> or not.
It's not. This certainly requires a change in strtod. Patching scanf
doesn't sounds like the right solution here. The problem shouldn't
have happened.
Can you try if the below patch fixes the issue? It's just a manual
update of strtod (or rather _strtod_r) to the latest implementation
from NetBSD. There were a few changes which might explain your problem
as well as the one encountered by Christian Bruel.
Christian, can you please try this patch in your scenario, too?
Additionally, when looking closely into the code, there seem to be a few
places where an `#ifndef _DOUBLE_IS_32BITS' might be missing, and that's
not just due to the below patch. Rather it seems there are a few
unguarded tests for 'dword1(rv)' which look wrong in 32 bit double mode.
Can you please have a look?
Thanks,
Corinna
* libc/stdlib/strtod.c: Manual update to latest algorithm from
NetBSD.
Index: libc/stdlib/strtod.c
===================================================================
RCS file: /cvs/src/src/newlib/libc/stdlib/strtod.c,v
retrieving revision 1.19
diff -u -p -r1.19 strtod.c
--- libc/stdlib/strtod.c 19 Dec 2012 10:16:00 -0000 1.19
+++ libc/stdlib/strtod.c 23 Apr 2013 12:05:08 -0000
@@ -128,10 +128,10 @@ THIS SOFTWARE.
#ifndef NO_IEEE_Scale
#define Avoid_Underflow
#undef tinytens
-/* The factor of 2^53 in tinytens[4] helps us avoid setting the underflow */
+/* The factor of 2^106 in tinytens[4] helps us avoid setting the underflow */
/* flag unnecessarily. It leads to a song and dance at the end of strtod. */
static _CONST double tinytens[] = { 1e-16, 1e-32, 1e-64, 1e-128,
- 9007199254740992.e-256
+ 9007199254740992.*9007199254740992.e-256
};
#endif
#endif
@@ -144,6 +144,26 @@ static _CONST double tinytens[] = { 1e-1
#define Rounding Flt_Rounds
#endif
+#ifdef Avoid_Underflow /*{*/
+ static double
+_DEFUN (sulp, (x, scale),
+ U x _AND
+ int scale)
+{
+ U u;
+ double rv;
+ int i;
+
+ rv = ulp(dval(x));
+ if (!scale || (i = 2*P + 1 - ((dword0(x) & Exp_mask) >> Exp_shift)) <= 0)
+ return rv; /* Is there an example where i <= 0 ? */
+ dword0(u) = Exp_1 + (i << Exp_shift);
+ dword1(u) = 0;
+ return rv * u.d;
+ }
+#endif /*}*/
+
+
#ifndef NO_HEX_FP
static void
@@ -221,7 +241,10 @@ _DEFUN (_strtod_r, (ptr, s00, se),
U aadj1, rv, rv0;
Long L;
__ULong y, z;
- _Bigint *bb, *bb1, *bd, *bd0, *bs, *delta;
+ _Bigint *bb = NULL, *bb1, *bd = NULL, *bd0, *bs = NULL, *delta = NULL;
+#ifdef Avoid_Underflow
+ __ULong Lsb, Lsb1;
+#endif
#ifdef SET_INEXACT
int inexact, oldinexact;
#endif
@@ -279,6 +302,8 @@ _DEFUN (_strtod_r, (ptr, s00, se),
switch((i = gethex(ptr, &s, &fpi1, &exp, &bb, sign)) & STRTOG_Retmask) {
case STRTOG_NoNumber:
s = s00;
+ sign = 0;
+ /* FALLTHROUGH */
case STRTOG_Zero:
break;
default:
@@ -299,14 +324,11 @@ _DEFUN (_strtod_r, (ptr, s00, se),
}
s0 = s;
y = z = 0;
- for(nd = nf = 0; (c = *s) >= '0' && c <= '9'; nd++, s++) {
- if (nd < DBL_DIG + 1) {
- if (nd < 9)
- y = 10*y + c - '0';
- else
- z = 10*z + c - '0';
- }
- }
+ for(nd = nf = 0; (c = *s) >= '0' && c <= '9'; nd++, s++)
+ if (nd < 9)
+ y = 10*y + c - '0';
+ else
+ z = 10*z + c - '0';
nd0 = nd;
if (strncmp (s, _localeconv_r (ptr)->decimal_point,
strlen (_localeconv_r (ptr)->decimal_point)) == 0)
@@ -329,20 +351,15 @@ _DEFUN (_strtod_r, (ptr, s00, se),
nz++;
if (c -= '0') {
nf += nz;
- for(i = 1; i < nz; i++) {
- if (nd++ <= DBL_DIG + 1) {
- if (nd < 10)
- y *= 10;
- else
- z *= 10;
- }
- }
- if (nd++ <= DBL_DIG + 1) {
- if (nd < 10)
- y = 10*y + c;
- else
- z = 10*z + c;
- }
+ for(i = 1; i < nz; i++)
+ if (nd++ < 9)
+ y *= 10;
+ else if (nd <= DBL_DIG + 1)
+ z *= 10;
+ if (nd++ < 9)
+ y = 10*y + c;
+ else if (nd <= DBL_DIG + 1)
+ z = 10*z + c;
nz = 0;
}
}
@@ -691,12 +708,20 @@ _DEFUN (_strtod_r, (ptr, s00, se),
/* Put digits into bd: true value = bd * 10^e */
bd0 = s2b(ptr, s0, nd0, nd, y);
+ if (bd0 == NULL)
+ goto ovfl;
for(;;) {
bd = Balloc(ptr,bd0->_k);
+ if (bd == NULL)
+ goto ovfl;
Bcopy(bd, bd0);
bb = d2b(ptr,dval(rv), &bbe, &bbbits); /* rv = bb * 2^bbe */
+ if (bb == NULL)
+ goto ovfl;
bs = i2b(ptr,1);
+ if (bs == NULL)
+ goto ovfl;
if (e >= 0) {
bb2 = bb5 = 0;
@@ -716,12 +741,19 @@ _DEFUN (_strtod_r, (ptr, s00, se),
bs2++;
#endif
#ifdef Avoid_Underflow
+ Lsb = LSB;
+ Lsb1 = 0;
j = bbe - scale;
i = j + bbbits - 1; /* logb(rv) */
- if (i < Emin) /* denormal */
- j += P - Emin;
- else
- j = P + 1 - bbbits;
+ j = P + 1 - bbbits;
+ if (i < Emin) { /* denormal */
+ i = Emin - i;
+ j -= i;
+ if (i < 32)
+ Lsb <<= i;
+ else
+ Lsb1 = Lsb << (i-32);
+ }
#else /*Avoid_Underflow*/
#ifdef Sudden_Underflow
#ifdef IBM
@@ -753,19 +785,37 @@ _DEFUN (_strtod_r, (ptr, s00, se),
}
if (bb5 > 0) {
bs = pow5mult(ptr, bs, bb5);
+ if (bs == NULL)
+ goto ovfl;
bb1 = mult(ptr, bs, bb);
+ if (bb1 == NULL)
+ goto ovfl;
Bfree(ptr, bb);
bb = bb1;
}
- if (bb2 > 0)
+ if (bb2 > 0) {
bb = lshift(ptr, bb, bb2);
- if (bd5 > 0)
+ if (bb == NULL)
+ goto ovfl;
+ }
+ if (bd5 > 0) {
bd = pow5mult(ptr, bd, bd5);
- if (bd2 > 0)
+ if (bd == NULL)
+ goto ovfl;
+ }
+ if (bd2 > 0) {
bd = lshift(ptr, bd, bd2);
- if (bs2 > 0)
+ if (bd == NULL)
+ goto ovfl;
+ }
+ if (bs2 > 0) {
bs = lshift(ptr, bs, bs2);
+ if (bs == NULL)
+ goto ovfl;
+ }
delta = diff(ptr, bb, bd);
+ if (delta == NULL)
+ goto ovfl;
dsign = delta->_sign;
delta->_sign = 0;
i = cmp(delta, bs);
@@ -852,7 +902,9 @@ _DEFUN (_strtod_r, (ptr, s00, se),
#endif /*Sudden_Underflow*/
#endif /*Avoid_Underflow*/
adj *= ulp(dval(rv));
- if (dsign)
+ if (dsign) {
+ if (dword0(rv) == Big0 && dword1(rv) == Big1)
+ goto ovfl;
dval(rv) += adj;
else
dval(rv) -= adj;
@@ -902,6 +954,8 @@ _DEFUN (_strtod_r, (ptr, s00, se),
#endif
0xffffffff)) {
/*boundary case -- increment exponent*/
+ if (dword0(rv) == Big0 && dword1(rv) == Big1)
+ goto ovfl;
dword0(rv) = (dword0(rv) & Exp_mask)
+ Exp_msk1
#ifdef IBM
@@ -960,14 +1014,31 @@ _DEFUN (_strtod_r, (ptr, s00, se),
#endif
}
#ifndef ROUND_BIASED
+#ifdef Avoid_Underflow
+ if (Lsb1) {
+ if (!(dword0(rv) & Lsb1))
+ break;
+ }
+ else if (!(dword1(rv) & Lsb))
+ break;
+#else
if (!(dword1(rv) & LSB))
break;
#endif
+#endif
if (dsign)
+#ifdef Avoid_Underflow
+ dval(rv) += sulp(rv, scale);
+#else
dval(rv) += ulp(dval(rv));
+#endif
#ifndef ROUND_BIASED
else {
+#ifdef Avoid_Underflow
+ dval(rv) -= sulp(rv, scale);
+#else
dval(rv) -= ulp(dval(rv));
+#endif
#ifndef Sudden_Underflow
if (!dval(rv))
goto undfl;
@@ -1044,7 +1115,7 @@ _DEFUN (_strtod_r, (ptr, s00, se),
#ifdef Avoid_Underflow
if (scale && y <= 2*P*Exp_msk1) {
if (aadj <= 0x7fffffff) {
- if ((z = aadj) <= 0)
+ if ((z = aadj) == 0)
z = 1;
aadj = z;
dval(aadj1) = dsign ? aadj : -aadj;
--
Corinna Vinschen
Cygwin Maintainer
Red Hat