>From 97e23d40a659c85048b59852e1871823f727aeb2 Mon Sep 17 00:00:00 2001
From: Bruno Haible
Date: Fri, 1 Feb 2019 03:12:28 +0100
Subject: [PATCH 4/4] strtod, strtold: Use the locale's decimal point.
* lib/strtod.c: Include , , .
(decimal_point_char): New function, copied from lib/vasnprintf.c.
(parse_number): Add a radixchar argument. Use it instead of '.'.
(STRTOD): Invoke decimal_point_char and pass the result to parse_number.
* m4/strtod.m4 (gl_PREREQ_STRTOD): Test whether nl_langinfo exists.
* m4/strtold.m4 (gl_PREREQ_STRTOLD): Likewise.
* tests/test-strtod1.c: New file.
* tests/test-strtod1.sh: New file.
* modules/strtod-tests (Files): Add test-strtod1.{sh,c}. Add
locale-fr.m4 and its dependencies.
(configure.ac): Invoke gt_LOCALE_FR, gt_LOCALE_FR_UTF8.
(Makefile.am): Arrange to compile test-strtod1.c and run
test-strtod1.sh.
* tests/test-strtold1.c: New file.
* tests/test-strtold1.sh: New file.
* modules/strtold-tests (Files): Add test-strtold1.{sh,c}. Add
locale-fr.m4 and its dependencies.
(configure.ac): Invoke gt_LOCALE_FR, gt_LOCALE_FR_UTF8.
(Makefile.am): Arrange to compile test-strtold1.c and run
test-strtold1.sh.
---
ChangeLog | 24 +++++++++++++
lib/strtod.c | 59 +++++++++++++++++++++++-------
m4/strtod.m4 | 3 +-
m4/strtold.m4 | 3 +-
modules/strtod-tests | 12 +++++++
modules/strtold-tests | 12 +++++++
tests/test-strtod1.c | 97 ++++++++++++++++++++++++++++++++++++++++++++++++++
tests/test-strtod1.sh | 23 ++++++++++++
tests/test-strtold1.c | 97 ++++++++++++++++++++++++++++++++++++++++++++++++++
tests/test-strtold1.sh | 23 ++++++++++++
10 files changed, 338 insertions(+), 15 deletions(-)
create mode 100644 tests/test-strtod1.c
create mode 100755 tests/test-strtod1.sh
create mode 100644 tests/test-strtold1.c
create mode 100755 tests/test-strtold1.sh
diff --git a/ChangeLog b/ChangeLog
index 37c9202..3dbab7a 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,29 @@
2019-01-31 Bruno Haible
+ strtod, strtold: Use the locale's decimal point.
+ * lib/strtod.c: Include , , .
+ (decimal_point_char): New function, copied from lib/vasnprintf.c.
+ (parse_number): Add a radixchar argument. Use it instead of '.'.
+ (STRTOD): Invoke decimal_point_char and pass the result to parse_number.
+ * m4/strtod.m4 (gl_PREREQ_STRTOD): Test whether nl_langinfo exists.
+ * m4/strtold.m4 (gl_PREREQ_STRTOLD): Likewise.
+ * tests/test-strtod1.c: New file.
+ * tests/test-strtod1.sh: New file.
+ * modules/strtod-tests (Files): Add test-strtod1.{sh,c}. Add
+ locale-fr.m4 and its dependencies.
+ (configure.ac): Invoke gt_LOCALE_FR, gt_LOCALE_FR_UTF8.
+ (Makefile.am): Arrange to compile test-strtod1.c and run
+ test-strtod1.sh.
+ * tests/test-strtold1.c: New file.
+ * tests/test-strtold1.sh: New file.
+ * modules/strtold-tests (Files): Add test-strtold1.{sh,c}. Add
+ locale-fr.m4 and its dependencies.
+ (configure.ac): Invoke gt_LOCALE_FR, gt_LOCALE_FR_UTF8.
+ (Makefile.am): Arrange to compile test-strtold1.c and run
+ test-strtold1.sh.
+
+2019-01-31 Bruno Haible
+
strtod, strtold tests: Simplify tests.
* tests/test-strtod.c (main): Assume no rounding errors for 0.5.
* tests/test-strtold.c (main): Likewise.
diff --git a/lib/strtod.c b/lib/strtod.c
index c6ce580..b9eaa51 100644
--- a/lib/strtod.c
+++ b/lib/strtod.c
@@ -21,13 +21,18 @@
/* Specification. */
#include
-#include
+#include /* isspace() */
#include
-#include
-#include
-#include
+#include /* {DBL,LDBL}_{MIN,MAX} */
+#include /* LONG_{MIN,MAX} */
+#include /* localeconv() */
+#include /* NAN */
#include
-#include
+#include /* sprintf() */
+#include /* strdup() */
+#if HAVE_NL_LANGINFO
+# include
+#endif
#include "c-ctype.h"
@@ -72,6 +77,28 @@ locale_isspace (char c)
return isspace (uc) != 0;
}
+/* Determine the decimal-point character according to the current locale. */
+static char
+decimal_point_char (void)
+{
+ const char *point;
+ /* Determine it in a multithread-safe way. We know nl_langinfo is
+ multithread-safe on glibc systems and Mac OS X systems, but is not required
+ to be multithread-safe by POSIX. sprintf(), however, is multithread-safe.
+ localeconv() is rarely multithread-safe. */
+#if HAVE_NL_LANGINFO && (__GLIBC__ || defined __UCLIBC__ || (defined __APPLE__ && defined __MACH__))
+ point = nl_langinfo (RADIXCHAR);
+#elif 1
+ char pointbuf[5];
+ sprintf (pointbuf, "%#.0f", 1.0);
+ point = &pointbuf[1];
+#else
+ point = localeconv () -> decimal_point;
+#endif
+ /* The decimal point is always a single byte: either '.' or ','. */
+ return (point[0] != '\0' ? point[0] : '.');
+}
+
#if !USE_LDEXP
#undef LDEXP
#define LDEXP dummy_ldexp
@@ -146,7 +173,8 @@ scale_radix_exp (DOUBLE x, int radix, long int exponent)
EXPCHAR. BASE is RADIX**RADIX_MULTIPLIER. */
static DOUBLE
parse_number (const char *nptr,
- int base, int radix, int radix_multiplier, char expchar,
+ int base, int radix, int radix_multiplier, char radixchar,
+ char expchar,
char **endptr)
{
const char *s = nptr;
@@ -163,7 +191,7 @@ parse_number (const char *nptr,
{
if (base == 16 ? c_isxdigit (*s) : c_isdigit (*s))
;
- else if (radixchar_ptr == NULL && *s == '.')
+ else if (radixchar_ptr == NULL && *s == radixchar)
{
/* Record that we have found the decimal point. */
radixchar_ptr = s;
@@ -289,11 +317,13 @@ STRTOD (const char *nptr, char **endptr)
# endif
#else
# undef STRTOD
-# define STRTOD(NPTR,ENDPTR) parse_number (NPTR, 10, 10, 1, 'e', ENDPTR)
+# define STRTOD(NPTR,ENDPTR) \
+ parse_number (NPTR, 10, 10, 1, radixchar, 'e', ENDPTR)
#endif
/* From here on, STRTOD refers to the underlying implementation. It needs
to handle only finite unsigned decimal numbers with non-null ENDPTR. */
{
+ char radixchar;
bool negative = false;
/* The number so far. */
@@ -304,6 +334,8 @@ STRTOD (const char *nptr, char **endptr)
char *endbuf;
int saved_errno = errno;
+ radixchar = decimal_point_char ();
+
/* Eat whitespace. */
while (locale_isspace (*s))
++s;
@@ -316,7 +348,7 @@ STRTOD (const char *nptr, char **endptr)
num = STRTOD (s, &endbuf);
end = endbuf;
- if (c_isdigit (s[*s == '.']))
+ if (c_isdigit (s[*s == radixchar]))
{
/* If a hex float was converted incorrectly, do it ourselves.
If the string starts with "0x" but does not contain digits,
@@ -324,7 +356,7 @@ STRTOD (const char *nptr, char **endptr)
'p' but no exponent, then adjust the end pointer. */
if (*s == '0' && c_tolower (s[1]) == 'x')
{
- if (! c_isxdigit (s[2 + (s[2] == '.')]))
+ if (! c_isxdigit (s[2 + (s[2] == radixchar)]))
{
end = s + 1;
@@ -333,7 +365,7 @@ STRTOD (const char *nptr, char **endptr)
}
else if (end <= s + 2)
{
- num = parse_number (s + 2, 16, 2, 4, 'p', &endbuf);
+ num = parse_number (s + 2, 16, 2, 4, radixchar, 'p', &endbuf);
end = endbuf;
}
else
@@ -349,7 +381,8 @@ STRTOD (const char *nptr, char **endptr)
{
/* Not really our day, is it. Rounding errors are
better than outright failure. */
- num = parse_number (s + 2, 16, 2, 4, 'p', &endbuf);
+ num =
+ parse_number (s + 2, 16, 2, 4, radixchar, 'p', &endbuf);
}
else
{
@@ -379,7 +412,7 @@ STRTOD (const char *nptr, char **endptr)
{
/* Not really our day, is it. Rounding errors are
better than outright failure. */
- num = parse_number (s, 10, 10, 1, 'e', &endbuf);
+ num = parse_number (s, 10, 10, 1, radixchar, 'e', &endbuf);
}
else
{
diff --git a/m4/strtod.m4 b/m4/strtod.m4
index 97fb39b..9912217 100644
--- a/m4/strtod.m4
+++ b/m4/strtod.m4
@@ -1,4 +1,4 @@
-# strtod.m4 serial 23
+# strtod.m4 serial 24
dnl Copyright (C) 2002-2003, 2006-2019 Free Software Foundation, Inc.
dnl This file is free software; the Free Software Foundation
dnl gives unlimited permission to copy and/or distribute it,
@@ -138,4 +138,5 @@ AC_DEFUN([gl_PREREQ_STRTOD], [
AC_DEFINE([HAVE_LDEXP_IN_LIBC], [1],
[Define if the ldexp function is available in libc.])
fi
+ AC_CHECK_FUNCS([nl_langinfo])
])
diff --git a/m4/strtold.m4 b/m4/strtold.m4
index 117b7f1..c1c05d1 100644
--- a/m4/strtold.m4
+++ b/m4/strtold.m4
@@ -1,4 +1,4 @@
-# strtold.m4 serial 1
+# strtold.m4 serial 2
dnl Copyright (C) 2002-2003, 2006-2019 Free Software Foundation, Inc.
dnl This file is free software; the Free Software Foundation
dnl gives unlimited permission to copy and/or distribute it,
@@ -117,4 +117,5 @@ AC_DEFUN([gl_PREREQ_STRTOLD], [
AC_DEFINE([HAVE_LDEXPL_IN_LIBC], [1],
[Define if the ldexpl function is available in libc.])
fi
+ AC_CHECK_FUNCS([nl_langinfo])
])
diff --git a/modules/strtod-tests b/modules/strtod-tests
index 38aebe8..e3cd57f 100644
--- a/modules/strtod-tests
+++ b/modules/strtod-tests
@@ -1,8 +1,12 @@
Files:
tests/test-strtod.c
+tests/test-strtod1.sh
+tests/test-strtod1.c
tests/signature.h
tests/minus-zero.h
tests/macros.h
+m4/locale-fr.m4
+m4/codeset.m4
Depends-on:
float
@@ -10,7 +14,15 @@ isnand-nolibm
signbit
configure.ac:
+gt_LOCALE_FR
+gt_LOCALE_FR_UTF8
Makefile.am:
TESTS += test-strtod
check_PROGRAMS += test-strtod
+
+TESTS += test-strtod1.sh
+TESTS_ENVIRONMENT += \
+ LOCALE_FR='@LOCALE_FR@' \
+ LOCALE_FR_UTF8='@LOCALE_FR_UTF8@'
+check_PROGRAMS += test-strtod1
diff --git a/modules/strtold-tests b/modules/strtold-tests
index 8a812d2..c58e52c 100644
--- a/modules/strtold-tests
+++ b/modules/strtold-tests
@@ -1,8 +1,12 @@
Files:
tests/test-strtold.c
+tests/test-strtold1.sh
+tests/test-strtold1.c
tests/signature.h
tests/minus-zero.h
tests/macros.h
+m4/locale-fr.m4
+m4/codeset.m4
Depends-on:
float
@@ -10,7 +14,15 @@ isnanl-nolibm
signbit
configure.ac:
+gt_LOCALE_FR
+gt_LOCALE_FR_UTF8
Makefile.am:
TESTS += test-strtold
check_PROGRAMS += test-strtold
+
+TESTS += test-strtold1.sh
+TESTS_ENVIRONMENT += \
+ LOCALE_FR='@LOCALE_FR@' \
+ LOCALE_FR_UTF8='@LOCALE_FR_UTF8@'
+check_PROGRAMS += test-strtold1
diff --git a/tests/test-strtod1.c b/tests/test-strtod1.c
new file mode 100644
index 0000000..75200cc
--- /dev/null
+++ b/tests/test-strtod1.c
@@ -0,0 +1,97 @@
+/* Test of strtod() in a French locale.
+ Copyright (C) 2019 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 . */
+
+#include
+
+#include
+
+#include
+#include
+
+#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;
+ double result;
+ errno = 0;
+ result = strtod (input, &ptr);
+ ASSERT (result == 1.0);
+ ASSERT (ptr == input + 2);
+ ASSERT (errno == 0);
+ }
+ {
+ const char input[] = ",5";
+ char *ptr;
+ double result;
+ errno = 0;
+ result = strtod (input, &ptr);
+ ASSERT (result == 0.5);
+ ASSERT (ptr == input + 2);
+ ASSERT (errno == 0);
+ }
+ {
+ const char input[] = "1,5";
+ char *ptr;
+ double result;
+ errno = 0;
+ result = strtod (input, &ptr);
+ ASSERT (result == 1.5);
+ ASSERT (ptr == input + 3);
+ ASSERT (errno == 0);
+ }
+ {
+ const char input[] = "1.5";
+ char *ptr;
+ double result;
+ errno = 0;
+ result = strtod (input, &ptr);
+ ASSERT (result == 1.0);
+ ASSERT (ptr == input + 1);
+ ASSERT (errno == 0);
+ }
+ {
+ const char input[] = "123.456,789";
+ char *ptr;
+ double result;
+ errno = 0;
+ result = strtod (input, &ptr);
+ ASSERT (result == 123.0);
+ ASSERT (ptr == input + 3);
+ ASSERT (errno == 0);
+ }
+ {
+ const char input[] = "123,456.789";
+ char *ptr;
+ double result;
+ errno = 0;
+ result = strtod (input, &ptr);
+ ASSERT (result > 123.45 && result < 123.46);
+ ASSERT (ptr == input + 7);
+ ASSERT (errno == 0);
+ }
+
+ return 0;
+}
diff --git a/tests/test-strtod1.sh b/tests/test-strtod1.sh
new file mode 100755
index 0000000..d220f13
--- /dev/null
+++ b/tests/test-strtod1.sh
@@ -0,0 +1,23 @@
+#!/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 test $LOCALE_FR != none; then
+ LC_ALL=$LOCALE_FR ./test-strtod1${EXEEXT} || exit 1
+fi
+
+if test $LOCALE_FR_UTF8 != none; then
+ LC_ALL=$LOCALE_FR_UTF8 ./test-strtod1${EXEEXT} || exit 1
+fi
+
+exit 0
diff --git a/tests/test-strtold1.c b/tests/test-strtold1.c
new file mode 100644
index 0000000..3a2f533
--- /dev/null
+++ b/tests/test-strtold1.c
@@ -0,0 +1,97 @@
+/* Test of strtold() in a French locale.
+ Copyright (C) 2019 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 . */
+
+#include
+
+#include
+
+#include
+#include
+
+#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;
+ long double result;
+ errno = 0;
+ result = strtold (input, &ptr);
+ ASSERT (result == 1.0L);
+ ASSERT (ptr == input + 2);
+ ASSERT (errno == 0);
+ }
+ {
+ const char input[] = ",5";
+ char *ptr;
+ long double result;
+ errno = 0;
+ result = strtold (input, &ptr);
+ ASSERT (result == 0.5L);
+ ASSERT (ptr == input + 2);
+ ASSERT (errno == 0);
+ }
+ {
+ const char input[] = "1,5";
+ char *ptr;
+ long double result;
+ errno = 0;
+ result = strtold (input, &ptr);
+ ASSERT (result == 1.5L);
+ ASSERT (ptr == input + 3);
+ ASSERT (errno == 0);
+ }
+ {
+ const char input[] = "1.5";
+ char *ptr;
+ long double result;
+ errno = 0;
+ result = strtold (input, &ptr);
+ ASSERT (result == 1.0L);
+ ASSERT (ptr == input + 1);
+ ASSERT (errno == 0);
+ }
+ {
+ const char input[] = "123.456,789";
+ char *ptr;
+ long double result;
+ errno = 0;
+ result = strtold (input, &ptr);
+ ASSERT (result == 123.0L);
+ ASSERT (ptr == input + 3);
+ ASSERT (errno == 0);
+ }
+ {
+ const char input[] = "123,456.789";
+ char *ptr;
+ long double result;
+ errno = 0;
+ result = strtold (input, &ptr);
+ ASSERT (result > 123.45L && result < 123.46L);
+ ASSERT (ptr == input + 7);
+ ASSERT (errno == 0);
+ }
+
+ return 0;
+}
diff --git a/tests/test-strtold1.sh b/tests/test-strtold1.sh
new file mode 100755
index 0000000..edec4d3
--- /dev/null
+++ b/tests/test-strtold1.sh
@@ -0,0 +1,23 @@
+#!/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 test $LOCALE_FR != none; then
+ LC_ALL=$LOCALE_FR ./test-strtold1${EXEEXT} || exit 1
+fi
+
+if test $LOCALE_FR_UTF8 != none; then
+ LC_ALL=$LOCALE_FR_UTF8 ./test-strtold1${EXEEXT} || exit 1
+fi
+
+exit 0
--
2.7.4