+2026-05-02 Bruno Haible <bruno@clisp.org>
+
+ strtof, strtod, strtold: Return NaNs with correct sign.
+ Reported by Mohammad-Reza Nabipoor <mnabipoor@gnu.org> in
+ <https://lists.gnu.org/archive/html/bug-gnulib/2026-04/msg00136.html>.
+ * m4/strtof.m4 (gl_FUNC_STRTOF): Add test whether strtod works on signed
+ NaNs.
+ * m4/strtod.m4 (gl_FUNC_STRTOD): Likewise.
+ * m4/strtold.m4 (gl_FUNC_STRTOLD): Likewise.
+ * lib/strtod.c: Include isnan?-nolibm.h.
+ (HAS_MINUS_NAN_BUG): New macro.
+ (STRTOD): Add separate implementation for HAS_MINUS_NAN_BUG.
+ * modules/strtof (Files): Add m4/signbit.m4.
+ (Depends-on): Add isnanf-nolibm, signbit-no-c++.
+ * modules/strtod (Files): Add m4/signbit.m4.
+ (Depends-on): Add isnand-nolibm, signbit-no-c++.
+ * modules/strtold (Files): Add m4/signbit.m4.
+ (Depends-on): Add isnanl-nolibm, signbit-no-c++.
+ * tests/test-strtof.h (test_function): Enable the test of sign bits of
+ NaN.
+ * tests/test-strtod.h (test_function): Likewise.
+ * tests/test-strtold.h (test_function): Likewise.
+ * doc/posix-functions/strtof.texi: Document the bug regarding "-nan".
+ * doc/posix-functions/strtod.texi: Likewise.
+ * doc/posix-functions/strtold.texi: Likewise.
+
2026-05-02 Bruno Haible <bruno@clisp.org>
pthread_sigmask tests: Fix a build failure on NetBSD.
This function misparses @samp{nan(} on some platforms:
macOS 10.6.6.
+@item
+This function returns @samp{NaN} for @samp{-NaN} on some platforms:
+glibc 2.27, musl libc.
+
@item
This function fails to parse C99 hexadecimal floating point on some
platforms:
platforms:
glibc 2.7, mingw, MSVC 14.
+@item
+This function returns @samp{NaN} for @samp{-NaN} on some platforms:
+glibc 2.27, musl libc.
+
@item
This function fails to correctly parse very long strings on some
platforms:
the wrong end pointer on some platforms:
glibc-2.3.2, mingw, Haiku.
+@item
+This function returns @samp{NaN} for @samp{-NaN} on some platforms:
+glibc 2.27, musl libc.
+
@item
This function fails to parse C99 hexadecimal floating point on some
platforms:
#include "c-ctype.h"
+#if defined USE_FLOAT
+# include "isnanf-nolibm.h"
+#elif defined USE_LONG_DOUBLE
+# include "isnanl-nolibm.h"
+#else
+# include "isnand-nolibm.h"
+#endif
+
#undef MIN
#undef MAX
#if defined USE_FLOAT
# define HAVE_UNDERLYING_STRTOD HAVE_STRTOF
# endif
# define HAS_GRADUAL_UNDERFLOW_PROBLEM STRTOF_HAS_GRADUAL_UNDERFLOW_PROBLEM
+# define HAS_MINUS_NAN_BUG STRTOF_HAS_MINUS_NAN_BUG
# define DOUBLE float
# define MIN FLT_MIN
# define MAX FLT_MAX
# define HAVE_UNDERLYING_STRTOD HAVE_STRTOLD
# endif
# define HAS_GRADUAL_UNDERFLOW_PROBLEM STRTOLD_HAS_GRADUAL_UNDERFLOW_PROBLEM
+# define HAS_MINUS_NAN_BUG STRTOLD_HAS_MINUS_NAN_BUG
# define DOUBLE long double
# define MIN LDBL_MIN
# define MAX LDBL_MAX
# define HAVE_UNDERLYING_STRTOD 1
# endif
# define HAS_GRADUAL_UNDERFLOW_PROBLEM STRTOD_HAS_GRADUAL_UNDERFLOW_PROBLEM
+# define HAS_MINUS_NAN_BUG STRTOD_HAS_MINUS_NAN_BUG
# define DOUBLE double
# define MIN DBL_MIN
# define MAX DBL_MAX
return isspace (uc) != 0;
}
+#if HAS_MINUS_NAN_BUG
+
+/* The underlying implementation works fine, except for signed NaNs. */
+
+DOUBLE
+STRTOD (const char *nptr, char **endptr)
+# undef STRTOD
+# if defined USE_FLOAT
+# undef strtof
+# define STRTOD strtof
+# define ISNAN isnanf
+# elif defined USE_LONG_DOUBLE
+# undef strtold
+# define STRTOD strtold
+# define ISNAN isnanl
+# else
+# undef strtod
+# define STRTOD strtod
+# define ISNAN isnand
+# endif
+{
+ DOUBLE value = STRTOD (nptr, endptr);
+ if (ISNAN (value))
+ {
+ while (locale_isspace (*nptr))
+ nptr++;
+ if (*nptr == '-' && !signbit (value))
+ value = - value;
+ }
+ return value;
+}
+
+#else
+
/* Determine the decimal-point character according to the current locale. */
static char
decimal_point_char (void)
thread-safe on glibc systems and Mac OS X systems, but is not required
to be thread-safe by POSIX. sprintf(), however, is thread-safe.
localeconv() is rarely thread-safe. */
-#if HAVE_NL_LANGINFO && (__GLIBC__ || defined __UCLIBC__ || (defined __APPLE__ && defined __MACH__))
+# if HAVE_NL_LANGINFO && (__GLIBC__ || defined __UCLIBC__ || (defined __APPLE__ && defined __MACH__))
point = nl_langinfo (RADIXCHAR);
-#elif 1
+# elif 1
char pointbuf[5];
sprintf (pointbuf, "%#.0f", 1.0);
point = &pointbuf[1];
-#else
+# else
point = localeconv () -> decimal_point;
-#endif
+# endif
/* The decimal point is always a single byte: either '.' or ','. */
return (point[0] != '\0' ? point[0] : '.');
}
-#if !USE_LDEXP
+# if !USE_LDEXP
#undef LDEXP
#define LDEXP dummy_ldexp
/* A dummy definition that will never be invoked. */
abort ();
return L_(0.0);
}
-#endif
+# endif
/* Return X * BASE**EXPONENT. Return an extreme value and set errno
to ERANGE if underflow or overflow occurs. */
static DOUBLE
minus_zero (void)
{
-#if defined __hpux || defined __ICC
+# if defined __hpux || defined __ICC
return -MIN * MIN;
-#else
+# else
return -0.0;
-#endif
+# endif
}
/* 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)
-#if HAVE_UNDERLYING_STRTOD
-# if defined USE_FLOAT
-# undef strtof
-# elif defined USE_LONG_DOUBLE
-# undef strtold
-# 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)
+# if HAVE_UNDERLYING_STRTOD
+# if defined USE_FLOAT
+# undef strtof
+# elif defined USE_LONG_DOUBLE
+# undef strtold
+# 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
-#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. */
{
return minus_zero ();
return negative ? -num : num;
}
+
+#endif
# strtod.m4
-# serial 32
+# serial 33
dnl Copyright (C) 2002-2003, 2006-2026 Free Software Foundation, Inc.
dnl This file is free software; the Free Software Foundation
dnl gives unlimited permission to copy and/or distribute it,
esac
;;
esac
+ if test $REPLACE_STRTOD = 0; then
+ gl_DOUBLE_SIGN_LOCATION
+ AC_CACHE_CHECK([whether strtod works on signed NaNs],
+ [gl_cv_func_strtod_nan_works],
+ [AC_RUN_IFELSE([AC_LANG_PROGRAM([[
+#include <stdlib.h>
+]], [[
+ int result = 0;
+ # define NWORDS \
+ ((sizeof (double) + sizeof (unsigned int) - 1) / sizeof (unsigned int))
+ union { double value; unsigned int word[NWORDS]; } m;
+ {
+ /* On glibc <= 2.27 and Alpine Linux, strtod("-nan") returns a NaN with a
+ wrong sign bit. */
+ const char *string = "-nan";
+ char *term;
+ m.value = strtod (string, &term);
+ if (((m.word[DBL_SIGNBIT_WORD] >> DBL_SIGNBIT_BIT) & 1) == 0)
+ result |= 1;
+ }
+ return result;
+]])],
+ [gl_cv_func_strtod_nan_works=yes],
+ [gl_cv_func_strtod_nan_works=no],
+ [case "$host_os" in
+ # Guess no on glibc systems.
+ *-gnu* | gnu*) gl_cv_func_strtod_nan_works="guessing no" ;;
+ # Guess no on musl systems.
+ *-musl* | midipix*) gl_cv_func_strtod_nan_works="guessing no" ;;
+ *) gl_cv_func_strtod_nan_works="$gl_cross_guess_normal" ;;
+ esac
+ ])
+ ])
+ case "$gl_cv_func_strtod_nan_works" in
+ *yes) ;;
+ *)
+ REPLACE_STRTOD=1
+ AC_DEFINE([STRTOD_HAS_MINUS_NAN_BUG], [1],
+ [Define to 1 if strtod may return a NaN with a wrong sign bit.])
+ ;;
+ esac
+ fi
fi
])
# strtof.m4
-# serial 5
+# serial 6
dnl Copyright (C) 2002-2003, 2006-2026 Free Software Foundation, Inc.
dnl This file is free software; the Free Software Foundation
dnl gives unlimited permission to copy and/or distribute it,
esac
;;
esac
+ if test $REPLACE_STRTOF = 0; then
+ gl_FLOAT_SIGN_LOCATION
+ AC_CACHE_CHECK([whether strtof works on signed NaNs],
+ [gl_cv_func_strtof_nan_works],
+ [AC_RUN_IFELSE([AC_LANG_PROGRAM([[
+#include <stdlib.h>
+]], [[
+ int result = 0;
+ # define NWORDS \
+ ((sizeof (float) + sizeof (unsigned int) - 1) / sizeof (unsigned int))
+ union { float value; unsigned int word[NWORDS]; } m;
+ {
+ /* On glibc <= 2.27 and Alpine Linux, strtof("-nan") returns a NaN with a
+ wrong sign bit. */
+ const char *string = "-nan";
+ char *term;
+ m.value = strtof (string, &term);
+ if (((m.word[FLT_SIGNBIT_WORD] >> FLT_SIGNBIT_BIT) & 1) == 0)
+ result |= 1;
+ }
+ return result;
+]])],
+ [gl_cv_func_strtof_nan_works=yes],
+ [gl_cv_func_strtof_nan_works=no],
+ [case "$host_os" in
+ # Guess no on glibc systems.
+ *-gnu* | gnu*) gl_cv_func_strtof_nan_works="guessing no" ;;
+ # Guess no on musl systems.
+ *-musl* | midipix*) gl_cv_func_strtof_nan_works="guessing no" ;;
+ *) gl_cv_func_strtof_nan_works="$gl_cross_guess_normal" ;;
+ esac
+ ])
+ ])
+ case "$gl_cv_func_strtof_nan_works" in
+ *yes) ;;
+ *)
+ REPLACE_STRTOF=1
+ AC_DEFINE([STRTOF_HAS_MINUS_NAN_BUG], [1],
+ [Define to 1 if strtof may return a NaN with a wrong sign bit.])
+ ;;
+ esac
+ fi
fi
])
# strtold.m4
-# serial 11
+# serial 12
dnl Copyright (C) 2002-2003, 2006-2026 Free Software Foundation, Inc.
dnl This file is free software; the Free Software Foundation
dnl gives unlimited permission to copy and/or distribute it,
esac
;;
esac
+ if test $REPLACE_STRTOLD = 0; then
+ gl_LONG_DOUBLE_SIGN_LOCATION
+ AC_CACHE_CHECK([whether strtold works on signed NaNs],
+ [gl_cv_func_strtold_nan_works],
+ [AC_RUN_IFELSE([AC_LANG_PROGRAM([[
+#include <stdlib.h>
+]], [[
+ int result = 0;
+ # define NWORDS \
+ ((sizeof (long double) + sizeof (unsigned int) - 1) / sizeof (unsigned int))
+ union { long double value; unsigned int word[NWORDS]; } m;
+ {
+ /* On glibc <= 2.27 and Alpine Linux, strtold("-nan") returns a NaN with a
+ wrong sign bit. */
+ const char *string = "-nan";
+ char *term;
+ m.value = strtold (string, &term);
+ if (((m.word[LDBL_SIGNBIT_WORD] >> LDBL_SIGNBIT_BIT) & 1) == 0)
+ result |= 1;
+ }
+ return result;
+]])],
+ [gl_cv_func_strtold_nan_works=yes],
+ [gl_cv_func_strtold_nan_works=no],
+ [case "$host_os" in
+ # Guess no on glibc systems.
+ *-gnu* | gnu*) gl_cv_func_strtold_nan_works="guessing no" ;;
+ # Guess no on musl systems.
+ *-musl* | midipix*) gl_cv_func_strtold_nan_works="guessing no" ;;
+ *) gl_cv_func_strtold_nan_works="$gl_cross_guess_normal" ;;
+ esac
+ ])
+ ])
+ case "$gl_cv_func_strtold_nan_works" in
+ *yes) ;;
+ *)
+ REPLACE_STRTOLD=1
+ AC_DEFINE([STRTOLD_HAS_MINUS_NAN_BUG], [1],
+ [Define to 1 if strtold may return a NaN with a wrong sign bit.])
+ ;;
+ esac
+ fi
fi
])
Files:
lib/strtod.c
m4/strtod.m4
+m4/signbit.m4
m4/ldexp.m4
Depends-on:
math-h [test $HAVE_STRTOD = 0 || test $REPLACE_STRTOD = 1]
bool [test $HAVE_STRTOD = 0 || test $REPLACE_STRTOD = 1]
isinf-no-c++ [test $HAVE_STRTOD = 0 || test $REPLACE_STRTOD = 1]
+isnand-nolibm [test $REPLACE_STRTOD = 1]
+signbit-no-c++ [test $REPLACE_STRTOD = 1]
configure.ac:
gl_FUNC_STRTOD
lib/strtof.c
lib/strtod.c
m4/strtof.m4
+m4/signbit.m4
m4/ldexpf.m4
Depends-on:
math-h [test $HAVE_STRTOF = 0 || test $REPLACE_STRTOF = 1]
bool [test $HAVE_STRTOF = 0 || test $REPLACE_STRTOF = 1]
isinf-no-c++ [test $HAVE_STRTOF = 0 || test $REPLACE_STRTOF = 1]
+isnanf-nolibm [test $REPLACE_STRTOF = 1]
+signbit-no-c++ [test $REPLACE_STRTOF = 1]
configure.ac:
gl_FUNC_STRTOF
lib/strtod.c
m4/strtold.m4
m4/math_h.m4
+m4/signbit.m4
m4/ldexpl.m4
Depends-on:
math-h [test $HAVE_STRTOLD = 0 || test $REPLACE_STRTOLD = 1]
bool [test $HAVE_STRTOLD = 0 || test $REPLACE_STRTOLD = 1]
isinf-no-c++ [test $HAVE_STRTOLD = 0 || test $REPLACE_STRTOLD = 1]
+isnanl-nolibm [test $REPLACE_STRTOLD = 1]
+signbit-no-c++ [test $REPLACE_STRTOLD = 1]
strtod [{ test $HAVE_STRTOLD = 0 || test $REPLACE_STRTOLD = 1; } && test $HAVE_SAME_LONG_DOUBLE_AS_DOUBLE = 1]
configure.ac:
#if 1 /* All known CPUs support NaNs. */
ASSERT (isnand (result1)); /* OpenBSD 4.0, mingw */
ASSERT (isnand (result2)); /* OpenBSD 4.0, mingw */
-# if 0
- /* Sign bits of NaN is a portability sticking point, not worth
- worrying about. */
- ASSERT (!!signbit (result1) != !!signbit (result2)); /* glibc-2.3.6, mingw */
-# endif
+ /* Sign bits of NaN are particularly hairy. */
+ ASSERT (!!signbit (result1) != !!signbit (result2)); /* glibc-2.27, mingw, musl libc */
ASSERT (ptr1 == input + 4); /* OpenBSD 4.0, Solaris 2.5.1, mingw */
ASSERT (ptr2 == input + 4); /* OpenBSD 4.0, Solaris 2.5.1, mingw */
ASSERT (errno == 0); /* HP-UX 11.11 */
#if 1 /* All known CPUs support NaNs. */
ASSERT (isnanf (result1)); /* OpenBSD 4.0, mingw */
ASSERT (isnanf (result2)); /* OpenBSD 4.0, mingw */
-# if 0
- /* Sign bits of NaN is a portability sticking point, not worth
- worrying about. */
- ASSERT (!!signbit (result1) != !!signbit (result2)); /* glibc-2.3.6, mingw */
-# endif
+ /* Sign bits of NaN are particularly hairy. */
+ ASSERT (!!signbit (result1) != !!signbit (result2)); /* glibc-2.27, mingw, musl libc */
ASSERT (ptr1 == input + 4); /* OpenBSD 4.0, Solaris 2.5.1, mingw */
ASSERT (ptr2 == input + 4); /* OpenBSD 4.0, Solaris 2.5.1, mingw */
ASSERT (errno == 0); /* HP-UX 11.11 */
#if 1 /* All known CPUs support NaNs. */
ASSERT (isnanl (result1));
ASSERT (isnanl (result2));
-# if 0
- /* Sign bits of NaN is a portability sticking point, not worth
- worrying about. */
- ASSERT (!!signbit (result1) != !!signbit (result2));
-# endif
+ /* Sign bits of NaN are particularly hairy. */
+ ASSERT (!!signbit (result1) != !!signbit (result2)); /* glibc-2.27, mingw, musl libc */
ASSERT (ptr1 == input + 4);
ASSERT (ptr2 == input + 4);
ASSERT (errno == 0); /* HP-UX 11.31/ia64 */