changeset 40172:31ab89a208b9

strtod, strtold: Use the locale's decimal point. * lib/strtod.c: Include <locale.h>, <stdio.h>, <langinfo.h>. (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.
author Bruno Haible <bruno@clisp.org>
date Fri, 01 Feb 2019 03:12:28 +0100
parents fbfa1d6417d9
children b716418da8b9
files ChangeLog lib/strtod.c m4/strtod.m4 m4/strtold.m4 modules/strtod-tests modules/strtold-tests tests/test-strtod1.c tests/test-strtod1.sh tests/test-strtold1.c tests/test-strtold1.sh
diffstat 10 files changed, 338 insertions(+), 15 deletions(-) [+]
line wrap: on
line diff
--- a/ChangeLog	Fri Feb 01 02:51:20 2019 +0100
+++ b/ChangeLog	Fri Feb 01 03:12:28 2019 +0100
@@ -1,3 +1,27 @@
+2019-01-31  Bruno Haible  <bruno@clisp.org>
+
+	strtod, strtold: Use the locale's decimal point.
+	* lib/strtod.c: Include <locale.h>, <stdio.h>, <langinfo.h>.
+	(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  <bruno@clisp.org>
 
 	strtod, strtold tests: Simplify tests.
--- a/lib/strtod.c	Fri Feb 01 02:51:20 2019 +0100
+++ b/lib/strtod.c	Fri Feb 01 03:12:28 2019 +0100
@@ -21,13 +21,18 @@
 /* Specification.  */
 #include <stdlib.h>
 
-#include <ctype.h>
+#include <ctype.h>      /* isspace() */
 #include <errno.h>
-#include <float.h>
-#include <limits.h>
-#include <math.h>
+#include <float.h>      /* {DBL,LDBL}_{MIN,MAX} */
+#include <limits.h>     /* LONG_{MIN,MAX} */
+#include <locale.h>     /* localeconv() */
+#include <math.h>       /* NAN */
 #include <stdbool.h>
-#include <string.h>
+#include <stdio.h>      /* sprintf() */
+#include <string.h>     /* strdup() */
+#if HAVE_NL_LANGINFO
+# include <langinfo.h>
+#endif
 
 #include "c-ctype.h"
 
@@ -72,6 +77,28 @@
   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 @@
    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 @@
     {
       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 @@
 # 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 @@
   char *endbuf;
   int saved_errno = errno;
 
+  radixchar = decimal_point_char ();
+
   /* Eat whitespace.  */
   while (locale_isspace (*s))
     ++s;
@@ -316,7 +348,7 @@
   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 @@
          '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 @@
             }
           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 @@
                     {
                       /* 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 @@
                 {
                   /* 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
                 {
--- a/m4/strtod.m4	Fri Feb 01 02:51:20 2019 +0100
+++ b/m4/strtod.m4	Fri Feb 01 03:12:28 2019 +0100
@@ -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_DEFINE([HAVE_LDEXP_IN_LIBC], [1],
       [Define if the ldexp function is available in libc.])
   fi
+  AC_CHECK_FUNCS([nl_langinfo])
 ])
--- a/m4/strtold.m4	Fri Feb 01 02:51:20 2019 +0100
+++ b/m4/strtold.m4	Fri Feb 01 03:12:28 2019 +0100
@@ -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_DEFINE([HAVE_LDEXPL_IN_LIBC], [1],
       [Define if the ldexpl function is available in libc.])
   fi
+  AC_CHECK_FUNCS([nl_langinfo])
 ])
--- a/modules/strtod-tests	Fri Feb 01 02:51:20 2019 +0100
+++ b/modules/strtod-tests	Fri Feb 01 03:12:28 2019 +0100
@@ -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 @@
 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
--- a/modules/strtold-tests	Fri Feb 01 02:51:20 2019 +0100
+++ b/modules/strtold-tests	Fri Feb 01 03:12:28 2019 +0100
@@ -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 @@
 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
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-strtod1.c	Fri Feb 01 03:12:28 2019 +0100
@@ -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 <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;
+    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;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-strtod1.sh	Fri Feb 01 03:12:28 2019 +0100
@@ -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
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-strtold1.c	Fri Feb 01 03:12:28 2019 +0100
@@ -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 <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;
+    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;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-strtold1.sh	Fri Feb 01 03:12:28 2019 +0100
@@ -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