+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.
#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
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:
# 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
# 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. */
++s;
num = STRTOD (s, &endbuf);
+ SET_ERRNO_UPON_GRADUAL_UNDERFLOW (num);
end = endbuf;
if (c_isdigit (s[*s == radixchar]))
{
dup[p - s] = '\0';
num = STRTOD (dup, &endbuf);
+ SET_ERRNO_UPON_GRADUAL_UNDERFLOW (num);
saved_errno = errno;
free (dup);
errno = saved_errno;
{
dup[e - s] = '\0';
num = STRTOD (dup, &endbuf);
+ SET_ERRNO_UPON_GRADUAL_UNDERFLOW (num);
saved_errno = errno;
free (dup);
errno = saved_errno;
# 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,
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 ==.
char *term;
strtod (string, &term);
if (term != string && *(term - 1) == 0)
- result |= 2;
+ result |= 1;
}
{
/* Older glibc and Cygwin mis-parse "-0x". */
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. */
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,
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()". */
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(". */
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],
[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" ;;
*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
# 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,
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 ==.
char *term;
strtof (string, &term);
if (term != string && *(term - 1) == 0)
- result |= 2;
+ result |= 1;
}
{
/* Older glibc and Cygwin mis-parse "-0x". */
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. */
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,
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()". */
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(". */
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],
[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
])
*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
# 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,
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. */
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;
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,
*-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
])
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
;;