These two patches add a module 'strtof', similar to 'strtod' and 'strtold'.


2024-02-21  Bruno Haible  <br...@clisp.org>

        strtof: Add tests.
        * tests/test-strtof.c: New file, based on tests/test-strtod.c.
        * tests/test-strtof1.sh: New file, based on tests/test-strtod1.sh.
        * tests/test-strtof1.c: New file, based on tests/test-strtod1.c.
        * modules/strtof-tests: New file, based on modules/strtod-tests.

        strtof: New module.
        * lib/stdlib.in.h (strtof): New declaration.
        * lib/strtod.c: Support USE_FLOAT.
        * lib/strtof.c: New file.
        * m4/strtof.m4: New file, based on m4/strtod.m4.
        * m4/ldexpf.m4 (gl_CHECK_LDEXPF_NO_LIBM): New macro, based on
        m4/ldexp.m4.
        * m4/stdlib_h.m4 (gl_STDLIB_H_REQUIRE_DEFAULTS): Initialize
        GNULIB_STRTOF.
        * modules/stdlib (Makefile.am): Substitute GNULIB_STRTOF, HAVE_STRTOF,
        REPLACE_STRTOF.
        * modules/strtof: New file.
        * tests/test-stdlib-c++.cc (strtof): Check signature.
        * doc/posix-functions/strtof.texi: Mention the new module and the bugs
        that it fixes.
        (gl_STDLIB_H_DEFAULTS): Initialize HAVE_STRTOF, REPLACE_STRTOF.

>From 01a485ed9b3b2ce3d533581fbca94970bdef9998 Mon Sep 17 00:00:00 2001
From: Bruno Haible <br...@clisp.org>
Date: Wed, 21 Feb 2024 22:45:47 +0100
Subject: [PATCH 1/4] strtof: New module.

* lib/stdlib.in.h (strtof): New declaration.
* lib/strtod.c: Support USE_FLOAT.
* lib/strtof.c: New file.
* m4/strtof.m4: New file, based on m4/strtod.m4.
* m4/ldexpf.m4 (gl_CHECK_LDEXPF_NO_LIBM): New macro, based on
m4/ldexp.m4.
* m4/stdlib_h.m4 (gl_STDLIB_H_REQUIRE_DEFAULTS): Initialize
GNULIB_STRTOF.
* modules/stdlib (Makefile.am): Substitute GNULIB_STRTOF, HAVE_STRTOF,
REPLACE_STRTOF.
* modules/strtof: New file.
* tests/test-stdlib-c++.cc (strtof): Check signature.
* doc/posix-functions/strtof.texi: Mention the new module and the bugs
that it fixes.
(gl_STDLIB_H_DEFAULTS): Initialize HAVE_STRTOF, REPLACE_STRTOF.
---
 ChangeLog                       |  19 +++++
 doc/posix-functions/strtof.texi |  32 ++++++-
 lib/stdlib.in.h                 |  32 +++++++
 lib/strtod.c                    |  39 ++++++---
 lib/strtof.c                    |  22 +++++
 m4/ldexpf.m4                    |  22 ++++-
 m4/stdlib_h.m4                  |   5 +-
 m4/strtof.m4                    | 142 ++++++++++++++++++++++++++++++++
 modules/stdlib                  |   3 +
 modules/strtof                  |  37 +++++++++
 tests/test-stdlib-c++.cc        |   4 +
 11 files changed, 342 insertions(+), 15 deletions(-)
 create mode 100644 lib/strtof.c
 create mode 100644 m4/strtof.m4
 create mode 100644 modules/strtof

diff --git a/ChangeLog b/ChangeLog
index 767644ef1b..6df8be5ada 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,22 @@
+2024-02-21  Bruno Haible  <br...@clisp.org>
+
+	strtof: New module.
+	* lib/stdlib.in.h (strtof): New declaration.
+	* lib/strtod.c: Support USE_FLOAT.
+	* lib/strtof.c: New file.
+	* m4/strtof.m4: New file, based on m4/strtod.m4.
+	* m4/ldexpf.m4 (gl_CHECK_LDEXPF_NO_LIBM): New macro, based on
+	m4/ldexp.m4.
+	* m4/stdlib_h.m4 (gl_STDLIB_H_REQUIRE_DEFAULTS): Initialize
+	GNULIB_STRTOF.
+	* modules/stdlib (Makefile.am): Substitute GNULIB_STRTOF, HAVE_STRTOF,
+	REPLACE_STRTOF.
+	* modules/strtof: New file.
+	* tests/test-stdlib-c++.cc (strtof): Check signature.
+	* doc/posix-functions/strtof.texi: Mention the new module and the bugs
+	that it fixes.
+	(gl_STDLIB_H_DEFAULTS): Initialize HAVE_STRTOF, REPLACE_STRTOF.
+
 2024-02-21  Bruno Haible  <br...@clisp.org>
 
 	strtod, strtold tests: Avoid a test failure on native Windows.
diff --git a/doc/posix-functions/strtof.texi b/doc/posix-functions/strtof.texi
index bf19d2ad3b..08d69ec5c6 100644
--- a/doc/posix-functions/strtof.texi
+++ b/doc/posix-functions/strtof.texi
@@ -4,15 +4,41 @@
 
 POSIX specification:@* @url{https://pubs.opengroup.org/onlinepubs/9699919799/functions/strtof.html}
 
-Gnulib module: ---
+Gnulib module: strtof
 
 Portability problems fixed by Gnulib:
 @itemize
+@item
+This function is missing on some platforms:
+NetBSD 3.0, OpenBSD 3.8, Minix 3.1.8, HP-UX 11, IRIX 6.5, Solaris 9, MSVC 9, Android 4.4.
+
+@item
+This function returns the wrong end pointer for @samp{-0x} on some
+platforms:
+glibc 2.4, Mac OS X 10.5, FreeBSD 6.2.
+
+@item
+This function fails to parse @samp{NaN()} on some platforms:
+glibc-2.5, FreeBSD 6.2.
 @end itemize
 
 Portability problems not fixed by Gnulib:
 @itemize
 @item
-This function is missing on some platforms:
-NetBSD 3.0, OpenBSD 3.8, Minix 3.1.8, HP-UX 11, IRIX 6.5, Solaris 9, MSVC 9, Android 4.4.
+This function returns +0.0 (not @minus{}0.0) for negative underflow on some
+platforms:
+glibc 2.7, mingw, MSVC 14.
+
+@item
+This function cannot distinguish between ``nan'' and ``-nan'' on some
+platforms:
+glibc 2.7, mingw, MSVC 14.
+
+@item
+This function fails to correctly parse very long strings on some
+platforms:
+Mac OS X 10.5, FreeBSD 6.2, NetBSD 5.0, Cygwin, mingw, MSVC 14.
+
+@item
+The replacement function does not always return correctly rounded results.
 @end itemize
diff --git a/lib/stdlib.in.h b/lib/stdlib.in.h
index b901d175ae..e74e7c18d1 100644
--- a/lib/stdlib.in.h
+++ b/lib/stdlib.in.h
@@ -1591,6 +1591,38 @@ _GL_WARN_ON_USE (strtod, "strtod is unportable - "
 # endif
 #endif
 
+#if @GNULIB_STRTOF@
+ /* Parse a float from STRING, updating ENDP if appropriate.  */
+# if @REPLACE_STRTOF@
+#  if !(defined __cplusplus && defined GNULIB_NAMESPACE)
+#   define strtof rpl_strtof
+#  endif
+#  define GNULIB_defined_strtof_function 1
+_GL_FUNCDECL_RPL (strtof, float,
+                  (const char *restrict str, char **restrict endp)
+                  _GL_ARG_NONNULL ((1)));
+_GL_CXXALIAS_RPL (strtof, float,
+                  (const char *restrict str, char **restrict endp));
+# else
+#  if !@HAVE_STRTOF@
+_GL_FUNCDECL_SYS (strtof, float,
+                  (const char *restrict str, char **restrict endp)
+                  _GL_ARG_NONNULL ((1)));
+#  endif
+_GL_CXXALIAS_SYS (strtof, float,
+                  (const char *restrict str, char **restrict endp));
+# endif
+# if __GLIBC__ >= 2
+_GL_CXXALIASWARN (strtof);
+# endif
+#elif defined GNULIB_POSIXCHECK
+# undef strtof
+# if HAVE_RAW_DECL_STRTOF
+_GL_WARN_ON_USE (strtof, "strtof is unportable - "
+                 "use gnulib module strtof for portability");
+# endif
+#endif
+
 #if @GNULIB_STRTOLD@
  /* Parse a 'long double' from STRING, updating ENDP if appropriate.  */
 # if @REPLACE_STRTOLD@
diff --git a/lib/strtod.c b/lib/strtod.c
index c744d2f43b..a545be09a4 100644
--- a/lib/strtod.c
+++ b/lib/strtod.c
@@ -14,7 +14,7 @@
    You should have received a copy of the GNU Lesser General Public License
    along with this program.  If not, see <https://www.gnu.org/licenses/>.  */
 
-#if ! defined USE_LONG_DOUBLE
+#if ! (defined USE_FLOAT || defined USE_LONG_DOUBLE)
 # include <config.h>
 #endif
 
@@ -23,7 +23,7 @@
 
 #include <ctype.h>      /* isspace() */
 #include <errno.h>
-#include <float.h>      /* {DBL,LDBL}_{MIN,MAX} */
+#include <float.h>      /* {FLT,DBL,LDBL}_{MIN,MAX} */
 #include <limits.h>     /* LONG_{MIN,MAX} */
 #include <locale.h>     /* localeconv() */
 #include <math.h>       /* NAN */
@@ -37,7 +37,20 @@
 
 #undef MIN
 #undef MAX
-#ifdef USE_LONG_DOUBLE
+#if defined USE_FLOAT
+# define STRTOD strtof
+# define LDEXP ldexpf
+# define HAVE_UNDERLYING_STRTOD HAVE_STRTOF
+# define DOUBLE float
+# define MIN FLT_MIN
+# define MAX FLT_MAX
+# define L_(literal) literal##f
+# if HAVE_LDEXPF_IN_LIBC
+#  define USE_LDEXP 1
+# else
+#  define USE_LDEXP 0
+# endif
+#elif defined USE_LONG_DOUBLE
 # define STRTOD strtold
 # define LDEXP ldexpl
 # if defined __hpux && defined __hppa
@@ -54,6 +67,11 @@
 # define MIN LDBL_MIN
 # define MAX LDBL_MAX
 # define L_(literal) literal##L
+# if HAVE_LDEXPL_IN_LIBC
+#  define USE_LDEXP 1
+# else
+#  define USE_LDEXP 0
+# endif
 #else
 # define STRTOD strtod
 # define LDEXP ldexp
@@ -62,12 +80,11 @@
 # define MIN DBL_MIN
 # define MAX DBL_MAX
 # define L_(literal) literal
-#endif
-
-#if (defined USE_LONG_DOUBLE ? HAVE_LDEXPM_IN_LIBC : HAVE_LDEXP_IN_LIBC)
-# define USE_LDEXP 1
-#else
-# define USE_LDEXP 0
+# if HAVE_LDEXP_IN_LIBC
+#  define USE_LDEXP 1
+# else
+#  define USE_LDEXP 0
+# endif
 #endif
 
 /* Return true if C is a space in the current locale, avoiding
@@ -311,7 +328,9 @@ minus_zero (void)
 DOUBLE
 STRTOD (const char *nptr, char **endptr)
 #if HAVE_UNDERLYING_STRTOD
-# ifdef USE_LONG_DOUBLE
+# if defined USE_FLOAT
+#  undef strtof
+# elif defined USE_LONG_DOUBLE
 #  undef strtold
 # else
 #  undef strtod
diff --git a/lib/strtof.c b/lib/strtof.c
new file mode 100644
index 0000000000..194962ad32
--- /dev/null
+++ b/lib/strtof.c
@@ -0,0 +1,22 @@
+/* Convert string to 'float'.
+   Copyright (C) 2024 Free Software Foundation, Inc.
+
+   This file is free software: you can redistribute it and/or modify
+   it under the terms of the GNU Lesser General Public License as
+   published by the Free Software Foundation, either version 3 of the
+   License, or (at your option) any later version.
+
+   This file is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public License
+   along with this program.  If not, see <https://www.gnu.org/licenses/>.  */
+
+/* Written by Bruno Haible <br...@clisp.org>, 2024.  */
+
+#include <config.h>
+
+#define USE_FLOAT
+#include "strtod.c"
diff --git a/m4/ldexpf.m4 b/m4/ldexpf.m4
index 9297ae3fc9..b6cdcb4a59 100644
--- a/m4/ldexpf.m4
+++ b/m4/ldexpf.m4
@@ -1,4 +1,4 @@
-# ldexpf.m4 serial 2
+# ldexpf.m4 serial 3
 dnl Copyright (C) 2011-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,
@@ -23,3 +23,23 @@ AC_DEFUN([gl_FUNC_LDEXPF]
   fi
   AC_SUBST([LDEXPF_LIBM])
 ])
+
+dnl Test whether ldexpf() can be used without linking with libm.
+dnl Set gl_cv_func_ldexpf_no_libm to 'yes' or 'no' accordingly.
+AC_DEFUN([gl_CHECK_LDEXPF_NO_LIBM],
+[
+  AC_CACHE_CHECK([whether ldexpf() can be used without linking with libm],
+    [gl_cv_func_ldexpf_no_libm],
+    [
+      AC_LINK_IFELSE(
+        [AC_LANG_PROGRAM([[#ifndef __NO_MATH_INLINES
+                           # define __NO_MATH_INLINES 1 /* for glibc */
+                           #endif
+                           #include <math.h>
+                           float (*funcptr) (float, int) = ldexpf;
+                           float x;]],
+                         [[return ldexpf (x, -1) > 0;]])],
+        [gl_cv_func_ldexpf_no_libm=yes],
+        [gl_cv_func_ldexpf_no_libm=no])
+    ])
+])
diff --git a/m4/stdlib_h.m4 b/m4/stdlib_h.m4
index 92e67a74bb..88ccd14137 100644
--- a/m4/stdlib_h.m4
+++ b/m4/stdlib_h.m4
@@ -1,4 +1,4 @@
-# stdlib_h.m4 serial 76
+# stdlib_h.m4 serial 77
 dnl Copyright (C) 2007-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,
@@ -145,6 +145,7 @@ AC_DEFUN([gl_STDLIB_H_REQUIRE_DEFAULTS]
     gl_MODULE_INDICATOR_INIT_VARIABLE([GNULIB_SECURE_GETENV])
     gl_MODULE_INDICATOR_INIT_VARIABLE([GNULIB_SETENV])
     gl_MODULE_INDICATOR_INIT_VARIABLE([GNULIB_STRTOD])
+    gl_MODULE_INDICATOR_INIT_VARIABLE([GNULIB_STRTOF])
     gl_MODULE_INDICATOR_INIT_VARIABLE([GNULIB_STRTOL])
     gl_MODULE_INDICATOR_INIT_VARIABLE([GNULIB_STRTOLD])
     gl_MODULE_INDICATOR_INIT_VARIABLE([GNULIB_STRTOLL])
@@ -205,6 +206,7 @@ AC_DEFUN([gl_STDLIB_H_DEFAULTS]
   HAVE_SETSTATE=1;           AC_SUBST([HAVE_SETSTATE])
   HAVE_DECL_SETSTATE=1;      AC_SUBST([HAVE_DECL_SETSTATE])
   HAVE_STRTOD=1;             AC_SUBST([HAVE_STRTOD])
+  HAVE_STRTOF=1;             AC_SUBST([HAVE_STRTOF])
   HAVE_STRTOL=1;             AC_SUBST([HAVE_STRTOL])
   HAVE_STRTOLD=1;            AC_SUBST([HAVE_STRTOLD])
   HAVE_STRTOLL=1;            AC_SUBST([HAVE_STRTOLL])
@@ -248,6 +250,7 @@ AC_DEFUN([gl_STDLIB_H_DEFAULTS]
   REPLACE_SETENV=0;          AC_SUBST([REPLACE_SETENV])
   REPLACE_SETSTATE=0;        AC_SUBST([REPLACE_SETSTATE])
   REPLACE_STRTOD=0;          AC_SUBST([REPLACE_STRTOD])
+  REPLACE_STRTOF=0;          AC_SUBST([REPLACE_STRTOF])
   REPLACE_STRTOL=0;          AC_SUBST([REPLACE_STRTOL])
   REPLACE_STRTOLD=0;         AC_SUBST([REPLACE_STRTOLD])
   REPLACE_STRTOLL=0;         AC_SUBST([REPLACE_STRTOLL])
diff --git a/m4/strtof.m4 b/m4/strtof.m4
new file mode 100644
index 0000000000..483fa107a3
--- /dev/null
+++ b/m4/strtof.m4
@@ -0,0 +1,142 @@
+# strtof.m4 serial 1
+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,
+dnl with or without modifications, as long as this notice is preserved.
+
+AC_DEFUN([gl_FUNC_STRTOF],
+[
+  AC_REQUIRE([gl_STDLIB_H_DEFAULTS])
+  AC_REQUIRE([AC_CANONICAL_HOST]) dnl for cross-compiles
+  dnl Test whether strtof is declared.
+  dnl Don't call AC_FUNC_STRTOF, because it does not have the right guess
+  dnl when cross-compiling.
+  dnl Don't call AC_CHECK_FUNCS([strtof]) because it would collide with the
+  dnl ac_cv_func_strtof variable set by the AC_FUNC_STRTOF macro.
+  AC_CHECK_DECLS_ONCE([strtof])
+  if test $ac_cv_have_decl_strtof != yes; then
+    HAVE_STRTOF=0
+  fi
+  if test $HAVE_STRTOF = 1; then
+    AC_CACHE_CHECK([whether strtof obeys C99], [gl_cv_func_strtof_works],
+      [AC_RUN_IFELSE([AC_LANG_PROGRAM([[
+#include <stdlib.h>
+#include <math.h>
+#include <errno.h>
+/* Compare two numbers with ==.
+   This is a separate function because IRIX 6.5 "cc -O" miscompiles an
+   'x == x' test.  */
+static int
+numeric_equal (float x, float y)
+{
+  return x == y;
+}
+]], [[
+  int result = 0;
+  {
+    /* In some old versions of Linux (2000 or before), strtof mis-parses
+       strings with leading '+'.  */
+    const char *string = " +69";
+    char *term;
+    float value = strtof (string, &term);
+    if (value != 69 || term != (string + 4))
+      result |= 1;
+  }
+  {
+    /* Under Solaris 2.4, strtof returns the wrong value for the
+       terminating character under some conditions.  */
+    const char *string = "NaN";
+    char *term;
+    strtof (string, &term);
+    if (term != string && *(term - 1) == 0)
+      result |= 2;
+  }
+  {
+    /* Older glibc and Cygwin mis-parse "-0x".  */
+    const char *string = "-0x";
+    char *term;
+    float value = strtof (string, &term);
+    float zero = 0.0f;
+    if (1.0f / value != -1.0f / zero || term != (string + 2))
+      result |= 4;
+  }
+  {
+    /* Many platforms do not parse hex floats.  */
+    const char *string = "0XaP+1";
+    char *term;
+    float value = strtof (string, &term);
+    if (value != 20.0f || term != (string + 6))
+      result |= 8;
+  }
+  {
+    /* Many platforms do not parse infinities.  HP-UX 11.31 parses inf,
+       but mistakenly sets errno.  */
+    const char *string = "inf";
+    char *term;
+    float value;
+    errno = 0;
+    value = strtof (string, &term);
+    if (value != HUGE_VAL || term != (string + 3) || errno)
+      result |= 16;
+  }
+  {
+    /* glibc 2.7 and cygwin 1.5.24 misparse "nan()".  */
+    const char *string = "nan()";
+    char *term;
+    float value = strtof (string, &term);
+    if (numeric_equal (value, value) || term != (string + 5))
+      result |= 32;
+  }
+  {
+    /* darwin 10.6.1 misparses "nan(".  */
+    const char *string = "nan(";
+    char *term;
+    float value = strtof (string, &term);
+    if (numeric_equal (value, value) || term != (string + 3))
+      result |= 64;
+  }
+  return result;
+]])],
+        [gl_cv_func_strtof_works=yes],
+        [gl_cv_func_strtof_works=no],
+        [dnl The last known bugs in glibc strtof(), as of this writing,
+         dnl were fixed in version 2.8
+         AC_EGREP_CPP([Lucky user],
+           [
+#include <features.h>
+#ifdef __GNU_LIBRARY__
+ #if ((__GLIBC__ == 2 && __GLIBC_MINOR__ >= 8) || (__GLIBC__ > 2)) \
+     && !defined __UCLIBC__
+  Lucky user
+ #endif
+#endif
+           ],
+           [gl_cv_func_strtof_works="guessing yes"],
+           [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" ;;
+              *)                  gl_cv_func_strtof_works="$gl_cross_guess_normal" ;;
+            esac
+           ])
+        ])
+      ])
+    case "$gl_cv_func_strtof_works" in
+      *yes) ;;
+      *)
+        REPLACE_STRTOF=1
+        ;;
+    esac
+  fi
+])
+
+# Prerequisites of lib/strtof.c.
+AC_DEFUN([gl_PREREQ_STRTOF], [
+  AC_REQUIRE([gl_CHECK_LDEXPF_NO_LIBM])
+  if test $gl_cv_func_ldexpf_no_libm = yes; then
+    AC_DEFINE([HAVE_LDEXPF_IN_LIBC], [1],
+      [Define if the ldexpf function is available in libc.])
+  fi
+  gl_CHECK_FUNCS_ANDROID([nl_langinfo], [[#include <langinfo.h>]])
+])
diff --git a/modules/stdlib b/modules/stdlib
index baf4b29a8c..7b0194db41 100644
--- a/modules/stdlib
+++ b/modules/stdlib
@@ -73,6 +73,7 @@ stdlib.h: stdlib.in.h $(top_builddir)/config.status $(CXXDEFS_H) \
 	      -e 's/@''GNULIB_SECURE_GETENV''@/$(GNULIB_SECURE_GETENV)/g' \
 	      -e 's/@''GNULIB_SETENV''@/$(GNULIB_SETENV)/g' \
 	      -e 's/@''GNULIB_STRTOD''@/$(GNULIB_STRTOD)/g' \
+	      -e 's/@''GNULIB_STRTOF''@/$(GNULIB_STRTOF)/g' \
 	      -e 's/@''GNULIB_STRTOL''@/$(GNULIB_STRTOL)/g' \
 	      -e 's/@''GNULIB_STRTOLD''@/$(GNULIB_STRTOLD)/g' \
 	      -e 's/@''GNULIB_STRTOLL''@/$(GNULIB_STRTOLL)/g' \
@@ -125,6 +126,7 @@ stdlib.h: stdlib.in.h $(top_builddir)/config.status $(CXXDEFS_H) \
 	      -e 's|@''HAVE_SETSTATE''@|$(HAVE_SETSTATE)|g' \
 	      -e 's|@''HAVE_DECL_SETSTATE''@|$(HAVE_DECL_SETSTATE)|g' \
 	      -e 's|@''HAVE_STRTOD''@|$(HAVE_STRTOD)|g' \
+	      -e 's|@''HAVE_STRTOF''@|$(HAVE_STRTOF)|g' \
 	      -e 's|@''HAVE_STRTOL''@|$(HAVE_STRTOL)|g' \
 	      -e 's|@''HAVE_STRTOLD''@|$(HAVE_STRTOLD)|g' \
 	      -e 's|@''HAVE_STRTOLL''@|$(HAVE_STRTOLL)|g' \
@@ -170,6 +172,7 @@ stdlib.h: stdlib.in.h $(top_builddir)/config.status $(CXXDEFS_H) \
 	      -e 's|@''REPLACE_SETENV''@|$(REPLACE_SETENV)|g' \
 	      -e 's|@''REPLACE_SETSTATE''@|$(REPLACE_SETSTATE)|g' \
 	      -e 's|@''REPLACE_STRTOD''@|$(REPLACE_STRTOD)|g' \
+	      -e 's|@''REPLACE_STRTOF''@|$(REPLACE_STRTOF)|g' \
 	      -e 's|@''REPLACE_STRTOL''@|$(REPLACE_STRTOL)|g' \
 	      -e 's|@''REPLACE_STRTOLD''@|$(REPLACE_STRTOLD)|g' \
 	      -e 's|@''REPLACE_STRTOLL''@|$(REPLACE_STRTOLL)|g' \
diff --git a/modules/strtof b/modules/strtof
new file mode 100644
index 0000000000..12f5258366
--- /dev/null
+++ b/modules/strtof
@@ -0,0 +1,37 @@
+Description:
+strtof() function: convert string to 'float'.
+
+Files:
+lib/strtof.c
+lib/strtod.c
+m4/strtof.m4
+m4/ldexpf.m4
+
+Depends-on:
+stdlib
+c-ctype         [test $HAVE_STRTOF = 0 || test $REPLACE_STRTOF = 1]
+math            [test $HAVE_STRTOF = 0 || test $REPLACE_STRTOF = 1]
+stdbool         [test $HAVE_STRTOF = 0 || test $REPLACE_STRTOF = 1]
+
+configure.ac:
+gl_FUNC_STRTOF
+gl_CONDITIONAL([GL_COND_OBJ_STRTOF],
+               [test $HAVE_STRTOF = 0 || test $REPLACE_STRTOF = 1])
+AM_COND_IF([GL_COND_OBJ_STRTOF], [
+  gl_PREREQ_STRTOF
+])
+gl_STDLIB_MODULE_INDICATOR([strtof])
+
+Makefile.am:
+if GL_COND_OBJ_STRTOF
+lib_SOURCES += strtof.c
+endif
+
+Include:
+<stdlib.h>
+
+License:
+LGPL
+
+Maintainer:
+all
diff --git a/tests/test-stdlib-c++.cc b/tests/test-stdlib-c++.cc
index 55a71a6cc6..0b02ca6e75 100644
--- a/tests/test-stdlib-c++.cc
+++ b/tests/test-stdlib-c++.cc
@@ -187,6 +187,10 @@ SIGNATURE_CHECK (GNULIB_NAMESPACE::setenv, int,
 SIGNATURE_CHECK (GNULIB_NAMESPACE::strtod, double, (const char *, char **));
 #endif
 
+#if GNULIB_TEST_STRTOF
+SIGNATURE_CHECK (GNULIB_NAMESPACE::strtof, float, (const char *, char **));
+#endif
+
 #if GNULIB_TEST_STRTOLL
 SIGNATURE_CHECK (GNULIB_NAMESPACE::strtoll, long long,
                  (const char *, char **, int));
-- 
2.34.1

>From 471a56952835d2d586c729434262d6cd40eb8e09 Mon Sep 17 00:00:00 2001
From: Bruno Haible <br...@clisp.org>
Date: Thu, 22 Feb 2024 01:27:30 +0100
Subject: [PATCH 2/4] strtof: Add tests.

* tests/test-strtof.c: New file, based on tests/test-strtod.c.
* tests/test-strtof1.sh: New file, based on tests/test-strtod1.sh.
* tests/test-strtof1.c: New file, based on tests/test-strtod1.c.
* modules/strtof-tests: New file, based on modules/strtod-tests.
---
 ChangeLog             |   6 +
 modules/strtof-tests  |  31 ++
 tests/test-strtof.c   | 980 ++++++++++++++++++++++++++++++++++++++++++
 tests/test-strtof1.c  | 101 +++++
 tests/test-strtof1.sh |  30 ++
 5 files changed, 1148 insertions(+)
 create mode 100644 modules/strtof-tests
 create mode 100644 tests/test-strtof.c
 create mode 100644 tests/test-strtof1.c
 create mode 100755 tests/test-strtof1.sh

diff --git a/ChangeLog b/ChangeLog
index 6df8be5ada..475c3f16f4 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,11 @@
 2024-02-21  Bruno Haible  <br...@clisp.org>
 
+	strtof: Add tests.
+	* tests/test-strtof.c: New file, based on tests/test-strtod.c.
+	* tests/test-strtof1.sh: New file, based on tests/test-strtod1.sh.
+	* tests/test-strtof1.c: New file, based on tests/test-strtod1.c.
+	* modules/strtof-tests: New file, based on modules/strtod-tests.
+
 	strtof: New module.
 	* lib/stdlib.in.h (strtof): New declaration.
 	* lib/strtod.c: Support USE_FLOAT.
diff --git a/modules/strtof-tests b/modules/strtof-tests
new file mode 100644
index 0000000000..00e5df709b
--- /dev/null
+++ b/modules/strtof-tests
@@ -0,0 +1,31 @@
+Files:
+tests/test-strtof.c
+tests/test-strtof1.sh
+tests/test-strtof1.c
+tests/signature.h
+tests/minus-zero.h
+tests/macros.h
+m4/locale-fr.m4
+m4/codeset.m4
+
+Depends-on:
+float
+isnanf-nolibm
+signbit
+setlocale
+
+configure.ac:
+gt_LOCALE_FR
+gt_LOCALE_FR_UTF8
+
+Makefile.am:
+TESTS += test-strtof
+check_PROGRAMS += test-strtof
+
+TESTS += test-strtof1.sh
+TESTS_ENVIRONMENT += \
+  LOCALE_FR='@LOCALE_FR@' \
+  LOCALE_FR_UTF8='@LOCALE_FR_UTF8@' \
+  LC_NUMERIC_IMPLEMENTED='@LC_NUMERIC_IMPLEMENTED@'
+check_PROGRAMS += test-strtof1
+test_strtof1_LDADD = $(LDADD) $(SETLOCALE_LIB)
diff --git a/tests/test-strtof.c b/tests/test-strtof.c
new file mode 100644
index 0000000000..84f60dbcd7
--- /dev/null
+++ b/tests/test-strtof.c
@@ -0,0 +1,980 @@
+/*
+ * Copyright (C) 2008-2024 Free Software Foundation, Inc.
+ * Written by Eric Blake
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.  */
+
+#include <config.h>
+
+#include <stdlib.h>
+
+#include "signature.h"
+SIGNATURE_CHECK (strtof, float, (char const *, char **));
+
+#include <errno.h>
+#include <float.h>
+#include <math.h>
+#include <string.h>
+
+#include "isnanf-nolibm.h"
+#include "minus-zero.h"
+#include "macros.h"
+
+/* Avoid requiring -lm just for fabsf.  */
+#define FABS(f) ((f) < 0.0f ? -(f) : (f))
+
+int
+main (void)
+{
+  int status = 0;
+  /* Subject sequence empty or invalid.  */
+  {
+    const char input[] = "";
+    char *ptr;
+    float result;
+    errno = 0;
+    result = strtof (input, &ptr);
+    ASSERT (result == 0.0f);
+    ASSERT (!signbit (result));
+    ASSERT (ptr == input);
+    ASSERT (errno == 0 || errno == EINVAL);
+  }
+  {
+    const char input[] = " ";
+    char *ptr;
+    float result;
+    errno = 0;
+    result = strtof (input, &ptr);
+    ASSERT (result == 0.0f);
+    ASSERT (!signbit (result));
+    ASSERT (ptr == input);
+    ASSERT (errno == 0 || errno == EINVAL);
+  }
+  {
+    const char input[] = " +";
+    char *ptr;
+    float result;
+    errno = 0;
+    result = strtof (input, &ptr);
+    ASSERT (result == 0.0f);
+    ASSERT (!signbit (result));
+    ASSERT (ptr == input);
+    ASSERT (errno == 0 || errno == EINVAL);
+  }
+  {
+    const char input[] = " .";
+    char *ptr;
+    float result;
+    errno = 0;
+    result = strtof (input, &ptr);
+    ASSERT (result == 0.0f);
+    ASSERT (!signbit (result));
+    ASSERT (ptr == input);
+    ASSERT (errno == 0 || errno == EINVAL);
+  }
+  {
+    const char input[] = " .e0";
+    char *ptr;
+    float result;
+    errno = 0;
+    result = strtof (input, &ptr);
+    ASSERT (result == 0.0f);
+    ASSERT (!signbit (result));
+    ASSERT (ptr == input);              /* IRIX 6.5, OSF/1 5.1 */
+    ASSERT (errno == 0 || errno == EINVAL);
+  }
+  {
+    const char input[] = " +.e-0";
+    char *ptr;
+    float result;
+    errno = 0;
+    result = strtof (input, &ptr);
+    ASSERT (result == 0.0f);
+    ASSERT (!signbit (result));
+    ASSERT (ptr == input);              /* IRIX 6.5, OSF/1 5.1 */
+    ASSERT (errno == 0 || errno == EINVAL);
+  }
+  {
+    const char input[] = " in";
+    char *ptr;
+    float result;
+    errno = 0;
+    result = strtof (input, &ptr);
+    ASSERT (result == 0.0f);
+    ASSERT (!signbit (result));
+    ASSERT (ptr == input);
+    ASSERT (errno == 0 || errno == EINVAL);
+  }
+  {
+    const char input[] = " na";
+    char *ptr;
+    float result;
+    errno = 0;
+    result = strtof (input, &ptr);
+    ASSERT (result == 0.0f);
+    ASSERT (!signbit (result));
+    ASSERT (ptr == input);
+    ASSERT (errno == 0 || errno == EINVAL);
+  }
+
+  /* Simple floating point values.  */
+  {
+    const char input[] = "1";
+    char *ptr;
+    float result;
+    errno = 0;
+    result = strtof (input, &ptr);
+    ASSERT (result == 1.0f);
+    ASSERT (ptr == input + 1);
+    ASSERT (errno == 0);
+  }
+  {
+    const char input[] = "1.";
+    char *ptr;
+    float result;
+    errno = 0;
+    result = strtof (input, &ptr);
+    ASSERT (result == 1.0f);
+    ASSERT (ptr == input + 2);
+    ASSERT (errno == 0);
+  }
+  {
+    const char input[] = ".5";
+    char *ptr;
+    float result;
+    errno = 0;
+    result = strtof (input, &ptr);
+    ASSERT (result == 0.5f);
+    ASSERT (ptr == input + 2);
+    ASSERT (errno == 0);
+  }
+  {
+    const char input[] = " 1";
+    char *ptr;
+    float result;
+    errno = 0;
+    result = strtof (input, &ptr);
+    ASSERT (result == 1.0f);
+    ASSERT (ptr == input + 2);
+    ASSERT (errno == 0);
+  }
+  {
+    const char input[] = "+1";
+    char *ptr;
+    float result;
+    errno = 0;
+    result = strtof (input, &ptr);
+    ASSERT (result == 1.0f);
+    ASSERT (ptr == input + 2);
+    ASSERT (errno == 0);
+  }
+  {
+    const char input[] = "-1";
+    char *ptr;
+    float result;
+    errno = 0;
+    result = strtof (input, &ptr);
+    ASSERT (result == -1.0f);
+    ASSERT (ptr == input + 2);
+    ASSERT (errno == 0);
+  }
+  {
+    const char input[] = "1e0";
+    char *ptr;
+    float result;
+    errno = 0;
+    result = strtof (input, &ptr);
+    ASSERT (result == 1.0f);
+    ASSERT (ptr == input + 3);
+    ASSERT (errno == 0);
+  }
+  {
+    const char input[] = "1e+0";
+    char *ptr;
+    float result;
+    errno = 0;
+    result = strtof (input, &ptr);
+    ASSERT (result == 1.0f);
+    ASSERT (ptr == input + 4);
+    ASSERT (errno == 0);
+  }
+  {
+    const char input[] = "1e-0";
+    char *ptr;
+    float result;
+    errno = 0;
+    result = strtof (input, &ptr);
+    ASSERT (result == 1.0f);
+    ASSERT (ptr == input + 4);
+    ASSERT (errno == 0);
+  }
+  {
+    const char input[] = "1e1";
+    char *ptr;
+    float result;
+    errno = 0;
+    result = strtof (input, &ptr);
+    ASSERT (result == 10.0f);
+    ASSERT (ptr == input + 3);
+    ASSERT (errno == 0);
+  }
+  {
+    const char input[] = "5e-1";
+    char *ptr;
+    float result;
+    errno = 0;
+    result = strtof (input, &ptr);
+    ASSERT (result == 0.5f);
+    ASSERT (ptr == input + 4);
+    ASSERT (errno == 0);
+  }
+
+  /* Zero.  */
+  {
+    const char input[] = "0";
+    char *ptr;
+    float result;
+    errno = 0;
+    result = strtof (input, &ptr);
+    ASSERT (result == 0.0f);
+    ASSERT (!signbit (result));
+    ASSERT (ptr == input + 1);
+    ASSERT (errno == 0);
+  }
+  {
+    const char input[] = ".0";
+    char *ptr;
+    float result;
+    errno = 0;
+    result = strtof (input, &ptr);
+    ASSERT (result == 0.0f);
+    ASSERT (!signbit (result));
+    ASSERT (ptr == input + 2);
+    ASSERT (errno == 0);
+  }
+  {
+    const char input[] = "0e0";
+    char *ptr;
+    float result;
+    errno = 0;
+    result = strtof (input, &ptr);
+    ASSERT (result == 0.0f);
+    ASSERT (!signbit (result));
+    ASSERT (ptr == input + 3);
+    ASSERT (errno == 0);
+  }
+  {
+    const char input[] = "0e+9999999";
+    char *ptr;
+    float result;
+    errno = 0;
+    result = strtof (input, &ptr);
+    ASSERT (result == 0.0f);
+    ASSERT (!signbit (result));
+    ASSERT (ptr == input + 10);
+    ASSERT (errno == 0);
+  }
+  {
+    const char input[] = "0e-9999999";
+    char *ptr;
+    float result;
+    errno = 0;
+    result = strtof (input, &ptr);
+    ASSERT (result == 0.0f);
+    ASSERT (!signbit (result));
+    ASSERT (ptr == input + 10);
+    ASSERT (errno == 0);
+  }
+  {
+    const char input[] = "-0";
+    char *ptr;
+    float result;
+    errno = 0;
+    result = strtof (input, &ptr);
+    ASSERT (result == 0.0f);
+    ASSERT (!!signbit (result) == !!signbit (minus_zerof)); /* IRIX 6.5, OSF/1 4.0 */
+    ASSERT (ptr == input + 2);
+    ASSERT (errno == 0);
+  }
+
+  /* Suffixes.  */
+  {
+    const char input[] = "1f";
+    char *ptr;
+    float result;
+    errno = 0;
+    result = strtof (input, &ptr);
+    ASSERT (result == 1.0f);
+    ASSERT (ptr == input + 1);
+    ASSERT (errno == 0);
+  }
+  {
+    const char input[] = "1.f";
+    char *ptr;
+    float result;
+    errno = 0;
+    result = strtof (input, &ptr);
+    ASSERT (result == 1.0f);
+    ASSERT (ptr == input + 2);
+    ASSERT (errno == 0);
+  }
+  {
+    const char input[] = "1e";
+    char *ptr;
+    float result;
+    errno = 0;
+    result = strtof (input, &ptr);
+    ASSERT (result == 1.0f);
+    ASSERT (ptr == input + 1);
+    ASSERT (errno == 0);
+  }
+  {
+    const char input[] = "1e+";
+    char *ptr;
+    float result;
+    errno = 0;
+    result = strtof (input, &ptr);
+    ASSERT (result == 1.0f);
+    ASSERT (ptr == input + 1);
+    ASSERT (errno == 0);
+  }
+  {
+    const char input[] = "1e-";
+    char *ptr;
+    float result;
+    errno = 0;
+    result = strtof (input, &ptr);
+    ASSERT (result == 1.0f);
+    ASSERT (ptr == input + 1);
+    ASSERT (errno == 0);
+  }
+  {
+    const char input[] = "1E 2";
+    char *ptr;
+    float result;
+    errno = 0;
+    result = strtof (input, &ptr);
+    ASSERT (result == 1.0f);            /* HP-UX 11.11, IRIX 6.5, OSF/1 4.0 */
+    ASSERT (ptr == input + 1);          /* HP-UX 11.11, IRIX 6.5 */
+    ASSERT (errno == 0);
+  }
+  {
+    const char input[] = "0x";
+    char *ptr;
+    float result;
+    errno = 0;
+    result = strtof (input, &ptr);
+    ASSERT (result == 0.0f);
+    ASSERT (!signbit (result));
+    ASSERT (ptr == input + 1);          /* glibc-2.3.6, Mac OS X 10.3, FreeBSD 6.2, AIX 7.1 */
+    ASSERT (errno == 0);
+  }
+  {
+    const char input[] = "00x1";
+    char *ptr;
+    float result;
+    errno = 0;
+    result = strtof (input, &ptr);
+    ASSERT (result == 0.0f);
+    ASSERT (!signbit (result));
+    ASSERT (ptr == input + 2);
+    ASSERT (errno == 0);
+  }
+  {
+    const char input[] = "-0x";
+    char *ptr;
+    float result;
+    errno = 0;
+    result = strtof (input, &ptr);
+    ASSERT (result == 0.0f);
+    ASSERT (!!signbit (result) == !!signbit (minus_zerof)); /* Mac OS X 10.3, FreeBSD 6.2, IRIX 6.5, OSF/1 4.0 */
+    ASSERT (ptr == input + 2);          /* glibc-2.3.6, Mac OS X 10.3, FreeBSD 6.2, AIX 7.1 */
+    ASSERT (errno == 0);
+  }
+  {
+    const char input[] = "0xg";
+    char *ptr;
+    float result;
+    errno = 0;
+    result = strtof (input, &ptr);
+    ASSERT (result == 0.0f);
+    ASSERT (!signbit (result));
+    ASSERT (ptr == input + 1);          /* glibc-2.3.6, Mac OS X 10.3, FreeBSD 6.2, AIX 7.1 */
+    ASSERT (errno == 0);
+  }
+  {
+    const char input[] = "0xp";
+    char *ptr;
+    float result;
+    errno = 0;
+    result = strtof (input, &ptr);
+    ASSERT (result == 0.0f);
+    ASSERT (!signbit (result));
+    ASSERT (ptr == input + 1);          /* glibc-2.3.6, Mac OS X 10.3, FreeBSD 6.2, AIX 7.1 */
+    ASSERT (errno == 0);
+  }
+  {
+    const char input[] = "0XP";
+    char *ptr;
+    float result;
+    errno = 0;
+    result = strtof (input, &ptr);
+    ASSERT (result == 0.0f);
+    ASSERT (!signbit (result));
+    ASSERT (ptr == input + 1);          /* glibc-2.3.6, Mac OS X 10.3, FreeBSD 6.2, AIX 7.1 */
+    ASSERT (errno == 0);
+  }
+  {
+    const char input[] = "0x.";
+    char *ptr;
+    float result;
+    errno = 0;
+    result = strtof (input, &ptr);
+    ASSERT (result == 0.0f);
+    ASSERT (!signbit (result));
+    ASSERT (ptr == input + 1);          /* glibc-2.3.6, Mac OS X 10.3, FreeBSD 6.2, AIX 7.1 */
+    ASSERT (errno == 0);
+  }
+  {
+    const char input[] = "0xp+";
+    char *ptr;
+    float result;
+    errno = 0;
+    result = strtof (input, &ptr);
+    ASSERT (result == 0.0f);
+    ASSERT (!signbit (result));
+    ASSERT (ptr == input + 1);          /* glibc-2.3.6, Mac OS X 10.3, FreeBSD 6.2, AIX 7.1 */
+    ASSERT (errno == 0);
+  }
+  {
+    const char input[] = "0xp+1";
+    char *ptr;
+    float result;
+    errno = 0;
+    result = strtof (input, &ptr);
+    ASSERT (result == 0.0f);
+    ASSERT (!signbit (result));
+    ASSERT (ptr == input + 1);          /* glibc-2.3.6, Mac OS X 10.3, FreeBSD 6.2, AIX 7.1 */
+    ASSERT (errno == 0);
+  }
+  {
+    const char input[] = "0x.p+1";
+    char *ptr;
+    float result;
+    errno = 0;
+    result = strtof (input, &ptr);
+    ASSERT (result == 0.0f);
+    ASSERT (!signbit (result));
+    ASSERT (ptr == input + 1);          /* glibc-2.3.6, Mac OS X 10.3, FreeBSD 6.2, AIX 7.1 */
+    ASSERT (errno == 0);
+  }
+  {
+    const char input[] = "1p+1";
+    char *ptr;
+    float result;
+    errno = 0;
+    result = strtof (input, &ptr);
+    ASSERT (result == 1.0f);
+    ASSERT (ptr == input + 1);
+    ASSERT (errno == 0);
+  }
+  {
+    const char input[] = "1P+1";
+    char *ptr;
+    float result;
+    errno = 0;
+    result = strtof (input, &ptr);
+    ASSERT (result == 1.0f);
+    ASSERT (ptr == input + 1);
+    ASSERT (errno == 0);
+  }
+
+  /* Overflow/underflow.  */
+  {
+    const char input[] = "1E1000000";
+    char *ptr;
+    float result;
+    errno = 0;
+    result = strtof (input, &ptr);
+    ASSERT (result == HUGE_VAL);
+    ASSERT (ptr == input + 9);          /* OSF/1 5.1 */
+    ASSERT (errno == ERANGE);
+  }
+  {
+    const char input[] = "-1E1000000";
+    char *ptr;
+    float result;
+    errno = 0;
+    result = strtof (input, &ptr);
+    ASSERT (result == -HUGE_VAL);
+    ASSERT (ptr == input + 10);
+    ASSERT (errno == ERANGE);
+  }
+  {
+    const char input[] = "1E-100000";
+    char *ptr;
+    float result;
+    errno = 0;
+    result = strtof (input, &ptr);
+    ASSERT (0.0f <= result && result <= FLT_MIN);
+    ASSERT (!signbit (result));
+    ASSERT (ptr == input + 9);
+    ASSERT (errno == ERANGE);
+  }
+  {
+    const char input[] = "-1E-100000";
+    char *ptr;
+    float result;
+    errno = 0;
+    result = 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.  */
+    ASSERT (!!signbit (result) == !!signbit (minus_zerof)); /* glibc-2.3.6, mingw */
+#endif
+    ASSERT (ptr == input + 10);
+    ASSERT (errno == ERANGE);
+  }
+  {
+    const char input[] = "1E 1000000";
+    char *ptr;
+    float result;
+    errno = 0;
+    result = strtof (input, &ptr);
+    ASSERT (result == 1.0f);            /* HP-UX 11.11, IRIX 6.5, OSF/1 4.0 */
+    ASSERT (ptr == input + 1);          /* HP-UX 11.11, IRIX 6.5 */
+    ASSERT (errno == 0);
+  }
+  {
+    const char input[] = "0x1P 1000000";
+    char *ptr;
+    float result;
+    errno = 0;
+    result = strtof (input, &ptr);
+    ASSERT (result == 1.0f);            /* NetBSD 3.0, OpenBSD 4.0, AIX 7.1, HP-UX 11.11, IRIX 6.5, OSF/1 5.1, Solaris 10, mingw */
+    ASSERT (ptr == input + 3);          /* NetBSD 3.0, OpenBSD 4.0, AIX 7.1, HP-UX 11.11, IRIX 6.5, OSF/1 5.1, Solaris 10, mingw */
+    ASSERT (errno == 0);
+  }
+
+  /* Infinity.  */
+  {
+    const char input[] = "iNf";
+    char *ptr;
+    float result;
+    errno = 0;
+    result = strtof (input, &ptr);
+    ASSERT (result == HUGE_VAL);        /* OpenBSD 4.0, IRIX 6.5, OSF/1 5.1, mingw */
+    ASSERT (ptr == input + 3);          /* OpenBSD 4.0, HP-UX 11.00, IRIX 6.5, OSF/1 5.1, Solaris 9, mingw */
+    ASSERT (errno == 0);                /* HP-UX 11.11, OSF/1 4.0 */
+  }
+  {
+    const char input[] = "-InF";
+    char *ptr;
+    float result;
+    errno = 0;
+    result = strtof (input, &ptr);
+    ASSERT (result == -HUGE_VAL);       /* OpenBSD 4.0, IRIX 6.5, OSF/1 5.1, mingw */
+    ASSERT (ptr == input + 4);          /* OpenBSD 4.0, HP-UX 11.00, IRIX 6.5, OSF/1 4.0, Solaris 9, mingw */
+    ASSERT (errno == 0);                /* HP-UX 11.11, OSF/1 4.0 */
+  }
+  {
+    const char input[] = "infinite";
+    char *ptr;
+    float result;
+    errno = 0;
+    result = strtof (input, &ptr);
+    ASSERT (result == HUGE_VAL);        /* OpenBSD 4.0, HP-UX 11.11, IRIX 6.5, OSF/1 5.1, mingw */
+    ASSERT (ptr == input + 3);          /* OpenBSD 4.0, HP-UX 11.11, IRIX 6.5, OSF/1 5.1, mingw */
+    ASSERT (errno == 0);                /* OSF/1 4.0 */
+  }
+  {
+    const char input[] = "infinitY";
+    char *ptr;
+    float result;
+    errno = 0;
+    result = strtof (input, &ptr);
+    ASSERT (result == HUGE_VAL);        /* OpenBSD 4.0, IRIX 6.5, OSF/1 5.1, mingw */
+    ASSERT (ptr == input + 8);          /* OpenBSD 4.0, HP-UX 11.00, IRIX 6.5, OSF/1 5.1, Solaris 9, mingw */
+    ASSERT (errno == 0);                /* HP-UX 11.11, OSF/1 4.0 */
+  }
+  {
+    const char input[] = "infinitY.";
+    char *ptr;
+    float result;
+    errno = 0;
+    result = strtof (input, &ptr);
+    ASSERT (result == HUGE_VAL);        /* OpenBSD 4.0, HP-UX 11.11, IRIX 6.5, OSF/1 5.1, mingw */
+    ASSERT (ptr == input + 8);          /* OpenBSD 4.0, HP-UX 11.11, IRIX 6.5, OSF/1 5.1, mingw */
+    ASSERT (errno == 0);                /* OSF/1 4.0 */
+  }
+
+  /* NaN.  Some processors set the sign bit of the default NaN, so all
+     we check is that using a sign changes the result.  */
+  {
+    const char input[] = "-nan";
+    char *ptr1;
+    char *ptr2;
+    float result1;
+    float result2;
+    errno = 0;
+    result1 = strtof (input, &ptr1);
+    result2 = strtof (input + 1, &ptr2);
+#if 1 /* All known CPUs support NaNs.  */
+    ASSERT (isnanf (result1));          /* OpenBSD 4.0, IRIX 6.5, OSF/1 5.1, mingw */
+    ASSERT (isnanf (result2));          /* OpenBSD 4.0, IRIX 6.5, OSF/1 5.1, 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, IRIX 6.5, OSF/1 5.1, mingw */
+# endif
+    ASSERT (ptr1 == input + 4);         /* OpenBSD 4.0, IRIX 6.5, OSF/1 5.1, Solaris 2.5.1, mingw */
+    ASSERT (ptr2 == input + 4);         /* OpenBSD 4.0, IRIX 6.5, OSF/1 5.1, Solaris 2.5.1, mingw */
+    ASSERT (errno == 0);                /* HP-UX 11.11 */
+#else
+    ASSERT (result1 == 0.0f);
+    ASSERT (result2 == 0.0f);
+    ASSERT (!signbit (result1));
+    ASSERT (!signbit (result2));
+    ASSERT (ptr1 == input);
+    ASSERT (ptr2 == input + 1);
+    ASSERT (errno == 0 || errno == EINVAL);
+#endif
+  }
+  {
+    const char input[] = "+nan(";
+    char *ptr1;
+    char *ptr2;
+    float result1;
+    float result2;
+    errno = 0;
+    result1 = strtof (input, &ptr1);
+    result2 = strtof (input + 1, &ptr2);
+#if 1 /* All known CPUs support NaNs.  */
+    ASSERT (isnanf (result1));          /* OpenBSD 4.0, HP-UX 11.11, IRIX 6.5, OSF/1 5.1, mingw */
+    ASSERT (isnanf (result2));          /* OpenBSD 4.0, HP-UX 11.11, IRIX 6.5, OSF/1 5.1, mingw */
+    ASSERT (!!signbit (result1) == !!signbit (result2));
+    ASSERT (ptr1 == input + 4);         /* OpenBSD 4.0, HP-UX 11.11, IRIX 6.5, OSF/1 5.1, Solaris 2.5.1, mingw */
+    ASSERT (ptr2 == input + 4);         /* OpenBSD 4.0, HP-UX 11.11, IRIX 6.5, OSF/1 5.1, Solaris 2.5.1, mingw */
+    ASSERT (errno == 0);
+#else
+    ASSERT (result1 == 0.0f);
+    ASSERT (result2 == 0.0f);
+    ASSERT (!signbit (result1));
+    ASSERT (!signbit (result2));
+    ASSERT (ptr1 == input);
+    ASSERT (ptr2 == input + 1);
+    ASSERT (errno == 0 || errno == EINVAL);
+#endif
+  }
+  {
+    const char input[] = "-nan()";
+    char *ptr1;
+    char *ptr2;
+    float result1;
+    float result2;
+    errno = 0;
+    result1 = strtof (input, &ptr1);
+    result2 = strtof (input + 1, &ptr2);
+#if 1 /* All known CPUs support NaNs.  */
+    ASSERT (isnanf (result1));          /* OpenBSD 4.0, HP-UX 11.11, IRIX 6.5, OSF/1 5.1, mingw */
+    ASSERT (isnanf (result2));          /* OpenBSD 4.0, HP-UX 11.11, IRIX 6.5, OSF/1 5.1, 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, IRIX 6.5, OSF/1 5.1, mingw */
+# endif
+    ASSERT (ptr1 == input + 6);         /* glibc-2.3.6, Mac OS X 10.3, FreeBSD 6.2, OpenBSD 4.0, AIX 7.1, HP-UX 11.11, IRIX 6.5, OSF/1 5.1, mingw */
+    ASSERT (ptr2 == input + 6);         /* glibc-2.3.6, Mac OS X 10.3, FreeBSD 6.2, OpenBSD 4.0, AIX 7.1, HP-UX 11.11, IRIX 6.5, OSF/1 5.1, mingw */
+    ASSERT (errno == 0);
+#else
+    ASSERT (result1 == 0.0f);
+    ASSERT (result2 == 0.0f);
+    ASSERT (!signbit (result1));
+    ASSERT (!signbit (result2));
+    ASSERT (ptr1 == input);
+    ASSERT (ptr2 == input + 1);
+    ASSERT (errno == 0 || errno == EINVAL);
+#endif
+  }
+  {
+    const char input[] = " nan().";
+    char *ptr;
+    float result;
+    errno = 0;
+    result = strtof (input, &ptr);
+#if 1 /* All known CPUs support NaNs.  */
+    ASSERT (isnanf (result));           /* OpenBSD 4.0, HP-UX 11.11, IRIX 6.5, OSF/1 5.1, mingw */
+    ASSERT (ptr == input + 6);          /* glibc-2.3.6, Mac OS X 10.3, FreeBSD 6.2, OpenBSD 4.0, AIX 7.1, HP-UX 11.11, IRIX 6.5, OSF/1 5.1, mingw */
+    ASSERT (errno == 0);
+#else
+    ASSERT (result == 0.0f);
+    ASSERT (!signbit (result));
+    ASSERT (ptr == input);
+    ASSERT (errno == 0 || errno == EINVAL);
+#endif
+  }
+  {
+    /* The behavior of nan(0) is implementation-defined, but all
+       implementations we know of which handle optional
+       n-char-sequences handle nan(0) the same as nan().  */
+    const char input[] = "-nan(0).";
+    char *ptr1;
+    char *ptr2;
+    float result1;
+    float result2;
+    errno = 0;
+    result1 = strtof (input, &ptr1);
+    result2 = strtof (input + 1, &ptr2);
+#if 1 /* All known CPUs support NaNs.  */
+    ASSERT (isnanf (result1));          /* OpenBSD 4.0, HP-UX 11.11, IRIX 6.5, OSF/1 5.1, mingw */
+    ASSERT (isnanf (result2));          /* OpenBSD 4.0, HP-UX 11.11, IRIX 6.5, OSF/1 5.1, 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, IRIX 6.5, OSF/1 5.1, mingw */
+# endif
+    ASSERT (ptr1 == input + 7);         /* glibc-2.3.6, OpenBSD 4.0, AIX 7.1, HP-UX 11.11, IRIX 6.5, OSF/1 5.1, mingw */
+    ASSERT (ptr2 == input + 7);         /* glibc-2.3.6, OpenBSD 4.0, AIX 7.1, HP-UX 11.11, IRIX 6.5, OSF/1 5.1, mingw */
+    ASSERT (errno == 0);
+#else
+    ASSERT (result1 == 0.0f);
+    ASSERT (result2 == 0.0f);
+    ASSERT (!signbit (result1));
+    ASSERT (!signbit (result2));
+    ASSERT (ptr1 == input);
+    ASSERT (ptr2 == input + 1);
+    ASSERT (errno == 0 || errno == EINVAL);
+#endif
+  }
+
+  /* Hex.  */
+  {
+    const char input[] = "0xa";
+    char *ptr;
+    float result;
+    errno = 0;
+    result = strtof (input, &ptr);
+    ASSERT (result == 10.0f);           /* NetBSD 3.0, OpenBSD 4.0, AIX 5.1, HP-UX 11.11, IRIX 6.5, OSF/1 5.1, Solaris 10, mingw */
+    ASSERT (ptr == input + 3);          /* NetBSD 3.0, OpenBSD 4.0, AIX 5.1, HP-UX 11.11, IRIX 6.5, OSF/1 5.1, Solaris 10, mingw */
+    ASSERT (errno == 0);
+  }
+  {
+    const char input[] = "0XA";
+    char *ptr;
+    float result;
+    errno = 0;
+    result = strtof (input, &ptr);
+    ASSERT (result == 10.0f);           /* NetBSD 3.0, OpenBSD 4.0, AIX 5.1, HP-UX 11.11, IRIX 6.5, OSF/1 5.1, Solaris 10, mingw */
+    ASSERT (ptr == input + 3);          /* NetBSD 3.0, OpenBSD 4.0, AIX 5.1, HP-UX 11.11, IRIX 6.5, OSF/1 5.1, Solaris 10, mingw */
+    ASSERT (errno == 0);
+  }
+  {
+    const char input[] = "0x1p";
+    char *ptr;
+    float result;
+    errno = 0;
+    result = strtof (input, &ptr);
+    ASSERT (result == 1.0f);            /* NetBSD 3.0, OpenBSD 4.0, AIX 7.1, HP-UX 11.11, IRIX 6.5, OSF/1 5.1, Solaris 10, mingw */
+    ASSERT (ptr == input + 3);          /* NetBSD 3.0, OpenBSD 4.0, AIX 7.1, HP-UX 11.11, IRIX 6.5, OSF/1 5.1, Solaris 10, mingw */
+    ASSERT (errno == 0);
+  }
+  {
+    const char input[] = "0x1p+";
+    char *ptr;
+    float result;
+    errno = 0;
+    result = strtof (input, &ptr);
+    ASSERT (result == 1.0f);            /* NetBSD 3.0, OpenBSD 4.0, AIX 5.1, HP-UX 11.11, IRIX 6.5, OSF/1 5.1, Solaris 10, mingw */
+    ASSERT (ptr == input + 3);          /* NetBSD 3.0, OpenBSD 4.0, AIX 5.1, HP-UX 11.11, IRIX 6.5, OSF/1 5.1, Solaris 10, mingw */
+    ASSERT (errno == 0);
+  }
+  {
+    const char input[] = "0x1P+";
+    char *ptr;
+    float result;
+    errno = 0;
+    result = strtof (input, &ptr);
+    ASSERT (result == 1.0f);            /* NetBSD 3.0, OpenBSD 4.0, AIX 5.1, HP-UX 11.11, IRIX 6.5, OSF/1 5.1, Solaris 10, mingw */
+    ASSERT (ptr == input + 3);          /* NetBSD 3.0, OpenBSD 4.0, AIX 5.1, HP-UX 11.11, IRIX 6.5, OSF/1 5.1, Solaris 10, mingw */
+    ASSERT (errno == 0);
+  }
+  {
+    const char input[] = "0x1p+1";
+    char *ptr;
+    float result;
+    errno = 0;
+    result = strtof (input, &ptr);
+    ASSERT (result == 2.0f);            /* NetBSD 3.0, OpenBSD 4.0, AIX 5.1, HP-UX 11.11, IRIX 6.5, OSF/1 5.1, Solaris 10, mingw */
+    ASSERT (ptr == input + 6);          /* NetBSD 3.0, OpenBSD 4.0, AIX 5.1, HP-UX 11.11, IRIX 6.5, OSF/1 5.1, Solaris 10, mingw */
+    ASSERT (errno == 0);
+  }
+  {
+    const char input[] = "0X1P+1";
+    char *ptr;
+    float result;
+    errno = 0;
+    result = strtof (input, &ptr);
+    ASSERT (result == 2.0f);            /* NetBSD 3.0, OpenBSD 4.0, AIX 5.1, HP-UX 11.11, IRIX 6.5, OSF/1 5.1, Solaris 10, mingw */
+    ASSERT (ptr == input + 6);          /* NetBSD 3.0, OpenBSD 4.0, AIX 5.1, HP-UX 11.11, IRIX 6.5, OSF/1 5.1, Solaris 10, mingw */
+    ASSERT (errno == 0);
+  }
+  {
+    const char input[] = "0x1p+1a";
+    char *ptr;
+    float result;
+    errno = 0;
+    result = strtof (input, &ptr);
+    ASSERT (result == 2.0f);            /* NetBSD 3.0, OpenBSD 4.0, AIX 5.1, HP-UX 11.11, IRIX 6.5, OSF/1 5.1, Solaris 10, mingw */
+    ASSERT (ptr == input + 6);          /* NetBSD 3.0, OpenBSD 4.0, AIX 5.1, HP-UX 11.11, IRIX 6.5, OSF/1 5.1, Solaris 10, mingw */
+    ASSERT (errno == 0);
+  }
+  {
+    const char input[] = "0x1p 2";
+    char *ptr;
+    float result;
+    errno = 0;
+    result = strtof (input, &ptr);
+    ASSERT (result == 1.0f);            /* NetBSD 3.0, OpenBSD 4.0, AIX 7.1, HP-UX 11.11, IRIX 6.5, OSF/1 5.1, Solaris 10, mingw */
+    ASSERT (ptr == input + 3);          /* NetBSD 3.0, OpenBSD 4.0, AIX 7.1, HP-UX 11.11, IRIX 6.5, OSF/1 5.1, Solaris 10, mingw */
+    ASSERT (errno == 0);
+  }
+
+  /* Large buffers.  */
+  {
+    size_t m = 1000000;
+    char *input = malloc (m + 1);
+    if (input)
+      {
+        char *ptr;
+        float result;
+        memset (input, '\t', m - 1);
+        input[m - 1] = '1';
+        input[m] = '\0';
+        errno = 0;
+        result = strtof (input, &ptr);
+        ASSERT (result == 1.0f);
+        ASSERT (ptr == input + m);
+        ASSERT (errno == 0);
+      }
+    free (input);
+  }
+  {
+    size_t m = 1000000;
+    char *input = malloc (m + 1);
+    if (input)
+      {
+        char *ptr;
+        float result;
+        memset (input, '0', m - 1);
+        input[m - 1] = '1';
+        input[m] = '\0';
+        errno = 0;
+        result = strtof (input, &ptr);
+        ASSERT (result == 1.0f);
+        ASSERT (ptr == input + m);
+        ASSERT (errno == 0);
+      }
+    free (input);
+  }
+#if 0
+  /* Newlib has an artificial limit of 20000 for the exponent.  TODO -
+     gnulib should fix this.  */
+  {
+    size_t m = 1000000;
+    char *input = malloc (m + 1);
+    if (input)
+      {
+        char *ptr;
+        float result;
+        input[0] = '.';
+        memset (input + 1, '0', m - 10);
+        input[m - 9] = '1';
+        input[m - 8] = 'e';
+        input[m - 7] = '+';
+        input[m - 6] = '9';
+        input[m - 5] = '9';
+        input[m - 4] = '9';
+        input[m - 3] = '9';
+        input[m - 2] = '9';
+        input[m - 1] = '1';
+        input[m] = '\0';
+        errno = 0;
+        result = strtof (input, &ptr);
+        ASSERT (result == 1.0f);        /* Mac OS X 10.3, FreeBSD 6.2, NetBSD 3.0, OpenBSD 4.0, IRIX 6.5, OSF/1 5.1, mingw */
+        ASSERT (ptr == input + m);      /* OSF/1 5.1 */
+        ASSERT (errno == 0);            /* Mac OS X 10.3, FreeBSD 6.2, NetBSD 3.0, OpenBSD 4.0, IRIX 6.5, OSF/1 5.1, mingw */
+      }
+    free (input);
+  }
+  {
+    size_t m = 1000000;
+    char *input = malloc (m + 1);
+    if (input)
+      {
+        char *ptr;
+        float result;
+        input[0] = '1';
+        memset (input + 1, '0', m - 9);
+        input[m - 8] = 'e';
+        input[m - 7] = '-';
+        input[m - 6] = '9';
+        input[m - 5] = '9';
+        input[m - 4] = '9';
+        input[m - 3] = '9';
+        input[m - 2] = '9';
+        input[m - 1] = '1';
+        input[m] = '\0';
+        errno = 0;
+        result = strtof (input, &ptr);
+        ASSERT (result == 1.0f);        /* Mac OS X 10.3, FreeBSD 6.2, NetBSD 3.0, OpenBSD 4.0, IRIX 6.5, OSF/1 5.1, mingw */
+        ASSERT (ptr == input + m);
+        ASSERT (errno == 0);            /* Mac OS X 10.3, FreeBSD 6.2, NetBSD 3.0, OpenBSD 4.0, IRIX 6.5, OSF/1 5.1, mingw */
+      }
+    free (input);
+  }
+#endif
+  {
+    size_t m = 1000000;
+    char *input = malloc (m + 1);
+    if (input)
+      {
+        char *ptr;
+        float result;
+        input[0] = '-';
+        input[1] = '0';
+        input[2] = 'e';
+        input[3] = '1';
+        memset (input + 4, '0', m - 3);
+        input[m] = '\0';
+        errno = 0;
+        result = strtof (input, &ptr);
+        ASSERT (result == 0.0f);
+        ASSERT (!!signbit (result) == !!signbit (minus_zerof)); /* IRIX 6.5, OSF/1 4.0 */
+        ASSERT (ptr == input + m);
+        ASSERT (errno == 0);
+      }
+    free (input);
+  }
+
+  /* Rounding.  */
+  /* TODO - is it worth some tests of rounding for typical IEEE corner
+     cases, such as .5 ULP rounding up to the smallest denormal and
+     not causing underflow, or FLT_MIN - .5 ULP not causing an
+     infinite loop?  */
+
+  return status;
+}
diff --git a/tests/test-strtof1.c b/tests/test-strtof1.c
new file mode 100644
index 0000000000..bde446405a
--- /dev/null
+++ b/tests/test-strtof1.c
@@ -0,0 +1,101 @@
+/* Test of strtof() in a French locale.
+   Copyright (C) 2019-2024 Free Software Foundation, Inc.
+
+   This program is free software: you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation, either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <https://www.gnu.org/licenses/>.  */
+
+#include <config.h>
+
+#include <stdlib.h>
+
+#include <errno.h>
+#include <locale.h>
+
+#include "macros.h"
+
+int
+main (int argc, char *argv[])
+{
+  /* Try to set the locale by implicitly looking at the LC_ALL environment
+     variable.
+     configure should already have checked that the locale is supported.  */
+  if (setlocale (LC_ALL, "") == NULL)
+    return 1;
+
+  {
+    const char input[] = "1,";
+    char *ptr;
+    float result;
+    errno = 0;
+    result = strtof (input, &ptr);
+    ASSERT (result == 1.0f);
+    ASSERT (ptr == input + 2);
+    ASSERT (errno == 0);
+  }
+  {
+    const char input[] = ",5";
+    char *ptr;
+    float result;
+    errno = 0;
+    result = strtof (input, &ptr);
+    ASSERT (result == 0.5f);
+    ASSERT (ptr == input + 2);
+    ASSERT (errno == 0);
+  }
+  {
+    const char input[] = "1,5";
+    char *ptr;
+    float result;
+    errno = 0;
+    result = strtof (input, &ptr);
+    ASSERT (result == 1.5f);
+    ASSERT (ptr == input + 3);
+    ASSERT (errno == 0);
+  }
+  {
+    const char input[] = "1.5";
+    char *ptr;
+    float result;
+    errno = 0;
+    result = strtof (input, &ptr);
+    /* On AIX 7.2, in the French locale, '.' is recognized as an alternate
+       radix character.  */
+    ASSERT ((ptr == input + 1 && result == 1.0f)
+            || (ptr == input + 3 && result == 1.5f));
+    ASSERT (errno == 0);
+  }
+  {
+    const char input[] = "123.456,789";
+    char *ptr;
+    float result;
+    errno = 0;
+    result = strtof (input, &ptr);
+    /* On AIX 7.2, in the French locale, '.' is recognized as an alternate
+       radix character.  */
+    ASSERT ((ptr == input + 3 && result == 123.0f)
+            || (ptr == input + 7 && result > 123.45f && result < 123.46f));
+    ASSERT (errno == 0);
+  }
+  {
+    const char input[] = "123,456.789";
+    char *ptr;
+    float result;
+    errno = 0;
+    result = strtof (input, &ptr);
+    ASSERT (result > 123.45f && result < 123.46f);
+    ASSERT (ptr == input + 7);
+    ASSERT (errno == 0);
+  }
+
+  return 0;
+}
diff --git a/tests/test-strtof1.sh b/tests/test-strtof1.sh
new file mode 100755
index 0000000000..14592a8ed6
--- /dev/null
+++ b/tests/test-strtof1.sh
@@ -0,0 +1,30 @@
+#!/bin/sh
+
+: "${LOCALE_FR=fr_FR}"
+: "${LOCALE_FR_UTF8=fr_FR.UTF-8}"
+
+if test $LOCALE_FR = none && test $LOCALE_FR_UTF8 = none; then
+  if test -f /usr/bin/localedef; then
+    echo "Skipping test: no locale for testing is installed"
+  else
+    echo "Skipping test: no locale for testing is supported"
+  fi
+  exit 77
+fi
+
+if $LC_NUMERIC_IMPLEMENTED; then
+  :
+else
+  echo "Skipping test: LC_NUMERIC category of locales is not implemented"
+  exit 77
+fi
+
+if test $LOCALE_FR != none; then
+  LC_ALL=$LOCALE_FR      ${CHECKER} ./test-strtof1${EXEEXT} || exit 1
+fi
+
+if test $LOCALE_FR_UTF8 != none; then
+  LC_ALL=$LOCALE_FR_UTF8 ${CHECKER} ./test-strtof1${EXEEXT} || exit 1
+fi
+
+exit 0
-- 
2.34.1

Reply via email to