From: Eric Blake Date: Sat, 29 Mar 2008 19:50:21 +0000 (-0600) Subject: Document various strtod bugs, with some fixes. X-Git-Tag: v0.1~7634 X-Git-Url: http://erislabs.net/gitweb/?a=commitdiff_plain;h=de138ad63af1ad9cb74b16743c511eebf0ccfbaa;p=gnulib.git 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 --- diff --git a/ChangeLog b/ChangeLog index f6c7dc0d3..1e0254d74 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,5 +1,23 @@ 2008-03-29 Eric Blake + 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 + Fix ftell on mingw. * lib/ftell.c (EOVERFLOW): Define if the system lacks it. * modules/ftell-tests (Depends-on): Add binary-io. diff --git a/doc/posix-functions/atof.texi b/doc/posix-functions/atof.texi index c17dc3a1e..c20a60f68 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 +@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 diff --git a/doc/posix-functions/strtod.texi b/doc/posix-functions/strtod.texi index 71e097869..349802ab1 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 +@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 diff --git a/lib/stdlib.in.h b/lib/stdlib.in.h index 100ff5265..0181dd237 100644 --- a/lib/stdlib.in.h +++ b/lib/stdlib.in.h @@ -1,6 +1,6 @@ /* A GNU-like . - 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 diff --git a/lib/strtod.c b/lib/strtod.c index bba537af2..147a5bf63 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 -#include +#include #include - -#include - +#include #include -#include +#include +#include #include /* 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() is implementation-defined, + we define it by ignoring . 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 fe4ce122e..d9240b46a 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 1de2f2fb9..7a10a21fc 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 +#include +]], [[ + { + /* 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 d9a6e9788..b1195301c 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 4098b1364..5f2dce554 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 000000000..e40a274d6 --- /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 000000000..c54a8fda7 --- /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 . */ + +#include + +#include + +#include +#include +#include +#include +#include + +#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; +}