bug-gnulib
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

strtod bugs


From: Eric Blake
Subject: strtod bugs
Date: Sat, 29 Mar 2008 15:29:13 -0600
User-agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.12) Gecko/20080213 Thunderbird/2.0.0.12 Mnenhy/0.7.5.666

-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

Wow - I had no idea that strtod implementations were so buggy.  Of all the
systems I tried, I did not find a single one that was fully C99 compliant.

glibc 2.3.6 has several bugs, such as when parsing "-0x" or "nan()".

glibc 2.4 has improvements, but has a bug that was fixed in CVS just just
today: http://sources.redhat.com/bugzilla/show_bug.cgi?id=5995

cygwin 1.5.25-11 included several bug fixes, but has an artificial limit
that the exponent can't be larger than 20000.  Also, it's strtof suffers
from double-rounding (there are some decimal numbers which round up to
double, then round up again when cast to float, when they should have
rounded down if parsed directly as float).

It just gets worse elsewhere: mingw and OpenBSD don't even parse 'inf' or
'nan'.  And many platforms are not as exact as cygwin or glibc at
correctly rounding long numbers (for IEEE denormals, it can take more than
1000 characters to decide whether a number rounds up or down).

Unfortunately, even the gnulib replacement has its own share of bugs.
While this patch fixes some of them, it does not implement hex-nan
parsing, and it suffers from rounding errors.

Also, it would be nice if gnulib's math.h would guarantee things like
HUGE_VALF and INFINITY, even in C89 mode (glibc refuses to declare them
except in C99).

Therefore, I'm asking for opinions - should I apply this patch as is, to
at least guarantee 'inf' and 'nan' parsing on all platforms, even though
it can cause regressions on the accuracy of the parse and loses hex-nan
for platforms like cygwin 1.5.24 or glibc 2.3.x?  Does anyone feel up to
the task of writing a hex-nan parser?  Should we pull in glibc's or gmp's
algorithm for accurately parsing decimal into floating point, rather than
relying on the inaccuracy of pow (and in the process, avoid pulling in -lm)?

- --
Don't work too hard, make some time for fun as well!

Eric Blake             address@hidden
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.8 (Cygwin)
Comment: Public key at home.comcast.net/~ericblake/eblake.gpg
Comment: Using GnuPG with Mozilla - http://enigmail.mozdev.org

iEYEARECAAYFAkfutKgACgkQ84KuGfSFAYBhMACcDQVTQ1al8fENLZEOP65vA83G
hPgAnjESKp6OZwzUm6I4wBQwGt9v43Tt
=n6vP
-----END PGP SIGNATURE-----
>From 004badc4956312f48089f06919a7d4d10d8aea3e Mon Sep 17 00:00:00 2001
From: Eric Blake <address@hidden>
Date: Sat, 29 Mar 2008 13:50:21 -0600
Subject: [PATCH] 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.

Signed-off-by: Eric Blake <address@hidden>
---
 ChangeLog                       |   18 +
 doc/posix-functions/atof.texi   |   35 ++
 doc/posix-functions/strtod.texi |   38 ++
 lib/stdlib.in.h                 |   19 +-
 lib/strtod.c                    |   99 ++++--
 m4/stdlib_h.m4                  |    7 +-
 m4/strtod.m4                    |   42 ++-
 modules/stdlib                  |    3 +
 modules/strtod                  |    3 +
 modules/strtod-tests            |   14 +
 tests/test-strtod.c             |  822 +++++++++++++++++++++++++++++++++++++++
 11 files changed, 1065 insertions(+), 35 deletions(-)
 create mode 100644 modules/strtod-tests
 create mode 100644 tests/test-strtod.c

diff --git a/ChangeLog b/ChangeLog
index 5b5933c..4245f6b 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,23 @@
 2008-03-29  Eric Blake  <address@hidden>
 
+       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  <address@hidden>
+
        Pass test-freadseek on cygwin.
        * modules/freadseek (Depends-on): Use freadptr, not freadseek.
        * lib/freadseek.c (freadseek): Don't increment beyond bounds of
diff --git a/doc/posix-functions/atof.texi b/doc/posix-functions/atof.texi
index c17dc3a..c20a60f 100644
--- a/doc/posix-functions/atof.texi
+++ b/doc/posix-functions/atof.texi
@@ -12,4 +12,39 @@ Portability problems fixed by Gnulib:
 
 Portability problems not fixed by Gnulib:
 @itemize
address@hidden
+This function mis-parses strings with leading @samp{+} on some old platforms:
+Old versions of Linux.
+
address@hidden
+This function returns a positive value for negative underflow on some
+platforms:
+glibc 2.4, Mingw, Cygwin.
+
address@hidden
+This function fails to do a valid parse of @samp{-0x} on some
+platforms:
+glibc 2.4, Cygwin < 1.5.25-11.
+
address@hidden
+This function fails to parse Infinities and plain NaNs on some platforms:
+Solaris 8, Mingw, OpenBSD 4.0.
+
address@hidden
+This function fails to parse NaN() on some platforms:
+Solaris 8, Mingw, OpenBSD 4.0, Cygwin < 1.5.25-11.
+
address@hidden
+This function fails to parse NaN(n-char-sequence) on some platforms:
+Solaris 8, Mingw, OpenBSD 4.0.
+
address@hidden
+This function fails to parse C99 hexadecimal floating point on some
+platforms:
+Solaris 8, Mingw, OpenBSD 4.0.
+
address@hidden
+This function fails to correctly parse very long strings on some
+platforms:
+Mingw, Cygwin.
 @end itemize
diff --git a/doc/posix-functions/strtod.texi b/doc/posix-functions/strtod.texi
index 71e0978..349802a 100644
--- a/doc/posix-functions/strtod.texi
+++ b/doc/posix-functions/strtod.texi
@@ -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
address@hidden
+This function returns a positive value for negative underflow on some
+platforms:
+glibc 2.4, Mingw, Cygwin.
+
address@hidden
+This function fails to do a valid parse of @samp{-0x} on some
+platforms:
+glibc 2.4, Cygwin < 1.5.25-11.
+
address@hidden
+This function fails to parse Infinities and plain NaNs on some platforms:
+Solaris 8, Mingw, OpenBSD 4.0.
+
address@hidden
+This function fails to parse NaN() on some platforms:
+Solaris 8, Mingw, OpenBSD 4.0, Cygwin < 1.5.25-11.
+
address@hidden
+This function fails to parse NaN(n-char-sequence) on some platforms:
+Solaris 8, Mingw, OpenBSD 4.0.
+
address@hidden
+This function returns the wrong end pointer when parsing
+NaN(n-char-sequence) on some platforms:
+glibc 2.4.
+
address@hidden
+This function fails to parse C99 hexadecimal floating point on some
+platforms:
+Solaris 8, Mingw, OpenBSD 4.0.
+
address@hidden
+This function fails to correctly parse very long strings on some
+platforms:
+Mingw, Cygwin.
 @end itemize
diff --git a/lib/stdlib.in.h b/lib/stdlib.in.h
index 100ff52..0181dd2 100644
--- a/lib/stdlib.in.h
+++ b/lib/stdlib.in.h
@@ -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 address@hidden@ || @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
diff --git a/lib/strtod.c b/lib/strtod.c
index bba537a..147a5bf 100644
--- a/lib/strtod.c
+++ b/lib/strtod.c
@@ -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
@@ -15,14 +16,13 @@
 
 #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
@@ -30,14 +30,14 @@
 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;
 }
diff --git a/m4/stdlib_h.m4 b/m4/stdlib_h.m4
index fe4ce12..d9240b4 100644
--- a/m4/stdlib_h.m4
+++ b/m4/stdlib_h.m4
@@ -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])
 ])
diff --git a/m4/strtod.m4 b/m4/strtod.m4
index 1de2f2f..7a10a21 100644
--- a/m4/strtod.m4
+++ b/m4/strtod.m4
@@ -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
 ])
 
diff --git a/modules/stdlib b/modules/stdlib
index d9a6e97..b119530 100644
--- a/modules/stdlib
+++ b/modules/stdlib
@@ -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; \
diff --git a/modules/strtod b/modules/strtod
index 4098b13..5f2dce5 100644
--- a/modules/strtod
+++ b/modules/strtod
@@ -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
index 0000000..e40a274
--- /dev/null
+++ b/modules/strtod-tests
@@ -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
index 0000000..c54a8fd
--- /dev/null
+++ b/tests/test-strtod.c
@@ -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;
+}
-- 
1.5.4


reply via email to

[Prev in Thread] Current Thread [Next in Thread]