* 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: 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.
/* 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"
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
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;
{
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;
# 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. */
char *endbuf;
int saved_errno = errno;
+ radixchar = decimal_point_char ();
+
/* Eat whitespace. */
while (locale_isspace (*s))
++s;
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,
'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;
}
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
{
/* 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
{
{
/* 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
{
-# 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,
AC_DEFINE([HAVE_LDEXP_IN_LIBC], [1],
[Define if the ldexp function is available in libc.])
fi
+ AC_CHECK_FUNCS([nl_langinfo])
])
-# 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,
AC_DEFINE([HAVE_LDEXPL_IN_LIBC], [1],
[Define if the ldexpl function is available in libc.])
fi
+ AC_CHECK_FUNCS([nl_langinfo])
])
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
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
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
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
+/* 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
+#!/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
+/* 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
+#!/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