Document various strtod bugs, with some fixes.
authorEric Blake <ebb9@byu.net>
Sat, 29 Mar 2008 19:50:21 +0000 (13:50 -0600)
committerEric Blake <ebb9@byu.net>
Sun, 30 Mar 2008 01:16:57 +0000 (19:16 -0600)
* doc/posix-functions/strtod.texi (strtod): Document bugs with
"-0x", "inf", "nan", and hex constants.
* doc/posix-functions/atof.texi (atof): Likewise.
* modules/stdlib (Makefile.am): Support strtod.
* m4/stdlib_h.m4 (gl_STDLIB_H_DEFAULTS): Likewise.
* m4/strtod.m4 (gl_FUNC_STRTOD): Fit in stdlib framework, and
detect additional strtod bugs.
* lib/stdlib.in.h (rpl_strtod): Add declarations.
* lib/strtod.c (strtod): Return -0.0 on negative underflow.  Use
bool where appropriate.  Parse 'inf' and 'nan'.
* tests/test-strtod.c: New file.
* modules/strtod (Depends-on): Add stdbool, stdlib.
(configure.ac): Turn on module indicator.
* modules/strtod-tests: New module.

Signed-off-by: Eric Blake <ebb9@byu.net>
ChangeLog
doc/posix-functions/atof.texi
doc/posix-functions/strtod.texi
lib/stdlib.in.h
lib/strtod.c
m4/stdlib_h.m4
m4/strtod.m4
modules/stdlib
modules/strtod
modules/strtod-tests [new file with mode: 0644]
tests/test-strtod.c [new file with mode: 0644]

index f6c7dc0..1e0254d 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,23 @@
 2008-03-29  Eric Blake  <ebb9@byu.net>
 
+       Document various strtod bugs, with some fixes.
+       * doc/posix-functions/strtod.texi (strtod): Document bugs with
+       "-0x", "inf", "nan", and hex constants.
+       * doc/posix-functions/atof.texi (atof): Likewise.
+       * modules/stdlib (Makefile.am): Support strtod.
+       * m4/stdlib_h.m4 (gl_STDLIB_H_DEFAULTS): Likewise.
+       * m4/strtod.m4 (gl_FUNC_STRTOD): Fit in stdlib framework, and
+       detect additional strtod bugs.
+       * lib/stdlib.in.h (rpl_strtod): Add declarations.
+       * lib/strtod.c (strtod): Return -0.0 on negative underflow.  Use
+       bool where appropriate.  Parse 'inf' and 'nan'.
+       * tests/test-strtod.c: New file.
+       * modules/strtod (Depends-on): Add stdbool, stdlib.
+       (configure.ac): Turn on module indicator.
+       * modules/strtod-tests: New module.
+
+2008-03-29  Eric Blake  <ebb9@byu.net>
+
        Fix ftell on mingw.
        * lib/ftell.c (EOVERFLOW): Define if the system lacks it.
        * modules/ftell-tests (Depends-on): Add binary-io.
index c17dc3a..c20a60f 100644 (file)
@@ -12,4 +12,39 @@ Portability problems fixed by Gnulib:
 
 Portability problems not fixed by Gnulib:
 @itemize
+@item
+This function mis-parses strings with leading @samp{+} on some old platforms:
+Old versions of Linux.
+
+@item
+This function returns a positive value for negative underflow on some
+platforms:
+glibc 2.4, Mingw, Cygwin.
+
+@item
+This function fails to do a valid parse of @samp{-0x} on some
+platforms:
+glibc 2.4, Cygwin < 1.5.25-11.
+
+@item
+This function fails to parse Infinities and plain NaNs on some platforms:
+Solaris 8, Mingw, OpenBSD 4.0.
+
+@item
+This function fails to parse NaN() on some platforms:
+Solaris 8, Mingw, OpenBSD 4.0, Cygwin < 1.5.25-11.
+
+@item
+This function fails to parse NaN(n-char-sequence) on some platforms:
+Solaris 8, Mingw, OpenBSD 4.0.
+
+@item
+This function fails to parse C99 hexadecimal floating point on some
+platforms:
+Solaris 8, Mingw, OpenBSD 4.0.
+
+@item
+This function fails to correctly parse very long strings on some
+platforms:
+Mingw, Cygwin.
 @end itemize
index 71e0978..349802a 100644 (file)
@@ -10,9 +10,11 @@ Portability problems fixed by Gnulib:
 @itemize
 @item
 This function is missing on some old platforms.
+
 @item
 This function mis-parses strings with leading @samp{+} on some old platforms:
 Old versions of Linux.
+
 @item
 This function returns a wrong end pointer on some platforms:
 Solaris 2.4.
@@ -20,4 +22,40 @@ Solaris 2.4.
 
 Portability problems not fixed by Gnulib:
 @itemize
+@item
+This function returns a positive value for negative underflow on some
+platforms:
+glibc 2.4, Mingw, Cygwin.
+
+@item
+This function fails to do a valid parse of @samp{-0x} on some
+platforms:
+glibc 2.4, Cygwin < 1.5.25-11.
+
+@item
+This function fails to parse Infinities and plain NaNs on some platforms:
+Solaris 8, Mingw, OpenBSD 4.0.
+
+@item
+This function fails to parse NaN() on some platforms:
+Solaris 8, Mingw, OpenBSD 4.0, Cygwin < 1.5.25-11.
+
+@item
+This function fails to parse NaN(n-char-sequence) on some platforms:
+Solaris 8, Mingw, OpenBSD 4.0.
+
+@item
+This function returns the wrong end pointer when parsing
+NaN(n-char-sequence) on some platforms:
+glibc 2.4.
+
+@item
+This function fails to parse C99 hexadecimal floating point on some
+platforms:
+Solaris 8, Mingw, OpenBSD 4.0.
+
+@item
+This function fails to correctly parse very long strings on some
+platforms:
+Mingw, Cygwin.
 @end itemize
index 100ff52..0181dd2 100644 (file)
@@ -1,6 +1,6 @@
 /* A GNU-like <stdlib.h>.
 
-   Copyright (C) 1995, 2001-2004, 2006-2007 Free Software Foundation, Inc.
+   Copyright (C) 1995, 2001-2004, 2006-2008 Free Software Foundation, Inc.
 
    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
@@ -199,6 +199,23 @@ extern int unsetenv (const char *name);
 #endif
 
 
+#if @GNULIB_STRTOD@
+# if @REPLACE_STRTOD@
+#  define strtod rpl_strtod
+# endif
+# if !@HAVE_STRTOD@ || @REPLACE_STRTOD@
+ /* Parse a double from STRING, updating ENDP if appropriate.  */
+extern double strtod (const char *str, char **endp);
+# endif
+#elif defined GNULIB_POSIXCHECK
+# undef strtod
+# define strtod(s, e)                           \
+    (GL_LINK_WARNING ("strtod is unportable - " \
+                      "use gnulib module strtod for portability"), \
+     strtod (s, e))
+#endif
+
+
 #ifdef __cplusplus
 }
 #endif
index bba537a..147a5bf 100644 (file)
@@ -1,4 +1,5 @@
-/* Copyright (C) 1991, 1992, 1997, 1999, 2003, 2006 Free Software Foundation, Inc.
+/* Copyright (C) 1991, 1992, 1997, 1999, 2003, 2006, 2008 Free
+   Software Foundation, Inc.
 
    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
 
 #include <config.h>
 
-#include <errno.h>
+#include <stdlib.h>
 
 #include <ctype.h>
-
-#include <math.h>
-
+#include <errno.h>
 #include <float.h>
-#include <stdlib.h>
+#include <math.h>
+#include <stdbool.h>
 #include <string.h>
 
 /* Convert NPTR to a double.  If ENDPTR is not NULL, a pointer to the
 double
 strtod (const char *nptr, char **endptr)
 {
-  register const char *s;
-  short int sign;
+  const char *s;
+  bool negative = false;
 
   /* The number so far.  */
   double num;
 
-  int got_dot;                 /* Found a decimal point.  */
-  int got_digit;               /* Seen any digits.  */
+  bool got_dot;                        /* Found a decimal point.  */
+  bool got_digit;              /* Seen any digits.  */
 
   /* The exponent of the number.  */
   long int exponent;
@@ -55,19 +55,19 @@ strtod (const char *nptr, char **endptr)
     ++s;
 
   /* Get the sign.  */
-  sign = *s == '-' ? -1 : 1;
+  negative = *s == '-';
   if (*s == '-' || *s == '+')
     ++s;
 
   num = 0.0;
-  got_dot = 0;
-  got_digit = 0;
+  got_dot = false;
+  got_digit = false;
   exponent = 0;
   for (;; ++s)
     {
       if ('0' <= *s && *s <= '9')
        {
-         got_digit = 1;
+         got_digit = true;
 
          /* Make sure that multiplication by 10 will not overflow.  */
          if (num > DBL_MAX * 0.1)
@@ -89,14 +89,54 @@ strtod (const char *nptr, char **endptr)
        }
       else if (!got_dot && *s == '.')
        /* Record that we have found the decimal point.  */
-       got_dot = 1;
+       got_dot = true;
       else
        /* Any other character terminates the number.  */
        break;
     }
 
   if (!got_digit)
-    goto noconv;
+    {
+      /* Check for infinities and NaNs.  */
+      if (tolower ((unsigned char) *s) == 'i'
+         && tolower ((unsigned char) s[1]) == 'n'
+         && tolower ((unsigned char) s[2]) == 'f')
+       {
+         s += 3;
+         num = HUGE_VAL;
+         if (tolower ((unsigned char) *s) == 'i'
+             && tolower ((unsigned char) s[1]) == 'n'
+             && tolower ((unsigned char) s[2]) == 'i'
+             && tolower ((unsigned char) s[3]) == 't'
+             && tolower ((unsigned char) s[4]) == 'y')
+           s += 5;
+         goto valid;
+       }
+#ifdef NAN
+      else if (tolower ((unsigned char) *s) == 'n'
+              && tolower ((unsigned char) s[1]) == 'a'
+              && tolower ((unsigned char) s[2]) == 'n')
+       {
+         s += 3;
+         num = NAN;
+         /* Since nan(<n-char-sequence>) is implementation-defined,
+            we define it by ignoring <n-char-sequence>.  A nicer
+            implementation would populate the bits of the NaN
+            according to interpreting n-char-sequence as a
+            hexadecimal number, but the result is still a NaN.  */
+         if (*s == '(')
+           {
+             const char *p = s + 1;
+             while (isalnum ((unsigned char) *p))
+               p++;
+             if (*p == ')')
+               s = p + 1;
+           }
+         goto valid;
+       }
+#endif
+      goto noconv;
+    }
 
   if (tolower ((unsigned char) *s) == 'e')
     {
@@ -108,7 +148,7 @@ strtod (const char *nptr, char **endptr)
       errno = 0;
       ++s;
       exp = strtol (s, &end, 10);
-      if (errno == ERANGE)
+      if (errno == ERANGE && num)
        {
          /* The exponent overflowed a `long int'.  It is probably a safe
             assumption that an exponent that cannot be represented by
@@ -129,11 +169,8 @@ strtod (const char *nptr, char **endptr)
       exponent += exp;
     }
 
-  if (endptr != NULL)
-    *endptr = (char *) s;
-
   if (num == 0.0)
-    return 0.0;
+    goto valid;
 
   /* Multiply NUM by 10 to the EXPONENT power,
      checking for overflow and underflow.  */
@@ -151,23 +188,29 @@ strtod (const char *nptr, char **endptr)
 
   num *= pow (10.0, (double) exponent);
 
-  return num * sign;
+ valid:
+  if (endptr != NULL)
+    *endptr = (char *) s;
+  return negative ? -num : num;
 
-overflow:
+ overflow:
   /* Return an overflow error.  */
+  if (endptr != NULL)
+    *endptr = (char *) s;
   errno = ERANGE;
-  return HUGE_VAL * sign;
+  return negative ? -HUGE_VAL : HUGE_VAL;
 
-underflow:
+ underflow:
   /* Return an underflow error.  */
   if (endptr != NULL)
-    *endptr = (char *) nptr;
+    *endptr = (char *) s;
   errno = ERANGE;
-  return 0.0;
+  return negative ? -0.0 : 0.0;
 
-noconv:
+ noconv:
   /* There was no number.  */
   if (endptr != NULL)
     *endptr = (char *) nptr;
+  errno = EINVAL;
   return 0.0;
 }
index fe4ce12..d9240b4 100644 (file)
@@ -1,5 +1,5 @@
-# stdlib_h.m4 serial 5
-dnl Copyright (C) 2007 Free Software Foundation, Inc.
+# stdlib_h.m4 serial 6
+dnl Copyright (C) 2007, 2008 Free Software Foundation, Inc.
 dnl This file is free software; the Free Software Foundation
 dnl gives unlimited permission to copy and/or distribute it,
 dnl with or without modifications, as long as this notice is preserved.
@@ -27,6 +27,7 @@ AC_DEFUN([gl_STDLIB_H_DEFAULTS],
   GNULIB_MKSTEMP=0;       AC_SUBST([GNULIB_MKSTEMP])
   GNULIB_PUTENV=0;        AC_SUBST([GNULIB_PUTENV])
   GNULIB_SETENV=0;        AC_SUBST([GNULIB_SETENV])
+  GNULIB_STRTOD=0;        AC_SUBST([GNULIB_STRTOD])
   GNULIB_UNSETENV=0;      AC_SUBST([GNULIB_UNSETENV])
   dnl Assume proper GNU behavior unless another module says otherwise.
   HAVE_CALLOC_POSIX=1;    AC_SUBST([HAVE_CALLOC_POSIX])
@@ -35,8 +36,10 @@ AC_DEFUN([gl_STDLIB_H_DEFAULTS],
   HAVE_MKDTEMP=1;         AC_SUBST([HAVE_MKDTEMP])
   HAVE_REALLOC_POSIX=1;   AC_SUBST([HAVE_REALLOC_POSIX])
   HAVE_SETENV=1;          AC_SUBST([HAVE_SETENV])
+  HAVE_STRTOD=1;          AC_SUBST([HAVE_STRTOD])
   HAVE_UNSETENV=1;        AC_SUBST([HAVE_UNSETENV])
   REPLACE_MKSTEMP=0;      AC_SUBST([REPLACE_MKSTEMP])
   REPLACE_PUTENV=0;       AC_SUBST([REPLACE_PUTENV])
+  REPLACE_STRTOD=0;       AC_SUBST([REPLACE_STRTOD])
   VOID_UNSETENV=0;        AC_SUBST([VOID_UNSETENV])
 ])
index 1de2f2f..7a10a21 100644 (file)
@@ -1,17 +1,51 @@
-# strtod.m4 serial 6
-dnl Copyright (C) 2002, 2003, 2006, 2007 Free Software Foundation, Inc.
+# strtod.m4 serial 7
+dnl Copyright (C) 2002, 2003, 2006, 2007, 2008 Free Software
+dnl Foundation, Inc.
 dnl This file is free software; the Free Software Foundation
 dnl gives unlimited permission to copy and/or distribute it,
 dnl with or without modifications, as long as this notice is preserved.
 
 AC_DEFUN([gl_FUNC_STRTOD],
 [
+  AC_REQUIRE([gl_STDLIB_H_DEFAULTS])
   AC_FUNC_STRTOD
   dnl Note: AC_FUNC_STRTOD does AC_LIBOBJ(strtod).
   if test $ac_cv_func_strtod = no; then
-    AC_DEFINE(strtod, rpl_strtod,
-      [Define to rpl_strtod if the replacement function should be used.])
+    HAVE_STRTOD=0
+    REPLACE_STRTOD=1
     gl_PREREQ_STRTOD
+  else
+    AC_CACHE_CHECK([whether strtod obeys C99], [gl_cv_func_strtod_works],
+      [AC_RUN_IFELSE([AC_LANG_PROGRAM([[
+#include <stdlib.h>
+#include <math.h>
+]], [[
+  {
+    /* Older glibc and Cygwin mis-parse "-0x".  */
+    char *string = "-0x";
+    char *term;
+    double value = strtod (string, &term);
+    if (1 / value != -HUGE_VAL || term != (string + 2))
+      return 1;
+  }
+  {
+    /* Many platforms do not parse infinities.  */
+    char *string = "inf";
+    char *term;
+    double value = strtod (string, &term);
+    if (value != HUGE_VAL || term != (string + 3))
+      return 1;
+  }
+]])],
+       [gl_cv_func_strtod_works=yes],
+       [gl_cv_func_strtod_works=no],
+       [gl_cv_func_strtod_works="guessing no"])])
+    if test "$gl_cv_func_strtod_works" != yes; then
+      REPLACE_STRTOD=1
+      gl_PREREQ_STRTOD
+      dnl Use undocumented macro to set POW_LIB correctly.
+      _AC_LIBOBJ_STRTOD
+    fi
   fi
 ])
 
index d9a6e97..b119530 100644 (file)
@@ -31,6 +31,7 @@ stdlib.h: stdlib.in.h
              -e 's|@''GNULIB_MKSTEMP''@|$(GNULIB_MKSTEMP)|g' \
              -e 's|@''GNULIB_PUTENV''@|$(GNULIB_PUTENV)|g' \
              -e 's|@''GNULIB_SETENV''@|$(GNULIB_SETENV)|g' \
+             -e 's|@''GNULIB_STRTOD''@|$(GNULIB_STRTOD)|g' \
              -e 's|@''GNULIB_UNSETENV''@|$(GNULIB_UNSETENV)|g' \
              -e 's|@''HAVE_CALLOC_POSIX''@|$(HAVE_CALLOC_POSIX)|g' \
              -e 's|@''HAVE_GETSUBOPT''@|$(HAVE_GETSUBOPT)|g' \
@@ -38,9 +39,11 @@ stdlib.h: stdlib.in.h
              -e 's|@''HAVE_MKDTEMP''@|$(HAVE_MKDTEMP)|g' \
              -e 's|@''HAVE_REALLOC_POSIX''@|$(HAVE_REALLOC_POSIX)|g' \
              -e 's|@''HAVE_SETENV''@|$(HAVE_SETENV)|g' \
+             -e 's|@''HAVE_STRTOD''@|$(HAVE_STRTOD)|g' \
              -e 's|@''HAVE_UNSETENV''@|$(HAVE_UNSETENV)|g' \
              -e 's|@''REPLACE_MKSTEMP''@|$(REPLACE_MKSTEMP)|g' \
              -e 's|@''REPLACE_PUTENV''@|$(REPLACE_PUTENV)|g' \
+             -e 's|@''REPLACE_STRTOD''@|$(REPLACE_STRTOD)|g' \
              -e 's|@''VOID_UNSETENV''@|$(VOID_UNSETENV)|g' \
              -e '/definition of GL_LINK_WARNING/r $(LINK_WARNING_H)' \
              < $(srcdir)/stdlib.in.h; \
index 4098b13..5f2dce5 100644 (file)
@@ -6,9 +6,12 @@ lib/strtod.c
 m4/strtod.m4
 
 Depends-on:
+stdbool
+stdlib
 
 configure.ac:
 gl_FUNC_STRTOD
+gl_STDLIB_MODULE_INDICATOR([strtod])
 
 Makefile.am:
 LIBS += $(POW_LIB)
diff --git a/modules/strtod-tests b/modules/strtod-tests
new file mode 100644 (file)
index 0000000..e40a274
--- /dev/null
@@ -0,0 +1,14 @@
+Files:
+tests/test-strtod.c
+
+Depends-on:
+float
+isnand-nolibm
+signbit
+
+configure.ac:
+
+Makefile.am:
+LIBS += $(POW_LIB)
+TESTS += test-strtod
+check_PROGRAMS += test-strtod
diff --git a/tests/test-strtod.c b/tests/test-strtod.c
new file mode 100644 (file)
index 0000000..c54a8fd
--- /dev/null
@@ -0,0 +1,822 @@
+/*
+ * Copyright (C) 2008 Free Software Foundation
+ * Written by Eric Blake
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
+
+#include <config.h>
+
+#include <stdlib.h>
+
+#include <errno.h>
+#include <float.h>
+#include <math.h>
+#include <stdio.h>
+#include <string.h>
+
+#define ASSERT(expr) \
+  do                                                                        \
+    {                                                                       \
+      if (!(expr))                                                          \
+       {                                                                    \
+         fprintf (stderr, "%s:%d: assertion failed\n", __FILE__, __LINE__); \
+         /* FIXME abort ();*/status = 1;                               \
+       }                                                                    \
+    }                                                                       \
+  while (0)
+
+int
+main ()
+{
+  int status = 0;
+  /* Subject sequence empty or invalid.  */
+  {
+    errno = 0;
+    const char input[] = "";
+    char *ptr;
+    double result = strtod (input, &ptr);
+    ASSERT (result == 0.0);
+    ASSERT (!signbit (result));
+    ASSERT (ptr == input);
+    ASSERT (errno == 0 || errno == EINVAL);
+  }
+  {
+    errno = 0;
+    const char input[] = " ";
+    char *ptr;
+    double result = strtod (input, &ptr);
+    ASSERT (result == 0.0);
+    ASSERT (!signbit (result));
+    ASSERT (ptr == input);
+    ASSERT (errno == 0 || errno == EINVAL);
+  }
+  {
+    errno = 0;
+    const char input[] = " +";
+    char *ptr;
+    double result = strtod (input, &ptr);
+    ASSERT (result == 0.0);
+    ASSERT (!signbit (result));
+    ASSERT (ptr == input);
+    ASSERT (errno == 0 || errno == EINVAL);
+  }
+  {
+    errno = 0;
+    const char input[] = " .";
+    char *ptr;
+    double result = strtod (input, &ptr);
+    ASSERT (result == 0.0);
+    ASSERT (!signbit (result));
+    ASSERT (ptr == input);
+    ASSERT (errno == 0 || errno == EINVAL);
+  }
+  {
+    errno = 0;
+    const char input[] = " .e0";
+    char *ptr;
+    double result = strtod (input, &ptr);
+    ASSERT (result == 0.0);
+    ASSERT (!signbit (result));
+    ASSERT (ptr == input);
+    ASSERT (errno == 0 || errno == EINVAL);
+  }
+  {
+    errno = 0;
+    const char input[] = " +.e-0";
+    char *ptr;
+    double result = strtod (input, &ptr);
+    ASSERT (result == 0.0);
+    ASSERT (!signbit (result));
+    ASSERT (ptr == input);
+    ASSERT (errno == 0 || errno == EINVAL);
+  }
+  {
+    errno = 0;
+    const char input[] = " in";
+    char *ptr;
+    double result = strtod (input, &ptr);
+    ASSERT (result == 0.0);
+    ASSERT (!signbit (result));
+    ASSERT (ptr == input);
+    ASSERT (errno == 0 || errno == EINVAL);
+  }
+  {
+    errno = 0;
+    const char input[] = " na";
+    char *ptr;
+    double result = strtod (input, &ptr);
+    ASSERT (result == 0.0);
+    ASSERT (!signbit (result));
+    ASSERT (ptr == input);
+    ASSERT (errno == 0 || errno == EINVAL);
+  }
+
+  /* Simple floating point values.  */
+  {
+    errno = 0;
+    const char input[] = "1";
+    char *ptr;
+    double result = strtod (input, &ptr);
+    ASSERT (result == 1.0);
+    ASSERT (ptr == input + 1);
+    ASSERT (errno == 0);
+  }
+  {
+    errno = 0;
+    const char input[] = "1.";
+    char *ptr;
+    double result = strtod (input, &ptr);
+    ASSERT (result == 1.0);
+    ASSERT (ptr == input + 2);
+    ASSERT (errno == 0);
+  }
+  {
+    errno = 0;
+    const char input[] = ".1";
+    char *ptr;
+    double result = strtod (input, &ptr);
+    ASSERT (result == 0.1);
+    ASSERT (ptr == input + 2);
+    ASSERT (errno == 0);
+  }
+  {
+    errno = 0;
+    const char input[] = " 1";
+    char *ptr;
+    double result = strtod (input, &ptr);
+    ASSERT (result == 1.0);
+    ASSERT (ptr == input + 2);
+    ASSERT (errno == 0);
+  }
+  {
+    errno = 0;
+    const char input[] = "+1";
+    char *ptr;
+    double result = strtod (input, &ptr);
+    ASSERT (result == 1.0);
+    ASSERT (ptr == input + 2);
+    ASSERT (errno == 0);
+  }
+  {
+    errno = 0;
+    const char input[] = "-1";
+    char *ptr;
+    double result = strtod (input, &ptr);
+    ASSERT (result == -1.0);
+    ASSERT (ptr == input + 2);
+    ASSERT (errno == 0);
+  }
+  {
+    errno = 0;
+    const char input[] = "1e0";
+    char *ptr;
+    double result = strtod (input, &ptr);
+    ASSERT (result == 1.0);
+    ASSERT (ptr == input + 3);
+    ASSERT (errno == 0);
+  }
+  {
+    errno = 0;
+    const char input[] = "1e+0";
+    char *ptr;
+    double result = strtod (input, &ptr);
+    ASSERT (result == 1.0);
+    ASSERT (ptr == input + 4);
+    ASSERT (errno == 0);
+  }
+  {
+    errno = 0;
+    const char input[] = "1e-0";
+    char *ptr;
+    double result = strtod (input, &ptr);
+    ASSERT (result == 1.0);
+    ASSERT (ptr == input + 4);
+    ASSERT (errno == 0);
+  }
+  {
+    errno = 0;
+    const char input[] = "1e1";
+    char *ptr;
+    double result = strtod (input, &ptr);
+    ASSERT (result == 10.0);
+    ASSERT (ptr == input + 3);
+    ASSERT (errno == 0);
+  }
+  {
+    errno = 0;
+    const char input[] = "1e-1";
+    char *ptr;
+    double result = strtod (input, &ptr);
+    ASSERT (result == 0.1);
+    ASSERT (ptr == input + 4);
+    ASSERT (errno == 0);
+  }
+
+  /* Zero.  */
+  {
+    errno = 0;
+    const char input[] = "0";
+    char *ptr;
+    double result = strtod (input, &ptr);
+    ASSERT (result == 0.0);
+    ASSERT (!signbit (result));
+    ASSERT (ptr == input + 1);
+    ASSERT (errno == 0);
+  }
+  {
+    errno = 0;
+    const char input[] = ".0";
+    char *ptr;
+    double result = strtod (input, &ptr);
+    ASSERT (result == 0.0);
+    ASSERT (!signbit (result));
+    ASSERT (ptr == input + 2);
+    ASSERT (errno == 0);
+  }
+  {
+    errno = 0;
+    const char input[] = "0e0";
+    char *ptr;
+    double result = strtod (input, &ptr);
+    ASSERT (result == 0.0);
+    ASSERT (!signbit (result));
+    ASSERT (ptr == input + 3);
+    ASSERT (errno == 0);
+  }
+  {
+    errno = 0;
+    const char input[] = "0e+9999999";
+    char *ptr;
+    double result = strtod (input, &ptr);
+    ASSERT (result == 0.0);
+    ASSERT (!signbit (result));
+    ASSERT (ptr == input + 10);
+    ASSERT (errno == 0);
+  }
+  {
+    errno = 0;
+    const char input[] = "0e-9999999";
+    char *ptr;
+    double result = strtod (input, &ptr);
+    ASSERT (result == 0.0);
+    ASSERT (!signbit (result));
+    ASSERT (ptr == input + 10);
+    ASSERT (errno == 0);
+  }
+  {
+    errno = 0;
+    const char input[] = "-0";
+    char *ptr;
+    double result = strtod (input, &ptr);
+    ASSERT (result == 0.0);
+    ASSERT (signbit (result) == signbit (-0.0));
+    ASSERT (ptr == input + 2);
+    ASSERT (errno == 0);
+  }
+
+  /* Suffixes.  */
+  {
+    errno = 0;
+    const char input[] = "1f";
+    char *ptr;
+    double result = strtod (input, &ptr);
+    ASSERT (result == 1.0);
+    ASSERT (ptr == input + 1);
+    ASSERT (errno == 0);
+  }
+  {
+    errno = 0;
+    const char input[] = "1.f";
+    char *ptr;
+    double result = strtod (input, &ptr);
+    ASSERT (result == 1.0);
+    ASSERT (ptr == input + 2);
+    ASSERT (errno == 0);
+  }
+  {
+    errno = 0;
+    const char input[] = "1e";
+    char *ptr;
+    double result = strtod (input, &ptr);
+    ASSERT (result == 1.0);
+    ASSERT (ptr == input + 1);
+    ASSERT (errno == 0);
+  }
+  {
+    errno = 0;
+    const char input[] = "1e+";
+    char *ptr;
+    double result = strtod (input, &ptr);
+    ASSERT (result == 1.0);
+    ASSERT (ptr == input + 1);
+    ASSERT (errno == 0);
+  }
+  {
+    errno = 0;
+    const char input[] = "1e-";
+    char *ptr;
+    double result = strtod (input, &ptr);
+    ASSERT (result == 1.0);
+    ASSERT (ptr == input + 1);
+    ASSERT (errno == 0);
+  }
+  {
+    errno = 0;
+    const char input[] = "0x";
+    char *ptr;
+    double result = strtod (input, &ptr);
+    ASSERT (result == 0.0);
+    ASSERT (!signbit (result));
+    ASSERT (ptr == input + 1);
+    ASSERT (errno == 0);
+  }
+  {
+    errno = 0;
+    const char input[] = "00x1";
+    char *ptr;
+    double result = strtod (input, &ptr);
+    ASSERT (result == 0.0);
+    ASSERT (!signbit (result));
+    ASSERT (ptr == input + 2);
+    ASSERT (errno == 0);
+  }
+  {
+    errno = 0;
+    const char input[] = "-0x";
+    char *ptr;
+    double result = strtod (input, &ptr);
+    ASSERT (result == 0.0);
+    ASSERT (signbit (result) == signbit (-0.0));
+    ASSERT (ptr == input + 2);
+    ASSERT (errno == 0);
+  }
+  {
+    errno = 0;
+    const char input[] = "0xg";
+    char *ptr;
+    double result = strtod (input, &ptr);
+    ASSERT (result == 0.0);
+    ASSERT (!signbit (result));
+    ASSERT (ptr == input + 1);
+    ASSERT (errno == 0);
+  }
+  {
+    errno = 0;
+    const char input[] = "0xp";
+    char *ptr;
+    double result = strtod (input, &ptr);
+    ASSERT (result == 0.0);
+    ASSERT (!signbit (result));
+    ASSERT (ptr == input + 1);
+    ASSERT (errno == 0);
+  }
+  {
+    errno = 0;
+    const char input[] = "0x.";
+    char *ptr;
+    double result = strtod (input, &ptr);
+    ASSERT (result == 0.0);
+    ASSERT (!signbit (result));
+    ASSERT (ptr == input + 1);
+    ASSERT (errno == 0);
+  }
+  {
+    errno = 0;
+    const char input[] = "0xp+";
+    char *ptr;
+    double result = strtod (input, &ptr);
+    ASSERT (result == 0.0);
+    ASSERT (!signbit (result));
+    ASSERT (ptr == input + 1);
+    ASSERT (errno == 0);
+  }
+  {
+    errno = 0;
+    const char input[] = "0xp+1";
+    char *ptr;
+    double result = strtod (input, &ptr);
+    ASSERT (result == 0.0);
+    ASSERT (!signbit (result));
+    ASSERT (ptr == input + 1);
+    ASSERT (errno == 0);
+  }
+  {
+    errno = 0;
+    const char input[] = "0x.p+1";
+    char *ptr;
+    double result = strtod (input, &ptr);
+    ASSERT (result == 0.0);
+    ASSERT (!signbit (result));
+    ASSERT (ptr == input + 1);
+    ASSERT (errno == 0);
+  }
+  {
+    errno = 0;
+    const char input[] = "1p+1";
+    char *ptr;
+    double result = strtod (input, &ptr);
+    ASSERT (result == 1.0);
+    ASSERT (ptr == input + 1);
+    ASSERT (errno == 0);
+  }
+
+  /* Overflow/underflow.  */
+  {
+    errno = 0;
+    const char input[] = "1E100000";
+    char *ptr;
+    double result = strtod (input, &ptr);
+    ASSERT (result == HUGE_VAL);
+    ASSERT (ptr == input + 8);
+    ASSERT (errno == ERANGE);
+  }
+  {
+    errno = 0;
+    const char input[] = "-1E100000";
+    char *ptr;
+    double result = strtod (input, &ptr);
+    ASSERT (result == -HUGE_VAL);
+    ASSERT (ptr == input + 9);
+    ASSERT (errno == ERANGE);
+  }
+  {
+    errno = 0;
+    const char input[] = "1E-100000";
+    char *ptr;
+    double result = strtod (input, &ptr);
+    ASSERT (0.0 <= result && result <= FLT_MIN);
+    ASSERT (!signbit (result));
+    ASSERT (ptr == input + 9);
+    ASSERT (errno == ERANGE);
+  }
+  {
+    errno = 0;
+    const char input[] = "-1E-100000";
+    char *ptr;
+    double result = strtod (input, &ptr);
+    ASSERT (-FLT_MIN <= result && result <= 0.0);
+    ASSERT (signbit (result) == signbit (-0.0));
+    ASSERT (ptr == input + 10);
+    ASSERT (errno == ERANGE);
+  }
+
+  /* Infinity.  */
+  {
+    errno = 0;
+    const char input[] = "iNf";
+    char *ptr;
+    double result = strtod (input, &ptr);
+    ASSERT (result == HUGE_VAL);
+    ASSERT (ptr == input + 3);
+    ASSERT (errno == 0);
+  }
+  {
+    errno = 0;
+    const char input[] = "-InF";
+    char *ptr;
+    double result = strtod (input, &ptr);
+    ASSERT (result == -HUGE_VAL);
+    ASSERT (ptr == input + 4);
+    ASSERT (errno == 0);
+  }
+  {
+    errno = 0;
+    const char input[] = "infinite";
+    char *ptr;
+    double result = strtod (input, &ptr);
+    ASSERT (result == HUGE_VAL);
+    ASSERT (ptr == input + 3);
+    ASSERT (errno == 0);
+  }
+  {
+    errno = 0;
+    const char input[] = "infinitY";
+    char *ptr;
+    double result = strtod (input, &ptr);
+    ASSERT (result == HUGE_VAL);
+    ASSERT (ptr == input + 8);
+    ASSERT (errno == 0);
+  }
+  {
+    errno = 0;
+    const char input[] = "infinitY.";
+    char *ptr;
+    double result = strtod (input, &ptr);
+    ASSERT (result == HUGE_VAL);
+    ASSERT (ptr == input + 8);
+    ASSERT (errno == 0);
+  }
+
+  /* NaN.  Some processors set the sign bit of the default NaN, so all
+     we check is that using a sign changes the result.  */
+  {
+    errno = 0;
+    const char input[] = "-nan";
+    char *ptr1;
+    char *ptr2;
+    double result1 = strtod (input, &ptr1);
+    double result2 = strtod (input + 1, &ptr2);
+#ifdef NAN
+    ASSERT (isnan (result1));
+    ASSERT (isnan (result2));
+    ASSERT (signbit (result1) != signbit (result2));
+    ASSERT (ptr1 == input + 4);
+    ASSERT (ptr2 == input + 4);
+    ASSERT (errno == 0);
+#else
+    ASSERT (result1 == 0.0);
+    ASSERT (result2 == 0.0);
+    ASSERT (!signbit (result1));
+    ASSERT (!signbit (result2));
+    ASSERT (ptr1 == input);
+    ASSERT (ptr2 == input + 1);
+    ASSERT (errno == 0 || errno == EINVAL);
+#endif
+  }
+  {
+    errno = 0;
+    const char input[] = "+nan(";
+    char *ptr1;
+    char *ptr2;
+    double result1 = strtod (input, &ptr1);
+    double result2 = strtod (input + 1, &ptr2);
+#ifdef NAN
+    ASSERT (isnan (result1));
+    ASSERT (isnan (result2));
+    ASSERT (signbit (result1) == signbit (result2));
+    ASSERT (ptr1 == input + 4);
+    ASSERT (ptr2 == input + 4);
+    ASSERT (errno == 0);
+#else
+    ASSERT (result1 == 0.0);
+    ASSERT (result2 == 0.0);
+    ASSERT (!signbit (result1));
+    ASSERT (!signbit (result2));
+    ASSERT (ptr1 == input);
+    ASSERT (ptr2 == input + 1);
+    ASSERT (errno == 0 || errno == EINVAL);
+#endif
+  }
+  {
+    errno = 0;
+    const char input[] = "-nan()";
+    char *ptr1;
+    char *ptr2;
+    double result1 = strtod (input, &ptr1);
+    double result2 = strtod (input + 1, &ptr2);
+#ifdef NAN
+    ASSERT (isnan (result1));
+    ASSERT (isnan (result2));
+    ASSERT (signbit (result1) != signbit (result2));
+    ASSERT (ptr1 == input + 6);
+    ASSERT (ptr2 == input + 6);
+    ASSERT (errno == 0);
+#else
+    ASSERT (result1 == 0.0);
+    ASSERT (result2 == 0.0);
+    ASSERT (!signbit (result1));
+    ASSERT (!signbit (result2));
+    ASSERT (ptr1 == input);
+    ASSERT (ptr2 == input + 1);
+    ASSERT (errno == 0 || errno == EINVAL);
+#endif
+  }
+  {
+    errno = 0;
+    const char input[] = " nan().";
+    char *ptr;
+    double result = strtod (input, &ptr);
+#ifdef NAN
+    ASSERT (isnan (result));
+    ASSERT (ptr == input + 6);
+    ASSERT (errno == 0);
+#else
+    ASSERT (result == 0.0);
+    ASSERT (!signbit (result));
+    ASSERT (ptr == input);
+    ASSERT (errno == 0 || errno == EINVAL);
+#endif
+  }
+  {
+    errno = 0;
+    /* The behavior of nan(0) is implementation-defined, but all
+       implementations we know of which handle optional
+       n-char-sequences handle nan(0) the same as nan().  */
+    const char input[] = "-nan(0).";
+    char *ptr1;
+    char *ptr2;
+    double result1 = strtod (input, &ptr1);
+    double result2 = strtod (input + 1, &ptr2);
+#ifdef NAN
+    ASSERT (isnan (result1));
+    ASSERT (isnan (result2));
+    ASSERT (signbit (result1) != signbit (result2));
+    ASSERT (ptr1 == input + 7);
+    ASSERT (ptr2 == input + 7);
+    ASSERT (errno == 0);
+#else
+    ASSERT (result1 == 0.0);
+    ASSERT (result2 == 0.0);
+    ASSERT (!signbit (result1));
+    ASSERT (!signbit (result2));
+    ASSERT (ptr1 == input);
+    ASSERT (ptr2 == input + 1);
+    ASSERT (errno == 0 || errno == EINVAL);
+#endif
+  }
+
+  /* Hex.  */
+#if 0
+  /* TODO - gnulib doesn't implement this yet.  */
+  {
+    errno = 0;
+    const char input[] = "0xa";
+    char *ptr;
+    double result = strtod (input, &ptr);
+    ASSERT (result == 10.0);
+    ASSERT (ptr == input + 3);
+    ASSERT (errno == 0);
+  }
+  {
+    errno = 0;
+    const char input[] = "0XA";
+    char *ptr;
+    double result = strtod (input, &ptr);
+    ASSERT (result == 10.0);
+    ASSERT (ptr == input + 3);
+    ASSERT (errno == 0);
+  }
+  {
+    errno = 0;
+    const char input[] = "0x1p";
+    char *ptr;
+    double result = strtod (input, &ptr);
+    ASSERT (result == 1.0);
+    ASSERT (ptr == input + 3);
+    ASSERT (errno == 0);
+  }
+  {
+    errno = 0;
+    const char input[] = "0x1p+";
+    char *ptr;
+    double result = strtod (input, &ptr);
+    ASSERT (result == 1.0);
+    ASSERT (ptr == input + 3);
+    ASSERT (errno == 0);
+  }
+  {
+    errno = 0;
+    const char input[] = "0x1p+1";
+    char *ptr;
+    double result = strtod (input, &ptr);
+    ASSERT (result == 2.0);
+    ASSERT (ptr == input + 6);
+    ASSERT (errno == 0);
+  }
+  {
+    errno = 0;
+    const char input[] = "0x1p+1a";
+    char *ptr;
+    double result = strtod (input, &ptr);
+    ASSERT (result == 2.0);
+    ASSERT (ptr == input + 6);
+    ASSERT (errno == 0);
+  }
+#endif
+
+  /* Large buffers.  */
+  {
+    errno = 0;
+    size_t m = 1000000;
+    char *input = malloc (m + 1);
+    if (input)
+      {
+       char *ptr;
+       double result;
+       memset (input, '\t', m - 1);
+       input[m - 1] = '1';
+       input[m] = '\0';
+       result = strtod (input, &ptr);
+       ASSERT (result == 1.0);
+       ASSERT (ptr == input + m);
+       ASSERT (errno == 0);
+      }
+    free (input);
+  }
+  {
+    errno = 0;
+    size_t m = 1000000;
+    char *input = malloc (m + 1);
+    if (input)
+      {
+       char *ptr;
+       double result;
+       memset (input, '0', m - 1);
+       input[m - 1] = '1';
+       input[m] = '\0';
+       result = strtod (input, &ptr);
+       ASSERT (result == 1.0);
+       ASSERT (ptr == input + m);
+       ASSERT (errno == 0);
+      }
+    free (input);
+  }
+#if 0
+  /* Newlib has an artificial limit of 20000 for the exponent.  TODO -
+     gnulib should fix this.  */
+  {
+    errno = 0;
+    size_t m = 1000000;
+    char *input = malloc (m + 1);
+    if (input)
+      {
+       char *ptr;
+       double result;
+       input[0] = '.';
+       memset (input + 1, '0', m - 10);
+       input[m - 9] = '1';
+       input[m - 8] = 'e';
+       input[m - 7] = '+';
+       input[m - 6] = '9';
+       input[m - 5] = '9';
+       input[m - 4] = '9';
+       input[m - 3] = '9';
+       input[m - 2] = '9';
+       input[m - 1] = '1';
+       input[m] = '\0';
+       result = strtod (input, &ptr);
+       ASSERT (result == 1.0);
+       ASSERT (ptr == input + m);
+       ASSERT (errno == 0);
+      }
+    free (input);
+  }
+  {
+    errno = 0;
+    size_t m = 1000000;
+    char *input = malloc (m + 1);
+    if (input)
+      {
+       char *ptr;
+       double result;
+       input[0] = '1';
+       memset (input + 1, '0', m - 9);
+       input[m - 8] = 'e';
+       input[m - 7] = '-';
+       input[m - 6] = '9';
+       input[m - 5] = '9';
+       input[m - 4] = '9';
+       input[m - 3] = '9';
+       input[m - 2] = '9';
+       input[m - 1] = '1';
+       input[m] = '\0';
+       result = strtod (input, &ptr);
+       ASSERT (result == 1.0);
+       ASSERT (ptr == input + m);
+       ASSERT (errno == 0);
+      }
+    free (input);
+  }
+#endif
+  {
+    errno = 0;
+    size_t m = 1000000;
+    char *input = malloc (m + 1);
+    if (input)
+      {
+       char *ptr;
+       double result;
+       input[0] = '-';
+       input[1] = '0';
+       input[2] = 'e';
+       input[3] = '1';
+       memset (input + 4, '0', m - 3);
+       input[m] = '\0';
+       result = strtod (input, &ptr);
+       ASSERT (result == 0.0);
+       ASSERT (signbit (result) == signbit (-0.0));
+       ASSERT (ptr == input + m);
+       ASSERT (errno == 0);
+      }
+    free (input);
+  }
+
+  /* Rounding.  */
+  /* TODO - is it worth some tests of rounding for typical IEEE corner
+     cases, such as .5 ULP rounding up to the smallest denormal and
+     not causing underflow, or FLT_MIN - .5 ULP not causing an
+     infinite loop?  */
+
+  return status;
+}