bug-gnulib@gnu.org mirror (unofficial)
 help / color / mirror / Atom feed
* new module 'strtold'
@ 2019-01-30  3:05 Bruno Haible
  2019-01-30  5:42 ` Pádraig Brady
  2019-02-01  2:16 ` strtod, strtold: various fixes Bruno Haible
  0 siblings, 2 replies; 6+ messages in thread
From: Bruno Haible @ 2019-01-30  3:05 UTC (permalink / raw)
  To: bug-gnulib

[-- Attachment #1: Type: text/plain, Size: 2514 bytes --]

Hi,

coreutils has recently started to use strtold() [1]. This causes compilation
errors on Android 4.3:

  CC       lib/cl-strtod.o
  CC       lib/cl-strtold.o
In file included from ../lib/cl-strtold.c:2:
../lib/cl-strtod.c: In function 'cl_strtold':
../lib/cl-strtod.c:62: warning: implicit declaration of function 'strtold'

  CCLD     src/printf
lib/libcoreutils.a(cl-strtold.o): In function `cl_strtold':
cl-strtold.c:(.text+0x14): undefined reference to `strtold'
collect2: ld returned 1 exit status
make[2]: *** [src/printf] Error 1

  CCLD     src/seq
lib/libcoreutils.a(cl-strtold.o): In function `cl_strtold':
cl-strtold.c:(.text+0x14): undefined reference to `strtold'
collect2: ld returned 1 exit status
make[2]: *** [src/seq] Error 1

and similarly on Solaris 9 (both lack the strtold() function)
and on HP-UX 11/hppa (where the function is declared to return a struct,
not a 'long double').

And of course the function is not POSIX compliant on some platforms.

Here comes a new gnulib module 'strtold'. Tested on

- a glibc 2.3.2 system (Fedora 1)
- Mac OS X 10.5, Mac OS X 10.4/PowerPC
- FreeBSD 11.0
- NetBSD 7.1
- OpenBSD 6.0
- Minix 3.3
- AIX 7.1
- HP-UX 11.31/hppa
- HP-UX 11.31/ia64 (test failure in line 574, also in test-strtod.c:574)
- IRIX 6.5
- Solaris 9/SPARC, Solaris 10/SPARC, Solaris 10/x86
- Cygwin
- mingw
- Haiku
- Android 4.3

Bruno

[1] https://git.savannah.gnu.org/gitweb/?p=coreutils.git;a=commitdiff;h=eb73e23f31f4ce363b1505cc77424832d5c39063


2019-01-29  Bruno Haible  <bruno@clisp.org>

	strtold: New module.
	* lib/stdlib.in.h (strtold): New declaration.
	* lib/strtold.c: New file.
	* lib/strtod.c: Consider USE_LONG_DOUBLE.
	(STRTOD, LDEXP, HAVE_UNDERLYING_STRTOD, DOUBLE, MIN, MAX, L_,
	USE_LDEXP): New macros.
	(LDEXP, scale_radix_exp, parse_number, STRTOD): Adapt for
	USE_LONG_DOUBLE.
	(underlying_strtod): Remove function. Replace with some macros.
	Re-add the code for a missing underlying function that was removed on
	2013-02-19.
	* m4/strtold.m4: New file.
	* m4/stdlib_h.m4 (gl_STDLIB_H): Test whether strtold is declared.
	(gl_STDLIB_H_DEFAULTS): Initialize GNULIB_STRTOLD, HAVE_STRTOLD,
	REPLACE_STRTOLD.
	* modules/stdlib (Makefile.am): Substitute GNULIB_STRTOLD, HAVE_STRTOLD,
	REPLACE_STRTOLD.
	* modules/strtold: New file.
	* doc/posix-functions/strtold.texi: Document the new module.

2019-01-29  Bruno Haible  <bruno@clisp.org>

	strtold: Add tests.
	* tests/test-strtold.c: New file, based on tests/test-strtod.c.
	* modules/strtold-tests: New file.


[-- Attachment #2: 0001-strtold-New-module.patch --]
[-- Type: text/x-patch, Size: 23187 bytes --]

From ebad0cfebbb201a4e645495d244f899a39a305e3 Mon Sep 17 00:00:00 2001
From: Bruno Haible <bruno@clisp.org>
Date: Wed, 30 Jan 2019 03:52:31 +0100
Subject: [PATCH 1/2] strtold: New module.

* lib/stdlib.in.h (strtold): New declaration.
* lib/strtold.c: New file.
* lib/strtod.c: Consider USE_LONG_DOUBLE.
(STRTOD, LDEXP, HAVE_UNDERLYING_STRTOD, DOUBLE, MIN, MAX, L_,
USE_LDEXP): New macros.
(LDEXP, scale_radix_exp, parse_number, STRTOD): Adapt for
USE_LONG_DOUBLE.
(underlying_strtod): Remove function. Replace with some macros.
Re-add the code for a missing underlying function that was removed on
2013-02-19.
* m4/strtold.m4: New file.
* m4/stdlib_h.m4 (gl_STDLIB_H): Test whether strtold is declared.
(gl_STDLIB_H_DEFAULTS): Initialize GNULIB_STRTOLD, HAVE_STRTOLD,
REPLACE_STRTOLD.
* modules/stdlib (Makefile.am): Substitute GNULIB_STRTOLD, HAVE_STRTOLD,
REPLACE_STRTOLD.
* modules/strtold: New file.
* doc/posix-functions/strtold.texi: Document the new module.
---
 ChangeLog                        |  22 +++++++
 doc/posix-functions/strtold.texi |  54 +++++++++++++++++-
 lib/stdlib.in.h                  |  25 ++++++++
 lib/strtod.c                     | 113 +++++++++++++++++++++++-------------
 lib/strtold.c                    |  37 ++++++++++++
 m4/stdlib_h.m4                   |   7 ++-
 m4/strtold.m4                    | 120 +++++++++++++++++++++++++++++++++++++++
 modules/stdlib                   |   3 +
 modules/strtold                  |  35 ++++++++++++
 9 files changed, 371 insertions(+), 45 deletions(-)
 create mode 100644 lib/strtold.c
 create mode 100644 m4/strtold.m4
 create mode 100644 modules/strtold

diff --git a/ChangeLog b/ChangeLog
index 51daf91..ef10452 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,27 @@
 2019-01-29  Bruno Haible  <bruno@clisp.org>
 
+	strtold: New module.
+	* lib/stdlib.in.h (strtold): New declaration.
+	* lib/strtold.c: New file.
+	* lib/strtod.c: Consider USE_LONG_DOUBLE.
+	(STRTOD, LDEXP, HAVE_UNDERLYING_STRTOD, DOUBLE, MIN, MAX, L_,
+	USE_LDEXP): New macros.
+	(LDEXP, scale_radix_exp, parse_number, STRTOD): Adapt for
+	USE_LONG_DOUBLE.
+	(underlying_strtod): Remove function. Replace with some macros.
+	Re-add the code for a missing underlying function that was removed on
+	2013-02-19.
+	* m4/strtold.m4: New file.
+	* m4/stdlib_h.m4 (gl_STDLIB_H): Test whether strtold is declared.
+	(gl_STDLIB_H_DEFAULTS): Initialize GNULIB_STRTOLD, HAVE_STRTOLD,
+	REPLACE_STRTOLD.
+	* modules/stdlib (Makefile.am): Substitute GNULIB_STRTOLD, HAVE_STRTOLD,
+	REPLACE_STRTOLD.
+	* modules/strtold: New file.
+	* doc/posix-functions/strtold.texi: Document the new module.
+
+2019-01-29  Bruno Haible  <bruno@clisp.org>
+
 	strtod: Fix compilation error on IRIX 6.5.
 	* modules/strtod (Depends-on): Add 'math'.
 
diff --git a/doc/posix-functions/strtold.texi b/doc/posix-functions/strtold.texi
index ff6f195..f136c54 100644
--- a/doc/posix-functions/strtold.texi
+++ b/doc/posix-functions/strtold.texi
@@ -4,15 +4,63 @@
 
 POSIX specification:@* @url{http://www.opengroup.org/onlinepubs/9699919799/functions/strtold.html}
 
-Gnulib module: ---
+Gnulib module: strtold
 
 Portability problems fixed by Gnulib:
 @itemize
+@item
+This function is missing on some platforms:
+NetBSD 3.0, OpenBSD 3.8, Minix 3.1.8, older IRIX 6.5, OSF/1 4.0, Solaris 9, older Cygwin, MSVC 9, Interix 3.5, BeOS, Android 4.4.
+
+@item
+This function returns a struct, not a @code{long double}, on some platforms:
+HP-UX 11.31/hppa.
+
+@item
+This function consumes whitespace even when there is nothing that should
+be parsed on some platforms:
+IRIX 6.5.
+
+@item
+This function allows whitespace between @samp{e} and the exponent on
+some platforms:
+HP-UX 11.31/ia64, IRIX 6.5.
+
+@item
+This function returns the wrong end pointer for @samp{-0x} on some
+platforms:
+glibc-2.3.2, Mac OS X 10.5, Haiku.
+
+@item
+This function returns +0.0 (not @minus{}0.0) for @samp{-0} on some platforms:
+IRIX 6.5.
+
+@item
+This function fails to parse Infinities and plain NaNs on some platforms:
+HP-UX 11.31/ia64, IRIX 6.5.
+
+@item
+This function fails to parse @samp{NaN()} on some platforms:
+glibc-2.3.2, IRIX 6.5, mingw, Haiku.
+
+@item
+This function fails to parse @samp{NaN(@var{n-char-sequence})} on some
+platforms:
+IRIX 6.5.
+
+@item
+This function parses @samp{NaN(@var{n-char-sequence})}, but returns
+the wrong end pointer on some platforms:
+glibc-2.3.2, mingw, Haiku.
+
+@item
+This function fails to parse C99 hexadecimal floating point on some
+platforms:
+IRIX 6.5, mingw.
 @end itemize
 
 Portability problems not fixed by Gnulib:
 @itemize
 @item
-This function is missing on some platforms:
-NetBSD 3.0, OpenBSD 3.8, Minix 3.1.8, IRIX 6.5, OSF/1 4.0, Solaris 9, Cygwin, MSVC 9, Interix 3.5, BeOS, Android 4.4.
+The replacement function does not always return correctly rounded results.
 @end itemize
diff --git a/lib/stdlib.in.h b/lib/stdlib.in.h
index be8ab3b..b70444d 100644
--- a/lib/stdlib.in.h
+++ b/lib/stdlib.in.h
@@ -948,6 +948,31 @@ _GL_WARN_ON_USE (strtod, "strtod is unportable - "
 # endif
 #endif
 
+#if @GNULIB_STRTOLD@
+ /* Parse a 'long double' from STRING, updating ENDP if appropriate.  */
+# if @REPLACE_STRTOLD@
+#  if !(defined __cplusplus && defined GNULIB_NAMESPACE)
+#   define strtold rpl_strtold
+#  endif
+_GL_FUNCDECL_RPL (strtold, long double, (const char *str, char **endp)
+                                        _GL_ARG_NONNULL ((1)));
+_GL_CXXALIAS_RPL (strtold, long double, (const char *str, char **endp));
+# else
+#  if !@HAVE_STRTOLD@
+_GL_FUNCDECL_SYS (strtold, long double, (const char *str, char **endp)
+                                        _GL_ARG_NONNULL ((1)));
+#  endif
+_GL_CXXALIAS_SYS (strtold, long double, (const char *str, char **endp));
+# endif
+_GL_CXXALIASWARN (strtold);
+#elif defined GNULIB_POSIXCHECK
+# undef strtold
+# if HAVE_RAW_DECL_STRTOLD
+_GL_WARN_ON_USE (strtold, "strtold is unportable - "
+                 "use gnulib module strtold for portability");
+# endif
+#endif
+
 #if @GNULIB_STRTOLL@
 /* Parse a signed integer whose textual representation starts at STRING.
    The integer is expected to be in base BASE (2 <= BASE <= 36); if BASE == 0,
diff --git a/lib/strtod.c b/lib/strtod.c
index 9a28f4b..1d1ba27 100644
--- a/lib/strtod.c
+++ b/lib/strtod.c
@@ -14,8 +14,11 @@
    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <https://www.gnu.org/licenses/>.  */
 
-#include <config.h>
+#if ! defined USE_LONG_DOUBLE
+# include <config.h>
+#endif
 
+/* Specification.  */
 #include <stdlib.h>
 
 #include <ctype.h>
@@ -28,8 +31,36 @@
 
 #include "c-ctype.h"
 
-#ifndef HAVE_LDEXP_IN_LIBC
-#define HAVE_LDEXP_IN_LIBC 0
+#undef MIN
+#undef MAX
+#ifdef USE_LONG_DOUBLE
+# define STRTOD strtold
+# define LDEXP ldexpl
+# if defined __hpux && defined __hppa
+   /* We cannot call strtold on HP-UX/hppa, because its return type is a struct,
+      not a 'long double'.  */
+#  define HAVE_UNDERLYING_STRTOD 0
+# else
+#  define HAVE_UNDERLYING_STRTOD HAVE_STRTOLD
+# endif
+# define DOUBLE long double
+# define MIN LDBL_MIN
+# define MAX LDBL_MAX
+# define L_(literal) literal##L
+#else
+# define STRTOD strtod
+# define LDEXP ldexp
+# define HAVE_UNDERLYING_STRTOD 1
+# define DOUBLE double
+# define MIN DBL_MIN
+# define MAX DBL_MAX
+# define L_(literal) literal
+#endif
+
+#if (defined USE_LONG_DOUBLE ? HAVE_LDEXPM_IN_LIBC : HAVE_LDEXP_IN_LIBC)
+# define USE_LDEXP 1
+#else
+# define USE_LDEXP 0
 #endif
 
 /* Return true if C is a space in the current locale, avoiding
@@ -41,20 +72,21 @@ locale_isspace (char c)
   return isspace (uc) != 0;
 }
 
-#if !HAVE_LDEXP_IN_LIBC
- #define ldexp dummy_ldexp
+#if !USE_LDEXP
+ #undef LDEXP
+ #define LDEXP dummy_ldexp
  /* A dummy definition that will never be invoked.  */
- static double ldexp (double x _GL_UNUSED, int exponent _GL_UNUSED)
+ static DOUBLE LDEXP (DOUBLE x _GL_UNUSED, int exponent _GL_UNUSED)
  {
    abort ();
-   return 0.0;
+   return L_(0.0);
  }
 #endif
 
 /* Return X * BASE**EXPONENT.  Return an extreme value and set errno
    to ERANGE if underflow or overflow occurs.  */
-static double
-scale_radix_exp (double x, int radix, long int exponent)
+static DOUBLE
+scale_radix_exp (DOUBLE x, int radix, long int exponent)
 {
   /* If RADIX == 10, this code is neither precise nor fast; it is
      merely a straightforward and relatively portable approximation.
@@ -63,11 +95,11 @@ scale_radix_exp (double x, int radix, long int exponent)
 
   long int e = exponent;
 
-  if (HAVE_LDEXP_IN_LIBC && radix == 2)
-    return ldexp (x, e < INT_MIN ? INT_MIN : INT_MAX < e ? INT_MAX : e);
+  if (USE_LDEXP && radix == 2)
+    return LDEXP (x, e < INT_MIN ? INT_MIN : INT_MAX < e ? INT_MAX : e);
   else
     {
-      double r = x;
+      DOUBLE r = x;
 
       if (r != 0)
         {
@@ -87,12 +119,12 @@ scale_radix_exp (double x, int radix, long int exponent)
             {
               while (e-- != 0)
                 {
-                  if (r < -DBL_MAX / radix)
+                  if (r < -MAX / radix)
                     {
                       errno = ERANGE;
                       return -HUGE_VAL;
                     }
-                  else if (DBL_MAX / radix < r)
+                  else if (MAX / radix < r)
                     {
                       errno = ERANGE;
                       return HUGE_VAL;
@@ -113,7 +145,7 @@ scale_radix_exp (double x, int radix, long int exponent)
    radix RADIX (either 10 or 2) exponent, and exponent character
    EXPCHAR.  To convert from a number of digits to a radix exponent,
    multiply by RADIX_MULTIPLIER (either 1 or 4).  */
-static double
+static DOUBLE
 parse_number (const char *nptr,
               int base, int radix, int radix_multiplier, char expchar,
               char **endptr)
@@ -121,7 +153,7 @@ parse_number (const char *nptr,
   const char *s = nptr;
   bool got_dot = false;
   long int exponent = 0;
-  double num = 0;
+  DOUBLE num = 0;
 
   for (;; ++s)
     {
@@ -141,12 +173,12 @@ parse_number (const char *nptr,
         break;
 
       /* Make sure that multiplication by base will not overflow.  */
-      if (num <= DBL_MAX / base)
+      if (num <= MAX / base)
         num = num * base + digit;
       else
         {
           /* The value of the digit doesn't matter, since we have already
-             gotten as many digits as can be represented in a 'double'.
+             gotten as many digits as can be represented in a 'DOUBLE'.
              This doesn't necessarily mean the result will overflow.
              The exponent may reduce it to within range.
 
@@ -185,32 +217,42 @@ parse_number (const char *nptr,
   return scale_radix_exp (num, radix, exponent);
 }
 
-static double underlying_strtod (const char *, char **);
-
 /* HP cc on HP-UX 10.20 has a bug with the constant expression -0.0.
    ICC 10.0 has a bug when optimizing the expression -zero.
-   The expression -DBL_MIN * DBL_MIN does not work when cross-compiling
+   The expression -MIN * MIN does not work when cross-compiling
    to PowerPC on Mac OS X 10.5.  */
 #if defined __hpux || defined __sgi || defined __ICC
-static double
+static DOUBLE
 compute_minus_zero (void)
 {
-  return -DBL_MIN * DBL_MIN;
+  return -MIN * MIN;
 }
 # define minus_zero compute_minus_zero ()
 #else
-double minus_zero = -0.0;
+DOUBLE minus_zero = -0.0;
 #endif
 
-/* Convert NPTR to a double.  If ENDPTR is not NULL, a pointer to the
+/* Convert NPTR to a DOUBLE.  If ENDPTR is not NULL, a pointer to the
    character after the last one used in the number is put in *ENDPTR.  */
-double
-strtod (const char *nptr, char **endptr)
+DOUBLE
+STRTOD (const char *nptr, char **endptr)
+#if HAVE_UNDERLYING_STRTOD
+# ifdef USE_LONG_DOUBLE
+#  undef strtold
+# else
+#  undef strtod
+# endif
+#else
+# undef STRTOD
+# define STRTOD(NPTR,ENDPTR) parse_number (NPTR, 10, 10, 1, 'e', ENDPTR)
+#endif
+/* From here on, STRTOD refers to the underlying implementation.  It needs
+   to handle only finite unsigned decimal numbers with non-null ENDPTR.  */
 {
   bool negative = false;
 
   /* The number so far.  */
-  double num;
+  DOUBLE num;
 
   const char *s = nptr;
   const char *end;
@@ -226,7 +268,7 @@ strtod (const char *nptr, char **endptr)
   if (*s == '-' || *s == '+')
     ++s;
 
-  num = underlying_strtod (s, &endbuf);
+  num = STRTOD (s, &endbuf);
   end = endbuf;
 
   if (c_isdigit (s[*s == '.']))
@@ -261,7 +303,7 @@ strtod (const char *nptr, char **endptr)
       else
         {
           /* If "1e 1" was misparsed as 10.0 instead of 1.0, re-do the
-             underlying strtod on a copy of the original string
+             underlying STRTOD on a copy of the original string
              truncated to avoid the bug.  */
           const char *e = s + 1;
           while (e < end && c_tolower (*e) != 'e')
@@ -279,7 +321,7 @@ strtod (const char *nptr, char **endptr)
               else
                 {
                   dup[e - s] = '\0';
-                  num = underlying_strtod (dup, &endbuf);
+                  num = STRTOD (dup, &endbuf);
                   saved_errno = errno;
                   free (dup);
                   errno = saved_errno;
@@ -344,12 +386,3 @@ strtod (const char *nptr, char **endptr)
     return minus_zero;
   return negative ? -num : num;
 }
-
-/* The underlying strtod implementation.  This must be defined
-   after strtod because it #undefs strtod.  */
-static double
-underlying_strtod (const char *nptr, char **endptr)
-{
-#undef strtod
-  return strtod (nptr, endptr);
-}
diff --git a/lib/strtold.c b/lib/strtold.c
new file mode 100644
index 0000000..3791303
--- /dev/null
+++ b/lib/strtold.c
@@ -0,0 +1,37 @@
+/* Convert string to 'long double'.
+   Copyright (C) 2019 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
+   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 <https://www.gnu.org/licenses/>.  */
+
+/* Written by Bruno Haible <bruno@clisp.org>, 2019.  */
+
+#include <config.h>
+
+#if HAVE_SAME_LONG_DOUBLE_AS_DOUBLE
+
+/* Specification.  */
+# include <stdlib.h>
+
+long double
+strtold (const char *str, char **endp)
+{
+  return strtod (str, endp);
+}
+
+#else
+
+# define USE_LONG_DOUBLE
+# include "strtod.c"
+
+#endif
diff --git a/m4/stdlib_h.m4 b/m4/stdlib_h.m4
index c5db804..6121602 100644
--- a/m4/stdlib_h.m4
+++ b/m4/stdlib_h.m4
@@ -1,4 +1,4 @@
-# stdlib_h.m4 serial 47
+# stdlib_h.m4 serial 48
 dnl Copyright (C) 2007-2019 Free Software Foundation, Inc.
 dnl This file is free software; the Free Software Foundation
 dnl gives unlimited permission to copy and/or distribute it,
@@ -26,7 +26,7 @@ AC_DEFUN([gl_STDLIB_H],
     initstate initstate_r mbtowc mkdtemp mkostemp mkostemps mkstemp mkstemps
     posix_openpt ptsname ptsname_r qsort_r random random_r reallocarray
     realpath rpmatch secure_getenv setenv setstate setstate_r srandom
-    srandom_r strtod strtoll strtoull unlockpt unsetenv])
+    srandom_r strtod strtold strtoll strtoull unlockpt unsetenv])
 ])
 
 AC_DEFUN([gl_STDLIB_MODULE_INDICATOR],
@@ -68,6 +68,7 @@ AC_DEFUN([gl_STDLIB_H_DEFAULTS],
   GNULIB_SECURE_GETENV=0; AC_SUBST([GNULIB_SECURE_GETENV])
   GNULIB_SETENV=0;        AC_SUBST([GNULIB_SETENV])
   GNULIB_STRTOD=0;        AC_SUBST([GNULIB_STRTOD])
+  GNULIB_STRTOLD=0;       AC_SUBST([GNULIB_STRTOLD])
   GNULIB_STRTOLL=0;       AC_SUBST([GNULIB_STRTOLL])
   GNULIB_STRTOULL=0;      AC_SUBST([GNULIB_STRTOULL])
   GNULIB_SYSTEM_POSIX=0;  AC_SUBST([GNULIB_SYSTEM_POSIX])
@@ -105,6 +106,7 @@ AC_DEFUN([gl_STDLIB_H_DEFAULTS],
   HAVE_SETSTATE=1;           AC_SUBST([HAVE_SETSTATE])
   HAVE_DECL_SETSTATE=1;      AC_SUBST([HAVE_DECL_SETSTATE])
   HAVE_STRTOD=1;             AC_SUBST([HAVE_STRTOD])
+  HAVE_STRTOLD=1;            AC_SUBST([HAVE_STRTOLD])
   HAVE_STRTOLL=1;            AC_SUBST([HAVE_STRTOLL])
   HAVE_STRTOULL=1;           AC_SUBST([HAVE_STRTOULL])
   HAVE_STRUCT_RANDOM_DATA=1; AC_SUBST([HAVE_STRUCT_RANDOM_DATA])
@@ -128,6 +130,7 @@ AC_DEFUN([gl_STDLIB_H_DEFAULTS],
   REPLACE_SETENV=0;          AC_SUBST([REPLACE_SETENV])
   REPLACE_SETSTATE=0;        AC_SUBST([REPLACE_SETSTATE])
   REPLACE_STRTOD=0;          AC_SUBST([REPLACE_STRTOD])
+  REPLACE_STRTOLD=0;         AC_SUBST([REPLACE_STRTOLD])
   REPLACE_UNSETENV=0;        AC_SUBST([REPLACE_UNSETENV])
   REPLACE_WCTOMB=0;          AC_SUBST([REPLACE_WCTOMB])
 ])
diff --git a/m4/strtold.m4 b/m4/strtold.m4
new file mode 100644
index 0000000..117b7f1
--- /dev/null
+++ b/m4/strtold.m4
@@ -0,0 +1,120 @@
+# strtold.m4 serial 1
+dnl Copyright (C) 2002-2003, 2006-2019 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.
+
+AC_DEFUN([gl_FUNC_STRTOLD],
+[
+  AC_REQUIRE([gl_STDLIB_H_DEFAULTS])
+  AC_REQUIRE([gl_LONG_DOUBLE_VS_DOUBLE])
+  AC_REQUIRE([AC_CANONICAL_HOST]) dnl for cross-compiles
+  AC_CHECK_FUNCS_ONCE([strtold])
+  if test $ac_cv_func_strtold != yes; then
+    HAVE_STRTOLD=0
+  else
+    AC_CACHE_CHECK([whether strtold obeys C99], [gl_cv_func_strtold_works],
+      [AC_RUN_IFELSE([AC_LANG_PROGRAM([[
+#include <stdlib.h>
+#include <math.h>
+#include <errno.h>
+/* Compare two numbers with ==.
+   This is a separate function because IRIX 6.5 "cc -O" miscompiles an
+   'x == x' test.  */
+static int
+numeric_equal (long double x, long double y)
+{
+  return x == y;
+}
+]], [[
+  int result = 0;
+  {
+    /* Under Solaris 2.4, strtod returns the wrong value for the
+       terminating character under some conditions.  */
+    const char *string = "NaN";
+    char *term;
+    strtod (string, &term);
+    if (term != string && *(term - 1) == 0)
+      result |= 2;
+  }
+  {
+    /* Older glibc and Cygwin mis-parse "-0x".  */
+    const char *string = "-0x";
+    char *term;
+    long double value = strtold (string, &term);
+    long double zero = 0.0L;
+    if (1.0L / value != -1.0L / zero || term != (string + 2))
+      result |= 4;
+  }
+  {
+    /* IRIX 6.5, mingw do not parse hex floats.  */
+    const char *string = "0XaP+1";
+    char *term;
+    long double value = strtold (string, &term);
+    if (value != 20.0L || term != (string + 6))
+      result |= 8;
+  }
+  {
+    /* IRIX 6.5 does not parse infinities.  HP-UX 11.31/ia64 parses inf,
+       but mistakenly sets errno.  */
+    const char *string = "inf";
+    char *term;
+    long double value;
+    errno = 0;
+    value = strtod (string, &term);
+    if (value != HUGE_VAL || term != (string + 3) || errno)
+      result |= 16;
+  }
+  {
+    /* glibc-2.3.2, IRIX 6.5, mingw, Haiku misparse "nan()".  */
+    const char *string = "nan()";
+    char *term;
+    long double value = strtold (string, &term);
+    if (numeric_equal (value, value) || term != (string + 5))
+      result |= 32;
+  }
+  {
+    /* Mac OS X 10.5, IRIX 6.5 misparse "nan(".  */
+    const char *string = "nan(";
+    char *term;
+    long double value = strtold (string, &term);
+    if (numeric_equal (value, value) || term != (string + 3))
+      result |= 64;
+  }
+  return result;
+]])],
+        [gl_cv_func_strtold_works=yes],
+        [gl_cv_func_strtold_works=no],
+        [dnl The last known bugs in glibc strtold(), as of this writing,
+         dnl were fixed in version 2.8
+         AC_EGREP_CPP([Lucky user],
+           [
+#include <features.h>
+#ifdef __GNU_LIBRARY__
+ #if ((__GLIBC__ == 2 && __GLIBC_MINOR__ >= 8) || (__GLIBC__ > 2)) \
+     && !defined __UCLIBC__
+  Lucky user
+ #endif
+#endif
+           ],
+           [gl_cv_func_strtold_works="guessing yes"],
+           [gl_cv_func_strtod_works="guessing no"])
+        ])
+      ])
+    case "$gl_cv_func_strtold_works" in
+      *yes) ;;
+      *)
+        REPLACE_STRTOLD=1
+        ;;
+    esac
+  fi
+])
+
+# Prerequisites of lib/strtold.c.
+AC_DEFUN([gl_PREREQ_STRTOLD], [
+  AC_REQUIRE([gl_CHECK_LDEXPL_NO_LIBM])
+  if test $gl_cv_func_ldexpl_no_libm = yes; then
+    AC_DEFINE([HAVE_LDEXPL_IN_LIBC], [1],
+      [Define if the ldexpl function is available in libc.])
+  fi
+])
diff --git a/modules/stdlib b/modules/stdlib
index a43f79c..ea838c9 100644
--- a/modules/stdlib
+++ b/modules/stdlib
@@ -59,6 +59,7 @@ stdlib.h: stdlib.in.h $(top_builddir)/config.status $(CXXDEFS_H) \
 	      -e 's/@''GNULIB_SECURE_GETENV''@/$(GNULIB_SECURE_GETENV)/g' \
 	      -e 's/@''GNULIB_SETENV''@/$(GNULIB_SETENV)/g' \
 	      -e 's/@''GNULIB_STRTOD''@/$(GNULIB_STRTOD)/g' \
+	      -e 's/@''GNULIB_STRTOLD''@/$(GNULIB_STRTOLD)/g' \
 	      -e 's/@''GNULIB_STRTOLL''@/$(GNULIB_STRTOLL)/g' \
 	      -e 's/@''GNULIB_STRTOULL''@/$(GNULIB_STRTOULL)/g' \
 	      -e 's/@''GNULIB_SYSTEM_POSIX''@/$(GNULIB_SYSTEM_POSIX)/g' \
@@ -95,6 +96,7 @@ stdlib.h: stdlib.in.h $(top_builddir)/config.status $(CXXDEFS_H) \
 	      -e 's|@''HAVE_SETSTATE''@|$(HAVE_SETSTATE)|g' \
 	      -e 's|@''HAVE_DECL_SETSTATE''@|$(HAVE_DECL_SETSTATE)|g' \
 	      -e 's|@''HAVE_STRTOD''@|$(HAVE_STRTOD)|g' \
+	      -e 's|@''HAVE_STRTOLD''@|$(HAVE_STRTOLD)|g' \
 	      -e 's|@''HAVE_STRTOLL''@|$(HAVE_STRTOLL)|g' \
 	      -e 's|@''HAVE_STRTOULL''@|$(HAVE_STRTOULL)|g' \
 	      -e 's|@''HAVE_STRUCT_RANDOM_DATA''@|$(HAVE_STRUCT_RANDOM_DATA)|g' \
@@ -118,6 +120,7 @@ stdlib.h: stdlib.in.h $(top_builddir)/config.status $(CXXDEFS_H) \
 	      -e 's|@''REPLACE_SETENV''@|$(REPLACE_SETENV)|g' \
 	      -e 's|@''REPLACE_SETSTATE''@|$(REPLACE_SETSTATE)|g' \
 	      -e 's|@''REPLACE_STRTOD''@|$(REPLACE_STRTOD)|g' \
+	      -e 's|@''REPLACE_STRTOLD''@|$(REPLACE_STRTOLD)|g' \
 	      -e 's|@''REPLACE_UNSETENV''@|$(REPLACE_UNSETENV)|g' \
 	      -e 's|@''REPLACE_WCTOMB''@|$(REPLACE_WCTOMB)|g' \
 	      -e '/definitions of _GL_FUNCDECL_RPL/r $(CXXDEFS_H)' \
diff --git a/modules/strtold b/modules/strtold
new file mode 100644
index 0000000..f99b9e6
--- /dev/null
+++ b/modules/strtold
@@ -0,0 +1,35 @@
+Description:
+strtold() function: convert string to 'long double'.
+
+Files:
+lib/strtold.c
+lib/strtod.c
+m4/strtold.m4
+m4/math_h.m4
+m4/ldexpl.m4
+
+Depends-on:
+stdlib
+c-ctype         [test $HAVE_STRTOLD = 0 || test $REPLACE_STRTOLD = 1]
+math            [test $HAVE_STRTOLD = 0 || test $REPLACE_STRTOLD = 1]
+stdbool         [test $HAVE_STRTOLD = 0 || test $REPLACE_STRTOLD = 1]
+strtod          [{ test $HAVE_STRTOLD = 0 || test $REPLACE_STRTOLD = 1; } && test $HAVE_SAME_LONG_DOUBLE_AS_DOUBLE = 1]
+
+configure.ac:
+gl_FUNC_STRTOLD
+if test $HAVE_STRTOLD = 0 || test $REPLACE_STRTOLD = 1; then
+  AC_LIBOBJ([strtold])
+  gl_PREREQ_STRTOLD
+fi
+gl_STDLIB_MODULE_INDICATOR([strtold])
+
+Makefile.am:
+
+Include:
+<stdlib.h>
+
+License:
+LGPL
+
+Maintainer:
+all
-- 
2.7.4


[-- Attachment #3: 0002-strtold-Add-tests.patch --]
[-- Type: text/x-patch, Size: 29385 bytes --]

From c39d926ef784d4c9de90e48a5aca16d0acd57512 Mon Sep 17 00:00:00 2001
From: Bruno Haible <bruno@clisp.org>
Date: Wed, 30 Jan 2019 03:53:43 +0100
Subject: [PATCH 2/2] strtold: Add tests.

* tests/test-strtold.c: New file, based on tests/test-strtod.c.
* modules/strtold-tests: New file.
---
 ChangeLog             |   6 +
 modules/strtold-tests |  16 +
 tests/test-strtold.c  | 986 ++++++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 1008 insertions(+)
 create mode 100644 modules/strtold-tests
 create mode 100644 tests/test-strtold.c

diff --git a/ChangeLog b/ChangeLog
index ef10452..5ab5d83 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,11 @@
 2019-01-29  Bruno Haible  <bruno@clisp.org>
 
+	strtold: Add tests.
+	* tests/test-strtold.c: New file, based on tests/test-strtod.c.
+	* modules/strtold-tests: New file.
+
+2019-01-29  Bruno Haible  <bruno@clisp.org>
+
 	strtold: New module.
 	* lib/stdlib.in.h (strtold): New declaration.
 	* lib/strtold.c: New file.
diff --git a/modules/strtold-tests b/modules/strtold-tests
new file mode 100644
index 0000000..8a812d2
--- /dev/null
+++ b/modules/strtold-tests
@@ -0,0 +1,16 @@
+Files:
+tests/test-strtold.c
+tests/signature.h
+tests/minus-zero.h
+tests/macros.h
+
+Depends-on:
+float
+isnanl-nolibm
+signbit
+
+configure.ac:
+
+Makefile.am:
+TESTS += test-strtold
+check_PROGRAMS += test-strtold
diff --git a/tests/test-strtold.c b/tests/test-strtold.c
new file mode 100644
index 0000000..cd31787
--- /dev/null
+++ b/tests/test-strtold.c
@@ -0,0 +1,986 @@
+/*
+ * Copyright (C) 2008-2019 Free Software Foundation, Inc.
+ * 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 <https://www.gnu.org/licenses/>.  */
+
+#include <config.h>
+
+#include <stdlib.h>
+
+#include "signature.h"
+SIGNATURE_CHECK (strtold, long double, (char const *, char **));
+
+#include <errno.h>
+#include <float.h>
+#include <math.h>
+#include <string.h>
+
+#include "isnanl-nolibm.h"
+#include "minus-zero.h"
+#include "macros.h"
+
+/* Avoid requiring -lm just for fabsl.  */
+#define FABSL(d) ((d) < 0.0L ? -(d) : (d))
+
+int
+main (void)
+{
+  int status = 0;
+  /* Subject sequence empty or invalid.  */
+  {
+    const char input[] = "";
+    char *ptr;
+    long double result;
+    errno = 0;
+    result = strtold (input, &ptr);
+    ASSERT (result == 0.0L);
+    ASSERT (!signbit (result));
+    ASSERT (ptr == input);
+    ASSERT (errno == 0 || errno == EINVAL);
+  }
+  {
+    const char input[] = " ";
+    char *ptr;
+    long double result;
+    errno = 0;
+    result = strtold (input, &ptr);
+    ASSERT (result == 0.0L);
+    ASSERT (!signbit (result));
+    ASSERT (ptr == input);
+    ASSERT (errno == 0 || errno == EINVAL);
+  }
+  {
+    const char input[] = " +";
+    char *ptr;
+    long double result;
+    errno = 0;
+    result = strtold (input, &ptr);
+    ASSERT (result == 0.0L);
+    ASSERT (!signbit (result));
+    ASSERT (ptr == input);
+    ASSERT (errno == 0 || errno == EINVAL);
+  }
+  {
+    const char input[] = " .";
+    char *ptr;
+    long double result;
+    errno = 0;
+    result = strtold (input, &ptr);
+    ASSERT (result == 0.0L);
+    ASSERT (!signbit (result));
+    ASSERT (ptr == input);
+    ASSERT (errno == 0 || errno == EINVAL);
+  }
+  {
+    const char input[] = " .e0";
+    char *ptr;
+    long double result;
+    errno = 0;
+    result = strtold (input, &ptr);
+    ASSERT (result == 0.0L);
+    ASSERT (!signbit (result));
+    ASSERT (ptr == input);              /* IRIX 6.5 */
+    ASSERT (errno == 0 || errno == EINVAL);
+  }
+  {
+    const char input[] = " +.e-0";
+    char *ptr;
+    long double result;
+    errno = 0;
+    result = strtold (input, &ptr);
+    ASSERT (result == 0.0L);
+    ASSERT (!signbit (result));
+    ASSERT (ptr == input);              /* IRIX 6.5 */
+    ASSERT (errno == 0 || errno == EINVAL);
+  }
+  {
+    const char input[] = " in";
+    char *ptr;
+    long double result;
+    errno = 0;
+    result = strtold (input, &ptr);
+    ASSERT (result == 0.0L);
+    ASSERT (!signbit (result));
+    ASSERT (ptr == input);
+    ASSERT (errno == 0 || errno == EINVAL);
+  }
+  {
+    const char input[] = " na";
+    char *ptr;
+    long double result;
+    errno = 0;
+    result = strtold (input, &ptr);
+    ASSERT (result == 0.0L);
+    ASSERT (!signbit (result));
+    ASSERT (ptr == input);
+    ASSERT (errno == 0 || errno == EINVAL);
+  }
+
+  /* Simple floating point values.  */
+  {
+    const char input[] = "1";
+    char *ptr;
+    long double result;
+    errno = 0;
+    result = strtold (input, &ptr);
+    ASSERT (result == 1.0L);
+    ASSERT (ptr == input + 1);
+    ASSERT (errno == 0);
+  }
+  {
+    const char input[] = "1.";
+    char *ptr;
+    long double result;
+    errno = 0;
+    result = strtold (input, &ptr);
+    ASSERT (result == 1.0L);
+    ASSERT (ptr == input + 2);
+    ASSERT (errno == 0);
+  }
+  {
+    const char input[] = ".5";
+    char *ptr;
+    long double result;
+    errno = 0;
+    result = strtold (input, &ptr);
+    /* FIXME - gnulib's version is rather inaccurate.  It would be
+       nice to guarantee an exact result, but for now, we settle for a
+       1-ulp error.  */
+    ASSERT (FABSL (result - 0.5L) < LDBL_EPSILON);
+    ASSERT (ptr == input + 2);
+    ASSERT (errno == 0);
+  }
+  {
+    const char input[] = " 1";
+    char *ptr;
+    long double result;
+    errno = 0;
+    result = strtold (input, &ptr);
+    ASSERT (result == 1.0L);
+    ASSERT (ptr == input + 2);
+    ASSERT (errno == 0);
+  }
+  {
+    const char input[] = "+1";
+    char *ptr;
+    long double result;
+    errno = 0;
+    result = strtold (input, &ptr);
+    ASSERT (result == 1.0L);
+    ASSERT (ptr == input + 2);
+    ASSERT (errno == 0);
+  }
+  {
+    const char input[] = "-1";
+    char *ptr;
+    long double result;
+    errno = 0;
+    result = strtold (input, &ptr);
+    ASSERT (result == -1.0L);
+    ASSERT (ptr == input + 2);
+    ASSERT (errno == 0);
+  }
+  {
+    const char input[] = "1e0";
+    char *ptr;
+    long double result;
+    errno = 0;
+    result = strtold (input, &ptr);
+    ASSERT (result == 1.0L);
+    ASSERT (ptr == input + 3);
+    ASSERT (errno == 0);
+  }
+  {
+    const char input[] = "1e+0";
+    char *ptr;
+    long double result;
+    errno = 0;
+    result = strtold (input, &ptr);
+    ASSERT (result == 1.0L);
+    ASSERT (ptr == input + 4);
+    ASSERT (errno == 0);
+  }
+  {
+    const char input[] = "1e-0";
+    char *ptr;
+    long double result;
+    errno = 0;
+    result = strtold (input, &ptr);
+    ASSERT (result == 1.0L);
+    ASSERT (ptr == input + 4);
+    ASSERT (errno == 0);
+  }
+  {
+    const char input[] = "1e1";
+    char *ptr;
+    long double result;
+    errno = 0;
+    result = strtold (input, &ptr);
+    ASSERT (result == 10.0L);
+    ASSERT (ptr == input + 3);
+    ASSERT (errno == 0);
+  }
+  {
+    const char input[] = "5e-1";
+    char *ptr;
+    long double result;
+    errno = 0;
+    result = strtold (input, &ptr);
+    /* FIXME - gnulib's version is rather inaccurate.  It would be
+       nice to guarantee an exact result, but for now, we settle for a
+       1-ulp error.  */
+    ASSERT (FABSL (result - 0.5L) < LDBL_EPSILON);
+    ASSERT (ptr == input + 4);
+    ASSERT (errno == 0);
+  }
+
+  /* Zero.  */
+  {
+    const char input[] = "0";
+    char *ptr;
+    long double result;
+    errno = 0;
+    result = strtold (input, &ptr);
+    ASSERT (result == 0.0L);
+    ASSERT (!signbit (result));
+    ASSERT (ptr == input + 1);
+    ASSERT (errno == 0);
+  }
+  {
+    const char input[] = ".0";
+    char *ptr;
+    long double result;
+    errno = 0;
+    result = strtold (input, &ptr);
+    ASSERT (result == 0.0L);
+    ASSERT (!signbit (result));
+    ASSERT (ptr == input + 2);
+    ASSERT (errno == 0);
+  }
+  {
+    const char input[] = "0e0";
+    char *ptr;
+    long double result;
+    errno = 0;
+    result = strtold (input, &ptr);
+    ASSERT (result == 0.0L);
+    ASSERT (!signbit (result));
+    ASSERT (ptr == input + 3);
+    ASSERT (errno == 0);
+  }
+  {
+    const char input[] = "0e+9999999";
+    char *ptr;
+    long double result;
+    errno = 0;
+    result = strtold (input, &ptr);
+    ASSERT (result == 0.0L);
+    ASSERT (!signbit (result));
+    ASSERT (ptr == input + 10);
+    ASSERT (errno == 0);
+  }
+  {
+    const char input[] = "0e-9999999";
+    char *ptr;
+    long double result;
+    errno = 0;
+    result = strtold (input, &ptr);
+    ASSERT (result == 0.0L);
+    ASSERT (!signbit (result));
+    ASSERT (ptr == input + 10);
+    ASSERT (errno == 0);
+  }
+  {
+    const char input[] = "-0";
+    char *ptr;
+    long double result;
+    errno = 0;
+    result = strtold (input, &ptr);
+    ASSERT (result == 0.0L);
+    ASSERT (!!signbit (result) == !!signbit (minus_zerol)); /* IRIX 6.5 */
+    ASSERT (ptr == input + 2);
+    ASSERT (errno == 0);
+  }
+
+  /* Suffixes.  */
+  {
+    const char input[] = "1f";
+    char *ptr;
+    long double result;
+    errno = 0;
+    result = strtold (input, &ptr);
+    ASSERT (result == 1.0L);
+    ASSERT (ptr == input + 1);
+    ASSERT (errno == 0);
+  }
+  {
+    const char input[] = "1.f";
+    char *ptr;
+    long double result;
+    errno = 0;
+    result = strtold (input, &ptr);
+    ASSERT (result == 1.0L);
+    ASSERT (ptr == input + 2);
+    ASSERT (errno == 0);
+  }
+  {
+    const char input[] = "1e";
+    char *ptr;
+    long double result;
+    errno = 0;
+    result = strtold (input, &ptr);
+    ASSERT (result == 1.0L);
+    ASSERT (ptr == input + 1);
+    ASSERT (errno == 0);
+  }
+  {
+    const char input[] = "1e+";
+    char *ptr;
+    long double result;
+    errno = 0;
+    result = strtold (input, &ptr);
+    ASSERT (result == 1.0L);
+    ASSERT (ptr == input + 1);
+    ASSERT (errno == 0);
+  }
+  {
+    const char input[] = "1e-";
+    char *ptr;
+    long double result;
+    errno = 0;
+    result = strtold (input, &ptr);
+    ASSERT (result == 1.0L);
+    ASSERT (ptr == input + 1);
+    ASSERT (errno == 0);
+  }
+  {
+    const char input[] = "1E 2";
+    char *ptr;
+    long double result;
+    errno = 0;
+    result = strtold (input, &ptr);
+    ASSERT (result == 1.0L);            /* HP-UX 11.31/ia64, IRIX 6.5 */
+    ASSERT (ptr == input + 1);          /* HP-UX 11.31/ia64, IRIX 6.5 */
+    ASSERT (errno == 0);
+  }
+  {
+    const char input[] = "0x";
+    char *ptr;
+    long double result;
+    errno = 0;
+    result = strtold (input, &ptr);
+    ASSERT (result == 0.0L);
+    ASSERT (!signbit (result));
+    ASSERT (ptr == input + 1);          /* glibc-2.3.2, Mac OS X 10.5, Haiku */
+    ASSERT (errno == 0);
+  }
+  {
+    const char input[] = "00x1";
+    char *ptr;
+    long double result;
+    errno = 0;
+    result = strtold (input, &ptr);
+    ASSERT (result == 0.0L);
+    ASSERT (!signbit (result));
+    ASSERT (ptr == input + 2);
+    ASSERT (errno == 0);
+  }
+  {
+    const char input[] = "-0x";
+    char *ptr;
+    long double result;
+    errno = 0;
+    result = strtold (input, &ptr);
+    ASSERT (result == 0.0L);
+    ASSERT (!!signbit (result) == !!signbit (minus_zerol)); /* Mac OS X 10.5, IRIX 6.5 */
+    ASSERT (ptr == input + 2);          /* glibc-2.3.2, Mac OS X 10.5, Haiku */
+    ASSERT (errno == 0);
+  }
+  {
+    const char input[] = "0xg";
+    char *ptr;
+    long double result;
+    errno = 0;
+    result = strtold (input, &ptr);
+    ASSERT (result == 0.0L);
+    ASSERT (!signbit (result));
+    ASSERT (ptr == input + 1);          /* glibc-2.3.2, Mac OS X 10.5, Haiku */
+    ASSERT (errno == 0);
+  }
+  {
+    const char input[] = "0xp";
+    char *ptr;
+    long double result;
+    errno = 0;
+    result = strtold (input, &ptr);
+    ASSERT (result == 0.0L);
+    ASSERT (!signbit (result));
+    ASSERT (ptr == input + 1);          /* glibc-2.3.2, Mac OS X 10.5, Haiku */
+    ASSERT (errno == 0);
+  }
+  {
+    const char input[] = "0XP";
+    char *ptr;
+    long double result;
+    errno = 0;
+    result = strtold (input, &ptr);
+    ASSERT (result == 0.0L);
+    ASSERT (!signbit (result));
+    ASSERT (ptr == input + 1);          /* glibc-2.3.2, Mac OS X 10.5, Haiku */
+    ASSERT (errno == 0);
+  }
+  {
+    const char input[] = "0x.";
+    char *ptr;
+    long double result;
+    errno = 0;
+    result = strtold (input, &ptr);
+    ASSERT (result == 0.0L);
+    ASSERT (!signbit (result));
+    ASSERT (ptr == input + 1);          /* glibc-2.3.2, Mac OS X 10.5, Haiku */
+    ASSERT (errno == 0);
+  }
+  {
+    const char input[] = "0xp+";
+    char *ptr;
+    long double result;
+    errno = 0;
+    result = strtold (input, &ptr);
+    ASSERT (result == 0.0L);
+    ASSERT (!signbit (result));
+    ASSERT (ptr == input + 1);          /* glibc-2.3.2, Mac OS X 10.5, Haiku */
+    ASSERT (errno == 0);
+  }
+  {
+    const char input[] = "0xp+1";
+    char *ptr;
+    long double result;
+    errno = 0;
+    result = strtold (input, &ptr);
+    ASSERT (result == 0.0L);
+    ASSERT (!signbit (result));
+    ASSERT (ptr == input + 1);          /* glibc-2.3.2, Mac OS X 10.5, Haiku */
+    ASSERT (errno == 0);
+  }
+  {
+    const char input[] = "0x.p+1";
+    char *ptr;
+    long double result;
+    errno = 0;
+    result = strtold (input, &ptr);
+    ASSERT (result == 0.0L);
+    ASSERT (!signbit (result));
+    ASSERT (ptr == input + 1);          /* glibc-2.3.2, Mac OS X 10.5, Haiku */
+    ASSERT (errno == 0);
+  }
+  {
+    const char input[] = "1p+1";
+    char *ptr;
+    long double result;
+    errno = 0;
+    result = strtold (input, &ptr);
+    ASSERT (result == 1.0L);
+    ASSERT (ptr == input + 1);
+    ASSERT (errno == 0);
+  }
+  {
+    const char input[] = "1P+1";
+    char *ptr;
+    long double result;
+    errno = 0;
+    result = strtold (input, &ptr);
+    ASSERT (result == 1.0L);
+    ASSERT (ptr == input + 1);
+    ASSERT (errno == 0);
+  }
+
+  /* Overflow/underflow.  */
+  {
+    const char input[] = "1E1000000";
+    char *ptr;
+    long double result;
+    errno = 0;
+    result = strtold (input, &ptr);
+    ASSERT (result == HUGE_VALL);
+    ASSERT (ptr == input + 9);
+    ASSERT (errno == ERANGE);
+  }
+  {
+    const char input[] = "-1E1000000";
+    char *ptr;
+    long double result;
+    errno = 0;
+    result = strtold (input, &ptr);
+    ASSERT (result == -HUGE_VALL);
+    ASSERT (ptr == input + 10);
+    ASSERT (errno == ERANGE);
+  }
+  {
+    const char input[] = "1E-100000";
+    char *ptr;
+    long double result;
+    errno = 0;
+    result = strtold (input, &ptr);
+    ASSERT (0.0L <= result && result <= LDBL_MIN);
+    ASSERT (!signbit (result));
+    ASSERT (ptr == input + 9);
+    ASSERT (errno == ERANGE);
+  }
+  {
+    const char input[] = "-1E-100000";
+    char *ptr;
+    long double result;
+    errno = 0;
+    result = strtold (input, &ptr);
+    ASSERT (-LDBL_MIN <= result && result <= 0.0L);
+#if 0
+    /* FIXME - this is glibc bug 5995; POSIX allows returning positive
+       0 on negative underflow, even though quality of implementation
+       demands preserving the sign.  Disable this test until fixed
+       glibc is more prevalent.  */
+    ASSERT (!!signbit (result) == !!signbit (minus_zerol)); /* glibc-2.3.2, Haiku */
+#endif
+    ASSERT (ptr == input + 10);
+    ASSERT (errno == ERANGE);
+  }
+  {
+    const char input[] = "1E 1000000";
+    char *ptr;
+    long double result;
+    errno = 0;
+    result = strtold (input, &ptr);
+    ASSERT (result == 1.0L);            /* HP-UX 11.31/ia64, IRIX 6.5 */
+    ASSERT (ptr == input + 1);          /* HP-UX 11.31/ia64, IRIX 6.5 */
+    ASSERT (errno == 0);                /* HP-UX 11.31/ia64, IRIX 6.5 */
+  }
+  {
+    const char input[] = "0x1P 1000000";
+    char *ptr;
+    long double result;
+    errno = 0;
+    result = strtold (input, &ptr);
+    ASSERT (result == 1.0L);            /* HP-UX 11.31/ia64, IRIX 6.5, mingw */
+    ASSERT (ptr == input + 3);          /* HP-UX 11.31/ia64, IRIX 6.5, mingw */
+    ASSERT (errno == 0);
+  }
+
+  /* Infinity.  */
+  {
+    const char input[] = "iNf";
+    char *ptr;
+    long double result;
+    errno = 0;
+    result = strtold (input, &ptr);
+    ASSERT (result == HUGE_VALL);       /* IRIX 6.5 */
+    ASSERT (ptr == input + 3);          /* IRIX 6.5 */
+    ASSERT (errno == 0);                /* HP-UX 11.31/ia64 */
+  }
+  {
+    const char input[] = "-InF";
+    char *ptr;
+    long double result;
+    errno = 0;
+    result = strtold (input, &ptr);
+    ASSERT (result == -HUGE_VALL);      /* IRIX 6.5 */
+    ASSERT (ptr == input + 4);          /* IRIX 6.5 */
+    ASSERT (errno == 0);                /* HP-UX 11.31/ia64 */
+  }
+  {
+    const char input[] = "infinite";
+    char *ptr;
+    long double result;
+    errno = 0;
+    result = strtold (input, &ptr);
+    ASSERT (result == HUGE_VALL);       /* IRIX 6.5 */
+    ASSERT (ptr == input + 3);          /* IRIX 6.5 */
+    ASSERT (errno == 0);                /* HP-UX 11.31/ia64 */
+  }
+  {
+    const char input[] = "infinitY";
+    char *ptr;
+    long double result;
+    errno = 0;
+    result = strtold (input, &ptr);
+    ASSERT (result == HUGE_VALL);       /* IRIX 6.5 */
+    ASSERT (ptr == input + 8);          /* IRIX 6.5 */
+    ASSERT (errno == 0);                /* HP-UX 11.31/ia64 */
+  }
+  {
+    const char input[] = "infinitY.";
+    char *ptr;
+    long double result;
+    errno = 0;
+    result = strtold (input, &ptr);
+    ASSERT (result == HUGE_VALL);       /* IRIX 6.5 */
+    ASSERT (ptr == input + 8);          /* IRIX 6.5 */
+    ASSERT (errno == 0);                /* HP-UX 11.31/ia64 */
+  }
+
+  /* NaN.  Some processors set the sign bit of the default NaN, so all
+     we check is that using a sign changes the result.  */
+  {
+    const char input[] = "-nan";
+    char *ptr1;
+    char *ptr2;
+    long double result1;
+    long double result2;
+    errno = 0;
+    result1 = strtold (input, &ptr1);
+    result2 = strtold (input + 1, &ptr2);
+#if 1 /* All known CPUs support NaNs.  */
+    ASSERT (isnanl (result1));          /* IRIX 6.5 */
+    ASSERT (isnanl (result2));          /* IRIX 6.5 */
+# if 0
+    /* Sign bits of NaN is a portability sticking point, not worth
+       worrying about.  */
+    ASSERT (!!signbit (result1) != !!signbit (result2));
+# endif
+    ASSERT (ptr1 == input + 4);         /* IRIX 6.5 */
+    ASSERT (ptr2 == input + 4);         /* IRIX 6.5 */
+    ASSERT (errno == 0);                /* HP-UX 11.31/ia64 */
+#else
+    ASSERT (result1 == 0.0L);
+    ASSERT (result2 == 0.0L);
+    ASSERT (!signbit (result1));
+    ASSERT (!signbit (result2));
+    ASSERT (ptr1 == input);
+    ASSERT (ptr2 == input + 1);
+    ASSERT (errno == 0 || errno == EINVAL);
+#endif
+  }
+  {
+    const char input[] = "+nan(";
+    char *ptr1;
+    char *ptr2;
+    long double result1;
+    long double result2;
+    errno = 0;
+    result1 = strtold (input, &ptr1);
+    result2 = strtold (input + 1, &ptr2);
+#if 1 /* All known CPUs support NaNs.  */
+    ASSERT (isnanl (result1));          /* IRIX 6.5 */
+    ASSERT (isnanl (result2));          /* IRIX 6.5 */
+    ASSERT (!!signbit (result1) == !!signbit (result2));
+    ASSERT (ptr1 == input + 4);         /* Mac OS X 10.5, IRIX 6.5 */
+    ASSERT (ptr2 == input + 4);         /* Mac OS X 10.5, IRIX 6.5 */
+    ASSERT (errno == 0);                /* HP-UX 11.31/ia64 */
+#else
+    ASSERT (result1 == 0.0L);
+    ASSERT (result2 == 0.0L);
+    ASSERT (!signbit (result1));
+    ASSERT (!signbit (result2));
+    ASSERT (ptr1 == input);
+    ASSERT (ptr2 == input + 1);
+    ASSERT (errno == 0 || errno == EINVAL);
+#endif
+  }
+  {
+    const char input[] = "-nan()";
+    char *ptr1;
+    char *ptr2;
+    long double result1;
+    long double result2;
+    errno = 0;
+    result1 = strtold (input, &ptr1);
+    result2 = strtold (input + 1, &ptr2);
+#if 1 /* All known CPUs support NaNs.  */
+    ASSERT (isnanl (result1));          /* IRIX 6.5 */
+    ASSERT (isnanl (result2));          /* IRIX 6.5 */
+# if 0
+    /* Sign bits of NaN is a portability sticking point, not worth
+       worrying about.  */
+    ASSERT (!!signbit (result1) != !!signbit (result2)); /* glibc-2.3.2, musl libc, OpenBSD 6.0, IRIX 6.5, mingw, Haiku */
+# endif
+    ASSERT (ptr1 == input + 6);         /* glibc-2.3.2, IRIX 6.5, mingw, Haiku */
+    ASSERT (ptr2 == input + 6);         /* glibc-2.3.2, IRIX 6.5, mingw, Haiku */
+    ASSERT (errno == 0);                /* HP-UX 11.31/ia64, Haiku */
+#else
+    ASSERT (result1 == 0.0L);
+    ASSERT (result2 == 0.0L);
+    ASSERT (!signbit (result1));
+    ASSERT (!signbit (result2));
+    ASSERT (ptr1 == input);
+    ASSERT (ptr2 == input + 1);
+    ASSERT (errno == 0 || errno == EINVAL);
+#endif
+  }
+  {
+    const char input[] = " nan().";
+    char *ptr;
+    long double result;
+    errno = 0;
+    result = strtold (input, &ptr);
+#if 1 /* All known CPUs support NaNs.  */
+    ASSERT (isnanl (result));           /* IRIX 6.5 */
+    ASSERT (ptr == input + 6);          /* glibc-2.3.2, IRIX 6.5, mingw, Haiku */
+    ASSERT (errno == 0);                /* HP-UX 11.31/ia64, Haiku */
+#else
+    ASSERT (result == 0.0L);
+    ASSERT (!signbit (result));
+    ASSERT (ptr == input);
+    ASSERT (errno == 0 || errno == EINVAL);
+#endif
+  }
+  {
+    /* 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;
+    long double result1;
+    long double result2;
+    errno = 0;
+    result1 = strtold (input, &ptr1);
+    result2 = strtold (input + 1, &ptr2);
+#if 1 /* All known CPUs support NaNs.  */
+    ASSERT (isnanl (result1));          /* IRIX 6.5 */
+    ASSERT (isnanl (result2));          /* IRIX 6.5 */
+# if 0
+    /* Sign bits of NaN is a portability sticking point, not worth
+       worrying about.  */
+    ASSERT (!!signbit (result1) != !!signbit (result2));
+# endif
+    ASSERT (ptr1 == input + 7);         /* glibc-2.3.2, IRIX 6.5, mingw, Haiku */
+    ASSERT (ptr2 == input + 7);         /* glibc-2.3.2, IRIX 6.5, mingw, Haiku */
+    ASSERT (errno == 0);                /* HP-UX 11.31/ia64 */
+#else
+    ASSERT (result1 == 0.0L);
+    ASSERT (result2 == 0.0L);
+    ASSERT (!signbit (result1));
+    ASSERT (!signbit (result2));
+    ASSERT (ptr1 == input);
+    ASSERT (ptr2 == input + 1);
+    ASSERT (errno == 0 || errno == EINVAL);
+#endif
+  }
+
+  /* Hex.  */
+  {
+    const char input[] = "0xa";
+    char *ptr;
+    long double result;
+    errno = 0;
+    result = strtold (input, &ptr);
+    ASSERT (result == 10.0L);           /* IRIX 6.5, mingw */
+    ASSERT (ptr == input + 3);          /* IRIX 6.5, mingw */
+    ASSERT (errno == 0);
+  }
+  {
+    const char input[] = "0XA";
+    char *ptr;
+    long double result;
+    errno = 0;
+    result = strtold (input, &ptr);
+    ASSERT (result == 10.0L);           /* IRIX 6.5, mingw */
+    ASSERT (ptr == input + 3);          /* IRIX 6.5, mingw */
+    ASSERT (errno == 0);
+  }
+  {
+    const char input[] = "0x1p";
+    char *ptr;
+    long double result;
+    errno = 0;
+    result = strtold (input, &ptr);
+    ASSERT (result == 1.0L);            /* IRIX 6.5, mingw */
+    ASSERT (ptr == input + 3);          /* IRIX 6.5, mingw */
+    ASSERT (errno == 0);
+  }
+  {
+    const char input[] = "0x1p+";
+    char *ptr;
+    long double result;
+    errno = 0;
+    result = strtold (input, &ptr);
+    ASSERT (result == 1.0L);            /* IRIX 6.5, mingw */
+    ASSERT (ptr == input + 3);          /* IRIX 6.5, mingw */
+    ASSERT (errno == 0);
+  }
+  {
+    const char input[] = "0x1P+";
+    char *ptr;
+    long double result;
+    errno = 0;
+    result = strtold (input, &ptr);
+    ASSERT (result == 1.0L);            /* IRIX 6.5, mingw */
+    ASSERT (ptr == input + 3);          /* IRIX 6.5, mingw */
+    ASSERT (errno == 0);
+  }
+  {
+    const char input[] = "0x1p+1";
+    char *ptr;
+    long double result;
+    errno = 0;
+    result = strtold (input, &ptr);
+    ASSERT (result == 2.0L);            /* IRIX 6.5, mingw */
+    ASSERT (ptr == input + 6);          /* IRIX 6.5, mingw */
+    ASSERT (errno == 0);
+  }
+  {
+    const char input[] = "0X1P+1";
+    char *ptr;
+    long double result;
+    errno = 0;
+    result = strtold (input, &ptr);
+    ASSERT (result == 2.0L);            /* IRIX 6.5, mingw */
+    ASSERT (ptr == input + 6);          /* IRIX 6.5, mingw */
+    ASSERT (errno == 0);
+  }
+  {
+    const char input[] = "0x1p+1a";
+    char *ptr;
+    long double result;
+    errno = 0;
+    result = strtold (input, &ptr);
+    ASSERT (result == 2.0L);            /* IRIX 6.5, mingw */
+    ASSERT (ptr == input + 6);          /* IRIX 6.5, mingw */
+    ASSERT (errno == 0);
+  }
+  {
+    const char input[] = "0x1p 2";
+    char *ptr;
+    long double result;
+    errno = 0;
+    result = strtold (input, &ptr);
+    ASSERT (result == 1.0L);            /* HP-UX 11.31/ia64, IRIX 6.5, mingw */
+    ASSERT (ptr == input + 3);          /* HP-UX 11.31/ia64, IRIX 6.5, mingw */
+    ASSERT (errno == 0);
+  }
+
+  /* Large buffers.  */
+  {
+    size_t m = 1000000;
+    char *input = malloc (m + 1);
+    if (input)
+      {
+        char *ptr;
+        long double result;
+        memset (input, '\t', m - 1);
+        input[m - 1] = '1';
+        input[m] = '\0';
+        errno = 0;
+        result = strtold (input, &ptr);
+        ASSERT (result == 1.0L);
+        ASSERT (ptr == input + m);
+        ASSERT (errno == 0);
+      }
+    free (input);
+  }
+  {
+    size_t m = 1000000;
+    char *input = malloc (m + 1);
+    if (input)
+      {
+        char *ptr;
+        long double result;
+        memset (input, '0', m - 1);
+        input[m - 1] = '1';
+        input[m] = '\0';
+        errno = 0;
+        result = strtold (input, &ptr);
+        ASSERT (result == 1.0L);
+        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.  */
+  {
+    size_t m = 1000000;
+    char *input = malloc (m + 1);
+    if (input)
+      {
+        char *ptr;
+        long 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';
+        errno = 0;
+        result = strtold (input, &ptr);
+        ASSERT (result == 1.0L);        /* Mac OS X 10.5, FreeBSD 11.0, NetBSD 7.1, OpenBSD 6.0, Minix 3.3, IRIX 6.5, mingw */
+        ASSERT (ptr == input + m);
+        ASSERT (errno == 0);            /* Mac OS X 10.5, FreeBSD 11.0, NetBSD 7.1, OpenBSD 6.0, Minix 3.3, IRIX 6.5, mingw */
+      }
+    free (input);
+  }
+  {
+    size_t m = 1000000;
+    char *input = malloc (m + 1);
+    if (input)
+      {
+        char *ptr;
+        long 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';
+        errno = 0;
+        result = strtold (input, &ptr);
+        ASSERT (result == 1.0L);        /* Mac OS X 10.5, FreeBSD 11.0, NetBSD 7.1, OpenBSD 6.0, Minix 3.3, IRIX 6.5, mingw */
+        ASSERT (ptr == input + m);
+        ASSERT (errno == 0);            /* Mac OS X 10.5, FreeBSD 11.0, NetBSD 7.1, OpenBSD 6.0, Minix 3.3, IRIX 6.5, mingw */
+      }
+    free (input);
+  }
+#endif
+  {
+    size_t m = 1000000;
+    char *input = malloc (m + 1);
+    if (input)
+      {
+        char *ptr;
+        long double result;
+        input[0] = '-';
+        input[1] = '0';
+        input[2] = 'e';
+        input[3] = '1';
+        memset (input + 4, '0', m - 3);
+        input[m] = '\0';
+        errno = 0;
+        result = strtold (input, &ptr);
+        ASSERT (result == 0.0L);
+        ASSERT (!!signbit (result) == !!signbit (minus_zerol)); /* IRIX 6.5 */
+        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 LDBL_MIN - .5 ULP not causing an
+     infinite loop?  */
+
+  return status;
+}
-- 
2.7.4


^ permalink raw reply related	[flat|nested] 6+ messages in thread

* Re: new module 'strtold'
  2019-01-30  3:05 new module 'strtold' Bruno Haible
@ 2019-01-30  5:42 ` Pádraig Brady
  2019-02-01  3:34   ` Bruno Haible
  2019-02-01  2:16 ` strtod, strtold: various fixes Bruno Haible
  1 sibling, 1 reply; 6+ messages in thread
From: Pádraig Brady @ 2019-01-30  5:42 UTC (permalink / raw)
  To: Bruno Haible, bug-gnulib

On 29/01/19 19:05, Bruno Haible wrote:
> Hi,
> 
> coreutils has recently started to use strtold() [1]. This causes compilation
> errors on Android 4.3:
> 
>   CC       lib/cl-strtod.o
>   CC       lib/cl-strtold.o
> In file included from ../lib/cl-strtold.c:2:
> ../lib/cl-strtod.c: In function 'cl_strtold':
> ../lib/cl-strtod.c:62: warning: implicit declaration of function 'strtold'

Oh wow, thanks for doing all that.
We previously guarded uses of strtold in coreutils with:
https://git.sv.gnu.org/gitweb/?p=coreutils.git;a=commitdiff;h=v8.5-8-g8b5087d
which did:

  #if HAVE_C99_STRTOLD /* provided by c-strtold module.  */
  # define STRTOD strtold
  #else
  # define STRTOD strtod
  #endif
  long double a = STRTOD (sa, &ea);

We should have guarded the new code similarly.
With this new module though we've more consistent functionality
across all platforms.

thanks!
Pádraig


^ permalink raw reply	[flat|nested] 6+ messages in thread

* strtod, strtold: various fixes
  2019-01-30  3:05 new module 'strtold' Bruno Haible
  2019-01-30  5:42 ` Pádraig Brady
@ 2019-02-01  2:16 ` Bruno Haible
  1 sibling, 0 replies; 6+ messages in thread
From: Bruno Haible @ 2019-02-01  2:16 UTC (permalink / raw)
  To: bug-gnulib

[-- Attachment #1: Type: text/plain, Size: 1883 bytes --]

I've pushed these fixes and improvements for the strtod, strtold modules.


2019-01-31  Bruno Haible  <bruno@clisp.org>

	strtod, strtold: Work around HP-UX 11.31/ia64 bug.
	* lib/strtod.c (STRTOD): When there is an extra character after the
	exponent marker 'p', reparse the number.
	* doc/posix-functions/strtod.texi: Document the HP-UX 11.31 bug.
	* doc/posix-functions/strtold.texi: Likewise.

2019-01-31  Bruno Haible  <bruno@clisp.org>

	strtod, strtold: Avoid unnecessary rounding errors.
	* lib/strtod.c (parse_number): Drop trailing zeroes before doing the
	decimal to DOUBLE conversion.

2019-01-31  Bruno Haible  <bruno@clisp.org>

	strtod, strtold tests: Simplify tests.
	* tests/test-strtod.c (main): Assume no rounding errors for 0.5.
	* tests/test-strtold.c (main): Likewise.

2019-01-31  Bruno Haible  <bruno@clisp.org>

	strtod, strtold: Use the locale's decimal point.
	* lib/strtod.c: Include <locale.h>, <stdio.h>, <langinfo.h>.
	(decimal_point_char): New function, copied from lib/vasnprintf.c.
	(parse_number): Add a radixchar argument. Use it instead of '.'.
	(STRTOD): Invoke decimal_point_char and pass the result to parse_number.
	* m4/strtod.m4 (gl_PREREQ_STRTOD): Test whether nl_langinfo exists.
	* m4/strtold.m4 (gl_PREREQ_STRTOLD): Likewise.
	* tests/test-strtod1.c: New file.
	* tests/test-strtod1.sh: New file.
	* modules/strtod-tests (Files): Add test-strtod1.{sh,c}. Add
	locale-fr.m4 and its dependencies.
	(configure.ac): Invoke gt_LOCALE_FR, gt_LOCALE_FR_UTF8.
	(Makefile.am): Arrange to compile test-strtod1.c and run
	test-strtod1.sh.
	* tests/test-strtold1.c: New file.
	* tests/test-strtold1.sh: New file.
	* modules/strtold-tests (Files): Add test-strtold1.{sh,c}. Add
	locale-fr.m4 and its dependencies.
	(configure.ac): Invoke gt_LOCALE_FR, gt_LOCALE_FR_UTF8.
	(Makefile.am): Arrange to compile test-strtold1.c and run
	test-strtold1.sh.

[-- Attachment #2: 0001-strtod-strtold-Work-around-HP-UX-11.31-ia64-bug.patch --]
[-- Type: text/x-patch, Size: 3401 bytes --]

From a908d89696b20446e276fd6da143262f959fae9a Mon Sep 17 00:00:00 2001
From: Bruno Haible <bruno@clisp.org>
Date: Fri, 1 Feb 2019 00:18:57 +0100
Subject: [PATCH 1/4] strtod, strtold: Work around HP-UX 11.31/ia64 bug.

* lib/strtod.c (STRTOD): When there is an extra character after the
exponent marker 'p', reparse the number.
* doc/posix-functions/strtod.texi: Document the HP-UX 11.31 bug.
* doc/posix-functions/strtold.texi: Likewise.
---
 ChangeLog                        |  8 ++++++++
 doc/posix-functions/strtod.texi  |  5 +++++
 doc/posix-functions/strtold.texi |  5 +++++
 lib/strtod.c                     | 20 +++++++++++++++++++-
 4 files changed, 37 insertions(+), 1 deletion(-)

diff --git a/ChangeLog b/ChangeLog
index 5ab5d83..991db2b 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,11 @@
+2019-01-31  Bruno Haible  <bruno@clisp.org>
+
+	strtod, strtold: Work around HP-UX 11.31/ia64 bug.
+	* lib/strtod.c (STRTOD): When there is an extra character after the
+	exponent marker 'p', reparse the number.
+	* doc/posix-functions/strtod.texi: Document the HP-UX 11.31 bug.
+	* doc/posix-functions/strtold.texi: Likewise.
+
 2019-01-29  Bruno Haible  <bruno@clisp.org>
 
 	strtold: Add tests.
diff --git a/doc/posix-functions/strtod.texi b/doc/posix-functions/strtod.texi
index 72ce8bc..ac9ed21 100644
--- a/doc/posix-functions/strtod.texi
+++ b/doc/posix-functions/strtod.texi
@@ -59,6 +59,11 @@ NetBSD 5.0, OpenBSD 4.0, AIX 5.1, HP-UX 11.11, IRIX 6.5, OSF/1 5.1,
 Solaris 11.4, mingw, MSVC 14.
 
 @item
+In hexadecimal floats, this function allows whitespace between @samp{p}
+and the exponent on some platforms:
+HP-UX 11.31/ia64.
+
+@item
 This function returns the wrong end pointer for @samp{0x1p} on some
 platforms:
 AIX 7.1.
diff --git a/doc/posix-functions/strtold.texi b/doc/posix-functions/strtold.texi
index f136c54..028915e 100644
--- a/doc/posix-functions/strtold.texi
+++ b/doc/posix-functions/strtold.texi
@@ -57,6 +57,11 @@ glibc-2.3.2, mingw, Haiku.
 This function fails to parse C99 hexadecimal floating point on some
 platforms:
 IRIX 6.5, mingw.
+
+@item
+In hexadecimal floats, this function allows whitespace between @samp{p}
+and the exponent on some platforms:
+HP-UX 11.31/ia64.
 @end itemize
 
 Portability problems not fixed by Gnulib:
diff --git a/lib/strtod.c b/lib/strtod.c
index 1d1ba27..2733ff9 100644
--- a/lib/strtod.c
+++ b/lib/strtod.c
@@ -297,7 +297,25 @@ STRTOD (const char *nptr, char **endptr)
               while (p < end && c_tolower (*p) != 'p')
                 p++;
               if (p < end && ! c_isdigit (p[1 + (p[1] == '-' || p[1] == '+')]))
-                end = p;
+                {
+                  char *dup = strdup (s);
+                  errno = saved_errno;
+                  if (!dup)
+                    {
+                      /* Not really our day, is it.  Rounding errors are
+                         better than outright failure.  */
+                      num = parse_number (s + 2, 16, 2, 4, 'p', &endbuf);
+                    }
+                  else
+                    {
+                      dup[p - s] = '\0';
+                      num = STRTOD (dup, &endbuf);
+                      saved_errno = errno;
+                      free (dup);
+                      errno = saved_errno;
+                    }
+                  end = p;
+                }
             }
         }
       else
-- 
2.7.4


[-- Attachment #3: 0002-strtod-strtold-Avoid-unnecessary-rounding-errors.patch --]
[-- Type: text/x-patch, Size: 6026 bytes --]

From 376b353f9dd93e843e9cf264ec192783a23ab21c Mon Sep 17 00:00:00 2001
From: Bruno Haible <bruno@clisp.org>
Date: Fri, 1 Feb 2019 01:43:41 +0100
Subject: [PATCH 2/4] strtod, strtold: Avoid unnecessary rounding errors.

* lib/strtod.c (parse_number): Drop trailing zeroes before doing the
decimal to DOUBLE conversion.
---
 ChangeLog    |   6 ++++
 lib/strtod.c | 111 +++++++++++++++++++++++++++++++++++++++++------------------
 2 files changed, 84 insertions(+), 33 deletions(-)

diff --git a/ChangeLog b/ChangeLog
index 991db2b..d4cf812 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,11 @@
 2019-01-31  Bruno Haible  <bruno@clisp.org>
 
+	strtod, strtold: Avoid unnecessary rounding errors.
+	* lib/strtod.c (parse_number): Drop trailing zeroes before doing the
+	decimal to DOUBLE conversion.
+
+2019-01-31  Bruno Haible  <bruno@clisp.org>
+
 	strtod, strtold: Work around HP-UX 11.31/ia64 bug.
 	* lib/strtod.c (STRTOD): When there is an extra character after the
 	exponent marker 'p', reparse the number.
diff --git a/lib/strtod.c b/lib/strtod.c
index 2733ff9..c6ce580 100644
--- a/lib/strtod.c
+++ b/lib/strtod.c
@@ -143,63 +143,108 @@ scale_radix_exp (DOUBLE x, int radix, long int exponent)
    except there are no leading spaces or signs or "0x", and ENDPTR is
    nonnull.  The number uses a base BASE (either 10 or 16) fraction, a
    radix RADIX (either 10 or 2) exponent, and exponent character
-   EXPCHAR.  To convert from a number of digits to a radix exponent,
-   multiply by RADIX_MULTIPLIER (either 1 or 4).  */
+   EXPCHAR.  BASE is RADIX**RADIX_MULTIPLIER.  */
 static DOUBLE
 parse_number (const char *nptr,
               int base, int radix, int radix_multiplier, char expchar,
               char **endptr)
 {
   const char *s = nptr;
-  bool got_dot = false;
-  long int exponent = 0;
-  DOUBLE num = 0;
+  const char *digits_start;
+  const char *digits_end;
+  const char *radixchar_ptr;
+  long int exponent;
+  DOUBLE num;
 
+  /* First, determine the start and end of the digit sequence.  */
+  digits_start = s;
+  radixchar_ptr = NULL;
   for (;; ++s)
     {
-      int digit;
-      if (c_isdigit (*s))
-        digit = *s - '0';
-      else if (base == 16 && c_isxdigit (*s))
-        digit = c_tolower (*s) - ('a' - 10);
-      else if (! got_dot && *s == '.')
+      if (base == 16 ? c_isxdigit (*s) : c_isdigit (*s))
+        ;
+      else if (radixchar_ptr == NULL && *s == '.')
         {
           /* Record that we have found the decimal point.  */
-          got_dot = true;
-          continue;
+          radixchar_ptr = s;
         }
       else
-        /* Any other character terminates the number.  */
+        /* Any other character terminates the digit sequence.  */
         break;
+    }
+  digits_end = s;
+  /* Now radixchar_ptr == NULL or
+     digits_start <= radixchar_ptr < digits_end.  */
+
+  if (false)
+    { /* Unoptimized.  */
+      exponent =
+        (radixchar_ptr != NULL
+         ? - (long int) (digits_end - radixchar_ptr - 1)
+         : 0);
+    }
+  else
+    { /* Remove trailing zero digits.  This reduces rounding errors for
+         inputs such as 1.0000000000 or 10000000000e-10.  */
+      while (digits_end > digits_start)
+        {
+          if (digits_end - 1 == radixchar_ptr || *(digits_end - 1) == '0')
+            digits_end--;
+          else
+            break;
+        }
+      exponent =
+        (radixchar_ptr != NULL
+         ? (digits_end > radixchar_ptr
+            ? - (long int) (digits_end - radixchar_ptr - 1)
+            : (long int) (radixchar_ptr - digits_end))
+         : (long int) (s - digits_end));
+    }
 
-      /* Make sure that multiplication by base will not overflow.  */
-      if (num <= MAX / base)
-        num = num * base + digit;
-      else
+  /* Then, convert the digit sequence to a number.  */
+  {
+    const char *dp;
+    num = 0;
+    for (dp = digits_start; dp < digits_end; dp++)
+      if (dp != radixchar_ptr)
         {
-          /* The value of the digit doesn't matter, since we have already
-             gotten as many digits as can be represented in a 'DOUBLE'.
-             This doesn't necessarily mean the result will overflow.
-             The exponent may reduce it to within range.
-
-             We just need to record that there was another
-             digit so that we can multiply by 10 later.  */
-          exponent += radix_multiplier;
+          int digit;
+
+          /* Make sure that multiplication by BASE will not overflow.  */
+          if (!(num <= MAX / base))
+            {
+              /* The value of the digit and all subsequent digits don't matter,
+                 since we have already gotten as many digits as can be
+                 represented in a 'DOUBLE'.  This doesn't necessarily mean that
+                 the result will overflow: The exponent may reduce it to within
+                 range.  */
+              exponent +=
+                (digits_end - dp)
+                - (radixchar_ptr >= dp && radixchar_ptr < digits_end ? 1 : 0);
+              break;
+            }
+
+          /* Eat the next digit.  */
+          if (c_isdigit (*dp))
+            digit = *dp - '0';
+          else if (base == 16 && c_isxdigit (*dp))
+            digit = c_tolower (*dp) - ('a' - 10);
+          else
+            abort ();
+          num = num * base + digit;
         }
+  }
 
-      /* Keep track of the number of digits after the decimal point.
-         If we just divided by base here, we might lose precision.  */
-      if (got_dot)
-        exponent -= radix_multiplier;
-    }
+  exponent = exponent * radix_multiplier;
 
+  /* Finally, parse the exponent.  */
   if (c_tolower (*s) == expchar && ! locale_isspace (s[1]))
     {
       /* Add any given exponent to the implicit one.  */
-      int save = errno;
+      int saved_errno = errno;
       char *end;
       long int value = strtol (s + 1, &end, 10);
-      errno = save;
+      errno = saved_errno;
 
       if (s + 1 != end)
         {
-- 
2.7.4


[-- Attachment #4: 0003-strtod-strtold-tests-Simplify-tests.patch --]
[-- Type: text/x-patch, Size: 2950 bytes --]

From cd825c6d50babc1f4fbaccfee3fff1785fe23cc3 Mon Sep 17 00:00:00 2001
From: Bruno Haible <bruno@clisp.org>
Date: Fri, 1 Feb 2019 02:51:20 +0100
Subject: [PATCH 3/4] strtod, strtold tests: Simplify tests.

* tests/test-strtod.c (main): Assume no rounding errors for 0.5.
* tests/test-strtold.c (main): Likewise.
---
 ChangeLog            |  6 ++++++
 tests/test-strtod.c  | 10 ++--------
 tests/test-strtold.c | 10 ++--------
 3 files changed, 10 insertions(+), 16 deletions(-)

diff --git a/ChangeLog b/ChangeLog
index d4cf812..37c9202 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,11 @@
 2019-01-31  Bruno Haible  <bruno@clisp.org>
 
+	strtod, strtold tests: Simplify tests.
+	* tests/test-strtod.c (main): Assume no rounding errors for 0.5.
+	* tests/test-strtold.c (main): Likewise.
+
+2019-01-31  Bruno Haible  <bruno@clisp.org>
+
 	strtod, strtold: Avoid unnecessary rounding errors.
 	* lib/strtod.c (parse_number): Drop trailing zeroes before doing the
 	decimal to DOUBLE conversion.
diff --git a/tests/test-strtod.c b/tests/test-strtod.c
index c2297b5..69eb974 100644
--- a/tests/test-strtod.c
+++ b/tests/test-strtod.c
@@ -155,10 +155,7 @@ main (void)
     double result;
     errno = 0;
     result = strtod (input, &ptr);
-    /* FIXME - gnulib's version is rather inaccurate.  It would be
-       nice to guarantee an exact result, but for now, we settle for a
-       1-ulp error.  */
-    ASSERT (FABS (result - 0.5) < DBL_EPSILON);
+    ASSERT (result == 0.5);
     ASSERT (ptr == input + 2);
     ASSERT (errno == 0);
   }
@@ -238,10 +235,7 @@ main (void)
     double result;
     errno = 0;
     result = strtod (input, &ptr);
-    /* FIXME - gnulib's version is rather inaccurate.  It would be
-       nice to guarantee an exact result, but for now, we settle for a
-       1-ulp error.  */
-    ASSERT (FABS (result - 0.5) < DBL_EPSILON);
+    ASSERT (result == 0.5);
     ASSERT (ptr == input + 4);
     ASSERT (errno == 0);
   }
diff --git a/tests/test-strtold.c b/tests/test-strtold.c
index cd31787..9815e7a 100644
--- a/tests/test-strtold.c
+++ b/tests/test-strtold.c
@@ -155,10 +155,7 @@ main (void)
     long double result;
     errno = 0;
     result = strtold (input, &ptr);
-    /* FIXME - gnulib's version is rather inaccurate.  It would be
-       nice to guarantee an exact result, but for now, we settle for a
-       1-ulp error.  */
-    ASSERT (FABSL (result - 0.5L) < LDBL_EPSILON);
+    ASSERT (result == 0.5L);
     ASSERT (ptr == input + 2);
     ASSERT (errno == 0);
   }
@@ -238,10 +235,7 @@ main (void)
     long double result;
     errno = 0;
     result = strtold (input, &ptr);
-    /* FIXME - gnulib's version is rather inaccurate.  It would be
-       nice to guarantee an exact result, but for now, we settle for a
-       1-ulp error.  */
-    ASSERT (FABSL (result - 0.5L) < LDBL_EPSILON);
+    ASSERT (result == 0.5L);
     ASSERT (ptr == input + 4);
     ASSERT (errno == 0);
   }
-- 
2.7.4


[-- Attachment #5: 0004-strtod-strtold-Use-the-locale-s-decimal-point.patch --]
[-- Type: text/x-patch, Size: 17585 bytes --]

From 97e23d40a659c85048b59852e1871823f727aeb2 Mon Sep 17 00:00:00 2001
From: Bruno Haible <bruno@clisp.org>
Date: Fri, 1 Feb 2019 03:12:28 +0100
Subject: [PATCH 4/4] strtod, strtold: Use the locale's decimal point.

* lib/strtod.c: Include <locale.h>, <stdio.h>, <langinfo.h>.
(decimal_point_char): New function, copied from lib/vasnprintf.c.
(parse_number): Add a radixchar argument. Use it instead of '.'.
(STRTOD): Invoke decimal_point_char and pass the result to parse_number.
* m4/strtod.m4 (gl_PREREQ_STRTOD): Test whether nl_langinfo exists.
* m4/strtold.m4 (gl_PREREQ_STRTOLD): Likewise.
* tests/test-strtod1.c: New file.
* tests/test-strtod1.sh: New file.
* modules/strtod-tests (Files): Add test-strtod1.{sh,c}. Add
locale-fr.m4 and its dependencies.
(configure.ac): Invoke gt_LOCALE_FR, gt_LOCALE_FR_UTF8.
(Makefile.am): Arrange to compile test-strtod1.c and run
test-strtod1.sh.
* tests/test-strtold1.c: New file.
* tests/test-strtold1.sh: New file.
* modules/strtold-tests (Files): Add test-strtold1.{sh,c}. Add
locale-fr.m4 and its dependencies.
(configure.ac): Invoke gt_LOCALE_FR, gt_LOCALE_FR_UTF8.
(Makefile.am): Arrange to compile test-strtold1.c and run
test-strtold1.sh.
---
 ChangeLog              | 24 +++++++++++++
 lib/strtod.c           | 59 +++++++++++++++++++++++-------
 m4/strtod.m4           |  3 +-
 m4/strtold.m4          |  3 +-
 modules/strtod-tests   | 12 +++++++
 modules/strtold-tests  | 12 +++++++
 tests/test-strtod1.c   | 97 ++++++++++++++++++++++++++++++++++++++++++++++++++
 tests/test-strtod1.sh  | 23 ++++++++++++
 tests/test-strtold1.c  | 97 ++++++++++++++++++++++++++++++++++++++++++++++++++
 tests/test-strtold1.sh | 23 ++++++++++++
 10 files changed, 338 insertions(+), 15 deletions(-)
 create mode 100644 tests/test-strtod1.c
 create mode 100755 tests/test-strtod1.sh
 create mode 100644 tests/test-strtold1.c
 create mode 100755 tests/test-strtold1.sh

diff --git a/ChangeLog b/ChangeLog
index 37c9202..3dbab7a 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,29 @@
 2019-01-31  Bruno Haible  <bruno@clisp.org>
 
+	strtod, strtold: Use the locale's decimal point.
+	* lib/strtod.c: Include <locale.h>, <stdio.h>, <langinfo.h>.
+	(decimal_point_char): New function, copied from lib/vasnprintf.c.
+	(parse_number): Add a radixchar argument. Use it instead of '.'.
+	(STRTOD): Invoke decimal_point_char and pass the result to parse_number.
+	* m4/strtod.m4 (gl_PREREQ_STRTOD): Test whether nl_langinfo exists.
+	* m4/strtold.m4 (gl_PREREQ_STRTOLD): Likewise.
+	* tests/test-strtod1.c: New file.
+	* tests/test-strtod1.sh: New file.
+	* modules/strtod-tests (Files): Add test-strtod1.{sh,c}. Add
+	locale-fr.m4 and its dependencies.
+	(configure.ac): Invoke gt_LOCALE_FR, gt_LOCALE_FR_UTF8.
+	(Makefile.am): Arrange to compile test-strtod1.c and run
+	test-strtod1.sh.
+	* tests/test-strtold1.c: New file.
+	* tests/test-strtold1.sh: New file.
+	* modules/strtold-tests (Files): Add test-strtold1.{sh,c}. Add
+	locale-fr.m4 and its dependencies.
+	(configure.ac): Invoke gt_LOCALE_FR, gt_LOCALE_FR_UTF8.
+	(Makefile.am): Arrange to compile test-strtold1.c and run
+	test-strtold1.sh.
+
+2019-01-31  Bruno Haible  <bruno@clisp.org>
+
 	strtod, strtold tests: Simplify tests.
 	* tests/test-strtod.c (main): Assume no rounding errors for 0.5.
 	* tests/test-strtold.c (main): Likewise.
diff --git a/lib/strtod.c b/lib/strtod.c
index c6ce580..b9eaa51 100644
--- a/lib/strtod.c
+++ b/lib/strtod.c
@@ -21,13 +21,18 @@
 /* Specification.  */
 #include <stdlib.h>
 
-#include <ctype.h>
+#include <ctype.h>      /* isspace() */
 #include <errno.h>
-#include <float.h>
-#include <limits.h>
-#include <math.h>
+#include <float.h>      /* {DBL,LDBL}_{MIN,MAX} */
+#include <limits.h>     /* LONG_{MIN,MAX} */
+#include <locale.h>     /* localeconv() */
+#include <math.h>       /* NAN */
 #include <stdbool.h>
-#include <string.h>
+#include <stdio.h>      /* sprintf() */
+#include <string.h>     /* strdup() */
+#if HAVE_NL_LANGINFO
+# include <langinfo.h>
+#endif
 
 #include "c-ctype.h"
 
@@ -72,6 +77,28 @@ locale_isspace (char c)
   return isspace (uc) != 0;
 }
 
+/* Determine the decimal-point character according to the current locale.  */
+static char
+decimal_point_char (void)
+{
+  const char *point;
+  /* Determine it in a multithread-safe way.  We know nl_langinfo is
+     multithread-safe on glibc systems and Mac OS X systems, but is not required
+     to be multithread-safe by POSIX.  sprintf(), however, is multithread-safe.
+     localeconv() is rarely multithread-safe.  */
+#if HAVE_NL_LANGINFO && (__GLIBC__ || defined __UCLIBC__ || (defined __APPLE__ && defined __MACH__))
+  point = nl_langinfo (RADIXCHAR);
+#elif 1
+  char pointbuf[5];
+  sprintf (pointbuf, "%#.0f", 1.0);
+  point = &pointbuf[1];
+#else
+  point = localeconv () -> decimal_point;
+#endif
+  /* The decimal point is always a single byte: either '.' or ','.  */
+  return (point[0] != '\0' ? point[0] : '.');
+}
+
 #if !USE_LDEXP
  #undef LDEXP
  #define LDEXP dummy_ldexp
@@ -146,7 +173,8 @@ scale_radix_exp (DOUBLE x, int radix, long int exponent)
    EXPCHAR.  BASE is RADIX**RADIX_MULTIPLIER.  */
 static DOUBLE
 parse_number (const char *nptr,
-              int base, int radix, int radix_multiplier, char expchar,
+              int base, int radix, int radix_multiplier, char radixchar,
+              char expchar,
               char **endptr)
 {
   const char *s = nptr;
@@ -163,7 +191,7 @@ parse_number (const char *nptr,
     {
       if (base == 16 ? c_isxdigit (*s) : c_isdigit (*s))
         ;
-      else if (radixchar_ptr == NULL && *s == '.')
+      else if (radixchar_ptr == NULL && *s == radixchar)
         {
           /* Record that we have found the decimal point.  */
           radixchar_ptr = s;
@@ -289,11 +317,13 @@ STRTOD (const char *nptr, char **endptr)
 # endif
 #else
 # undef STRTOD
-# define STRTOD(NPTR,ENDPTR) parse_number (NPTR, 10, 10, 1, 'e', ENDPTR)
+# define STRTOD(NPTR,ENDPTR) \
+   parse_number (NPTR, 10, 10, 1, radixchar, 'e', ENDPTR)
 #endif
 /* From here on, STRTOD refers to the underlying implementation.  It needs
    to handle only finite unsigned decimal numbers with non-null ENDPTR.  */
 {
+  char radixchar;
   bool negative = false;
 
   /* The number so far.  */
@@ -304,6 +334,8 @@ STRTOD (const char *nptr, char **endptr)
   char *endbuf;
   int saved_errno = errno;
 
+  radixchar = decimal_point_char ();
+
   /* Eat whitespace.  */
   while (locale_isspace (*s))
     ++s;
@@ -316,7 +348,7 @@ STRTOD (const char *nptr, char **endptr)
   num = STRTOD (s, &endbuf);
   end = endbuf;
 
-  if (c_isdigit (s[*s == '.']))
+  if (c_isdigit (s[*s == radixchar]))
     {
       /* If a hex float was converted incorrectly, do it ourselves.
          If the string starts with "0x" but does not contain digits,
@@ -324,7 +356,7 @@ STRTOD (const char *nptr, char **endptr)
          'p' but no exponent, then adjust the end pointer.  */
       if (*s == '0' && c_tolower (s[1]) == 'x')
         {
-          if (! c_isxdigit (s[2 + (s[2] == '.')]))
+          if (! c_isxdigit (s[2 + (s[2] == radixchar)]))
             {
               end = s + 1;
 
@@ -333,7 +365,7 @@ STRTOD (const char *nptr, char **endptr)
             }
           else if (end <= s + 2)
             {
-              num = parse_number (s + 2, 16, 2, 4, 'p', &endbuf);
+              num = parse_number (s + 2, 16, 2, 4, radixchar, 'p', &endbuf);
               end = endbuf;
             }
           else
@@ -349,7 +381,8 @@ STRTOD (const char *nptr, char **endptr)
                     {
                       /* Not really our day, is it.  Rounding errors are
                          better than outright failure.  */
-                      num = parse_number (s + 2, 16, 2, 4, 'p', &endbuf);
+                      num =
+                        parse_number (s + 2, 16, 2, 4, radixchar, 'p', &endbuf);
                     }
                   else
                     {
@@ -379,7 +412,7 @@ STRTOD (const char *nptr, char **endptr)
                 {
                   /* Not really our day, is it.  Rounding errors are
                      better than outright failure.  */
-                  num = parse_number (s, 10, 10, 1, 'e', &endbuf);
+                  num = parse_number (s, 10, 10, 1, radixchar, 'e', &endbuf);
                 }
               else
                 {
diff --git a/m4/strtod.m4 b/m4/strtod.m4
index 97fb39b..9912217 100644
--- a/m4/strtod.m4
+++ b/m4/strtod.m4
@@ -1,4 +1,4 @@
-# strtod.m4 serial 23
+# strtod.m4 serial 24
 dnl Copyright (C) 2002-2003, 2006-2019 Free Software Foundation, Inc.
 dnl This file is free software; the Free Software Foundation
 dnl gives unlimited permission to copy and/or distribute it,
@@ -138,4 +138,5 @@ AC_DEFUN([gl_PREREQ_STRTOD], [
     AC_DEFINE([HAVE_LDEXP_IN_LIBC], [1],
       [Define if the ldexp function is available in libc.])
   fi
+  AC_CHECK_FUNCS([nl_langinfo])
 ])
diff --git a/m4/strtold.m4 b/m4/strtold.m4
index 117b7f1..c1c05d1 100644
--- a/m4/strtold.m4
+++ b/m4/strtold.m4
@@ -1,4 +1,4 @@
-# strtold.m4 serial 1
+# strtold.m4 serial 2
 dnl Copyright (C) 2002-2003, 2006-2019 Free Software Foundation, Inc.
 dnl This file is free software; the Free Software Foundation
 dnl gives unlimited permission to copy and/or distribute it,
@@ -117,4 +117,5 @@ AC_DEFUN([gl_PREREQ_STRTOLD], [
     AC_DEFINE([HAVE_LDEXPL_IN_LIBC], [1],
       [Define if the ldexpl function is available in libc.])
   fi
+  AC_CHECK_FUNCS([nl_langinfo])
 ])
diff --git a/modules/strtod-tests b/modules/strtod-tests
index 38aebe8..e3cd57f 100644
--- a/modules/strtod-tests
+++ b/modules/strtod-tests
@@ -1,8 +1,12 @@
 Files:
 tests/test-strtod.c
+tests/test-strtod1.sh
+tests/test-strtod1.c
 tests/signature.h
 tests/minus-zero.h
 tests/macros.h
+m4/locale-fr.m4
+m4/codeset.m4
 
 Depends-on:
 float
@@ -10,7 +14,15 @@ isnand-nolibm
 signbit
 
 configure.ac:
+gt_LOCALE_FR
+gt_LOCALE_FR_UTF8
 
 Makefile.am:
 TESTS += test-strtod
 check_PROGRAMS += test-strtod
+
+TESTS += test-strtod1.sh
+TESTS_ENVIRONMENT += \
+  LOCALE_FR='@LOCALE_FR@' \
+  LOCALE_FR_UTF8='@LOCALE_FR_UTF8@'
+check_PROGRAMS += test-strtod1
diff --git a/modules/strtold-tests b/modules/strtold-tests
index 8a812d2..c58e52c 100644
--- a/modules/strtold-tests
+++ b/modules/strtold-tests
@@ -1,8 +1,12 @@
 Files:
 tests/test-strtold.c
+tests/test-strtold1.sh
+tests/test-strtold1.c
 tests/signature.h
 tests/minus-zero.h
 tests/macros.h
+m4/locale-fr.m4
+m4/codeset.m4
 
 Depends-on:
 float
@@ -10,7 +14,15 @@ isnanl-nolibm
 signbit
 
 configure.ac:
+gt_LOCALE_FR
+gt_LOCALE_FR_UTF8
 
 Makefile.am:
 TESTS += test-strtold
 check_PROGRAMS += test-strtold
+
+TESTS += test-strtold1.sh
+TESTS_ENVIRONMENT += \
+  LOCALE_FR='@LOCALE_FR@' \
+  LOCALE_FR_UTF8='@LOCALE_FR_UTF8@'
+check_PROGRAMS += test-strtold1
diff --git a/tests/test-strtod1.c b/tests/test-strtod1.c
new file mode 100644
index 0000000..75200cc
--- /dev/null
+++ b/tests/test-strtod1.c
@@ -0,0 +1,97 @@
+/* Test of strtod() in a French locale.
+   Copyright (C) 2019 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
+   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 <https://www.gnu.org/licenses/>.  */
+
+#include <config.h>
+
+#include <stdlib.h>
+
+#include <errno.h>
+#include <locale.h>
+
+#include "macros.h"
+
+int
+main (int argc, char *argv[])
+{
+  /* Try to set the locale by implicitly looking at the LC_ALL environment
+     variable.
+     configure should already have checked that the locale is supported.  */
+  if (setlocale (LC_ALL, "") == NULL)
+    return 1;
+
+  {
+    const char input[] = "1,";
+    char *ptr;
+    double result;
+    errno = 0;
+    result = strtod (input, &ptr);
+    ASSERT (result == 1.0);
+    ASSERT (ptr == input + 2);
+    ASSERT (errno == 0);
+  }
+  {
+    const char input[] = ",5";
+    char *ptr;
+    double result;
+    errno = 0;
+    result = strtod (input, &ptr);
+    ASSERT (result == 0.5);
+    ASSERT (ptr == input + 2);
+    ASSERT (errno == 0);
+  }
+  {
+    const char input[] = "1,5";
+    char *ptr;
+    double result;
+    errno = 0;
+    result = strtod (input, &ptr);
+    ASSERT (result == 1.5);
+    ASSERT (ptr == input + 3);
+    ASSERT (errno == 0);
+  }
+  {
+    const char input[] = "1.5";
+    char *ptr;
+    double result;
+    errno = 0;
+    result = strtod (input, &ptr);
+    ASSERT (result == 1.0);
+    ASSERT (ptr == input + 1);
+    ASSERT (errno == 0);
+  }
+  {
+    const char input[] = "123.456,789";
+    char *ptr;
+    double result;
+    errno = 0;
+    result = strtod (input, &ptr);
+    ASSERT (result == 123.0);
+    ASSERT (ptr == input + 3);
+    ASSERT (errno == 0);
+  }
+  {
+    const char input[] = "123,456.789";
+    char *ptr;
+    double result;
+    errno = 0;
+    result = strtod (input, &ptr);
+    ASSERT (result > 123.45 && result < 123.46);
+    ASSERT (ptr == input + 7);
+    ASSERT (errno == 0);
+  }
+
+  return 0;
+}
diff --git a/tests/test-strtod1.sh b/tests/test-strtod1.sh
new file mode 100755
index 0000000..d220f13
--- /dev/null
+++ b/tests/test-strtod1.sh
@@ -0,0 +1,23 @@
+#!/bin/sh
+
+: ${LOCALE_FR=fr_FR}
+: ${LOCALE_FR_UTF8=fr_FR.UTF-8}
+
+if test $LOCALE_FR = none && test $LOCALE_FR_UTF8 = none; then
+  if test -f /usr/bin/localedef; then
+    echo "Skipping test: no locale for testing is installed"
+  else
+    echo "Skipping test: no locale for testing is supported"
+  fi
+  exit 77
+fi
+
+if test $LOCALE_FR != none; then
+  LC_ALL=$LOCALE_FR      ./test-strtod1${EXEEXT} || exit 1
+fi
+
+if test $LOCALE_FR_UTF8 != none; then
+  LC_ALL=$LOCALE_FR_UTF8 ./test-strtod1${EXEEXT} || exit 1
+fi
+
+exit 0
diff --git a/tests/test-strtold1.c b/tests/test-strtold1.c
new file mode 100644
index 0000000..3a2f533
--- /dev/null
+++ b/tests/test-strtold1.c
@@ -0,0 +1,97 @@
+/* Test of strtold() in a French locale.
+   Copyright (C) 2019 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
+   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 <https://www.gnu.org/licenses/>.  */
+
+#include <config.h>
+
+#include <stdlib.h>
+
+#include <errno.h>
+#include <locale.h>
+
+#include "macros.h"
+
+int
+main (int argc, char *argv[])
+{
+  /* Try to set the locale by implicitly looking at the LC_ALL environment
+     variable.
+     configure should already have checked that the locale is supported.  */
+  if (setlocale (LC_ALL, "") == NULL)
+    return 1;
+
+  {
+    const char input[] = "1,";
+    char *ptr;
+    long double result;
+    errno = 0;
+    result = strtold (input, &ptr);
+    ASSERT (result == 1.0L);
+    ASSERT (ptr == input + 2);
+    ASSERT (errno == 0);
+  }
+  {
+    const char input[] = ",5";
+    char *ptr;
+    long double result;
+    errno = 0;
+    result = strtold (input, &ptr);
+    ASSERT (result == 0.5L);
+    ASSERT (ptr == input + 2);
+    ASSERT (errno == 0);
+  }
+  {
+    const char input[] = "1,5";
+    char *ptr;
+    long double result;
+    errno = 0;
+    result = strtold (input, &ptr);
+    ASSERT (result == 1.5L);
+    ASSERT (ptr == input + 3);
+    ASSERT (errno == 0);
+  }
+  {
+    const char input[] = "1.5";
+    char *ptr;
+    long double result;
+    errno = 0;
+    result = strtold (input, &ptr);
+    ASSERT (result == 1.0L);
+    ASSERT (ptr == input + 1);
+    ASSERT (errno == 0);
+  }
+  {
+    const char input[] = "123.456,789";
+    char *ptr;
+    long double result;
+    errno = 0;
+    result = strtold (input, &ptr);
+    ASSERT (result == 123.0L);
+    ASSERT (ptr == input + 3);
+    ASSERT (errno == 0);
+  }
+  {
+    const char input[] = "123,456.789";
+    char *ptr;
+    long double result;
+    errno = 0;
+    result = strtold (input, &ptr);
+    ASSERT (result > 123.45L && result < 123.46L);
+    ASSERT (ptr == input + 7);
+    ASSERT (errno == 0);
+  }
+
+  return 0;
+}
diff --git a/tests/test-strtold1.sh b/tests/test-strtold1.sh
new file mode 100755
index 0000000..edec4d3
--- /dev/null
+++ b/tests/test-strtold1.sh
@@ -0,0 +1,23 @@
+#!/bin/sh
+
+: ${LOCALE_FR=fr_FR}
+: ${LOCALE_FR_UTF8=fr_FR.UTF-8}
+
+if test $LOCALE_FR = none && test $LOCALE_FR_UTF8 = none; then
+  if test -f /usr/bin/localedef; then
+    echo "Skipping test: no locale for testing is installed"
+  else
+    echo "Skipping test: no locale for testing is supported"
+  fi
+  exit 77
+fi
+
+if test $LOCALE_FR != none; then
+  LC_ALL=$LOCALE_FR      ./test-strtold1${EXEEXT} || exit 1
+fi
+
+if test $LOCALE_FR_UTF8 != none; then
+  LC_ALL=$LOCALE_FR_UTF8 ./test-strtold1${EXEEXT} || exit 1
+fi
+
+exit 0
-- 
2.7.4


^ permalink raw reply related	[flat|nested] 6+ messages in thread

* Re: new module 'strtold'
  2019-01-30  5:42 ` Pádraig Brady
@ 2019-02-01  3:34   ` Bruno Haible
  2019-02-01 22:47     ` Paul Eggert
  0 siblings, 1 reply; 6+ messages in thread
From: Bruno Haible @ 2019-02-01  3:34 UTC (permalink / raw)
  To: Pádraig Brady; +Cc: bug-gnulib

Pádraig Brady wrote:
> 
>   #if HAVE_C99_STRTOLD /* provided by c-strtold module.  */
>   # define STRTOD strtold
>   #else
>   # define STRTOD strtod
>   #endif
>   long double a = STRTOD (sa, &ea);

But wait, the 'c-strtod' and 'c-strtold' modules are not up-to-date.
When strtod() or strtold() have bugs, you can assume that strtod_l()
and strtold_l() have the same bugs. In this case, it is better to
use the gnulib override of strtod() or strtold(), through 'uselocale'.
This affects at some versions of glibc and some versions of Mac OS X.
Here is a proposed patch.


2019-01-31  Bruno Haible  <bruno@clisp.org>

	c-strtod, c-strtold: Use the bug fixes for strtod, strtold.
	* lib/stdlib.in.h (GNULIB_defined_strtod_function,
	GNULIB_defined_strtold_function): New macros.
	* lib/c-strtod.c (HAVE_GOOD_STRTOD_L): New macro.
	(STRTOD): Ignore HAVE_C99_STRTOLD.
	(c_locale): Don't define it on platforms where strtod_l/strtold_l is
	deemed buggy. But do use it on platforms where uselocale exists and is
	usable.
	(C_STRTOD): Don't use STRTOD_L on platforms where strtod_l/strtold_l is
	deemed buggy. On platforms where uselocale exists and is usable, use
	uselocale and strtod/strtold.
	* m4/c-strtod.m4 (gl_C99_STRTOLD): Remove macro.
	(gl_C_STRTOD): Require gt_FUNC_USELOCALE.
	(gl_C_STRTOLD): Likewise. Define HAVE_C99_STRTOLD unconditionally.
	* modules/c-strtod (Files): Add m4/intl-thread-locale.m4.
	(Depends-on): Add strtod.
	* modules/c-strtold (Files): Add m4/intl-thread-locale.m4.
	(Depends-on): Add strtold.

diff --git a/lib/stdlib.in.h b/lib/stdlib.in.h
index b70444d..f829525 100644
--- a/lib/stdlib.in.h
+++ b/lib/stdlib.in.h
@@ -929,6 +929,7 @@ _GL_WARN_ON_USE (setenv, "setenv is unportable - "
 #  if !(defined __cplusplus && defined GNULIB_NAMESPACE)
 #   define strtod rpl_strtod
 #  endif
+#  define GNULIB_defined_strtod_function 1
 _GL_FUNCDECL_RPL (strtod, double, (const char *str, char **endp)
                                   _GL_ARG_NONNULL ((1)));
 _GL_CXXALIAS_RPL (strtod, double, (const char *str, char **endp));
@@ -954,6 +955,7 @@ _GL_WARN_ON_USE (strtod, "strtod is unportable - "
 #  if !(defined __cplusplus && defined GNULIB_NAMESPACE)
 #   define strtold rpl_strtold
 #  endif
+#  define GNULIB_defined_strtold_function 1
 _GL_FUNCDECL_RPL (strtold, long double, (const char *str, char **endp)
                                         _GL_ARG_NONNULL ((1)));
 _GL_CXXALIAS_RPL (strtold, long double, (const char *str, char **endp));
diff --git a/lib/c-strtod.c b/lib/c-strtod.c
index b5f88bc..53ae189 100644
--- a/lib/c-strtod.c
+++ b/lib/c-strtod.c
@@ -30,20 +30,19 @@
 # define C_STRTOD c_strtold
 # define DOUBLE long double
 # define STRTOD_L strtold_l
+# define HAVE_GOOD_STRTOD_L (HAVE_STRTOLD_L && !GNULIB_defined_strtold_function)
+# define STRTOD strtold
 #else
 # define C_STRTOD c_strtod
 # define DOUBLE double
 # define STRTOD_L strtod_l
-#endif
-
-/* c_strtold falls back on strtod if strtold doesn't conform to C99.  */
-#if LONG && HAVE_C99_STRTOLD
-# define STRTOD strtold
-#else
+# define HAVE_GOOD_STRTOD_L (HAVE_STRTOD_L && !GNULIB_defined_strtod_function)
 # define STRTOD strtod
 #endif
 
-#if defined LC_ALL_MASK && (LONG ? HAVE_STRTOLD_L : HAVE_STRTOD_L)
+#if defined LC_ALL_MASK \
+    && ((LONG ? HAVE_GOOD_STRTOLD_L : HAVE_GOOD_STRTOD_L) \
+        || HAVE_WORKING_USELOCALE)
 
 /* Cache for the C locale object.
    Marked volatile so that different threads see the same value
@@ -67,7 +66,9 @@ C_STRTOD (char const *nptr, char **endptr)
 {
   DOUBLE r;
 
-#if defined LC_ALL_MASK && (LONG ? HAVE_STRTOLD_L : HAVE_STRTOD_L)
+#if defined LC_ALL_MASK \
+    && ((LONG ? HAVE_GOOD_STRTOLD_L : HAVE_GOOD_STRTOD_L) \
+        || HAVE_WORKING_USELOCALE)
 
   locale_t locale = c_locale ();
   if (!locale)
@@ -77,8 +78,30 @@ C_STRTOD (char const *nptr, char **endptr)
       return 0; /* errno is set here */
     }
 
+# if (LONG ? HAVE_GOOD_STRTOLD_L : HAVE_GOOD_STRTOD_L)
+
   r = STRTOD_L (nptr, endptr, locale);
 
+# else /* HAVE_WORKING_USELOCALE */
+
+  locale_t old_locale = uselocale (locale);
+  if (old_locale == (locale_t)0)
+    {
+      if (endptr)
+        *endptr = (char *) nptr;
+      return 0; /* errno is set here */
+    }
+
+  r = STRTOD (nptr, endptr);
+
+  int saved_errno = errno;
+  if (uselocale (old_locale) == (locale_t)0)
+    /* We can't switch back to the old locale.  The thread is hosed.  */
+    abort ();
+  errno = saved_errno;
+
+# endif
+
 #else
 
   char *saved_locale = setlocale (LC_NUMERIC, NULL);
diff --git a/m4/c-strtod.m4 b/m4/c-strtod.m4
index 1694a23..1d4b7f8 100644
--- a/m4/c-strtod.m4
+++ b/m4/c-strtod.m4
@@ -1,4 +1,4 @@
-# c-strtod.m4 serial 16
+# c-strtod.m4 serial 17
 
 # Copyright (C) 2004-2006, 2009-2019 Free Software Foundation, Inc.
 # This file is free software; the Free Software Foundation
@@ -7,36 +7,11 @@
 
 # Written by Paul Eggert.
 
-AC_DEFUN([gl_C99_STRTOLD],
-[
-  AC_CACHE_CHECK([whether strtold conforms to C99],
-    [gl_cv_func_c99_strtold],
-    [AC_LINK_IFELSE(
-       [AC_LANG_PROGRAM(
-          [[/* On HP-UX before 11.23, strtold returns a struct instead of
-                long double.  Reject implementations like that, by requiring
-                compatibility with the C99 prototype.  */
-             #include <stdlib.h>
-             static long double (*p) (char const *, char **) = strtold;
-             static long double
-             test (char const *nptr, char **endptr)
-             {
-               long double r;
-               r = strtold (nptr, endptr);
-               return r;
-             }]],
-           [[return test ("1.0", NULL) != 1 || p ("1.0", NULL) != 1;]])],
-       [gl_cv_func_c99_strtold=yes],
-       [gl_cv_func_c99_strtold=no])])
-  if test $gl_cv_func_c99_strtold = yes; then
-    AC_DEFINE([HAVE_C99_STRTOLD], [1], [Define to 1 if strtold conforms to C99.])
-  fi
-])
-
 dnl Prerequisites of lib/c-strtod.c.
 AC_DEFUN([gl_C_STRTOD],
 [
   AC_REQUIRE([gl_USE_SYSTEM_EXTENSIONS])
+  AC_REQUIRE([gt_FUNC_USELOCALE])
 
   AC_CHECK_HEADERS_ONCE([xlocale.h])
   dnl We can't use AC_CHECK_FUNC here, because strtod_l() is defined as a
@@ -71,6 +46,8 @@ dnl Prerequisites of lib/c-strtold.c.
 AC_DEFUN([gl_C_STRTOLD],
 [
   AC_REQUIRE([gl_USE_SYSTEM_EXTENSIONS])
-  AC_REQUIRE([gl_C99_STRTOLD])
+  AC_REQUIRE([gt_FUNC_USELOCALE])
+  AC_DEFINE([HAVE_C99_STRTOLD], [1],
+    [Define to 1 because the gnulib 'strtold' module provides a C99-conforming strtold function.])
   AC_CHECK_FUNCS([strtold_l])
 ])
diff --git a/modules/c-strtod b/modules/c-strtod
index 7999d45..71b292e 100644
--- a/modules/c-strtod
+++ b/modules/c-strtod
@@ -5,11 +5,13 @@ Files:
 lib/c-strtod.c
 lib/c-strtod.h
 m4/c-strtod.m4
+m4/intl-thread-locale.m4
 
 Depends-on:
 extensions
 locale
 strdup-posix
+strtod
 
 configure.ac:
 gl_C_STRTOD
diff --git a/modules/c-strtold b/modules/c-strtold
index 72ff21d..c7f31db 100644
--- a/modules/c-strtold
+++ b/modules/c-strtold
@@ -6,11 +6,13 @@ lib/c-strtod.h
 lib/c-strtod.c
 lib/c-strtold.c
 m4/c-strtod.m4
+m4/intl-thread-locale.m4
 
 Depends-on:
 extensions
 locale
 strdup-posix
+strtold
 
 configure.ac:
 gl_C_STRTOLD



^ permalink raw reply related	[flat|nested] 6+ messages in thread

* Re: new module 'strtold'
  2019-02-01  3:34   ` Bruno Haible
@ 2019-02-01 22:47     ` Paul Eggert
  2019-02-02  1:44       ` Bruno Haible
  0 siblings, 1 reply; 6+ messages in thread
From: Paul Eggert @ 2019-02-01 22:47 UTC (permalink / raw)
  To: Bruno Haible, Pádraig Brady; +Cc: bug-gnulib

That looks good to me, thanks.


^ permalink raw reply	[flat|nested] 6+ messages in thread

* Re: new module 'strtold'
  2019-02-01 22:47     ` Paul Eggert
@ 2019-02-02  1:44       ` Bruno Haible
  0 siblings, 0 replies; 6+ messages in thread
From: Bruno Haible @ 2019-02-02  1:44 UTC (permalink / raw)
  To: Paul Eggert; +Cc: bug-gnulib

> That looks good to me, thanks.

OK. I verified that the of symbols used by c-strtod.o and c-strtold.o on
several platforms is as expected:

                 nm c-strtod.o                nm c-strtold.o
                 BEFORE       AFTER           BEFORE       AFTER

glibc-2.24       strtod_l     strtod_l        strtold_l    strtold_l

glibc-2.3.2      strtod_l     rpl_strtod      strtold_l    rpl_strtold
                              uselocale                    uselocale

Mac OS X 10.5    strtod_l     rpl_strtod      strtold_l    rpl_strtold
                              uselocale                    uselocale

Solaris 11.4     strtod       rpl_strtod      strtold      strtold
                 setlocale    uselocale       setlocale    uselocale

So I pushed the change.

Note: It looks odd that on Solaris 11.4, configure determines that strtold
works but strtod is buggy. But this is in fact right: Indeed, the string
"0XaP+1" is parsed correctly by strtold, but not by strtod.

Bruno



^ permalink raw reply	[flat|nested] 6+ messages in thread

end of thread, other threads:[~2019-02-02  1:44 UTC | newest]

Thread overview: 6+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2019-01-30  3:05 new module 'strtold' Bruno Haible
2019-01-30  5:42 ` Pádraig Brady
2019-02-01  3:34   ` Bruno Haible
2019-02-01 22:47     ` Paul Eggert
2019-02-02  1:44       ` Bruno Haible
2019-02-01  2:16 ` strtod, strtold: various fixes Bruno Haible

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).