The behaviour of math functions is "specified" (or, more exactly, left to the implementation) in ISO C 23 § 7.12.1.(6).
In the case of the strtof, strtod, strtold functions, a test program (attached: foo.c) showed that these functions show the following behaviour: * Errors are generally reported through errno. *Not* through <fenv.h> exceptions. Regardless of math_errhandling. * Generally (aside of bugs on specific platforms) flush-to-zero underflow is reported by errno = ERANGE. * Generally (aside of bugs on specific platforms) gradual underflow is handled properly (i.e. produces a non-zero result with absolute value < FLT_MIN | DBL_MIN | LDBL_MIN), and on all platforms except MSVC it is reported by errno = ERANGE. Gnulib's replacement function, when not using the system function, fails to set errno upon gradual underflow. This is allowed by ISO C, but still makes the the replacement function work worse than what glibc does. Patch 0001 fixes that. For strtof, the code intended to invoke the system's strtof if possible. However, this did not work, as the C macro HAVE_STRTOF was never defined. Patch 0002 fixes that. The foo.c test shows no clear rule of thumb whether a system's function sets errno = ERANGE in the two cases. Therefore patch 0003 determines this at configure time and uses the information in the implementation of the replacement function. Patches 0004, 0005, 0006 correct the documentation, improve comments, and add an overflow handling test (for mingw, which is buggy in that area as well). Patch 0007, finally, adds some more overflow tests, because in mingw strtof, "1e50" and "1e1000000" produce different behaviour(!). 2024-07-23 Bruno Haible <br...@clisp.org> strtof, strtod, strtold tests: Strengthen tests. * tests/test-strtof.h (test_function): Add another overflow test. * tests/test-strtod.h (test_function): Likewise. * tests/test-strtold.h (test_function): Likewise. 2024-07-23 Bruno Haible <br...@clisp.org> strtold: Revisit underflow behaviour. * doc/posix-functions/strtold.texi: Mention broken mingw 5.0. Mention that gradual underflow does not count as an error on MSVC. * tests/test-strtold.h (test_function): Add a gradual underflow test. Check the sign in case of flush-to-zero underflow. strtod: Revisit underflow behaviour. * doc/posix-functions/strtod.texi: Mention the macOS bug. Mention that gradual underflow does not count as an error on Cygwin 2.9 and MSVC. * m4/strtod.m4 (gl_FUNC_STRTOD): Update comment. * tests/test-strtod.h (test_function): Add a gradual underflow test. Check the sign in case of flush-to-zero underflow. strtof: Revisit underflow behaviour. * doc/posix-functions/strtof.texi: Mention the macOS bug. Mention the mingw overflow bug. Mention the underflow bugs on Cygwin 2.9 and mingw. Mention that gradual underflow does not count as an error on Cygwin 2.9, mingw, MSVC. * m4/strtof.m4 (gl_FUNC_STRTOF): Test against the mingw overflow bug. * tests/test-strtof.h (test_function): Add a gradual underflow test. Check the sign in case of flush-to-zero underflow. 2024-07-23 Bruno Haible <br...@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 <br...@clisp.org> strtof: Use the system's strtof() if available. * m4/strtof.m4 (gl_FUNC_STRTOF): Define HAVE_STRTOF if strtof exists. 2024-07-23 Bruno Haible <br...@clisp.org> strtof, strtod, strtold: Set errno upon gradual underflow. * lib/strtod.c (scale_radix_exp): If the result is a denormalized number, set errno to ERANGE.
#include <errno.h> #include <fenv.h> #include <float.h> #include <math.h> #include <stdio.h> #include <stdlib.h> volatile double z; volatile long double lz; int main () { /* strtof */ { errno = 0; feclearexcept (FE_ALL_EXCEPT); char *end; z = strtof ("1e50", &end); int err = errno; printf ("strtof +overflow = %d %d %g\n", fetestexcept (FE_OVERFLOW) ? 1 : 0, err == ERANGE, z); } { errno = 0; feclearexcept (FE_ALL_EXCEPT); char *end; z = strtof ("-1e50", &end); int err = errno; printf ("strtof -overflow = %d %d %g\n", fetestexcept (FE_OVERFLOW) ? 1 : 0, err == ERANGE, z); } { errno = 0; feclearexcept (FE_ALL_EXCEPT); char *end; z = strtof ("1e-40", &end); int err = errno; printf ("strtof +underflow = %d %d %g\n", fetestexcept (FE_UNDERFLOW) ? 1 : 0, err == ERANGE, z); } { errno = 0; feclearexcept (FE_ALL_EXCEPT); char *end; z = strtof ("-1e-40", &end); int err = errno; printf ("strtof -underflow = %d %d %g\n", fetestexcept (FE_UNDERFLOW) ? 1 : 0, err == ERANGE, z); } { errno = 0; feclearexcept (FE_ALL_EXCEPT); char *end; z = strtof ("1e-50", &end); int err = errno; printf ("strtof +underflow_0 = %d %d %g\n", fetestexcept (FE_UNDERFLOW) ? 1 : 0, err == ERANGE, z); } { errno = 0; feclearexcept (FE_ALL_EXCEPT); char *end; z = strtof ("-1e-50", &end); int err = errno; printf ("strtof -underflow_0 = %d %d %g\n", fetestexcept (FE_UNDERFLOW) ? 1 : 0, err == ERANGE, z); } /* strtod */ { errno = 0; feclearexcept (FE_ALL_EXCEPT); char *end; z = strtod ("1e500", &end); int err = errno; printf ("strtod +overflow = %d %d %g\n", fetestexcept (FE_OVERFLOW) ? 1 : 0, err == ERANGE, z); } { errno = 0; feclearexcept (FE_ALL_EXCEPT); char *end; z = strtod ("-1e500", &end); int err = errno; printf ("strtod -overflow = %d %d %g\n", fetestexcept (FE_OVERFLOW) ? 1 : 0, err == ERANGE, z); } { errno = 0; feclearexcept (FE_ALL_EXCEPT); char *end; z = strtod ("1e-320", &end); int err = errno; printf ("strtod +underflow = %d %d %g\n", fetestexcept (FE_UNDERFLOW) ? 1 : 0, err == ERANGE, z); } { errno = 0; feclearexcept (FE_ALL_EXCEPT); char *end; z = strtod ("-1e-320", &end); int err = errno; printf ("strtod -underflow = %d %d %g\n", fetestexcept (FE_UNDERFLOW) ? 1 : 0, err == ERANGE, z); } { errno = 0; feclearexcept (FE_ALL_EXCEPT); char *end; z = strtod ("1e-400", &end); int err = errno; printf ("strtod +underflow_0 = %d %d %g\n", fetestexcept (FE_UNDERFLOW) ? 1 : 0, err == ERANGE, z); } { errno = 0; feclearexcept (FE_ALL_EXCEPT); char *end; z = strtod ("-1e-400", &end); int err = errno; printf ("strtod -underflow_0 = %d %d %g\n", fetestexcept (FE_UNDERFLOW) ? 1 : 0, err == ERANGE, z); } /* strtold */ { errno = 0; feclearexcept (FE_ALL_EXCEPT); char *end; lz = strtold ("1e6000", &end); int err = errno; printf ("strtold +overflow = %d %d %Lg\n", fetestexcept (FE_OVERFLOW) ? 1 : 0, err == ERANGE, lz); } { errno = 0; feclearexcept (FE_ALL_EXCEPT); char *end; lz = strtold ("-1e6000", &end); int err = errno; printf ("strtold -overflow = %d %d %Lg\n", fetestexcept (FE_OVERFLOW) ? 1 : 0, err == ERANGE, lz); } { errno = 0; feclearexcept (FE_ALL_EXCEPT); char *end; #if LDBL_MAX_EXP > 10000 lz = strtold ("1e-4950", &end); #else lz = strtold ("1e-320", &end); #endif int err = errno; printf ("strtold +underflow = %d %d %Lg\n", fetestexcept (FE_UNDERFLOW) ? 1 : 0, err == ERANGE, lz); } { errno = 0; feclearexcept (FE_ALL_EXCEPT); char *end; #if LDBL_MAX_EXP > 10000 lz = strtold ("-1e-4950", &end); #else lz = strtold ("-1e-320", &end); #endif int err = errno; printf ("strtold -underflow = %d %d %Lg\n", fetestexcept (FE_UNDERFLOW) ? 1 : 0, err == ERANGE, lz); } { errno = 0; feclearexcept (FE_ALL_EXCEPT); char *end; #if LDBL_MAX_EXP > 10000 lz = strtold ("1e-6000", &end); #else lz = strtold ("1e-400", &end); #endif int err = errno; printf ("strtold +underflow_0 = %d %d %Lg\n", fetestexcept (FE_UNDERFLOW) ? 1 : 0, err == ERANGE, lz); } { errno = 0; feclearexcept (FE_ALL_EXCEPT); char *end; #if LDBL_MAX_EXP > 10000 lz = strtold ("-1e-6000", &end); #else lz = strtold ("-1e-400", &end); #endif int err = errno; printf ("strtold -underflow_0 = %d %d %Lg\n", fetestexcept (FE_UNDERFLOW) ? 1 : 0, err == ERANGE, lz); } return 0; } /* * gcc -Wall foo.c -lm */ /* glibc/x86_64: strtof +overflow = 1 1 inf strtof -overflow = 1 1 -inf strtof +underflow = 1 1 9.99995e-41 strtof -underflow = 1 1 -9.99995e-41 strtof +underflow_0 = 1 1 0 strtof -underflow_0 = 1 1 -0 strtod +overflow = 1 1 inf strtod -overflow = 1 1 -inf strtod +underflow = 1 1 9.99989e-321 strtod -underflow = 1 1 -9.99989e-321 strtod +underflow_0 = 1 1 0 strtod -underflow_0 = 1 1 -0 strtold +overflow = 1 1 inf strtold -overflow = 1 1 -inf strtold +underflow = 1 1 1.09356e-4950 strtold -underflow = 1 1 -1.09356e-4950 strtold +underflow_0 = 1 1 0 strtold -underflow_0 = 1 1 -0 glibc/alpha, glibc/arm64, glibc/riscv64, glibc/s390x, glibc/sparc64: strtof +overflow = 1 1 inf strtof -overflow = 1 1 -inf strtof +underflow = 1 1 9.99995e-41 strtof -underflow = 1 1 -9.99995e-41 strtof +underflow_0 = 1 1 0 strtof -underflow_0 = 1 1 -0 strtod +overflow = 1 1 inf strtod -overflow = 1 1 -inf strtod +underflow = 1 1 9.99989e-321 strtod -underflow = 1 1 -9.99989e-321 strtod +underflow_0 = 1 1 0 strtod -underflow_0 = 1 1 -0 strtold +overflow = 1 1 inf strtold -overflow = 1 1 -inf strtold +underflow = 1 1 1e-4950 strtold -underflow = 1 1 -1e-4950 strtold +underflow_0 = 1 1 0 strtold -underflow_0 = 1 1 -0 glibc/m68k: strtof +overflow = 0 1 inf strtof -overflow = 0 1 -inf strtof +underflow = 0 1 9.99995e-41 strtof -underflow = 0 1 -9.99995e-41 strtof +underflow_0 = 0 1 0 strtof -underflow_0 = 0 1 -0 strtod +overflow = 0 1 inf strtod -overflow = 0 1 -inf strtod +underflow = 0 1 9.99989e-321 strtod -underflow = 0 1 -9.99989e-321 strtod +underflow_0 = 0 1 0 strtod -underflow_0 = 0 1 -0 strtold +overflow = 0 1 inf strtold -overflow = 0 1 -inf strtold +underflow = 0 1 9.113e-4951 strtold -underflow = 0 1 -9.113e-4951 strtold +underflow_0 = 0 1 0 strtold -underflow_0 = 0 1 -0 glibc/mips64: strtof +overflow = 1 1 inf strtof -overflow = 1 1 -inf strtof +underflow = 1 1 9.99995e-41 strtof -underflow = 1 1 -9.99995e-41 strtof +underflow_0 = 1 1 0 strtof -underflow_0 = 1 1 -0 strtod +overflow = 1 1 inf strtod -overflow = 1 1 -inf strtod +underflow = 1 1 9.99989e-321 strtod -underflow = 1 1 -9.99989e-321 strtod +underflow_0 = 1 1 0 strtod -underflow_0 = 1 1 -0 strtold +overflow = 1 1 inf strtold -overflow = 1 1 -inf strtold +underflow = 1 1 9.99989e-321 strtold -underflow = 1 1 -9.99989e-321 strtold +underflow_0 = 1 1 0 strtold -underflow_0 = 1 1 -0 musl libc/x86_64: strtof +overflow = 1 1 inf strtof -overflow = 1 1 -inf strtof +underflow = 0 1 9.99995e-41 strtof -underflow = 0 1 -9.99995e-41 strtof +underflow_0 = 1 1 0 strtof -underflow_0 = 1 1 -0 strtod +overflow = 1 1 inf strtod -overflow = 1 1 -inf strtod +underflow = 0 1 9.99989e-321 strtod -underflow = 0 1 -9.99989e-321 strtod +underflow_0 = 1 1 0 strtod -underflow_0 = 1 1 -0 strtold +overflow = 1 1 inf strtold -overflow = 1 1 -inf strtold +underflow = 0 1 1.09356e-4950 strtold -underflow = 0 1 -1.09356e-4950 strtold +underflow_0 = 1 1 0 strtold -underflow_0 = 1 1 -0 macOS 12/arm64, Android/arm: strtof +overflow = 0 1 inf strtof -overflow = 0 1 -inf strtof +underflow = 0 1 9.99995e-41 strtof -underflow = 0 1 -9.99995e-41 strtof +underflow_0 = 0 1 0 strtof -underflow_0 = 0 1 -0 strtod +overflow = 0 1 inf strtod -overflow = 0 1 -inf strtod +underflow = 0 1 9.99989e-321 strtod -underflow = 0 1 -9.99989e-321 strtod +underflow_0 = 1 1 0 strtod -underflow_0 = 1 1 -0 strtold +overflow = 0 1 inf strtold -overflow = 0 1 -inf strtold +underflow = 0 1 9.99989e-321 strtold -underflow = 0 1 -9.99989e-321 strtold +underflow_0 = 1 1 0 strtold -underflow_0 = 1 1 -0 FreeBSD/x86_64, NetBSD/x86_64, OpenBSD/x86_64: strtof +overflow = 0 1 inf strtof -overflow = 0 1 -inf strtof +underflow = 0 1 9.99995e-41 strtof -underflow = 0 1 -9.99995e-41 strtof +underflow_0 = 0 1 0 strtof -underflow_0 = 0 1 -0 strtod +overflow = 0 1 inf strtod -overflow = 0 1 -inf strtod +underflow = 0 1 9.99989e-321 strtod -underflow = 0 1 -9.99989e-321 strtod +underflow_0 = 1 1 0 strtod -underflow_0 = 1 1 -0 strtold +overflow = 0 1 inf strtold -overflow = 0 1 -inf strtold +underflow = 0 1 1.09356e-4950 strtold -underflow = 0 1 -1.09356e-4950 strtold +underflow_0 = 0 1 0 strtold -underflow_0 = 0 1 -0 AIX/powerpc64: strtof +overflow = 1 1 INF strtof -overflow = 1 1 -INF strtof +underflow = 1 1 9.99995e-41 strtof -underflow = 1 1 -9.99995e-41 strtof +underflow_0 = 1 1 0 strtof -underflow_0 = 1 1 -0 strtod +overflow = 1 1 INF strtod -overflow = 1 1 -INF strtod +underflow = 1 1 9.99989e-321 strtod -underflow = 1 1 -9.99989e-321 strtod +underflow_0 = 1 1 0 strtod -underflow_0 = 1 1 -0 strtold +overflow = 1 1 INF strtold -overflow = 1 1 -INF strtold +underflow = 1 1 9.99989e-321 strtold -underflow = 1 1 -9.99989e-321 strtold +underflow_0 = 1 1 0 strtold -underflow_0 = 1 1 -0 Solaris 10/x86_64, Solaris 11/x86_64: strtof +overflow = 1 1 Inf strtof -overflow = 1 1 -Inf strtof +underflow = 1 1 9.99995e-41 strtof -underflow = 1 1 -9.99995e-41 strtof +underflow_0 = 1 1 0 strtof -underflow_0 = 1 1 -0 strtod +overflow = 1 1 Inf strtod -overflow = 1 1 -Inf strtod +underflow = 1 1 9.99989e-321 strtod -underflow = 1 1 -9.99989e-321 strtod +underflow_0 = 1 1 0 strtod -underflow_0 = 1 1 -0 strtold +overflow = 1 1 Inf strtold -overflow = 1 1 -Inf strtold +underflow = 1 1 1.09356e-4950 strtold -underflow = 1 1 -1.09356e-4950 strtold +underflow_0 = 1 1 0 strtold -underflow_0 = 1 1 -0 Solaris 10/sparc: strtof +overflow = 1 1 Inf strtof -overflow = 1 1 -Inf strtof +underflow = 1 1 9.99995e-41 strtof -underflow = 1 1 -9.99995e-41 strtof +underflow_0 = 1 1 0 strtof -underflow_0 = 1 1 -0 strtod +overflow = 1 1 Inf strtod -overflow = 1 1 -Inf strtod +underflow = 1 1 9.99989e-321 strtod -underflow = 1 1 -9.99989e-321 strtod +underflow_0 = 1 1 0 strtod -underflow_0 = 1 1 -0 strtold +overflow = 1 1 Inf strtold -overflow = 1 1 -Inf strtold +underflow = 1 1 1e-4950 strtold -underflow = 1 1 -1e-4950 strtold +underflow_0 = 1 1 0 strtold -underflow_0 = 1 1 -0 Cygwin 2.9.0/x86_64: strtof +overflow = 1 1 inf strtof -overflow = 1 1 -inf strtof +underflow = 1 0 9.99995e-41 strtof -underflow = 1 0 -9.99995e-41 strtof +underflow_0 = 1 0 0 strtof -underflow_0 = 1 0 -0 strtod +overflow = 0 1 inf strtod -overflow = 0 1 -inf strtod +underflow = 0 0 9.99989e-321 strtod -underflow = 0 0 -9.99989e-321 strtod +underflow_0 = 1 1 0 strtod -underflow_0 = 1 1 -0 strtold +overflow = 0 1 inf strtold -overflow = 0 1 -inf strtold +underflow = 0 0 1.09356e-4950 strtold -underflow = 0 0 -1.09356e-4950 strtold +underflow_0 = 0 0 0 strtold -underflow_0 = 0 0 -0 Cygwin 3.4.6/x86_64: strtof +overflow = 1 1 inf strtof -overflow = 1 1 -inf strtof +underflow = 1 1 9.99995e-41 strtof -underflow = 1 1 -9.99995e-41 strtof +underflow_0 = 1 1 0 strtof -underflow_0 = 1 1 -0 strtod +overflow = 0 1 inf strtod -overflow = 0 1 -inf strtod +underflow = 0 1 9.99989e-321 strtod -underflow = 0 1 -9.99989e-321 strtod +underflow_0 = 1 1 0 strtod -underflow_0 = 1 1 -0 strtold +overflow = 0 1 inf strtold -overflow = 0 1 -inf strtold +underflow = 0 1 1.09356e-4950 strtold -underflow = 0 1 -1.09356e-4950 strtold +underflow_0 = 0 1 0 strtold -underflow_0 = 0 1 -0 mingw 5/x86_64 without __USE_MINGW_ANSI_STDIO: strtof +overflow = 1 0 1.#INF strtof -overflow = 1 0 -1.#INF strtof +underflow = 1 0 9.99995e-041 strtof -underflow = 1 0 -9.99995e-041 strtof +underflow_0 = 1 0 0 strtof -underflow_0 = 1 0 -0 strtod +overflow = 0 1 1.#INF strtod -overflow = 0 1 -1.#INF strtod +underflow = 0 1 9.99989e-321 strtod -underflow = 0 1 -9.99989e-321 strtod +underflow_0 = 0 1 0 strtod -underflow_0 = 0 1 -0 strtold +overflow = 0 1 3.43181r-317 strtold -overflow = 0 1 3.43181r-317 strtold +underflow = 0 0 3.43181r-317 strtold -underflow = 0 0 3.43181r-317 strtold +underflow_0 = 0 1 3.43181r-317 strtold -underflow_0 = 0 1 3.43181r-317 mingw 5/x86_64 with __USE_MINGW_ANSI_STDIO, mingw 10/x86_64: strtof +overflow = 0 1 inf strtof -overflow = 0 1 -inf strtof +underflow = 0 1 9.99995e-41 strtof -underflow = 0 1 -9.99995e-41 strtof +underflow_0 = 0 1 0 strtof -underflow_0 = 0 1 -0 strtod +overflow = 0 1 inf strtod -overflow = 0 1 -inf strtod +underflow = 0 1 9.99989e-321 strtod -underflow = 0 1 -9.99989e-321 strtod +underflow_0 = 0 1 0 strtod -underflow_0 = 0 1 -0 strtold +overflow = 0 1 inf strtold -overflow = 0 1 -inf strtold +underflow = 0 1 1.09356e-4950 strtold -underflow = 0 1 -1.09356e-4950 strtold +underflow_0 = 0 1 0 strtold -underflow_0 = 0 1 -0 MSVC/x86_64: strtof +overflow = 0 1 inf strtof -overflow = 0 1 -inf strtof +underflow = 0 0 9.99995e-41 strtof -underflow = 0 0 -9.99995e-41 strtof +underflow_0 = 0 1 0 strtof -underflow_0 = 0 1 -0 strtod +overflow = 0 1 inf strtod -overflow = 0 1 -inf strtod +underflow = 0 0 9.99989e-321 strtod -underflow = 0 0 -9.99989e-321 strtod +underflow_0 = 0 1 0 strtod -underflow_0 = 0 1 -0 strtold +overflow = 0 1 inf strtold -overflow = 0 1 -inf strtold +underflow = 0 0 9.99989e-321 strtold -underflow = 0 0 -9.99989e-321 strtold +underflow_0 = 0 1 0 strtold -underflow_0 = 0 1 -0 */
>From 6be595445812bbcdf5d11c3f439a3eb3dbd7069a Mon Sep 17 00:00:00 2001 From: Bruno Haible <br...@clisp.org> Date: Tue, 23 Jul 2024 10:39:28 +0200 Subject: [PATCH 1/7] strtof, strtod, strtold: Set errno upon gradual underflow. * lib/strtod.c (scale_radix_exp): If the result is a denormalized number, set errno to ERANGE. --- ChangeLog | 6 ++++++ lib/strtod.c | 13 +++++++++++-- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/ChangeLog b/ChangeLog index cf20d60b76..90fdb4dee0 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,9 @@ +2024-07-23 Bruno Haible <br...@clisp.org> + + strtof, strtod, strtold: Set errno upon gradual underflow. + * lib/strtod.c (scale_radix_exp): If the result is a denormalized + number, set errno to ERANGE. + 2024-07-22 Collin Funk <collin.fu...@gmail.com> sys_socket tests: Improve tests for macro definitions. diff --git a/lib/strtod.c b/lib/strtod.c index e218a46f71..8847cae940 100644 --- a/lib/strtod.c +++ b/lib/strtod.c @@ -158,11 +158,20 @@ scale_radix_exp (DOUBLE x, int radix, long int exponent) { if (e < 0) { - while (e++ != 0) + for (;;) { + if (e++ == 0) + { + if (r < MIN && r > -MIN) + /* Gradual underflow, resulting in a denormalized + number. */ + errno = ERANGE; + break; + } r /= radix; - if (r == 0 && x != 0) + if (r == 0) { + /* Flush-to-zero underflow. */ errno = ERANGE; break; } -- 2.34.1
>From ae0879dafc29a1be482c5db96ddf0e0a1791eb9a Mon Sep 17 00:00:00 2001 From: Bruno Haible <br...@clisp.org> Date: Tue, 23 Jul 2024 12:40:15 +0200 Subject: [PATCH 2/7] strtof: Use the system's strtof() if available. * m4/strtof.m4 (gl_FUNC_STRTOF): Define HAVE_STRTOF if strtof exists. --- ChangeLog | 5 +++++ m4/strtof.m4 | 4 +++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/ChangeLog b/ChangeLog index 90fdb4dee0..8c84fd22d0 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,8 @@ +2024-07-23 Bruno Haible <br...@clisp.org> + + strtof: Use the system's strtof() if available. + * m4/strtof.m4 (gl_FUNC_STRTOF): Define HAVE_STRTOF if strtof exists. + 2024-07-23 Bruno Haible <br...@clisp.org> strtof, strtod, strtold: Set errno upon gradual underflow. diff --git a/m4/strtof.m4 b/m4/strtof.m4 index bdd3df8f48..72c30b3d31 100644 --- a/m4/strtof.m4 +++ b/m4/strtof.m4 @@ -1,5 +1,5 @@ # strtof.m4 -# serial 1 +# serial 2 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, @@ -19,6 +19,8 @@ AC_DEFUN([gl_FUNC_STRTOF] HAVE_STRTOF=0 fi if test $HAVE_STRTOF = 1; then + AC_DEFINE([HAVE_STRTOF], [1], + [Define to 1 if you have the 'strtof' function.]) AC_CACHE_CHECK([whether strtof obeys C99], [gl_cv_func_strtof_works], [AC_RUN_IFELSE([AC_LANG_PROGRAM([[ #include <stdlib.h> -- 2.34.1
>From cb76ba0b05d382e93957b4724fe0a4c20f583d12 Mon Sep 17 00:00:00 2001 From: Bruno Haible <br...@clisp.org> Date: Tue, 23 Jul 2024 12:40:19 +0200 Subject: [PATCH 3/7] 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. --- ChangeLog | 18 ++++++++++++++ lib/strtod.c | 34 +++++++++++++++++++++++--- m4/strtod.m4 | 62 ++++++++++++++++++++++++++++++++++++++++------ m4/strtof.m4 | 68 ++++++++++++++++++++++++++++++++++++++++++++------- m4/strtold.m4 | 44 +++++++++++++++++++++++++++++---- 5 files changed, 202 insertions(+), 24 deletions(-) diff --git a/ChangeLog b/ChangeLog index 8c84fd22d0..f1e93da002 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,21 @@ +2024-07-23 Bruno Haible <br...@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 <br...@clisp.org> 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 <stdlib.h> +#include <float.h> #include <math.h> #include <errno.h> /* Compare two numbers with ==. @@ -52,7 +53,7 @@ AC_DEFUN([gl_FUNC_STRTOD] 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 @@ AC_DEFUN([gl_FUNC_STRTOD] 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 @@ AC_DEFUN([gl_FUNC_STRTOD] 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 @@ AC_DEFUN([gl_FUNC_STRTOD] 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 @@ AC_DEFUN([gl_FUNC_STRTOD] 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 @@ AC_DEFUN([gl_FUNC_STRTOD] 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 @@ AC_DEFUN([gl_FUNC_STRTOD] [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 @@ AC_DEFUN([gl_FUNC_STRTOD] *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 <stdlib.h> +#include <float.h> #include <math.h> #include <errno.h> /* Compare two numbers with ==. @@ -52,7 +53,7 @@ AC_DEFUN([gl_FUNC_STRTOF] 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 @@ AC_DEFUN([gl_FUNC_STRTOF] 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 @@ AC_DEFUN([gl_FUNC_STRTOF] 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 @@ AC_DEFUN([gl_FUNC_STRTOF] 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 @@ AC_DEFUN([gl_FUNC_STRTOF] 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 @@ AC_DEFUN([gl_FUNC_STRTOF] 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 @@ AC_DEFUN([gl_FUNC_STRTOF] [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 @@ AC_DEFUN([gl_FUNC_STRTOF] *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 <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 @@ AC_DEFUN([gl_FUNC_STRTOLD] 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 @@ AC_DEFUN([gl_FUNC_STRTOLD] 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 @@ AC_DEFUN([gl_FUNC_STRTOLD] *-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 @@ AC_DEFUN([gl_FUNC_STRTOLD] 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 ;; -- 2.34.1
From edf38838fe0e409455ad2fe03bedaf06571d95bb Mon Sep 17 00:00:00 2001 From: Bruno Haible <br...@clisp.org> Date: Tue, 23 Jul 2024 12:40:33 +0200 Subject: [PATCH 4/7] strtof: Revisit underflow behaviour. * doc/posix-functions/strtof.texi: Mention the macOS bug. Mention the mingw overflow bug. Mention the underflow bugs on Cygwin 2.9 and mingw. Mention that gradual underflow does not count as an error on Cygwin 2.9, mingw, MSVC. * m4/strtof.m4 (gl_FUNC_STRTOF): Test against the mingw overflow bug. * tests/test-strtof.h (test_function): Add a gradual underflow test. Check the sign in case of flush-to-zero underflow. --- ChangeLog | 11 +++++++++ doc/posix-functions/strtof.texi | 22 +++++++++++++++++- m4/strtof.m4 | 15 +++++++++++-- tests/test-strtof.h | 40 +++++++++++++++++++++++++++------ 4 files changed, 78 insertions(+), 10 deletions(-) diff --git a/ChangeLog b/ChangeLog index f1e93da002..3da7b01049 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,14 @@ +2024-07-23 Bruno Haible <br...@clisp.org> + + strtof: Revisit underflow behaviour. + * doc/posix-functions/strtof.texi: Mention the macOS bug. Mention the + mingw overflow bug. Mention the underflow bugs on Cygwin 2.9 and mingw. + Mention that gradual underflow does not count as an error on Cygwin 2.9, + mingw, MSVC. + * m4/strtof.m4 (gl_FUNC_STRTOF): Test against the mingw overflow bug. + * tests/test-strtof.h (test_function): Add a gradual underflow test. + Check the sign in case of flush-to-zero underflow. + 2024-07-23 Bruno Haible <br...@clisp.org> strtof, strtod, strtold: Fix underflow behaviour of system function. diff --git a/doc/posix-functions/strtof.texi b/doc/posix-functions/strtof.texi index b7bf64e934..cffe033cf0 100644 --- a/doc/posix-functions/strtof.texi +++ b/doc/posix-functions/strtof.texi @@ -19,7 +19,22 @@ @item This function fails to parse @samp{NaN()} on some platforms: -glibc-2.5, FreeBSD 6.2. +glibc 2.5, FreeBSD 6.2. + +@item +This function misparses @samp{nan(} on some platforms: +macOS 10.6.6. + +@item +This function fails to set @code{errno} upon overflow on some platforms: +mingw 5.0. + +@item +@c The term "underflow", as defined by ISO C23 § 7.12.1.(6), includes both +@c "gradual underflow" (result is a denormalized number) and "flush-to-zero +@c underflow" (result is zero). +This function fails to set @code{errno} upon underflow on some platforms: +Cygwin 2.9, mingw 5.0. @end itemize Portability problems not fixed by Gnulib: @@ -39,6 +54,11 @@ platforms: Mac OS X 10.5, FreeBSD 6.2, NetBSD 5.0, Cygwin, mingw, MSVC 14. +@item +This function fails to set @code{errno} upon gradual underflow (resulting +in a denormalized number) on some platforms: +MSVC 14. + @item The replacement function does not always return correctly rounded results. @end itemize diff --git a/m4/strtof.m4 b/m4/strtof.m4 index e37310ebde..71516f1bf6 100644 --- a/m4/strtof.m4 +++ b/m4/strtof.m4 @@ -1,5 +1,5 @@ # strtof.m4 -# serial 3 +# serial 4 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, @@ -83,6 +83,17 @@ AC_DEFUN([gl_FUNC_STRTOF] if (value != HUGE_VAL || term != (string + 3) || errno) result |= 8; } + { + /* In mingw 5.0 without __USE_MINGW_ANSI_STDIO and __USE_MINGW_STRTOX, + strtof does not set errno upon overflow. */ + const char *string = "1e50"; + char *term; + float value; + errno = 0; + value = strtof (string, &term); + if (value != HUGE_VAL || term != (string + 4) || errno != ERANGE) + result |= 8; + } { /* glibc 2.7 and cygwin 1.5.24 misparse "nan()". */ const char *string = "nan()"; @@ -92,7 +103,7 @@ AC_DEFUN([gl_FUNC_STRTOF] result |= 16; } { - /* darwin 10.6.1 misparses "nan(". */ + /* Darwin 10.6.1 (macOS 10.6.6) misparses "nan(". */ const char *string = "nan("; char *term; float value = strtof (string, &term); diff --git a/tests/test-strtof.h b/tests/test-strtof.h index 1a7588f608..2ac6b898da 100644 --- a/tests/test-strtof.h +++ b/tests/test-strtof.h @@ -481,7 +481,7 @@ test_function (float (*my_strtof) (const char *, char **)) ASSERT (errno == 0); } - /* Overflow/underflow. */ + /* Overflow. */ { const char input[] = "1E1000000"; char *ptr; @@ -502,6 +502,34 @@ test_function (float (*my_strtof) (const char *, char **)) ASSERT (ptr == input + 10); ASSERT (errno == ERANGE); } + + /* Gradual underflow, resulting in a denormalized number. */ + { + const char input[] = "1e-40"; + char *ptr; + float result; + errno = 0; + result = my_strtof (input, &ptr); + ASSERT (0.0f < result && result <= FLT_MIN); + ASSERT (ptr == input + 5); +#if !defined _MSC_VER + ASSERT (errno == ERANGE); +#endif + } + { + const char input[] = "-1e-40"; + char *ptr; + float result; + errno = 0; + result = my_strtof (input, &ptr); + ASSERT (-FLT_MIN <= result && result < 0.0f); + ASSERT (ptr == input + 6); +#if !defined _MSC_VER + ASSERT (errno == ERANGE); +#endif + } + + /* Flush-to-zero underflow. */ { const char input[] = "1E-100000"; char *ptr; @@ -520,16 +548,14 @@ test_function (float (*my_strtof) (const char *, char **)) errno = 0; result = my_strtof (input, &ptr); ASSERT (-FLT_MIN <= result && result <= 0.0f); -#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. */ + /* Negative underflow. Expect a negative sign, although POSIX allows +0.0f. + See also <https://sourceware.org/bugzilla/show_bug.cgi?id=5995>. */ ASSERT (!!signbit (result) == !!signbit (minus_zerof)); /* glibc-2.3.6, mingw */ -#endif ASSERT (ptr == input + 10); ASSERT (errno == ERANGE); } + + /* Space before the exponent. */ { const char input[] = "1E 1000000"; char *ptr; -- 2.34.1
From bd61e8c8ca7a41ce6e265c2cd8d9de5f4c3c4bd5 Mon Sep 17 00:00:00 2001 From: Bruno Haible <br...@clisp.org> Date: Tue, 23 Jul 2024 12:40:37 +0200 Subject: [PATCH 5/7] strtod: Revisit underflow behaviour. * doc/posix-functions/strtod.texi: Mention the macOS bug. Mention that gradual underflow does not count as an error on Cygwin 2.9 and MSVC. * m4/strtod.m4 (gl_FUNC_STRTOD): Update comment. * tests/test-strtod.h (test_function): Add a gradual underflow test. Check the sign in case of flush-to-zero underflow. --- ChangeLog | 7 ++++++ doc/posix-functions/strtod.texi | 21 +++++++++++++++-- m4/strtod.m4 | 4 ++-- tests/test-strtod.h | 40 +++++++++++++++++++++++++++------ 4 files changed, 61 insertions(+), 11 deletions(-) diff --git a/ChangeLog b/ChangeLog index 3da7b01049..3f086d2a9f 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,5 +1,12 @@ 2024-07-23 Bruno Haible <br...@clisp.org> + strtod: Revisit underflow behaviour. + * doc/posix-functions/strtod.texi: Mention the macOS bug. Mention that + gradual underflow does not count as an error on Cygwin 2.9 and MSVC. + * m4/strtod.m4 (gl_FUNC_STRTOD): Update comment. + * tests/test-strtod.h (test_function): Add a gradual underflow test. + Check the sign in case of flush-to-zero underflow. + strtof: Revisit underflow behaviour. * doc/posix-functions/strtof.texi: Mention the macOS bug. Mention the mingw overflow bug. Mention the underflow bugs on Cygwin 2.9 and mingw. diff --git a/doc/posix-functions/strtod.texi b/doc/posix-functions/strtod.texi index 9b470eb465..a7d0143a13 100644 --- a/doc/posix-functions/strtod.texi +++ b/doc/posix-functions/strtod.texi @@ -31,7 +31,7 @@ @item This function fails to parse @samp{NaN()} on some platforms: -glibc-2.3.6, Mac OS X 10.5, FreeBSD 6.2, OpenBSD 4.0, AIX 7.1, HP-UX 11.11, Cygwin < 1.5.25-11, mingw, MSVC 14. +glibc 2.3.6, Mac OS X 10.5, FreeBSD 6.2, OpenBSD 4.0, AIX 7.1, HP-UX 11.11, Cygwin < 1.5.25-11, mingw, MSVC 14. @item This function fails to parse @samp{NaN(@var{n-char-sequence})} on some @@ -41,7 +41,11 @@ @item This function parses @samp{NaN(@var{n-char-sequence})}, but returns the wrong end pointer on some platforms: -glibc-2.4, AIX 7.1. +glibc 2.4, AIX 7.1. + +@item +This function misparses @samp{nan(} on some platforms: +macOS 10.6.6. @item This function fails to parse C99 hexadecimal floating point on some @@ -57,6 +61,14 @@ This function returns the wrong end pointer for @samp{0x1p} on some platforms: AIX 7.1. + +@item +@c The term "underflow", as defined by ISO C23 § 7.12.1.(6), includes both +@c "gradual underflow" (result is a denormalized number) and "flush-to-zero +@c underflow" (result is zero). +This function fails to set @code{errno} upon gradual underflow (resulting +in a denormalized number) on some platforms: +Cygwin 2.9. @end itemize Portability problems fixed by Gnulib module @code{strtod-obsolete}: @@ -82,6 +94,11 @@ platforms: Mac OS X 10.5, FreeBSD 6.2, NetBSD 5.0, OpenBSD 4.0, Cygwin, mingw, MSVC 14. +@item +This function fails to set @code{errno} upon gradual underflow (resulting +in a denormalized number) on some platforms: +MSVC 14. + @item The replacement function does not always return correctly rounded results. @end itemize diff --git a/m4/strtod.m4 b/m4/strtod.m4 index 2ccbf3139a..e974478845 100644 --- a/m4/strtod.m4 +++ b/m4/strtod.m4 @@ -1,5 +1,5 @@ # strtod.m4 -# serial 30 +# serial 31 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, @@ -92,7 +92,7 @@ AC_DEFUN([gl_FUNC_STRTOD] result |= 16; } { - /* darwin 10.6.1 misparses "nan(". */ + /* Darwin 10.6.1 (macOS 10.6.6) misparses "nan(". */ const char *string = "nan("; char *term; double value = strtod (string, &term); diff --git a/tests/test-strtod.h b/tests/test-strtod.h index 48d784f368..c806953e84 100644 --- a/tests/test-strtod.h +++ b/tests/test-strtod.h @@ -481,7 +481,7 @@ test_function (double (*my_strtod) (const char *, char **)) ASSERT (errno == 0); } - /* Overflow/underflow. */ + /* Overflow. */ { const char input[] = "1E1000000"; char *ptr; @@ -502,6 +502,34 @@ test_function (double (*my_strtod) (const char *, char **)) ASSERT (ptr == input + 10); ASSERT (errno == ERANGE); } + + /* Gradual underflow, resulting in a denormalized number. */ + { + const char input[] = "1e-320"; + char *ptr; + double result; + errno = 0; + result = my_strtod (input, &ptr); + ASSERT (0.0 < result && result <= DBL_MIN); + ASSERT (ptr == input + 6); +#if !defined _MSC_VER + ASSERT (errno == ERANGE); +#endif + } + { + const char input[] = "-1e-320"; + char *ptr; + double result; + errno = 0; + result = my_strtod (input, &ptr); + ASSERT (-DBL_MIN <= result && result < 0.0); + ASSERT (ptr == input + 7); +#if !defined _MSC_VER + ASSERT (errno == ERANGE); +#endif + } + + /* Flush-to-zero underflow. */ { const char input[] = "1E-100000"; char *ptr; @@ -520,16 +548,14 @@ test_function (double (*my_strtod) (const char *, char **)) errno = 0; result = my_strtod (input, &ptr); ASSERT (-DBL_MIN <= result && result <= 0.0); -#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. */ + /* Negative underflow. Expect a negative sign, although POSIX allows +0.0. + See also <https://sourceware.org/bugzilla/show_bug.cgi?id=5995>. */ ASSERT (!!signbit (result) == !!signbit (minus_zerod)); /* glibc-2.3.6, mingw */ -#endif ASSERT (ptr == input + 10); ASSERT (errno == ERANGE); } + + /* Space before the exponent. */ { const char input[] = "1E 1000000"; char *ptr; -- 2.34.1
From 423119a9f2a1549cbb9ef941fd76cf7cf5bfe1ea Mon Sep 17 00:00:00 2001 From: Bruno Haible <br...@clisp.org> Date: Tue, 23 Jul 2024 12:40:58 +0200 Subject: [PATCH 6/7] strtold: Revisit underflow behaviour. * doc/posix-functions/strtold.texi: Mention broken mingw 5.0. Mention that gradual underflow does not count as an error on MSVC. * tests/test-strtold.h (test_function): Add a gradual underflow test. Check the sign in case of flush-to-zero underflow. --- ChangeLog | 6 ++++ doc/posix-functions/strtold.texi | 12 ++++++++ tests/test-strtold.h | 48 +++++++++++++++++++++++++++----- 3 files changed, 59 insertions(+), 7 deletions(-) diff --git a/ChangeLog b/ChangeLog index 3f086d2a9f..b4d5b8f9f8 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,5 +1,11 @@ 2024-07-23 Bruno Haible <br...@clisp.org> + strtold: Revisit underflow behaviour. + * doc/posix-functions/strtold.texi: Mention broken mingw 5.0. Mention + that gradual underflow does not count as an error on MSVC. + * tests/test-strtold.h (test_function): Add a gradual underflow test. + Check the sign in case of flush-to-zero underflow. + strtod: Revisit underflow behaviour. * doc/posix-functions/strtod.texi: Mention the macOS bug. Mention that gradual underflow does not count as an error on Cygwin 2.9 and MSVC. diff --git a/doc/posix-functions/strtold.texi b/doc/posix-functions/strtold.texi index cafcead911..3aada2c405 100644 --- a/doc/posix-functions/strtold.texi +++ b/doc/posix-functions/strtold.texi @@ -16,6 +16,10 @@ This function returns a struct, not a @code{long double}, on some platforms: HP-UX 11.31/hppa. +@item +This function always returns a wrong value on some platforms: +mingw 5.0. + @item This function allows whitespace between @samp{e} and the exponent on some platforms: @@ -50,6 +54,9 @@ HP-UX 11.31/ia64. @item +@c The term "underflow", as defined by ISO C23 § 7.12.1.(6), includes both +@c "gradual underflow" (result is a denormalized number) and "flush-to-zero +@c underflow" (result is zero). This function fails to set @code{errno} upon underflow on some platforms: @c https://cygwin.com/ml/cygwin/2019-12/msg00072.html Cygwin 2.9. @@ -61,6 +68,11 @@ Portability problems not fixed by Gnulib: @itemize +@item +This function fails to set @code{errno} upon gradual underflow (resulting +in a denormalized number) on some platforms: +MSVC 14. + @item The replacement function does not always return correctly rounded results. @end itemize diff --git a/tests/test-strtold.h b/tests/test-strtold.h index b5d79dc07b..bd12988c4c 100644 --- a/tests/test-strtold.h +++ b/tests/test-strtold.h @@ -481,7 +481,7 @@ test_function (long double (*my_strtold) (const char *, char **)) ASSERT (errno == 0); } - /* Overflow/underflow. */ + /* Overflow. */ { const char input[] = "1E1000000"; char *ptr; @@ -502,6 +502,42 @@ test_function (long double (*my_strtold) (const char *, char **)) ASSERT (ptr == input + 10); ASSERT (errno == ERANGE); } + + /* Gradual underflow, resulting in a denormalized number. */ + { +#if LDBL_MAX_EXP > 10000 + const char input[] = "1e-4950"; +#else + const char input[] = "1e-320"; +#endif + char *ptr; + long double result; + errno = 0; + result = my_strtold (input, &ptr); + ASSERT (0.0L < result && result <= LDBL_MIN); + ASSERT (ptr == input + strlen (input)); +#if !defined _MSC_VER + ASSERT (errno == ERANGE); +#endif + } + { +#if LDBL_MAX_EXP > 10000 + const char input[] = "-1e-4950"; +#else + const char input[] = "-1e-320"; +#endif + char *ptr; + long double result; + errno = 0; + result = my_strtold (input, &ptr); + ASSERT (-LDBL_MIN <= result && result < 0.0L); + ASSERT (ptr == input + strlen (input)); +#if !defined _MSC_VER + ASSERT (errno == ERANGE); +#endif + } + + /* Flush-to-zero underflow. */ { const char input[] = "1E-100000"; char *ptr; @@ -520,16 +556,14 @@ test_function (long double (*my_strtold) (const char *, char **)) errno = 0; result = my_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. */ + /* Negative underflow. Expect a negative sign, although POSIX allows +0.0L. + See also <https://sourceware.org/bugzilla/show_bug.cgi?id=5995>. */ ASSERT (!!signbit (result) == !!signbit (minus_zerol)); /* glibc-2.3.2, Haiku */ -#endif ASSERT (ptr == input + 10); ASSERT (errno == ERANGE); } + + /* Space before the exponent. */ { const char input[] = "1E 1000000"; char *ptr; -- 2.34.1
>From a3b60438fb3a15add88aeceb85b1e4c8e98c7385 Mon Sep 17 00:00:00 2001 From: Bruno Haible <br...@clisp.org> Date: Tue, 23 Jul 2024 12:41:12 +0200 Subject: [PATCH 7/7] strtof, strtod, strtold tests: Strengthen tests. * tests/test-strtof.h (test_function): Add another overflow test. * tests/test-strtod.h (test_function): Likewise. * tests/test-strtold.h (test_function): Likewise. --- ChangeLog | 7 +++++++ tests/test-strtod.h | 10 ++++++++++ tests/test-strtof.h | 10 ++++++++++ tests/test-strtold.h | 10 ++++++++++ 4 files changed, 37 insertions(+) diff --git a/ChangeLog b/ChangeLog index b4d5b8f9f8..309f1d8ac0 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,10 @@ +2024-07-23 Bruno Haible <br...@clisp.org> + + strtof, strtod, strtold tests: Strengthen tests. + * tests/test-strtof.h (test_function): Add another overflow test. + * tests/test-strtod.h (test_function): Likewise. + * tests/test-strtold.h (test_function): Likewise. + 2024-07-23 Bruno Haible <br...@clisp.org> strtold: Revisit underflow behaviour. diff --git a/tests/test-strtod.h b/tests/test-strtod.h index c806953e84..211d9230c7 100644 --- a/tests/test-strtod.h +++ b/tests/test-strtod.h @@ -482,6 +482,16 @@ test_function (double (*my_strtod) (const char *, char **)) } /* Overflow. */ + { + const char input[] = "1e500"; + char *ptr; + double result; + errno = 0; + result = my_strtod (input, &ptr); + ASSERT (result == HUGE_VAL); + ASSERT (ptr == input + 5); /* OSF/1 5.1 */ + ASSERT (errno == ERANGE); + } { const char input[] = "1E1000000"; char *ptr; diff --git a/tests/test-strtof.h b/tests/test-strtof.h index 2ac6b898da..fd04f07873 100644 --- a/tests/test-strtof.h +++ b/tests/test-strtof.h @@ -482,6 +482,16 @@ test_function (float (*my_strtof) (const char *, char **)) } /* Overflow. */ + { + const char input[] = "1e50"; + char *ptr; + float result; + errno = 0; + result = my_strtof (input, &ptr); + ASSERT (result == HUGE_VAL); + ASSERT (ptr == input + 4); /* OSF/1 5.1 */ + ASSERT (errno == ERANGE); + } { const char input[] = "1E1000000"; char *ptr; diff --git a/tests/test-strtold.h b/tests/test-strtold.h index bd12988c4c..a872f8a882 100644 --- a/tests/test-strtold.h +++ b/tests/test-strtold.h @@ -482,6 +482,16 @@ test_function (long double (*my_strtold) (const char *, char **)) } /* Overflow. */ + { + const char input[] = "1e6000"; + char *ptr; + long double result; + errno = 0; + result = my_strtold (input, &ptr); + ASSERT (result == HUGE_VALL); + ASSERT (ptr == input + 6); + ASSERT (errno == ERANGE); + } { const char input[] = "1E1000000"; char *ptr; -- 2.34.1