]> Savannah Git Hosting - gnulib.git/commitdiff
strtof, strtod, strtold: Fix underflow behaviour of system function.
authorBruno Haible <bruno@clisp.org>
Tue, 23 Jul 2024 10:40:19 +0000 (12:40 +0200)
committerBruno Haible <bruno@clisp.org>
Mon, 12 Aug 2024 15:31:25 +0000 (17:31 +0200)
* m4/strtof.m4 (gl_FUNC_STRTOF): Test for strtof's behaviour upon
underflow. Conditionally define STRTOF_HAS_UNDERFLOW_BUG,
STRTOF_HAS_GRADUAL_UNDERFLOW_PROBLEM.
* m4/strtod.m4 (gl_FUNC_STRTOD): Test for strtod's behaviour upon
underflow. Conditionally define STRTOD_HAS_UNDERFLOW_BUG,
STRTOD_HAS_GRADUAL_UNDERFLOW_PROBLEM.
* m4/strtold.m4 (gl_FUNC_STRTOLD): Test for strtold's behaviour upon
gradual underflow. Conditionally define
STRTOLD_HAS_GRADUAL_UNDERFLOW_PROBLEM.
* lib/strtod.c (HAVE_UNDERLYING_STRTOD): Test STRTOF_HAS_UNDERFLOW_BUG,
STRTOD_HAS_UNDERFLOW_BUG.
(HAS_GRADUAL_UNDERFLOW_PROBLEM): New macro.
(SET_ERRNO_UPON_GRADUAL_UNDERFLOW): New macro.
(STRTOD): Use it.

ChangeLog
lib/strtod.c
m4/strtod.m4
m4/strtof.m4
m4/strtold.m4

index f0c55a0d608c15920c24e099b7893ae7abbfabee..2986e25d795ae6b021e8279c1ff8db1e5de25917 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,21 @@
+2024-07-23  Bruno Haible  <bruno@clisp.org>
+
+       strtof, strtod, strtold: Fix underflow behaviour of system function.
+       * m4/strtof.m4 (gl_FUNC_STRTOF): Test for strtof's behaviour upon
+       underflow. Conditionally define STRTOF_HAS_UNDERFLOW_BUG,
+       STRTOF_HAS_GRADUAL_UNDERFLOW_PROBLEM.
+       * m4/strtod.m4 (gl_FUNC_STRTOD): Test for strtod's behaviour upon
+       underflow. Conditionally define STRTOD_HAS_UNDERFLOW_BUG,
+       STRTOD_HAS_GRADUAL_UNDERFLOW_PROBLEM.
+       * m4/strtold.m4 (gl_FUNC_STRTOLD): Test for strtold's behaviour upon
+       gradual underflow. Conditionally define
+       STRTOLD_HAS_GRADUAL_UNDERFLOW_PROBLEM.
+       * lib/strtod.c (HAVE_UNDERLYING_STRTOD): Test STRTOF_HAS_UNDERFLOW_BUG,
+       STRTOD_HAS_UNDERFLOW_BUG.
+       (HAS_GRADUAL_UNDERFLOW_PROBLEM): New macro.
+       (SET_ERRNO_UPON_GRADUAL_UNDERFLOW): New macro.
+       (STRTOD): Use it.
+
 2024-07-23  Bruno Haible  <bruno@clisp.org>
 
        strtof: Use the system's strtof() if available.
index 8847cae9409863e8d36a5e5cff18e38cd8282c08..bece59fd68a76e32030edc959a61bae8cd935491 100644 (file)
 #if defined USE_FLOAT
 # define STRTOD strtof
 # define LDEXP ldexpf
-# define HAVE_UNDERLYING_STRTOD HAVE_STRTOF
+# if STRTOF_HAS_UNDERFLOW_BUG
+   /* strtof would not set errno=ERANGE upon flush-to-zero underflow.  */
+#  define HAVE_UNDERLYING_STRTOD 0
+# else
+#  define HAVE_UNDERLYING_STRTOD HAVE_STRTOF
+# endif
+# define HAS_GRADUAL_UNDERFLOW_PROBLEM STRTOF_HAS_GRADUAL_UNDERFLOW_PROBLEM
 # define DOUBLE float
 # define MIN FLT_MIN
 # define MAX FLT_MAX
@@ -58,7 +64,7 @@
       not a 'long double'.  */
 #  define HAVE_UNDERLYING_STRTOD 0
 # elif STRTOLD_HAS_UNDERFLOW_BUG
-   /* strtold would not set errno=ERANGE upon underflow.  */
+   /* strtold would not set errno=ERANGE upon flush-to-zero underflow.  */
 #  define HAVE_UNDERLYING_STRTOD 0
 # elif defined __MINGW32__ && __MINGW64_VERSION_MAJOR < 10
    /* strtold is broken in mingw versions before 10.0:
@@ -70,6 +76,7 @@
 # else
 #  define HAVE_UNDERLYING_STRTOD HAVE_STRTOLD
 # endif
+# define HAS_GRADUAL_UNDERFLOW_PROBLEM STRTOLD_HAS_GRADUAL_UNDERFLOW_PROBLEM
 # define DOUBLE long double
 # define MIN LDBL_MIN
 # define MAX LDBL_MAX
 #else
 # define STRTOD strtod
 # define LDEXP ldexp
-# define HAVE_UNDERLYING_STRTOD 1
+# if STRTOD_HAS_UNDERFLOW_BUG
+   /* strtod would not set errno=ERANGE upon flush-to-zero underflow.  */
+#  define HAVE_UNDERLYING_STRTOD 0
+# else
+#  define HAVE_UNDERLYING_STRTOD 1
+# endif
+# define HAS_GRADUAL_UNDERFLOW_PROBLEM STRTOD_HAS_GRADUAL_UNDERFLOW_PROBLEM
 # define DOUBLE double
 # define MIN DBL_MIN
 # define MAX DBL_MAX
@@ -351,10 +364,22 @@ STRTOD (const char *nptr, char **endptr)
 # else
 #  undef strtod
 # endif
+# if HAS_GRADUAL_UNDERFLOW_PROBLEM
+#  define SET_ERRNO_UPON_GRADUAL_UNDERFLOW(RESULT) \
+    do                                                          \
+      {                                                         \
+        if ((RESULT) != 0 && (RESULT) < MIN && (RESULT) > -MIN) \
+          errno = ERANGE;                                       \
+      }                                                         \
+    while (0)
+# else
+#  define SET_ERRNO_UPON_GRADUAL_UNDERFLOW(RESULT) (void)0
+# endif
 #else
 # undef STRTOD
 # define STRTOD(NPTR,ENDPTR) \
    parse_number (NPTR, 10, 10, 1, radixchar, 'e', ENDPTR)
+# define SET_ERRNO_UPON_GRADUAL_UNDERFLOW(RESULT) (void)0
 #endif
 /* From here on, STRTOD refers to the underlying implementation.  It needs
    to handle only finite unsigned decimal numbers with non-null ENDPTR.  */
@@ -382,6 +407,7 @@ STRTOD (const char *nptr, char **endptr)
     ++s;
 
   num = STRTOD (s, &endbuf);
+  SET_ERRNO_UPON_GRADUAL_UNDERFLOW (num);
   end = endbuf;
 
   if (c_isdigit (s[*s == radixchar]))
@@ -424,6 +450,7 @@ STRTOD (const char *nptr, char **endptr)
                     {
                       dup[p - s] = '\0';
                       num = STRTOD (dup, &endbuf);
+                      SET_ERRNO_UPON_GRADUAL_UNDERFLOW (num);
                       saved_errno = errno;
                       free (dup);
                       errno = saved_errno;
@@ -454,6 +481,7 @@ STRTOD (const char *nptr, char **endptr)
                 {
                   dup[e - s] = '\0';
                   num = STRTOD (dup, &endbuf);
+                  SET_ERRNO_UPON_GRADUAL_UNDERFLOW (num);
                   saved_errno = errno;
                   free (dup);
                   errno = saved_errno;
index 3f8bacc01a5a2986a4165d25933c8f98a4cc08da..2ccbf3139a49fb26968bd40cff9824b65855b329 100644 (file)
@@ -1,5 +1,5 @@
 # strtod.m4
-# serial 29
+# serial 30
 dnl Copyright (C) 2002-2003, 2006-2024 Free Software Foundation, Inc.
 dnl This file is free software; the Free Software Foundation
 dnl gives unlimited permission to copy and/or distribute it,
@@ -24,6 +24,7 @@ AC_DEFUN([gl_FUNC_STRTOD],
     AC_CACHE_CHECK([whether strtod obeys C99], [gl_cv_func_strtod_works],
       [AC_RUN_IFELSE([AC_LANG_PROGRAM([[
 #include <stdlib.h>
+#include <float.h>
 #include <math.h>
 #include <errno.h>
 /* Compare two numbers with ==.
@@ -52,7 +53,7 @@ numeric_equal (double x, double y)
     char *term;
     strtod (string, &term);
     if (term != string && *(term - 1) == 0)
-      result |= 2;
+      result |= 1;
   }
   {
     /* Older glibc and Cygwin mis-parse "-0x".  */
@@ -61,7 +62,7 @@ numeric_equal (double x, double y)
     double value = strtod (string, &term);
     double zero = 0.0;
     if (1.0 / value != -1.0 / zero || term != (string + 2))
-      result |= 4;
+      result |= 2;
   }
   {
     /* Many platforms do not parse hex floats.  */
@@ -69,7 +70,7 @@ numeric_equal (double x, double y)
     char *term;
     double value = strtod (string, &term);
     if (value != 20.0 || term != (string + 6))
-      result |= 8;
+      result |= 4;
   }
   {
     /* Many platforms do not parse infinities.  HP-UX 11.31 parses inf,
@@ -80,7 +81,7 @@ numeric_equal (double x, double y)
     errno = 0;
     value = strtod (string, &term);
     if (value != HUGE_VAL || term != (string + 3) || errno)
-      result |= 16;
+      result |= 8;
   }
   {
     /* glibc 2.7 and cygwin 1.5.24 misparse "nan()".  */
@@ -88,7 +89,7 @@ numeric_equal (double x, double y)
     char *term;
     double value = strtod (string, &term);
     if (numeric_equal (value, value) || term != (string + 5))
-      result |= 32;
+      result |= 16;
   }
   {
     /* darwin 10.6.1 misparses "nan(".  */
@@ -96,12 +97,47 @@ numeric_equal (double x, double y)
     char *term;
     double value = strtod (string, &term);
     if (numeric_equal (value, value) || term != (string + 3))
+      result |= 16;
+  }
+#ifndef _MSC_VER /* On MSVC, this is expected behaviour.  */
+  {
+    /* In Cygwin 2.9, strtod does not set errno upon
+       gradual underflow.  */
+    const char *string = "1e-320";
+    char *term;
+    double value;
+    errno = 0;
+    value = strtod (string, &term);
+    if (term != (string + 6)
+        || (value > 0.0 && value <= DBL_MIN && errno != ERANGE))
+      result |= 32;
+  }
+#endif
+  {
+    /* strtod could not set errno upon
+       flush-to-zero underflow.  */
+    const char *string = "1E-100000";
+    char *term;
+    double value;
+    errno = 0;
+    value = strtod (string, &term);
+    if (term != (string + 9) || (value == 0.0L && errno != ERANGE))
       result |= 64;
   }
   return result;
 ]])],
         [gl_cv_func_strtod_works=yes],
-        [gl_cv_func_strtod_works=no],
+        [result=$?
+         if expr $result '>=' 64 >/dev/null; then
+           gl_cv_func_strtod_works="no (underflow problem)"
+         else
+           if expr $result '>=' 32 >/dev/null; then
+             gl_cv_func_strtod_works="no (gradual underflow problem)"
+           else
+             gl_cv_func_strtod_works=no
+           fi
+         fi
+        ],
         [dnl The last known bugs in glibc strtod(), as of this writing,
          dnl were fixed in version 2.8
          AC_EGREP_CPP([Lucky user],
@@ -118,6 +154,8 @@ numeric_equal (double x, double y)
            [case "$host_os" in
                                   # Guess yes on musl systems.
               *-musl* | midipix*) gl_cv_func_strtod_works="guessing yes" ;;
+                                  # Guess 'no (gradual underflow problem)' on Cygwin.
+              cygwin*)            gl_cv_func_strtod_works="guessing no (gradual underflow problem)" ;;
                                   # Guess yes on native Windows.
               mingw* | windows*)  gl_cv_func_strtod_works="guessing yes" ;;
               *)                  gl_cv_func_strtod_works="$gl_cross_guess_normal" ;;
@@ -129,6 +167,16 @@ numeric_equal (double x, double y)
       *yes) ;;
       *)
         REPLACE_STRTOD=1
+        case "$gl_cv_func_strtod_works" in
+          *"no (underflow problem)")
+            AC_DEFINE([STRTOD_HAS_UNDERFLOW_BUG], [1],
+              [Define to 1 if strtod does not set errno upon flush-to-zero underflow.])
+            ;;
+          *"no (gradual underflow problem)")
+            AC_DEFINE([STRTOD_HAS_GRADUAL_UNDERFLOW_PROBLEM], [1],
+              [Define to 1 if strtod does not set errno upon gradual underflow.])
+            ;;
+        esac
         ;;
     esac
   fi
index 72c30b3d31e860ef2031a56bc539092aebfea992..e37310ebded3d1541db78124913ef46879cccfb4 100644 (file)
@@ -1,5 +1,5 @@
 # strtof.m4
-# serial 2
+# serial 3
 dnl Copyright (C) 2002-2003, 2006-2024 Free Software Foundation, Inc.
 dnl This file is free software; the Free Software Foundation
 dnl gives unlimited permission to copy and/or distribute it,
@@ -24,6 +24,7 @@ AC_DEFUN([gl_FUNC_STRTOF],
     AC_CACHE_CHECK([whether strtof obeys C99], [gl_cv_func_strtof_works],
       [AC_RUN_IFELSE([AC_LANG_PROGRAM([[
 #include <stdlib.h>
+#include <float.h>
 #include <math.h>
 #include <errno.h>
 /* Compare two numbers with ==.
@@ -52,7 +53,7 @@ numeric_equal (float x, float y)
     char *term;
     strtof (string, &term);
     if (term != string && *(term - 1) == 0)
-      result |= 2;
+      result |= 1;
   }
   {
     /* Older glibc and Cygwin mis-parse "-0x".  */
@@ -61,7 +62,7 @@ numeric_equal (float x, float y)
     float value = strtof (string, &term);
     float zero = 0.0f;
     if (1.0f / value != -1.0f / zero || term != (string + 2))
-      result |= 4;
+      result |= 2;
   }
   {
     /* Many platforms do not parse hex floats.  */
@@ -69,7 +70,7 @@ numeric_equal (float x, float y)
     char *term;
     float value = strtof (string, &term);
     if (value != 20.0f || term != (string + 6))
-      result |= 8;
+      result |= 4;
   }
   {
     /* Many platforms do not parse infinities.  HP-UX 11.31 parses inf,
@@ -80,7 +81,7 @@ numeric_equal (float x, float y)
     errno = 0;
     value = strtof (string, &term);
     if (value != HUGE_VAL || term != (string + 3) || errno)
-      result |= 16;
+      result |= 8;
   }
   {
     /* glibc 2.7 and cygwin 1.5.24 misparse "nan()".  */
@@ -88,7 +89,7 @@ numeric_equal (float x, float y)
     char *term;
     float value = strtof (string, &term);
     if (numeric_equal (value, value) || term != (string + 5))
-      result |= 32;
+      result |= 16;
   }
   {
     /* darwin 10.6.1 misparses "nan(".  */
@@ -96,12 +97,47 @@ numeric_equal (float x, float y)
     char *term;
     float value = strtof (string, &term);
     if (numeric_equal (value, value) || term != (string + 3))
+      result |= 16;
+  }
+#ifndef _MSC_VER /* On MSVC, this is expected behaviour.  */
+  {
+    /* In Cygwin 2.9 and mingw 5.0, strtof does not set errno upon
+       gradual underflow.  */
+    const char *string = "1e-40";
+    char *term;
+    float value;
+    errno = 0;
+    value = strtof (string, &term);
+    if (term != (string + 5)
+        || (value > 0.0f && value <= FLT_MIN && errno != ERANGE))
+      result |= 32;
+  }
+#endif
+  {
+    /* In Cygwin 2.9 and mingw 5.0, strtof does not set errno upon
+       flush-to-zero underflow.  */
+    const char *string = "1E-100000";
+    char *term;
+    float value;
+    errno = 0;
+    value = strtof (string, &term);
+    if (term != (string + 9) || (value == 0.0L && errno != ERANGE))
       result |= 64;
   }
   return result;
 ]])],
         [gl_cv_func_strtof_works=yes],
-        [gl_cv_func_strtof_works=no],
+        [result=$?
+         if expr $result '>=' 64 >/dev/null; then
+           gl_cv_func_strtof_works="no (underflow problem)"
+         else
+           if expr $result '>=' 32 >/dev/null; then
+             gl_cv_func_strtof_works="no (gradual underflow problem)"
+           else
+             gl_cv_func_strtof_works=no
+           fi
+         fi
+        ],
         [dnl The last known bugs in glibc strtof(), as of this writing,
          dnl were fixed in version 2.8
          AC_EGREP_CPP([Lucky user],
@@ -118,8 +154,12 @@ numeric_equal (float x, float y)
            [case "$host_os" in
                                   # Guess yes on musl systems.
               *-musl* | midipix*) gl_cv_func_strtof_works="guessing yes" ;;
-                                  # Guess yes on native Windows.
-              mingw* | windows*)  gl_cv_func_strtof_works="guessing yes" ;;
+                                  # Guess 'no (underflow problem)' on Cygwin.
+              cygwin*)            gl_cv_func_strtof_works="guessing no (underflow problem)" ;;
+                                  # Guess 'no (underflow problem)' on mingw.
+                                  # (Although the results may vary depending on
+                                  # __USE_MINGW_ANSI_STDIO and __USE_MINGW_STRTOX.)
+              mingw* | windows*)  gl_cv_func_strtof_works="guessing no (underflow problem)" ;;
               *)                  gl_cv_func_strtof_works="$gl_cross_guess_normal" ;;
             esac
            ])
@@ -129,6 +169,16 @@ numeric_equal (float x, float y)
       *yes) ;;
       *)
         REPLACE_STRTOF=1
+        case "$gl_cv_func_strtof_works" in
+          *"no (underflow problem)")
+            AC_DEFINE([STRTOF_HAS_UNDERFLOW_BUG], [1],
+              [Define to 1 if strtof does not set errno upon flush-to-zero underflow.])
+            ;;
+          *"no (gradual underflow problem)")
+            AC_DEFINE([STRTOF_HAS_GRADUAL_UNDERFLOW_PROBLEM], [1],
+              [Define to 1 if strtof does not set errno upon gradual underflow.])
+            ;;
+        esac
         ;;
     esac
   fi
index 4439b602661d74b775aea1774512153411f6cceb..1d46bcd822aa6ae66936da5d2b6221ebf74b66f1 100644 (file)
@@ -1,5 +1,5 @@
 # strtold.m4
-# serial 9
+# serial 10
 dnl Copyright (C) 2002-2003, 2006-2024 Free Software Foundation, Inc.
 dnl This file is free software; the Free Software Foundation
 dnl gives unlimited permission to copy and/or distribute it,
@@ -17,8 +17,10 @@ AC_DEFUN([gl_FUNC_STRTOLD],
     AC_CACHE_CHECK([whether strtold obeys POSIX], [gl_cv_func_strtold_works],
       [AC_RUN_IFELSE([AC_LANG_PROGRAM([[
 #include <stdlib.h>
+#include <float.h>
 #include <math.h>
 #include <errno.h>
+#include <string.h>
 /* Compare two numbers with ==.
    This is a separate function because IRIX 6.5 "cc -O" miscompiles an
    'x == x' test.  */
@@ -80,10 +82,29 @@ numeric_equal (long double x, long double y)
     char *term;
     long double value = strtold (string, &term);
     if (numeric_equal (value, value) || term != (string + 3))
+      result |= 16;
+  }
+#ifndef _MSC_VER /* On MSVC, this is expected behaviour.  */
+  {
+    /* In Cygwin 2.9 and mingw 5.0, strtold does not set errno upon
+       gradual underflow.  */
+# if LDBL_MAX_EXP > 10000
+    const char *string = "1e-4950";
+# else
+    const char *string = "1e-320";
+# endif
+    char *term;
+    long double value;
+    errno = 0;
+    value = strtold (string, &term);
+    if (term != (string + strlen (string))
+        || (value > 0.0L && value <= LDBL_MIN && errno != ERANGE))
       result |= 32;
   }
+#endif
   {
-    /* In Cygwin 2.9, strtold does not set errno upon underflow.  */
+    /* In Cygwin 2.9, strtold does not set errno upon
+       flush-to-zero underflow.  */
     const char *string = "1E-100000";
     char *term;
     long double value;
@@ -95,10 +116,15 @@ numeric_equal (long double x, long double y)
   return result;
 ]])],
         [gl_cv_func_strtold_works=yes],
-        [if expr $? '>=' 64 >/dev/null; then
+        [result=$?
+         if expr $result '>=' 64 >/dev/null; then
            gl_cv_func_strtold_works="no (underflow problem)"
          else
-           gl_cv_func_strtold_works=no
+           if expr $result '>=' 32 >/dev/null; then
+             gl_cv_func_strtold_works="no (gradual underflow problem)"
+           else
+             gl_cv_func_strtold_works=no
+           fi
          fi
         ],
         [dnl The last known bugs in glibc strtold(), as of this writing,
@@ -119,6 +145,10 @@ numeric_equal (long double x, long double y)
               *-musl* | midipix*) gl_cv_func_strtold_works="guessing yes" ;;
                                   # Guess 'no (underflow problem)' on Cygwin.
               cygwin*)            gl_cv_func_strtold_works="guessing no (underflow problem)" ;;
+                                  # Guess 'no (gradual underflow problem)' on mingw.
+                                  # (Although the results may vary depending on
+                                  # __USE_MINGW_ANSI_STDIO.)
+              mingw* | windows*)  gl_cv_func_strtold_works="guessing no (gradual underflow problem)" ;;
               *)                  gl_cv_func_strtold_works="$gl_cross_guess_normal" ;;
             esac
            ])
@@ -131,7 +161,11 @@ numeric_equal (long double x, long double y)
         case "$gl_cv_func_strtold_works" in
           *"no (underflow problem)")
             AC_DEFINE([STRTOLD_HAS_UNDERFLOW_BUG], [1],
-              [Define to 1 if strtold does not set errno upon underflow.])
+              [Define to 1 if strtold does not set errno upon flush-to-zero underflow.])
+            ;;
+          *"no (gradual underflow problem)")
+            AC_DEFINE([STRTOLD_HAS_GRADUAL_UNDERFLOW_PROBLEM], [1],
+              [Define to 1 if strtold does not set errno upon gradual underflow.])
             ;;
         esac
         ;;