>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