]> Savannah Git Hosting - gnulib.git/commitdiff
strtod, strtold: Use the locale's decimal point.
authorBruno Haible <bruno@clisp.org>
Fri, 1 Feb 2019 02:12:28 +0000 (03:12 +0100)
committerBruno Haible <bruno@clisp.org>
Fri, 1 Feb 2019 02:12:28 +0000 (03:12 +0100)
* 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.

ChangeLog
lib/strtod.c
m4/strtod.m4
m4/strtold.m4
modules/strtod-tests
modules/strtold-tests
tests/test-strtod1.c [new file with mode: 0644]
tests/test-strtod1.sh [new file with mode: 0755]
tests/test-strtold1.c [new file with mode: 0644]
tests/test-strtold1.sh [new file with mode: 0755]

index 37c92029696e37ebf4ceda591450939eff9540ee..3dbab7af443089cf7dfd2b1e1e33e9139a99490f 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -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.
index c6ce58024471dd3eb25c95d7836b4492891ab554..b9eaa51b488211ec42f66520c8c4e6cc1ade67a7 100644 (file)
 /* 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 @@ 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
                 {
index 97fb39b52f54904be0a0cc76915d1a3a1c0b4688..99122178a0e73732d0f9daa3a0bccded03af51fb 100644 (file)
@@ -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])
 ])
index 117b7f1856a372fe6db387e07d208bb218de7976..c1c05d1a7acd8227e6e2959d5e136af5e815ffb9 100644 (file)
@@ -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])
 ])
index 38aebe802ec3ec717d5066bb9df59e7850b8d0e7..e3cd57f66e03ab982bd60debcf1e14e0d92e7216 100644 (file)
@@ -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
index 8a812d28fd1e240464f0ae1189ffc8a733303473..c58e52c2ab6da671b5387664719aa684ded06ea1 100644 (file)
@@ -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 (file)
index 0000000..75200cc
--- /dev/null
@@ -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;
+}
diff --git a/tests/test-strtod1.sh b/tests/test-strtod1.sh
new file mode 100755 (executable)
index 0000000..d220f13
--- /dev/null
@@ -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 (file)
index 0000000..3a2f533
--- /dev/null
@@ -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;
+}
diff --git a/tests/test-strtold1.sh b/tests/test-strtold1.sh
new file mode 100755 (executable)
index 0000000..edec4d3
--- /dev/null
@@ -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