From: Bruno Haible Date: Tue, 23 Jul 2024 10:40:19 +0000 (+0200) Subject: strtof, strtod, strtold: Fix underflow behaviour of system function. X-Git-Url: https://gitweb.git.savannah.gnu.org/gitweb/?a=commitdiff_plain;h=05399873d9bea264c8b55e5ea62489ca7906a368;p=gnulib.git 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. --- diff --git a/ChangeLog b/ChangeLog index f0c55a0d60..2986e25d79 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,21 @@ +2024-07-23 Bruno Haible + + 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 strtof: Use the system's strtof() if available. diff --git a/lib/strtod.c b/lib/strtod.c index 8847cae940..bece59fd68 100644 --- a/lib/strtod.c +++ b/lib/strtod.c @@ -40,7 +40,13 @@ #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 @@ -82,7 +89,13 @@ #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; diff --git a/m4/strtod.m4 b/m4/strtod.m4 index 3f8bacc01a..2ccbf3139a 100644 --- a/m4/strtod.m4 +++ b/m4/strtod.m4 @@ -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 +#include #include #include /* 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 diff --git a/m4/strtof.m4 b/m4/strtof.m4 index 72c30b3d31..e37310ebde 100644 --- a/m4/strtof.m4 +++ b/m4/strtof.m4 @@ -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 +#include #include #include /* 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 diff --git a/m4/strtold.m4 b/m4/strtold.m4 index 4439b60266..1d46bcd822 100644 --- a/m4/strtold.m4 +++ b/m4/strtold.m4 @@ -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 +#include #include #include +#include /* 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 ;;