From f7a63a906ed956838b3e6d5545092cef264e0b6e Mon Sep 17 00:00:00 2001 From: Bruno Haible Date: Sat, 12 Apr 2025 00:09:23 +0200 Subject: [PATCH] vasnprintf: Work around two grouping bugs on many platforms. * m4/printf.m4 (gl_PRINTF_FLAG_GROUPING_INT_PRECISION): New macro. (gl_PRINTF_FLAG_GROUPING_MULTIBYTE): New macro. * m4/fprintf-posix.m4 (gl_FUNC_FPRINTF_IS_POSIX): Require gl_PRINTF_FLAG_GROUPING_INT_PRECISION, gl_PRINTF_FLAG_GROUPING_MULTIBYTE. Test gl_cv_func_printf_flag_grouping_int_precision, gl_cv_func_printf_flag_grouping_multibyte. * m4/dprintf-posix.m4 (gl_FUNC_DPRINTF_IS_POSIX): Likewise. * m4/snprintf-posix.m4 (gl_FUNC_SNPRINTF_IS_POSIX): Likewise. * m4/sprintf-posix.m4 (gl_FUNC_SPRINTF_IS_POSIX): Likewise. * m4/vasnprintf-posix.m4 (gl_FUNC_VASNPRINTF_IS_POSIX): Likewise. * m4/vasprintf-posix.m4 (gl_FUNC_VASPRINTF_IS_POSIX): Likewise. * m4/vdprintf-posix.m4 (gl_FUNC_VDPRINTF_IS_POSIX): Likewise. * m4/vfprintf-posix.m4 (gl_FUNC_VFPRINTF_IS_POSIX): Likewise. * m4/vsnprintf-posix.m4 (gl_FUNC_VSNPRINTF_IS_POSIX): Likewise. * m4/vsprintf-posix.m4 (gl_FUNC_VSPRINTF_IS_POSIX): Likewise. * m4/obstack-printf-posix.m4 (gl_FUNC_OBSTACK_PRINTF_IS_POSIX): Likewise. * m4/vasnprintf.m4 (gl_PREREQ_VASNPRINTF_FLAG_GROUPING): Require gl_PRINTF_FLAG_GROUPING_INT_PRECISION, gl_PRINTF_FLAG_GROUPING_MULTIBYTE. Test gl_cv_func_printf_flag_grouping_int_precision, gl_cv_func_printf_flag_grouping_multibyte. Define the C macro NEED_PRINTF_FLAG_GROUPING_INT. * lib/vasnprintf.c (thousands_separator_char): New function. (THOUSEP_CHAR_MAXLEN): New macro. (thousands_separator_wchar): New function. (THOUSEP_WCHAR_MAXLEN): New macro. (MAX_ROOM_NEEDED): Improve the room estimate for FLAG_GROUP. (VASNPRINTF): Add code for emitting thousands separators in the %f, %F, %g, %G implementations and in the code relies the system's sprintf/snprintf/swprintf function. Test NEED_PRINTF_FLAG_GROUPING and NEED_PRINTF_FLAG_GROUPING_INT where needed. * tests/test-vasnprintf-posix2.c (main): Add tests of the grouping flag. * tests/test-vasnwprintf-posix2.c (main): Likewise. * tests/test-vasnprintf-posix2.sh: Test in both French locales, not only in one of them. * tests/test-vasnwprintf-posix2.sh: Likewise. * modules/vasnprintf-posix-tests (Depends-on): Add localeconv. * modules/vasnwprintf-posix-tests (Depends-on): Likewise. * doc/posix-functions/fprintf.texi: Mention the grouping bug with precision on integers and the grouping bug with a multibyte thousands separator. * doc/posix-functions/vfprintf.texi: Likewise. * doc/posix-functions/printf.texi: Likewise. * doc/posix-functions/vprintf.texi: Likewise. * doc/posix-functions/sprintf.texi: Likewise. * doc/posix-functions/vsprintf.texi: Likewise. * doc/posix-functions/snprintf.texi: Likewise. * doc/posix-functions/vsnprintf.texi: Likewise. * doc/posix-functions/dprintf.texi: Likewise. * doc/posix-functions/vdprintf.texi: Likewise. * doc/posix-functions/fwprintf.texi: Likewise. * doc/posix-functions/vfwprintf.texi: Likewise. * doc/posix-functions/wprintf.texi: Likewise. * doc/posix-functions/vwprintf.texi: Likewise. * doc/posix-functions/swprintf.texi: Likewise. * doc/posix-functions/vswprintf.texi: Likewise. * doc/posix-functions/asprintf.texi: Likewise. * doc/posix-functions/vasprintf.texi: Likewise. * doc/glibc-functions/obstack_printf.texi: Likewise. * doc/glibc-functions/obstack_vprintf.texi: Likewise. --- ChangeLog | 66 ++++ doc/glibc-functions/obstack_printf.texi | 8 + doc/glibc-functions/obstack_vprintf.texi | 8 + doc/posix-functions/asprintf.texi | 8 + doc/posix-functions/dprintf.texi | 8 + doc/posix-functions/fprintf.texi | 8 + doc/posix-functions/fwprintf.texi | 8 + doc/posix-functions/printf.texi | 8 + doc/posix-functions/snprintf.texi | 8 + doc/posix-functions/sprintf.texi | 8 + doc/posix-functions/swprintf.texi | 8 + doc/posix-functions/vasprintf.texi | 8 + doc/posix-functions/vdprintf.texi | 8 + doc/posix-functions/vfprintf.texi | 8 + doc/posix-functions/vfwprintf.texi | 8 + doc/posix-functions/vprintf.texi | 8 + doc/posix-functions/vsnprintf.texi | 8 + doc/posix-functions/vsprintf.texi | 8 + doc/posix-functions/vswprintf.texi | 8 + doc/posix-functions/vwprintf.texi | 8 + doc/posix-functions/wprintf.texi | 8 + lib/vasnprintf.c | 484 +++++++++++++++++++++-- m4/dprintf-posix.m4 | 28 +- m4/fprintf-posix.m4 | 28 +- m4/obstack-printf-posix.m4 | 28 +- m4/printf.m4 | 252 ++++++++---- m4/snprintf-posix.m4 | 38 +- m4/sprintf-posix.m4 | 28 +- m4/vasnprintf-posix.m4 | 32 +- m4/vasnprintf.m4 | 17 +- m4/vasprintf-posix.m4 | 32 +- m4/vdprintf-posix.m4 | 28 +- m4/vfprintf-posix.m4 | 28 +- m4/vsnprintf-posix.m4 | 38 +- m4/vsprintf-posix.m4 | 28 +- modules/vasnprintf-posix-tests | 1 + modules/vasnwprintf-posix-tests | 1 + tests/test-vasnprintf-posix2.c | 370 +++++++++++++++++ tests/test-vasnprintf-posix2.sh | 26 +- tests/test-vasnwprintf-posix2.c | 275 +++++++++++++ tests/test-vasnwprintf-posix2.sh | 26 +- 41 files changed, 1763 insertions(+), 251 deletions(-) diff --git a/ChangeLog b/ChangeLog index dedd3ca34e..1794562eaf 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,69 @@ +2025-04-11 Bruno Haible + + vasnprintf: Work around two grouping bugs on many platforms. + * m4/printf.m4 (gl_PRINTF_FLAG_GROUPING_INT_PRECISION): New macro. + (gl_PRINTF_FLAG_GROUPING_MULTIBYTE): New macro. + * m4/fprintf-posix.m4 (gl_FUNC_FPRINTF_IS_POSIX): Require + gl_PRINTF_FLAG_GROUPING_INT_PRECISION, + gl_PRINTF_FLAG_GROUPING_MULTIBYTE. Test + gl_cv_func_printf_flag_grouping_int_precision, + gl_cv_func_printf_flag_grouping_multibyte. + * m4/dprintf-posix.m4 (gl_FUNC_DPRINTF_IS_POSIX): Likewise. + * m4/snprintf-posix.m4 (gl_FUNC_SNPRINTF_IS_POSIX): Likewise. + * m4/sprintf-posix.m4 (gl_FUNC_SPRINTF_IS_POSIX): Likewise. + * m4/vasnprintf-posix.m4 (gl_FUNC_VASNPRINTF_IS_POSIX): Likewise. + * m4/vasprintf-posix.m4 (gl_FUNC_VASPRINTF_IS_POSIX): Likewise. + * m4/vdprintf-posix.m4 (gl_FUNC_VDPRINTF_IS_POSIX): Likewise. + * m4/vfprintf-posix.m4 (gl_FUNC_VFPRINTF_IS_POSIX): Likewise. + * m4/vsnprintf-posix.m4 (gl_FUNC_VSNPRINTF_IS_POSIX): Likewise. + * m4/vsprintf-posix.m4 (gl_FUNC_VSPRINTF_IS_POSIX): Likewise. + * m4/obstack-printf-posix.m4 (gl_FUNC_OBSTACK_PRINTF_IS_POSIX): + Likewise. + * m4/vasnprintf.m4 (gl_PREREQ_VASNPRINTF_FLAG_GROUPING): Require + gl_PRINTF_FLAG_GROUPING_INT_PRECISION, + gl_PRINTF_FLAG_GROUPING_MULTIBYTE. Test + gl_cv_func_printf_flag_grouping_int_precision, + gl_cv_func_printf_flag_grouping_multibyte. Define the C macro + NEED_PRINTF_FLAG_GROUPING_INT. + * lib/vasnprintf.c (thousands_separator_char): New function. + (THOUSEP_CHAR_MAXLEN): New macro. + (thousands_separator_wchar): New function. + (THOUSEP_WCHAR_MAXLEN): New macro. + (MAX_ROOM_NEEDED): Improve the room estimate for FLAG_GROUP. + (VASNPRINTF): Add code for emitting thousands separators in the %f, %F, + %g, %G implementations and in the code relies the system's + sprintf/snprintf/swprintf function. Test NEED_PRINTF_FLAG_GROUPING and + NEED_PRINTF_FLAG_GROUPING_INT where needed. + * tests/test-vasnprintf-posix2.c (main): Add tests of the grouping flag. + * tests/test-vasnwprintf-posix2.c (main): Likewise. + * tests/test-vasnprintf-posix2.sh: Test in both French locales, not only + in one of them. + * tests/test-vasnwprintf-posix2.sh: Likewise. + * modules/vasnprintf-posix-tests (Depends-on): Add localeconv. + * modules/vasnwprintf-posix-tests (Depends-on): Likewise. + * doc/posix-functions/fprintf.texi: Mention the grouping bug with + precision on integers and the grouping bug with a multibyte thousands + separator. + * doc/posix-functions/vfprintf.texi: Likewise. + * doc/posix-functions/printf.texi: Likewise. + * doc/posix-functions/vprintf.texi: Likewise. + * doc/posix-functions/sprintf.texi: Likewise. + * doc/posix-functions/vsprintf.texi: Likewise. + * doc/posix-functions/snprintf.texi: Likewise. + * doc/posix-functions/vsnprintf.texi: Likewise. + * doc/posix-functions/dprintf.texi: Likewise. + * doc/posix-functions/vdprintf.texi: Likewise. + * doc/posix-functions/fwprintf.texi: Likewise. + * doc/posix-functions/vfwprintf.texi: Likewise. + * doc/posix-functions/wprintf.texi: Likewise. + * doc/posix-functions/vwprintf.texi: Likewise. + * doc/posix-functions/swprintf.texi: Likewise. + * doc/posix-functions/vswprintf.texi: Likewise. + * doc/posix-functions/asprintf.texi: Likewise. + * doc/posix-functions/vasprintf.texi: Likewise. + * doc/glibc-functions/obstack_printf.texi: Likewise. + * doc/glibc-functions/obstack_vprintf.texi: Likewise. + 2025-04-11 Bruno Haible vasnprintf: Follow glibc's behaviour on glibc systems. diff --git a/doc/glibc-functions/obstack_printf.texi b/doc/glibc-functions/obstack_printf.texi index 1f2d1ad5a1..c96a674ac2 100644 --- a/doc/glibc-functions/obstack_printf.texi +++ b/doc/glibc-functions/obstack_printf.texi @@ -42,6 +42,14 @@ printf @code{"%f"}, @code{"%e"}, @code{"%g"} of Infinity and NaN yields an incorrect result on some platforms: AIX 7.3, Solaris 11.4, mingw, MSVC 14. @item +This function doesn't support the @code{'} flag together with a precision +on an integer argument correctly on some platforms: +glibc 2.36, FreeBSD 14.0, NetBSD 10.0, Solaris 11, Cygwin 3.6.0, Haiku. +@item +This function doesn't support the @code{'} flag correctly in locales where +the thousands separator is a multibyte character on some platforms: +NetBSD 10.0, Solaris 11 OpenIndiana. +@item This function does not support the @samp{a} and @samp{A} directives on some platforms: FreeBSD 5.2.1, OpenBSD 4.0, AIX 5.2, HP-UX 11, diff --git a/doc/glibc-functions/obstack_vprintf.texi b/doc/glibc-functions/obstack_vprintf.texi index c94df7b26c..5fe389c486 100644 --- a/doc/glibc-functions/obstack_vprintf.texi +++ b/doc/glibc-functions/obstack_vprintf.texi @@ -42,6 +42,14 @@ printf @code{"%f"}, @code{"%e"}, @code{"%g"} of Infinity and NaN yields an incorrect result on some platforms: AIX 7.3, Solaris 11.4, mingw, MSVC 14. @item +This function doesn't support the @code{'} flag together with a precision +on an integer argument correctly on some platforms: +glibc 2.36, FreeBSD 14.0, NetBSD 10.0, Solaris 11, Cygwin 3.6.0, Haiku. +@item +This function doesn't support the @code{'} flag correctly in locales where +the thousands separator is a multibyte character on some platforms: +NetBSD 10.0, Solaris 11 OpenIndiana. +@item This function does not support the @samp{a} and @samp{A} directives on some platforms: FreeBSD 5.2.1, OpenBSD 4.0, AIX 5.2, HP-UX 11, diff --git a/doc/posix-functions/asprintf.texi b/doc/posix-functions/asprintf.texi index bf5fbb8f0a..dbbed305b5 100644 --- a/doc/posix-functions/asprintf.texi +++ b/doc/posix-functions/asprintf.texi @@ -71,6 +71,14 @@ NetBSD 3.0. This function doesn't support the @code{'} flag on some platforms: NetBSD 3.0, Cygwin 1.5.24. @item +This function doesn't support the @code{'} flag together with a precision +on an integer argument correctly on some platforms: +glibc 2.36, FreeBSD 14.0, NetBSD 10.0, Solaris 11, Cygwin 3.6.0, Haiku. +@item +This function doesn't support the @code{'} flag correctly in locales where +the thousands separator is a multibyte character on some platforms: +NetBSD 10.0, Solaris 11 OpenIndiana. +@item This function does not round the argument of the @samp{a} directive correctly on some platforms: macOS 14, FreeBSD 14.0, NetBSD 10.0, OpenBSD 7.5, AIX 7.3, Solaris 11.4. diff --git a/doc/posix-functions/dprintf.texi b/doc/posix-functions/dprintf.texi index e19b75c891..da9446ae99 100644 --- a/doc/posix-functions/dprintf.texi +++ b/doc/posix-functions/dprintf.texi @@ -28,6 +28,14 @@ printf @code{"%f"}, @code{"%e"}, @code{"%g"} of Infinity and NaN yields an incorrect result on some platforms: AIX 7.3, Solaris 11.4. @item +This function doesn't support the @code{'} flag together with a precision +on an integer argument correctly on some platforms: +glibc 2.36, FreeBSD 14.0, NetBSD 10.0, Solaris 11, Cygwin 3.6.0, Haiku. +@item +This function doesn't support the @code{'} flag correctly in locales where +the thousands separator is a multibyte character on some platforms: +NetBSD 10.0, Solaris 11 OpenIndiana. +@item This function does not support the @samp{a} and @samp{A} directives on some platforms: Solaris 11.4. diff --git a/doc/posix-functions/fprintf.texi b/doc/posix-functions/fprintf.texi index 5509008d82..407ad6bc30 100644 --- a/doc/posix-functions/fprintf.texi +++ b/doc/posix-functions/fprintf.texi @@ -58,6 +58,14 @@ NetBSD 3.0, mingw, MSVC 14. This function doesn't support the @code{'} flag on some platforms: NetBSD 3.0, Cygwin 1.5.24, mingw, MSVC 14. @item +This function doesn't support the @code{'} flag together with a precision +on an integer argument correctly on some platforms: +glibc 2.36, FreeBSD 14.0, NetBSD 10.0, Solaris 11, Cygwin 3.6.0, Haiku. +@item +This function doesn't support the @code{'} flag correctly in locales where +the thousands separator is a multibyte character on some platforms: +NetBSD 10.0, Solaris 11 OpenIndiana. +@item This function does not round the argument of the @samp{a} directive correctly on some platforms: macOS 14, FreeBSD 14.0, NetBSD 10.0, OpenBSD 7.5, AIX 7.3, Solaris 11.4. diff --git a/doc/posix-functions/fwprintf.texi b/doc/posix-functions/fwprintf.texi index e408a0f8f8..0dd8ec828a 100644 --- a/doc/posix-functions/fwprintf.texi +++ b/doc/posix-functions/fwprintf.texi @@ -24,6 +24,14 @@ This function does not support size specifiers as in C23 (@code{w8}, @code{wf64}) on some platforms: glibc 2.37, musl libc, macOS 14, FreeBSD 13.2, NetBSD 10.0, OpenBSD 7.5, AIX 7.3, Solaris 11.4, Cygwin 3.5.3, mingw, MSVC, Android 9.0. @item +This function doesn't support the @code{'} flag together with a precision +on an integer argument correctly on some platforms: +glibc 2.36, FreeBSD 14.0, NetBSD 10.0, Solaris 11, Cygwin 3.6.0, Haiku. +@item +This function doesn't support the @code{'} flag correctly in locales where +the thousands separator is a multibyte character on some platforms: +NetBSD 10.0, Solaris 11 OpenIndiana. +@item This function does not support the @samp{b} directive, required by ISO C23, on some platforms: glibc 2.34, musl libc, macOS 14, FreeBSD 13.2, NetBSD 10.0, OpenBSD 7.5, diff --git a/doc/posix-functions/printf.texi b/doc/posix-functions/printf.texi index aba01687b8..2b2c240531 100644 --- a/doc/posix-functions/printf.texi +++ b/doc/posix-functions/printf.texi @@ -30,6 +30,14 @@ printf @code{"%f"}, @code{"%e"}, @code{"%g"} of Infinity and NaN yields an incorrect result on some platforms: AIX 7.3, Solaris 11.4, mingw, MSVC 14. @item +This function doesn't support the @code{'} flag together with a precision +on an integer argument correctly on some platforms: +glibc 2.36, FreeBSD 14.0, NetBSD 10.0, Solaris 11, Cygwin 3.6.0, Haiku. +@item +This function doesn't support the @code{'} flag correctly in locales where +the thousands separator is a multibyte character on some platforms: +NetBSD 10.0, Solaris 11 OpenIndiana. +@item This function does not support the @samp{a} and @samp{A} directives on some platforms: FreeBSD 5.2.1, OpenBSD 4.0, AIX 5.2, HP-UX 11, diff --git a/doc/posix-functions/snprintf.texi b/doc/posix-functions/snprintf.texi index b7e0f8445a..a1aea4ea4b 100644 --- a/doc/posix-functions/snprintf.texi +++ b/doc/posix-functions/snprintf.texi @@ -43,6 +43,14 @@ printf @code{"%f"}, @code{"%e"}, @code{"%g"} of Infinity and NaN yields an incorrect result on some platforms: AIX 7.3, Solaris 11.4, mingw, MSVC 14. @item +This function doesn't support the @code{'} flag together with a precision +on an integer argument correctly on some platforms: +glibc 2.36, FreeBSD 14.0, NetBSD 10.0, Solaris 11, Cygwin 3.6.0, Haiku. +@item +This function doesn't support the @code{'} flag correctly in locales where +the thousands separator is a multibyte character on some platforms: +NetBSD 10.0, Solaris 11 OpenIndiana. +@item This function does not support the @samp{a} and @samp{A} directives on some platforms: FreeBSD 5.2.1, OpenBSD 4.0, AIX 5.2, HP-UX 11, diff --git a/doc/posix-functions/sprintf.texi b/doc/posix-functions/sprintf.texi index 5b666609ee..3b0bc0f725 100644 --- a/doc/posix-functions/sprintf.texi +++ b/doc/posix-functions/sprintf.texi @@ -27,6 +27,14 @@ printf @code{"%f"}, @code{"%e"}, @code{"%g"} of Infinity and NaN yields an incorrect result on some platforms: AIX 7.3, Solaris 11.4, mingw, MSVC 14. @item +This function doesn't support the @code{'} flag together with a precision +on an integer argument correctly on some platforms: +glibc 2.36, FreeBSD 14.0, NetBSD 10.0, Solaris 11, Cygwin 3.6.0, Haiku. +@item +This function doesn't support the @code{'} flag correctly in locales where +the thousands separator is a multibyte character on some platforms: +NetBSD 10.0, Solaris 11 OpenIndiana. +@item This function does not support the @samp{a} and @samp{A} directives on some platforms: FreeBSD 5.2.1, OpenBSD 4.0, AIX 5.2, HP-UX 11, diff --git a/doc/posix-functions/swprintf.texi b/doc/posix-functions/swprintf.texi index 7f6ee3848f..944dd4798c 100644 --- a/doc/posix-functions/swprintf.texi +++ b/doc/posix-functions/swprintf.texi @@ -60,6 +60,14 @@ characters on some platforms: musl libc 1.2.3, macOS 14, FreeBSD 13.1, NetBSD 10.0, OpenBSD 7.2, AIX 7.2, mingw. @item +This function doesn't support the @code{'} flag together with a precision +on an integer argument correctly on some platforms: +glibc 2.36, FreeBSD 14.0, NetBSD 10.0, Solaris 11, Cygwin 3.6.0, Haiku. +@item +This function doesn't support the @code{'} flag correctly in locales where +the thousands separator is a multibyte character on some platforms: +NetBSD 10.0, Solaris 11 OpenIndiana. +@item This function does not support the @samp{b} directive, required by ISO C23, on some platforms: glibc 2.34, musl libc, macOS 14, FreeBSD 13.2, NetBSD 10.0, OpenBSD 7.5, diff --git a/doc/posix-functions/vasprintf.texi b/doc/posix-functions/vasprintf.texi index 7c45295759..8805d6fb9a 100644 --- a/doc/posix-functions/vasprintf.texi +++ b/doc/posix-functions/vasprintf.texi @@ -45,6 +45,14 @@ printf @code{"%f"}, @code{"%e"}, @code{"%g"} of Infinity and NaN yields an incorrect result on some platforms: AIX 7.3, Solaris 11.4. @item +This function doesn't support the @code{'} flag together with a precision +on an integer argument correctly on some platforms: +glibc 2.36, FreeBSD 14.0, NetBSD 10.0, Solaris 11, Cygwin 3.6.0, Haiku. +@item +This function doesn't support the @code{'} flag correctly in locales where +the thousands separator is a multibyte character on some platforms: +NetBSD 10.0, Solaris 11 OpenIndiana. +@item This function does not support the @samp{a} and @samp{A} directives on some platforms: FreeBSD 5.2.1, OpenBSD 4.0, Solaris 11.4, Cygwin 1.5.x. diff --git a/doc/posix-functions/vdprintf.texi b/doc/posix-functions/vdprintf.texi index 619ff1260e..3277f6edb6 100644 --- a/doc/posix-functions/vdprintf.texi +++ b/doc/posix-functions/vdprintf.texi @@ -28,6 +28,14 @@ printf @code{"%f"}, @code{"%e"}, @code{"%g"} of Infinity and NaN yields an incorrect result on some platforms: AIX 7.3, Solaris 11.4. @item +This function doesn't support the @code{'} flag together with a precision +on an integer argument correctly on some platforms: +glibc 2.36, FreeBSD 14.0, NetBSD 10.0, Solaris 11, Cygwin 3.6.0, Haiku. +@item +This function doesn't support the @code{'} flag correctly in locales where +the thousands separator is a multibyte character on some platforms: +NetBSD 10.0, Solaris 11 OpenIndiana. +@item This function does not support the @samp{a} and @samp{A} directives on some platforms: Solaris 11.4. diff --git a/doc/posix-functions/vfprintf.texi b/doc/posix-functions/vfprintf.texi index ffebd0983d..cdf1f75cc8 100644 --- a/doc/posix-functions/vfprintf.texi +++ b/doc/posix-functions/vfprintf.texi @@ -30,6 +30,14 @@ printf @code{"%f"}, @code{"%e"}, @code{"%g"} of Infinity and NaN yields an incorrect result on some platforms: AIX 7.3, Solaris 11.4, mingw, MSVC 14. @item +This function doesn't support the @code{'} flag together with a precision +on an integer argument correctly on some platforms: +glibc 2.36, FreeBSD 14.0, NetBSD 10.0, Solaris 11, Cygwin 3.6.0, Haiku. +@item +This function doesn't support the @code{'} flag correctly in locales where +the thousands separator is a multibyte character on some platforms: +NetBSD 10.0, Solaris 11 OpenIndiana. +@item This function does not support the @samp{a} and @samp{A} directives on some platforms: FreeBSD 5.2.1, OpenBSD 4.0, AIX 5.2, HP-UX 11, diff --git a/doc/posix-functions/vfwprintf.texi b/doc/posix-functions/vfwprintf.texi index e92f3e08c7..0e7bb752fa 100644 --- a/doc/posix-functions/vfwprintf.texi +++ b/doc/posix-functions/vfwprintf.texi @@ -24,6 +24,14 @@ This function does not support size specifiers as in C23 (@code{w8}, @code{wf64}) on some platforms: glibc 2.37, musl libc, macOS 14, FreeBSD 13.2, NetBSD 10.0, OpenBSD 7.5, AIX 7.3, Solaris 11.4, Cygwin 3.5.3, mingw, MSVC, Android 9.0. @item +This function doesn't support the @code{'} flag together with a precision +on an integer argument correctly on some platforms: +glibc 2.36, FreeBSD 14.0, NetBSD 10.0, Solaris 11, Cygwin 3.6.0, Haiku. +@item +This function doesn't support the @code{'} flag correctly in locales where +the thousands separator is a multibyte character on some platforms: +NetBSD 10.0, Solaris 11 OpenIndiana. +@item This function does not support the @samp{b} directive, required by ISO C23, on some platforms: glibc 2.34, musl libc, macOS 14, FreeBSD 13.2, NetBSD 10.0, OpenBSD 7.5, diff --git a/doc/posix-functions/vprintf.texi b/doc/posix-functions/vprintf.texi index 85bdf6977d..ad82631b32 100644 --- a/doc/posix-functions/vprintf.texi +++ b/doc/posix-functions/vprintf.texi @@ -30,6 +30,14 @@ printf @code{"%f"}, @code{"%e"}, @code{"%g"} of Infinity and NaN yields an incorrect result on some platforms: AIX 7.3, Solaris 11.4, mingw, MSVC 14. @item +This function doesn't support the @code{'} flag together with a precision +on an integer argument correctly on some platforms: +glibc 2.36, FreeBSD 14.0, NetBSD 10.0, Solaris 11, Cygwin 3.6.0, Haiku. +@item +This function doesn't support the @code{'} flag correctly in locales where +the thousands separator is a multibyte character on some platforms: +NetBSD 10.0, Solaris 11 OpenIndiana. +@item This function does not support the @samp{a} and @samp{A} directives on some platforms: FreeBSD 5.2.1, OpenBSD 4.0, AIX 5.2, HP-UX 11, diff --git a/doc/posix-functions/vsnprintf.texi b/doc/posix-functions/vsnprintf.texi index e0372e8be7..0dc8280a7c 100644 --- a/doc/posix-functions/vsnprintf.texi +++ b/doc/posix-functions/vsnprintf.texi @@ -40,6 +40,14 @@ printf @code{"%f"}, @code{"%e"}, @code{"%g"} of Infinity and NaN yields an incorrect result on some platforms: AIX 7.3, Solaris 11.4, mingw, MSVC 14. @item +This function doesn't support the @code{'} flag together with a precision +on an integer argument correctly on some platforms: +glibc 2.36, FreeBSD 14.0, NetBSD 10.0, Solaris 11, Cygwin 3.6.0, Haiku. +@item +This function doesn't support the @code{'} flag correctly in locales where +the thousands separator is a multibyte character on some platforms: +NetBSD 10.0, Solaris 11 OpenIndiana. +@item This function does not support the @samp{a} and @samp{A} directives on some platforms: FreeBSD 5.2.1, OpenBSD 4.0, AIX 5.2, HP-UX 11, diff --git a/doc/posix-functions/vsprintf.texi b/doc/posix-functions/vsprintf.texi index 7289400e54..fb23110621 100644 --- a/doc/posix-functions/vsprintf.texi +++ b/doc/posix-functions/vsprintf.texi @@ -27,6 +27,14 @@ printf @code{"%f"}, @code{"%e"}, @code{"%g"} of Infinity and NaN yields an incorrect result on some platforms: AIX 7.3, Solaris 11.4, mingw, MSVC 14. @item +This function doesn't support the @code{'} flag together with a precision +on an integer argument correctly on some platforms: +glibc 2.36, FreeBSD 14.0, NetBSD 10.0, Solaris 11, Cygwin 3.6.0, Haiku. +@item +This function doesn't support the @code{'} flag correctly in locales where +the thousands separator is a multibyte character on some platforms: +NetBSD 10.0, Solaris 11 OpenIndiana. +@item This function does not support the @samp{a} and @samp{A} directives on some platforms: FreeBSD 5.2.1, OpenBSD 4.0, AIX 5.2, HP-UX 11, diff --git a/doc/posix-functions/vswprintf.texi b/doc/posix-functions/vswprintf.texi index 03a6c06eed..b237e8b086 100644 --- a/doc/posix-functions/vswprintf.texi +++ b/doc/posix-functions/vswprintf.texi @@ -26,6 +26,14 @@ This function does not support size specifiers as in C23 (@code{w8}, @code{wf64}) on some platforms: glibc 2.37, musl libc, macOS 14, FreeBSD 13.2, NetBSD 10.0, OpenBSD 7.5, AIX 7.3, Solaris 11.4, Cygwin 3.5.3, mingw, MSVC, Android 9.0. @item +This function doesn't support the @code{'} flag together with a precision +on an integer argument correctly on some platforms: +glibc 2.36, FreeBSD 14.0, NetBSD 10.0, Solaris 11, Cygwin 3.6.0, Haiku. +@item +This function doesn't support the @code{'} flag correctly in locales where +the thousands separator is a multibyte character on some platforms: +NetBSD 10.0, Solaris 11 OpenIndiana. +@item This function does not support the @samp{b} directive, required by ISO C23, on some platforms: glibc 2.34, musl libc, macOS 14, FreeBSD 13.2, NetBSD 10.0, OpenBSD 7.5, diff --git a/doc/posix-functions/vwprintf.texi b/doc/posix-functions/vwprintf.texi index 7065c7163c..faa3428143 100644 --- a/doc/posix-functions/vwprintf.texi +++ b/doc/posix-functions/vwprintf.texi @@ -27,6 +27,14 @@ This function does not support size specifiers as in C23 (@code{w8}, @code{wf64}) on some platforms: glibc 2.37, musl libc, macOS 14, FreeBSD 13.2, NetBSD 10.0, OpenBSD 7.5, AIX 7.3, Solaris 11.4, Cygwin 3.5.3, mingw, MSVC, Android 9.0. @item +This function doesn't support the @code{'} flag together with a precision +on an integer argument correctly on some platforms: +glibc 2.36, FreeBSD 14.0, NetBSD 10.0, Solaris 11, Cygwin 3.6.0, Haiku. +@item +This function doesn't support the @code{'} flag correctly in locales where +the thousands separator is a multibyte character on some platforms: +NetBSD 10.0, Solaris 11 OpenIndiana. +@item This function does not support the @samp{b} directive, required by ISO C23, on some platforms: glibc 2.34, musl libc, macOS 14, FreeBSD 13.2, NetBSD 10.0, OpenBSD 7.5, diff --git a/doc/posix-functions/wprintf.texi b/doc/posix-functions/wprintf.texi index ab169cf7ae..47353bcce8 100644 --- a/doc/posix-functions/wprintf.texi +++ b/doc/posix-functions/wprintf.texi @@ -27,6 +27,14 @@ This function does not support size specifiers as in C23 (@code{w8}, @code{wf64}) on some platforms: glibc 2.37, musl libc, macOS 14, FreeBSD 13.2, NetBSD 10.0, OpenBSD 7.5, AIX 7.3, Solaris 11.4, Cygwin 3.5.3, mingw, MSVC, Android 9.0. @item +This function doesn't support the @code{'} flag together with a precision +on an integer argument correctly on some platforms: +glibc 2.36, FreeBSD 14.0, NetBSD 10.0, Solaris 11, Cygwin 3.6.0, Haiku. +@item +This function doesn't support the @code{'} flag correctly in locales where +the thousands separator is a multibyte character on some platforms: +NetBSD 10.0, Solaris 11 OpenIndiana. +@item This function does not support the @samp{b} directive, required by ISO C23, on some platforms: glibc 2.34, musl libc, macOS 14, FreeBSD 13.2, NetBSD 10.0, OpenBSD 7.5, diff --git a/lib/vasnprintf.c b/lib/vasnprintf.c index f91300cbd1..45e7c724a5 100644 --- a/lib/vasnprintf.c +++ b/lib/vasnprintf.c @@ -88,7 +88,7 @@ #include /* errno */ #include /* CHAR_BIT, INT_MAX, INT_WIDTH, LONG_WIDTH */ #include /* DBL_MAX_EXP, LDBL_MAX_EXP, LDBL_MANT_DIG */ -#if HAVE_NL_LANGINFO +#if HAVE_NL_LANGINFO || __GLIBC__ >= 2 || defined __CYGWIN__ # include #endif #ifndef VASNPRINTF @@ -371,7 +371,7 @@ local_wctomb (char *s, wchar_t wc) # endif #endif -#if NEED_PRINTF_DIRECTIVE_A || NEED_PRINTF_LONG_DOUBLE || NEED_PRINTF_INFINITE_LONG_DOUBLE || NEED_PRINTF_DOUBLE || NEED_PRINTF_INFINITE_DOUBLE || (NEED_WPRINTF_DIRECTIVE_LA && WIDE_CHAR_VERSION) +#if NEED_PRINTF_DIRECTIVE_A || NEED_PRINTF_LONG_DOUBLE || NEED_PRINTF_INFINITE_LONG_DOUBLE || NEED_PRINTF_DOUBLE || NEED_PRINTF_INFINITE_DOUBLE || (NEED_WPRINTF_DIRECTIVE_LA && WIDE_CHAR_VERSION) || (NEED_PRINTF_FLAG_ALT_PRECISION_ZERO || NEED_PRINTF_UNBOUNDED_PRECISION || NEED_PRINTF_FLAG_GROUPING || NEED_PRINTF_FLAG_GROUPING_INT) /* Determine the decimal-point character according to the current locale. */ # ifndef decimal_point_char_defined # define decimal_point_char_defined 1 @@ -398,6 +398,94 @@ decimal_point_char (void) # endif #endif +#if (!WIDE_CHAR_VERSION && (NEED_PRINTF_DOUBLE || NEED_PRINTF_LONG_DOUBLE)) || ((!WIDE_CHAR_VERSION || !DCHAR_IS_TCHAR) && (NEED_PRINTF_FLAG_ALT_PRECISION_ZERO || NEED_PRINTF_UNBOUNDED_PRECISION || NEED_PRINTF_FLAG_GROUPING || NEED_PRINTF_FLAG_GROUPING_INT)) +/* Determine the thousands-separator character according to the current + locale. + It is a single multibyte character. + In glibc: 35x ".", 90x ",", 23x U+202F, 1x U+2019, 1x U+066C, on other + systems also U+00A0. */ +# ifndef thousands_separator_char_defined +# define thousands_separator_char_defined 1 +static const char * +thousands_separator_char (char stackbuf[10]) +{ + /* Determine it in a multithread-safe way. We know nl_langinfo is + multithread-safe on glibc systems, on Mac OS X systems, and on NetBSD, + 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__) || defined __NetBSD__) + return nl_langinfo (THOUSEP); +# elif 1 + sprintf (stackbuf, "%'.0f", 1000.0); + /* Now stackbuf = "1000". */ + stackbuf[strlen (stackbuf) - 3] = '\0'; +# if defined __sun + /* Solaris specific hack: Replace wrong result (0xC2 means U+00A0). */ + if (strcmp (&stackbuf[1], "\302") == 0) + strcpy (&stackbuf[1], MB_CUR_MAX > 1 ? "\302\240" : "\240"); +# endif + return &stackbuf[1]; +# else + return localeconv () -> thousands_sep; +# endif +} +# endif +#endif +/* Maximum number of 'char' in the char[] representation of the thousands + separator. */ +#define THOUSEP_CHAR_MAXLEN 3 + +#if WIDE_CHAR_VERSION && ((NEED_PRINTF_DOUBLE || NEED_PRINTF_LONG_DOUBLE) || ((NEED_PRINTF_FLAG_ALT_PRECISION_ZERO || NEED_PRINTF_UNBOUNDED_PRECISION || NEED_PRINTF_FLAG_GROUPING || NEED_PRINTF_FLAG_GROUPING_INT) && DCHAR_IS_TCHAR)) +/* Determine the thousands-separator character, as a wide character, according + to the current locale. + It is a single wide character. */ +# ifndef thousands_separator_wchar_defined +# define thousands_separator_wchar_defined 1 +static const wchar_t * +thousands_separator_wchar (wchar_t stackbuf[10]) +{ +# if __GLIBC__ >= 2 || defined __CYGWIN__ + /* On glibc, in the unibyte locale fr_FR, the *wprintf routines use U+202F + as separator, which cannot be represented in the locale encoding. */ + stackbuf[0] = + (wchar_t) (unsigned long) nl_langinfo (_NL_NUMERIC_THOUSANDS_SEP_WC); + stackbuf[1] = L'\0'; + return stackbuf; +# elif defined __sun + /* Use sprintf, because swprintf retrieves a wrong value for the + thousands-separator wide character (e.g. (wchar_t) 0xffffffa0). */ + char tmp[10]; + sprintf (tmp, "%'.0f", 1000.0); + /* Now tmp = L"1000". */ + tmp[strlen (tmp) - 3] = '\0'; + /* Solaris specific hack: Replace wrong result (0xC2 means U+00A0). */ + if (strcmp (&tmp[1], "\302") == 0) + strcpy (&tmp[1], MB_CUR_MAX > 1 ? "\302\240" : "\240"); + if (tmp[1] != '\0') + { + mbstate_t state; + mbszero (&state); + if ((int) mbrtowc (&stackbuf[0], &tmp[1], strlen (&tmp[1]), &state) > 0) + stackbuf[1] = L'\0'; + else + stackbuf[0] = L'\0'; + } + else + stackbuf[0] = L'\0'; + return stackbuf; +# else + swprintf (stackbuf, 10, L"%'.0f", 1000.0); + /* Now stackbuf = L"1000". */ + stackbuf[local_wcslen (stackbuf) - 3] = '\0'; + return &stackbuf[1]; +# endif +} +# endif +#endif +/* Maximum number of 'wchar_t' in the wchar_t[] representation of the thousands + separator. */ +#define THOUSEP_WCHAR_MAXLEN 1 + #if NEED_PRINTF_INFINITE_DOUBLE && !NEED_PRINTF_DOUBLE /* Equivalent to !isfinite(x) || x == 0, but does not require libm. */ @@ -1852,8 +1940,15 @@ MAX_ROOM_NEEDED (const arguments *ap, size_t arg_index, FCHAR_T conversion, } if (tmp_length < precision) tmp_length = precision; - /* Multiply by 2, as an estimate for FLAG_GROUP. */ - tmp_length = xsum (tmp_length, tmp_length); + /* Account for thousands separators. */ + if (flags & FLAG_GROUP) + { +# if WIDE_CHAR_VERSION + tmp_length = xsum (tmp_length, tmp_length / 3 * THOUSEP_WCHAR_MAXLEN); +# else + tmp_length = xsum (tmp_length, tmp_length / 3 * THOUSEP_CHAR_MAXLEN); +# endif + } /* Add 1, to account for a leading sign. */ tmp_length = xsum (tmp_length, 1); break; @@ -4903,6 +4998,16 @@ VASNPRINTF (DCHAR_T *resultbuf, size_t *lengthp, } } # endif + /* Account for thousands separators. */ + if (flags & FLAG_GROUP) + { +# if WIDE_CHAR_VERSION + tmp_length = xsum (tmp_length, tmp_length / 3 * THOUSEP_WCHAR_MAXLEN); +# else + + tmp_length = xsum (tmp_length, tmp_length / 3 * THOUSEP_CHAR_MAXLEN); +# endif + } /* Account for sign, decimal point etc. */ tmp_length = xsum (tmp_length, 12); @@ -4998,12 +5103,49 @@ VASNPRINTF (DCHAR_T *resultbuf, size_t *lengthp, ndigits = strlen (digits); if (ndigits > precision) - do - { - --ndigits; - *p++ = digits[ndigits]; - } - while (ndigits > precision); + { + const DCHAR_T *thousep = NULL; + DCHAR_T thousep_buf[10]; +# if !WIDE_CHAR_VERSION + size_t thousep_len; +# endif + + if ((flags & FLAG_GROUP) + && (ndigits - precision > 3)) + { + /* Determine the thousands separator of the + current locale. */ +# if WIDE_CHAR_VERSION + /* DCHAR_T is wchar_t. */ + thousep = thousands_separator_wchar (thousep_buf); +# else + /* DCHAR_T is char. */ + thousep = thousands_separator_char (thousep_buf); + thousep_len = strlen (thousep); +# endif + if (*thousep == 0) + thousep = NULL; + } + + for (;;) + { + --ndigits; + *p++ = digits[ndigits]; + if (ndigits == precision) + break; + if (thousep != NULL + /* implies (flags & FLAG_GROUP) */ + && ((ndigits - precision) % 3) == 0) + { +# if WIDE_CHAR_VERSION + *p++ = thousep[0]; +# else + memcpy (p, thousep, thousep_len); + p += thousep_len; +# endif + } + } + } else *p++ = '0'; /* Here ndigits <= precision. */ @@ -5258,8 +5400,49 @@ VASNPRINTF (DCHAR_T *resultbuf, size_t *lengthp, { size_t ecount = exponent + 1; /* Note: count <= precision = ndigits. */ - for (; ecount > 0; ecount--) - *p++ = digits[--ndigits]; + + const DCHAR_T *thousep = NULL; + DCHAR_T thousep_buf[10]; +# if !WIDE_CHAR_VERSION + size_t thousep_len; +# endif + + if ((flags & FLAG_GROUP) && (ecount > 3)) + { + /* Determine the thousands separator of the + current locale. */ +# if WIDE_CHAR_VERSION + /* DCHAR_T is wchar_t. */ + thousep = thousands_separator_wchar (thousep_buf); +# else + /* DCHAR_T is char. */ + thousep = thousands_separator_char (thousep_buf); + thousep_len = strlen (thousep); +# endif + if (*thousep == 0) + thousep = NULL; + } + + if (ecount > 0) + for (;;) + { + --ndigits; + *p++ = digits[ndigits]; + if (--ecount == 0) + break; + if (thousep != NULL + /* implies (flags & FLAG_GROUP) */ + && (ecount % 3) == 0) + { +# if WIDE_CHAR_VERSION + *p++ = thousep[0]; +# else + memcpy (p, thousep, thousep_len); + p += thousep_len; +# endif + } + } + if ((flags & FLAG_ALT) || ndigits > nzeroes) { *p++ = decimal_point_char (); @@ -5460,12 +5643,49 @@ VASNPRINTF (DCHAR_T *resultbuf, size_t *lengthp, ndigits = strlen (digits); if (ndigits > precision) - do - { - --ndigits; - *p++ = digits[ndigits]; - } - while (ndigits > precision); + { + const DCHAR_T *thousep = NULL; + DCHAR_T thousep_buf[10]; +# if !WIDE_CHAR_VERSION + size_t thousep_len; +# endif + + if ((flags & FLAG_GROUP) + && (ndigits - precision > 3)) + { + /* Determine the thousands separator of the + current locale. */ +# if WIDE_CHAR_VERSION + /* DCHAR_T is wchar_t. */ + thousep = thousands_separator_wchar (thousep_buf); +# else + /* DCHAR_T is char. */ + thousep = thousands_separator_char (thousep_buf); + thousep_len = strlen (thousep); +# endif + if (*thousep == 0) + thousep = NULL; + } + + for (;;) + { + --ndigits; + *p++ = digits[ndigits]; + if (ndigits == precision) + break; + if (thousep != NULL + /* implies (flags & FLAG_GROUP) */ + && ((ndigits - precision) % 3) == 0) + { +# if WIDE_CHAR_VERSION + *p++ = thousep[0]; +# else + memcpy (p, thousep, thousep_len); + p += thousep_len; +# endif + } + } + } else *p++ = '0'; /* Here ndigits <= precision. */ @@ -5728,8 +5948,49 @@ VASNPRINTF (DCHAR_T *resultbuf, size_t *lengthp, { size_t ecount = exponent + 1; /* Note: ecount <= precision = ndigits. */ - for (; ecount > 0; ecount--) - *p++ = digits[--ndigits]; + + const DCHAR_T *thousep = NULL; + DCHAR_T thousep_buf[10]; +# if !WIDE_CHAR_VERSION + size_t thousep_len; +# endif + + if ((flags & FLAG_GROUP) && (ecount > 3)) + { + /* Determine the thousands separator of the + current locale. */ +# if WIDE_CHAR_VERSION + /* DCHAR_T is wchar_t. */ + thousep = thousands_separator_wchar (thousep_buf); +# else + /* DCHAR_T is char. */ + thousep = thousands_separator_char (thousep_buf); + thousep_len = strlen (thousep); +# endif + if (*thousep == 0) + thousep = NULL; + } + + if (ecount > 0) + for (;;) + { + --ndigits; + *p++ = digits[ndigits]; + if (--ecount == 0) + break; + if (thousep != NULL + /* implies (flags & FLAG_GROUP) */ + && (ecount % 3) == 0) + { +# if WIDE_CHAR_VERSION + *p++ = thousep[0]; +# else + memcpy (p, thousep, thousep_len); + p += thousep_len; +# endif + } + } + if ((flags & FLAG_ALT) || ndigits > nzeroes) { *p++ = decimal_point_char (); @@ -5937,13 +6198,13 @@ VASNPRINTF (DCHAR_T *resultbuf, size_t *lengthp, { arg_type type = a.arg[dp->arg_index].type; int flags = dp->flags; -#if (WIDE_CHAR_VERSION && MUSL_LIBC) || !DCHAR_IS_TCHAR || ENABLE_UNISTDIO || NEED_PRINTF_FLAG_LEFTADJUST || NEED_PRINTF_FLAG_ZERO || NEED_PRINTF_FLAG_ALT_PRECISION_ZERO || NEED_PRINTF_UNBOUNDED_PRECISION +#if (WIDE_CHAR_VERSION && MUSL_LIBC) || NEED_PRINTF_FLAG_LEFTADJUST || !DCHAR_IS_TCHAR || ENABLE_UNISTDIO || NEED_PRINTF_FLAG_ZERO || NEED_PRINTF_FLAG_ALT_PRECISION_ZERO || NEED_PRINTF_UNBOUNDED_PRECISION || NEED_PRINTF_FLAG_GROUPING || NEED_PRINTF_FLAG_GROUPING_INT int has_width; #endif -#if !USE_SNPRINTF || WIDE_CHAR_VERSION || !HAVE_SNPRINTF_RETVAL_C99 || USE_MSVC__SNPRINTF || !DCHAR_IS_TCHAR || ENABLE_UNISTDIO || NEED_PRINTF_FLAG_LEFTADJUST || NEED_PRINTF_FLAG_ZERO || NEED_PRINTF_FLAG_ALT_PRECISION_ZERO || NEED_PRINTF_UNBOUNDED_PRECISION +#if !USE_SNPRINTF || WIDE_CHAR_VERSION || !HAVE_SNPRINTF_RETVAL_C99 || USE_MSVC__SNPRINTF || NEED_PRINTF_FLAG_LEFTADJUST || !DCHAR_IS_TCHAR || ENABLE_UNISTDIO || NEED_PRINTF_FLAG_ZERO || NEED_PRINTF_FLAG_ALT_PRECISION_ZERO || NEED_PRINTF_UNBOUNDED_PRECISION || NEED_PRINTF_FLAG_GROUPING || NEED_PRINTF_FLAG_GROUPING_INT size_t width; #endif -#if !USE_SNPRINTF || (WIDE_CHAR_VERSION && DCHAR_IS_TCHAR) || !HAVE_SNPRINTF_RETVAL_C99 || USE_MSVC__SNPRINTF || (WIDE_CHAR_VERSION && MUSL_LIBC) || !DCHAR_IS_TCHAR || ENABLE_UNISTDIO || NEED_PRINTF_FLAG_LEFTADJUST || NEED_PRINTF_FLAG_ZERO || NEED_PRINTF_FLAG_ALT_PRECISION_ZERO || NEED_PRINTF_UNBOUNDED_PRECISION +#if !USE_SNPRINTF || (WIDE_CHAR_VERSION && DCHAR_IS_TCHAR) || !HAVE_SNPRINTF_RETVAL_C99 || USE_MSVC__SNPRINTF || (WIDE_CHAR_VERSION && MUSL_LIBC) || NEED_PRINTF_FLAG_LEFTADJUST || !DCHAR_IS_TCHAR || ENABLE_UNISTDIO || NEED_PRINTF_FLAG_ZERO || NEED_PRINTF_FLAG_ALT_PRECISION_ZERO || NEED_PRINTF_UNBOUNDED_PRECISION || NEED_PRINTF_FLAG_GROUPING || NEED_PRINTF_FLAG_GROUPING_INT int has_precision; size_t precision; #endif @@ -5952,9 +6213,14 @@ VASNPRINTF (DCHAR_T *resultbuf, size_t *lengthp, #else # define prec_ourselves 0 #endif +#if NEED_PRINTF_FLAG_ALT_PRECISION_ZERO || NEED_PRINTF_UNBOUNDED_PRECISION || NEED_PRINTF_FLAG_GROUPING || NEED_PRINTF_FLAG_GROUPING_INT + int group_ourselves; +#else +# define group_ourselves 0 +#endif #if (WIDE_CHAR_VERSION && MUSL_LIBC) || NEED_PRINTF_FLAG_LEFTADJUST # define pad_ourselves 1 -#elif !DCHAR_IS_TCHAR || ENABLE_UNISTDIO || NEED_PRINTF_FLAG_ZERO || NEED_PRINTF_FLAG_ALT_PRECISION_ZERO || NEED_PRINTF_UNBOUNDED_PRECISION +#elif !DCHAR_IS_TCHAR || ENABLE_UNISTDIO || NEED_PRINTF_FLAG_ZERO || NEED_PRINTF_FLAG_ALT_PRECISION_ZERO || NEED_PRINTF_UNBOUNDED_PRECISION || NEED_PRINTF_FLAG_GROUPING || NEED_PRINTF_FLAG_GROUPING_INT int pad_ourselves; #else # define pad_ourselves 0 @@ -5969,10 +6235,10 @@ VASNPRINTF (DCHAR_T *resultbuf, size_t *lengthp, TCHAR_T *tmp; #endif -#if (WIDE_CHAR_VERSION && MUSL_LIBC) || !DCHAR_IS_TCHAR || ENABLE_UNISTDIO || NEED_PRINTF_FLAG_LEFTADJUST || NEED_PRINTF_FLAG_ZERO || NEED_PRINTF_FLAG_ALT_PRECISION_ZERO || NEED_PRINTF_UNBOUNDED_PRECISION +#if (WIDE_CHAR_VERSION && MUSL_LIBC) || NEED_PRINTF_FLAG_LEFTADJUST || !DCHAR_IS_TCHAR || ENABLE_UNISTDIO || NEED_PRINTF_FLAG_ZERO || NEED_PRINTF_FLAG_ALT_PRECISION_ZERO || NEED_PRINTF_UNBOUNDED_PRECISION || NEED_PRINTF_FLAG_GROUPING || NEED_PRINTF_FLAG_GROUPING_INT has_width = 0; #endif -#if !USE_SNPRINTF || WIDE_CHAR_VERSION || !HAVE_SNPRINTF_RETVAL_C99 || USE_MSVC__SNPRINTF || !DCHAR_IS_TCHAR || ENABLE_UNISTDIO || NEED_PRINTF_FLAG_LEFTADJUST || NEED_PRINTF_FLAG_ZERO || NEED_PRINTF_FLAG_ALT_PRECISION_ZERO || NEED_PRINTF_UNBOUNDED_PRECISION +#if !USE_SNPRINTF || WIDE_CHAR_VERSION || !HAVE_SNPRINTF_RETVAL_C99 || USE_MSVC__SNPRINTF || NEED_PRINTF_FLAG_LEFTADJUST || !DCHAR_IS_TCHAR || ENABLE_UNISTDIO || NEED_PRINTF_FLAG_ZERO || NEED_PRINTF_FLAG_ALT_PRECISION_ZERO || NEED_PRINTF_UNBOUNDED_PRECISION || NEED_PRINTF_FLAG_GROUPING || NEED_PRINTF_FLAG_GROUPING_INT width = 0; if (dp->width_start != dp->width_end) { @@ -6003,13 +6269,13 @@ VASNPRINTF (DCHAR_T *resultbuf, size_t *lengthp, if (width > (size_t) INT_MAX) goto overflow; # define WIDTH_IS_CHECKED 1 -# if (WIDE_CHAR_VERSION && MUSL_LIBC) || !DCHAR_IS_TCHAR || ENABLE_UNISTDIO || NEED_PRINTF_FLAG_LEFTADJUST || NEED_PRINTF_FLAG_ZERO || NEED_PRINTF_FLAG_ALT_PRECISION_ZERO || NEED_PRINTF_UNBOUNDED_PRECISION +# if (WIDE_CHAR_VERSION && MUSL_LIBC) || NEED_PRINTF_FLAG_LEFTADJUST || !DCHAR_IS_TCHAR || ENABLE_UNISTDIO || NEED_PRINTF_FLAG_ZERO || NEED_PRINTF_FLAG_ALT_PRECISION_ZERO || NEED_PRINTF_UNBOUNDED_PRECISION || NEED_PRINTF_FLAG_GROUPING || NEED_PRINTF_FLAG_GROUPING_INT has_width = 1; # endif } #endif -#if !USE_SNPRINTF || (WIDE_CHAR_VERSION && DCHAR_IS_TCHAR) || !HAVE_SNPRINTF_RETVAL_C99 || USE_MSVC__SNPRINTF || (WIDE_CHAR_VERSION && MUSL_LIBC) || !DCHAR_IS_TCHAR || ENABLE_UNISTDIO || NEED_PRINTF_FLAG_LEFTADJUST || NEED_PRINTF_FLAG_ZERO || NEED_PRINTF_FLAG_ALT_PRECISION_ZERO || NEED_PRINTF_UNBOUNDED_PRECISION +#if !USE_SNPRINTF || (WIDE_CHAR_VERSION && DCHAR_IS_TCHAR) || !HAVE_SNPRINTF_RETVAL_C99 || USE_MSVC__SNPRINTF || (WIDE_CHAR_VERSION && MUSL_LIBC) || NEED_PRINTF_FLAG_LEFTADJUST || !DCHAR_IS_TCHAR || ENABLE_UNISTDIO || NEED_PRINTF_FLAG_ZERO || NEED_PRINTF_FLAG_ALT_PRECISION_ZERO || NEED_PRINTF_UNBOUNDED_PRECISION || NEED_PRINTF_FLAG_GROUPING || NEED_PRINTF_FLAG_GROUPING_INT has_precision = 0; precision = 6; if (dp->precision_start != dp->precision_end) @@ -6074,8 +6340,37 @@ VASNPRINTF (DCHAR_T *resultbuf, size_t *lengthp, } #endif + /* Decide whether to add the thousands separators ourselves. */ +#if NEED_PRINTF_FLAG_ALT_PRECISION_ZERO || NEED_PRINTF_UNBOUNDED_PRECISION || NEED_PRINTF_FLAG_GROUPING || NEED_PRINTF_FLAG_GROUPING_INT + if (flags & FLAG_GROUP) + { + switch (dp->conversion) + { + case 'd': case 'i': case 'u': +# if NEED_PRINTF_FLAG_GROUPING || NEED_PRINTF_FLAG_GROUPING_INT + group_ourselves = 1; +# else + group_ourselves = prec_ourselves; +# endif + break; + case 'f': case 'F': case 'g': case 'G': +# if NEED_PRINTF_FLAG_GROUPING + group_ourselves = 1; +# else + group_ourselves = prec_ourselves; +# endif + break; + default: + group_ourselves = 0; + break; + } + } + else + group_ourselves = 0; +#endif + /* Decide whether to perform the padding ourselves. */ -#if !((WIDE_CHAR_VERSION && MUSL_LIBC) || NEED_PRINTF_FLAG_LEFTADJUST) && (!DCHAR_IS_TCHAR || ENABLE_UNISTDIO || NEED_PRINTF_FLAG_ZERO || NEED_PRINTF_FLAG_ALT_PRECISION_ZERO || NEED_PRINTF_UNBOUNDED_PRECISION) +#if !((WIDE_CHAR_VERSION && MUSL_LIBC) || NEED_PRINTF_FLAG_LEFTADJUST) && (!DCHAR_IS_TCHAR || ENABLE_UNISTDIO || NEED_PRINTF_FLAG_ZERO || NEED_PRINTF_FLAG_ALT_PRECISION_ZERO || NEED_PRINTF_UNBOUNDED_PRECISION || NEED_PRINTF_FLAG_GROUPING || NEED_PRINTF_FLAG_GROUPING_INT) switch (dp->conversion) { # if !DCHAR_IS_TCHAR || ENABLE_UNISTDIO @@ -6092,7 +6387,7 @@ VASNPRINTF (DCHAR_T *resultbuf, size_t *lengthp, pad_ourselves = 1; break; default: - pad_ourselves = prec_ourselves; + pad_ourselves = prec_ourselves | group_ourselves; break; } #endif @@ -6125,14 +6420,8 @@ VASNPRINTF (DCHAR_T *resultbuf, size_t *lengthp, sprintf. */ fbp = buf; *fbp++ = '%'; -#if NEED_PRINTF_FLAG_GROUPING - /* The underlying implementation doesn't support the ' flag. - Produce no grouping characters in this case; this is - acceptable because the grouping is locale dependent. */ -#else - if (flags & FLAG_GROUP) + if ((flags & FLAG_GROUP) && !group_ourselves) *fbp++ = '\''; -#endif if (flags & FLAG_LEFT) *fbp++ = '-'; if (flags & FLAG_SHOWSIGN) @@ -6947,6 +7236,124 @@ VASNPRINTF (DCHAR_T *resultbuf, size_t *lengthp, } #endif +#if NEED_PRINTF_FLAG_ALT_PRECISION_ZERO || NEED_PRINTF_UNBOUNDED_PRECISION || NEED_PRINTF_FLAG_GROUPING || NEED_PRINTF_FLAG_GROUPING_INT + if (group_ourselves) /* implies (flags & FLAG_GROUP) */ + /* Handle the grouping. */ + switch (dp->conversion) + { + /* These are the only conversion to which grouping + applies. */ + case 'd': case 'i': case 'u': + case 'f': case 'F': case 'g': case 'G': + { + /* Determine the thousands separator of the current + locale. */ + const TCHAR_T *thousep; + TCHAR_T thousep_buf[10]; + +# if WIDE_CHAR_VERSION && DCHAR_IS_TCHAR + /* TCHAR_T is wchar_t. */ + thousep = thousands_separator_wchar (thousep_buf); +# else + /* TCHAR_T is char. */ + thousep = thousands_separator_char (thousep_buf); +# endif + + /* Nothing to do in locales where thousep is the empty + string. */ + if (*thousep != 0) + { + /* Since FLAG_LOCALIZED is only supported on glibc + systems, here we can assume that all digits are + the ASCII digits '0'..'9'. */ + TCHAR_T *number_ptr = +# if USE_SNPRINTF + (TCHAR_T *) (result + length); +# else + tmp; +# endif + TCHAR_T *end_ptr = number_ptr + count; + + /* Find where the leading digits start. */ + TCHAR_T *digits_ptr = number_ptr; + if (count >= 1 + && (*digits_ptr == '-' || *digits_ptr == '+' + || *digits_ptr == ' ')) + digits_ptr++; + + /* Find where the leading digits end. */ + TCHAR_T *digits_end_ptr; + switch (dp->conversion) + { + case 'd': case 'i': case 'u': + digits_end_ptr = end_ptr; + break; + case 'f': case 'F': case 'g': case 'G': + { + TCHAR_T decimal_point = decimal_point_char (); + for (digits_end_ptr = digits_ptr; + digits_end_ptr < end_ptr; + digits_end_ptr++) + if (*digits_end_ptr == decimal_point + || *digits_end_ptr == 'e') + break; + } + break; + } + + /* Determine the number of thousands separators + to insert. */ + size_t insert = digits_end_ptr - digits_ptr; + insert = (insert > 0 ? (insert - 1) / 3 : 0); + if (insert > 0) + { +# if WIDE_CHAR_VERSION && DCHAR_IS_TCHAR +# define thousep_len 1 +# else + size_t thousep_len = strlen (thousep); +# endif +# if USE_SNPRINTF + size_t digits_offset = digits_ptr - number_ptr; + size_t digits_end_offset = digits_end_ptr - number_ptr; + size_t n = + xsum (length, + (count + insert * thousep_len + TCHARS_PER_DCHAR - 1) + / TCHARS_PER_DCHAR); + length += (count + TCHARS_PER_DCHAR - 1) / TCHARS_PER_DCHAR; + ENSURE_ALLOCATION (n); + length -= (count + TCHARS_PER_DCHAR - 1) / TCHARS_PER_DCHAR; + number_ptr = (TCHAR_T *) (result + length); + end_ptr = number_ptr + count; + digits_ptr = number_ptr + digits_offset; + digits_end_ptr = number_ptr + digits_end_offset; +# endif + + count += insert * thousep_len; + + const TCHAR_T *p = end_ptr; + TCHAR_T *q = end_ptr + insert * thousep_len; + while (p > digits_end_ptr) + *--q = *--p; + for (; insert > 0; insert--) + { + *--q = *--p; + *--q = *--p; + *--q = *--p; +# if WIDE_CHAR_VERSION && DCHAR_IS_TCHAR + *--q = *thousep; +# else + q -= thousep_len; + memcpy (q, thousep, thousep_len); +# endif + } + /* Here q == p. Done with the insertions. */ + } + } + } + break; + } +#endif + #if !USE_SNPRINTF if (count >= tmp_length) /* tmp_length was incorrectly calculated - fix the @@ -6957,6 +7364,7 @@ VASNPRINTF (DCHAR_T *resultbuf, size_t *lengthp, #if !DCHAR_IS_TCHAR /* Convert from TCHAR_T[] to DCHAR_T[]. */ if (dp->conversion == 'c' || dp->conversion == 's' + || (flags & FLAG_GROUP) # if __GLIBC__ >= 2 && !defined __UCLIBC__ || (flags & FLAG_LOCALIZED) # endif @@ -7098,7 +7506,7 @@ VASNPRINTF (DCHAR_T *resultbuf, size_t *lengthp, /* Here count <= allocated - length. */ /* Perform padding. */ -#if (WIDE_CHAR_VERSION && MUSL_LIBC) || !DCHAR_IS_TCHAR || ENABLE_UNISTDIO || NEED_PRINTF_FLAG_LEFTADJUST || NEED_PRINTF_FLAG_ZERO || NEED_PRINTF_FLAG_ALT_PRECISION_ZERO || NEED_PRINTF_UNBOUNDED_PRECISION +#if (WIDE_CHAR_VERSION && MUSL_LIBC) || NEED_PRINTF_FLAG_LEFTADJUST || !DCHAR_IS_TCHAR || ENABLE_UNISTDIO || NEED_PRINTF_FLAG_ZERO || NEED_PRINTF_FLAG_ALT_PRECISION_ZERO || NEED_PRINTF_UNBOUNDED_PRECISION || NEED_PRINTF_FLAG_GROUPING || NEED_PRINTF_FLAG_GROUPING_INT if (pad_ourselves && has_width) { size_t w; diff --git a/m4/dprintf-posix.m4 b/m4/dprintf-posix.m4 index 37a54c3de5..fa83b9a83e 100644 --- a/m4/dprintf-posix.m4 +++ b/m4/dprintf-posix.m4 @@ -1,5 +1,5 @@ # dprintf-posix.m4 -# serial 10 +# serial 11 dnl Copyright (C) 2007-2025 Free Software Foundation, Inc. dnl This file is free software; the Free Software Foundation dnl gives unlimited permission to copy and/or distribute it, @@ -33,6 +33,8 @@ AC_DEFUN([gl_FUNC_DPRINTF_IS_POSIX], AC_REQUIRE([gl_PRINTF_DIRECTIVE_LC]) AC_REQUIRE([gl_PRINTF_POSITIONS]) AC_REQUIRE([gl_PRINTF_FLAG_GROUPING]) + AC_REQUIRE([gl_PRINTF_FLAG_GROUPING_INT_PRECISION]) + AC_REQUIRE([gl_PRINTF_FLAG_GROUPING_MULTIBYTE]) AC_REQUIRE([gl_PRINTF_FLAG_LEFTADJUST]) AC_REQUIRE([gl_PRINTF_FLAG_ZERO]) AC_REQUIRE([gl_PRINTF_FLAG_ALT_PRECISION_ZERO]) @@ -67,19 +69,27 @@ AC_DEFUN([gl_FUNC_DPRINTF_IS_POSIX], *yes) case "$gl_cv_func_printf_flag_grouping" in *yes) - case "$gl_cv_func_printf_flag_leftadjust" in + case "$gl_cv_func_printf_flag_grouping_int_precision" in *yes) - case "$gl_cv_func_printf_flag_zero" in + case "$gl_cv_func_printf_flag_grouping_multibyte" in *yes) - case "$gl_cv_func_printf_flag_alt_precision_zero" in + case "$gl_cv_func_printf_flag_leftadjust" in *yes) - case "$gl_cv_func_printf_precision" in + case "$gl_cv_func_printf_flag_zero" in *yes) - case "$gl_cv_func_printf_enomem" in + case "$gl_cv_func_printf_flag_alt_precision_zero" in *yes) - # dprintf exists and is - # already POSIX compliant. - gl_cv_func_dprintf_posix=yes + case "$gl_cv_func_printf_precision" in + *yes) + case "$gl_cv_func_printf_enomem" in + *yes) + # dprintf exists and is + # already POSIX compliant. + gl_cv_func_dprintf_posix=yes + ;; + esac + ;; + esac ;; esac ;; diff --git a/m4/fprintf-posix.m4 b/m4/fprintf-posix.m4 index e6a149a0cf..62116311de 100644 --- a/m4/fprintf-posix.m4 +++ b/m4/fprintf-posix.m4 @@ -1,5 +1,5 @@ # fprintf-posix.m4 -# serial 21 +# serial 22 dnl Copyright (C) 2007-2025 Free Software Foundation, Inc. dnl This file is free software; the Free Software Foundation dnl gives unlimited permission to copy and/or distribute it, @@ -33,6 +33,8 @@ AC_DEFUN([gl_FUNC_FPRINTF_IS_POSIX], AC_REQUIRE([gl_PRINTF_DIRECTIVE_LC]) AC_REQUIRE([gl_PRINTF_POSITIONS]) AC_REQUIRE([gl_PRINTF_FLAG_GROUPING]) + AC_REQUIRE([gl_PRINTF_FLAG_GROUPING_INT_PRECISION]) + AC_REQUIRE([gl_PRINTF_FLAG_GROUPING_MULTIBYTE]) AC_REQUIRE([gl_PRINTF_FLAG_LEFTADJUST]) AC_REQUIRE([gl_PRINTF_FLAG_ZERO]) AC_REQUIRE([gl_PRINTF_FLAG_ALT_PRECISION_ZERO]) @@ -65,19 +67,27 @@ AC_DEFUN([gl_FUNC_FPRINTF_IS_POSIX], *yes) case "$gl_cv_func_printf_flag_grouping" in *yes) - case "$gl_cv_func_printf_flag_leftadjust" in + case "$gl_cv_func_printf_flag_grouping_int_precision" in *yes) - case "$gl_cv_func_printf_flag_zero" in + case "$gl_cv_func_printf_flag_grouping_multibyte" in *yes) - case "$gl_cv_func_printf_flag_alt_precision_zero" in + case "$gl_cv_func_printf_flag_leftadjust" in *yes) - case "$gl_cv_func_printf_precision" in + case "$gl_cv_func_printf_flag_zero" in *yes) - case "$gl_cv_func_printf_enomem" in + case "$gl_cv_func_printf_flag_alt_precision_zero" in *yes) - # fprintf exists and is - # already POSIX compliant. - gl_cv_func_fprintf_posix=yes + case "$gl_cv_func_printf_precision" in + *yes) + case "$gl_cv_func_printf_enomem" in + *yes) + # fprintf exists and is + # already POSIX compliant. + gl_cv_func_fprintf_posix=yes + ;; + esac + ;; + esac ;; esac ;; diff --git a/m4/obstack-printf-posix.m4 b/m4/obstack-printf-posix.m4 index 288f88a2d9..17494513e7 100644 --- a/m4/obstack-printf-posix.m4 +++ b/m4/obstack-printf-posix.m4 @@ -1,5 +1,5 @@ # obstack-printf-posix.m4 -# serial 11 +# serial 12 dnl Copyright (C) 2008-2025 Free Software Foundation, Inc. dnl This file is free software; the Free Software Foundation dnl gives unlimited permission to copy and/or distribute it, @@ -37,6 +37,8 @@ AC_DEFUN([gl_FUNC_OBSTACK_PRINTF_IS_POSIX], AC_REQUIRE([gl_PRINTF_DIRECTIVE_LC]) AC_REQUIRE([gl_PRINTF_POSITIONS]) AC_REQUIRE([gl_PRINTF_FLAG_GROUPING]) + AC_REQUIRE([gl_PRINTF_FLAG_GROUPING_INT_PRECISION]) + AC_REQUIRE([gl_PRINTF_FLAG_GROUPING_MULTIBYTE]) AC_REQUIRE([gl_PRINTF_FLAG_LEFTADJUST]) AC_REQUIRE([gl_PRINTF_FLAG_ZERO]) AC_REQUIRE([gl_PRINTF_FLAG_ALT_PRECISION_ZERO]) @@ -71,19 +73,27 @@ AC_DEFUN([gl_FUNC_OBSTACK_PRINTF_IS_POSIX], *yes) case "$gl_cv_func_printf_flag_grouping" in *yes) - case "$gl_cv_func_printf_flag_leftadjust" in + case "$gl_cv_func_printf_flag_grouping_int_precision" in *yes) - case "$gl_cv_func_printf_flag_zero" in + case "$gl_cv_func_printf_flag_grouping_multibyte" in *yes) - case "$gl_cv_func_printf_flag_alt_precision_zero" in + case "$gl_cv_func_printf_flag_leftadjust" in *yes) - case "$gl_cv_func_printf_precision" in + case "$gl_cv_func_printf_flag_zero" in *yes) - case "$gl_cv_func_printf_enomem" in + case "$gl_cv_func_printf_flag_alt_precision_zero" in *yes) - # obstack_printf exists and is - # already POSIX compliant. - gl_cv_func_obstack_printf_posix=yes + case "$gl_cv_func_printf_precision" in + *yes) + case "$gl_cv_func_printf_enomem" in + *yes) + # obstack_printf exists and is + # already POSIX compliant. + gl_cv_func_obstack_printf_posix=yes + ;; + esac + ;; + esac ;; esac ;; diff --git a/m4/printf.m4 b/m4/printf.m4 index 0bfd8eece9..62319e4af3 100644 --- a/m4/printf.m4 +++ b/m4/printf.m4 @@ -1,5 +1,5 @@ # printf.m4 -# serial 95 +# serial 96 dnl Copyright (C) 2003, 2007-2025 Free Software Foundation, Inc. dnl This file is free software; the Free Software Foundation dnl gives unlimited permission to copy and/or distribute it, @@ -1186,6 +1186,112 @@ changequote([,])dnl ]) ]) +dnl Test whether the *printf family of functions supports POSIX/XSI format +dnl strings with the ' flag for grouping of decimal digits on integers, +dnl together with a precision. +dnl Result is gl_cv_func_printf_flag_grouping_int_precision. + +AC_DEFUN([gl_PRINTF_FLAG_GROUPING_INT_PRECISION], +[ + AC_REQUIRE([AC_PROG_CC]) + AC_REQUIRE([AC_CANONICAL_HOST]) + AC_CACHE_CHECK([whether printf supports grouping on integers with a precision], + [gl_cv_func_printf_flag_grouping_int_precision], + [ + dnl Prepare a guess, used when cross-compiling or when specific locales + dnl are not available. + case "$host_os" in + # Guess no on FreeBSD, NetBSD, Solaris, Cygwin, Haiku. + freebsd* | dragonfly* | netbsd* | solaris* | cygwin* | haiku*) + gl_cv_func_printf_flag_grouping_int_precision="guessing no";; + *) + gl_cv_func_printf_flag_grouping_int_precision="guessing yes";; + esac + AC_RUN_IFELSE( + [AC_LANG_SOURCE([[ +#include +#include +#include +static char buf[100]; +int main () +{ + if (setlocale (LC_ALL, "fr_FR.UTF-8") != NULL + || setlocale (LC_ALL, "fr_FR") != NULL + || setlocale (LC_ALL, "fr_FR.ISO-8859-1") != NULL + || setlocale (LC_ALL, "fr_FR.ISO8859-1") != NULL) + { + if (sprintf (buf, "%'.10d", 1000) < 0) + return 1; + if (strlen (buf) == 10 && strcmp (buf, "0000001000") != 0) + /* The sprintf implementation has produced fewer than 10 digits. */ + return 2; + else + return 0; + } + return 3; +}]])], + [gl_cv_func_printf_flag_grouping_int_precision=yes], + [if test $? = 2; then + gl_cv_func_printf_flag_grouping_int_precision=no + fi + ], + [:]) + ]) +]) + +dnl Test whether the *printf family of functions supports POSIX/XSI format +dnl strings with the ' flag for grouping of decimal digits, when the thousands +dnl separator is a multibyte character (such as U+00A0 or U+202F in a UTF-8 +dnl locale). +dnl Result is gl_cv_func_printf_flag_grouping_multibyte. + +AC_DEFUN([gl_PRINTF_FLAG_GROUPING_MULTIBYTE], +[ + AC_REQUIRE([AC_PROG_CC]) + AC_REQUIRE([AC_CANONICAL_HOST]) + AC_CACHE_CHECK([whether printf supports grouping with a multibyte separator], + [gl_cv_func_printf_flag_grouping_multibyte], + [ + dnl Prepare a guess, used when cross-compiling or when specific locales + dnl are not available. + case "$host_os" in + # Guess no on NetBSD and Solaris 11 OpenIndiana. + netbsd* | solaris*) + gl_cv_func_printf_flag_grouping_multibyte="guessing no";; + *) + gl_cv_func_printf_flag_grouping_multibyte="guessing yes";; + esac + AC_RUN_IFELSE( + [AC_LANG_SOURCE([[ +#include +#include +#include +static char buf[100]; +int main () +{ + if (setlocale (LC_ALL, "fr_FR.UTF-8") == NULL) + return 0; + if (sprintf (buf, "%'.0f", 1000.0) < 0) + return 1; + if (strlen (localeconv ()->thousands_sep) > 1) + { + if (strlen (buf) <= 4 + 1) + return 2; + else + return 3; + } + return 0; +}]])], + [:], + [case $? in + 2) gl_cv_func_printf_flag_grouping_multibyte=no ;; + 3) gl_cv_func_printf_flag_grouping_multibyte=yes ;; + esac + ], + [:]) + ]) +]) + dnl Test whether the *printf family of functions supports the - flag correctly. dnl (ISO C99.) See dnl @@ -2161,20 +2267,22 @@ dnl 11 = gl_PRINTF_DIRECTIVE_LS dnl 12 = gl_PRINTF_DIRECTIVE_LC dnl 13 = gl_PRINTF_POSITIONS dnl 14 = gl_PRINTF_FLAG_GROUPING -dnl 15 = gl_PRINTF_FLAG_LEFTADJUST -dnl 16 = gl_PRINTF_FLAG_ZERO -dnl 17 = gl_PRINTF_FLAG_ALT_PRECISION_ZERO -dnl 18 = gl_PRINTF_PRECISION -dnl 19 = gl_PRINTF_ENOMEM -dnl 20 = gl_SNPRINTF_PRESENCE -dnl 21 = gl_SNPRINTF_TRUNCATION_C99 -dnl 22 = gl_SNPRINTF_RETVAL_C99 -dnl 23 = gl_SNPRINTF_DIRECTIVE_N -dnl 24 = gl_SNPRINTF_SIZE1 -dnl 25 = gl_VSNPRINTF_ZEROSIZE_C99 -dnl 26 = gl_SWPRINTF_WORKS -dnl 27 = gl_SWPRINTF_DIRECTIVE_LA -dnl 28 = gl_SWPRINTF_DIRECTIVE_LC +dnl 15 = gl_PRINTF_FLAG_GROUPING_INT_PRECISION +dnl 16 = gl_PRINTF_FLAG_GROUPING_MULTIBYTE +dnl 17 = gl_PRINTF_FLAG_LEFTADJUST +dnl 18 = gl_PRINTF_FLAG_ZERO +dnl 19 = gl_PRINTF_FLAG_ALT_PRECISION_ZERO +dnl 20 = gl_PRINTF_PRECISION +dnl 21 = gl_PRINTF_ENOMEM +dnl 22 = gl_SNPRINTF_PRESENCE +dnl 23 = gl_SNPRINTF_TRUNCATION_C99 +dnl 24 = gl_SNPRINTF_RETVAL_C99 +dnl 25 = gl_SNPRINTF_DIRECTIVE_N +dnl 26 = gl_SNPRINTF_SIZE1 +dnl 27 = gl_VSNPRINTF_ZEROSIZE_C99 +dnl 28 = gl_SWPRINTF_WORKS +dnl 29 = gl_SWPRINTF_DIRECTIVE_LA +dnl 30 = gl_SWPRINTF_DIRECTIVE_LC dnl dnl 1 = checking whether printf supports size specifiers as in C99... dnl 2 = checking whether printf supports size specifiers as in C23... @@ -2190,62 +2298,64 @@ dnl 11 = checking whether printf supports the 'ls' directive... dnl 12 = checking whether printf supports the 'lc' directive correctly... dnl 13 = checking whether printf supports POSIX/XSI format strings with positions... dnl 14 = checking whether printf supports the grouping flag... -dnl 15 = checking whether printf supports the left-adjust flag correctly... -dnl 16 = checking whether printf supports the zero flag correctly... -dnl 17 = checking whether printf supports the alternative flag with a zero precision... -dnl 18 = checking whether printf supports large precisions... -dnl 19 = checking whether printf survives out-of-memory conditions... -dnl 20 = checking for snprintf... -dnl 21 = checking whether snprintf truncates the result as in C99... -dnl 22 = checking whether snprintf returns a byte count as in C99... -dnl 23 = checking whether snprintf fully supports the 'n' directive... -dnl 24 = checking whether snprintf respects a size of 1... -dnl 25 = checking whether vsnprintf respects a zero size as in C99... -dnl 26 = checking whether swprintf works... -dnl 27 = checking whether swprintf supports the 'La' and 'LA' directives... -dnl 28 = checking whether swprintf supports the 'lc' directive... +dnl 15 = checking whether printf supports grouping on integers with a precision... +dnl 16 = checking whether printf supports grouping with a multibyte separator... +dnl 17 = checking whether printf supports the left-adjust flag correctly... +dnl 18 = checking whether printf supports the zero flag correctly... +dnl 19 = checking whether printf supports the alternative flag with a zero precision... +dnl 20 = checking whether printf supports large precisions... +dnl 21 = checking whether printf survives out-of-memory conditions... +dnl 22 = checking for snprintf... +dnl 23 = checking whether snprintf truncates the result as in C99... +dnl 24 = checking whether snprintf returns a byte count as in C99... +dnl 25 = checking whether snprintf fully supports the 'n' directive... +dnl 26 = checking whether snprintf respects a size of 1... +dnl 27 = checking whether vsnprintf respects a zero size as in C99... +dnl 28 = checking whether swprintf works... +dnl 29 = checking whether swprintf supports the 'La' and 'LA' directives... +dnl 30 = checking whether swprintf supports the 'lc' directive... dnl dnl . = yes, # = no. dnl -dnl 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 -dnl musl libc 1.2.3 . # . . . . # # . . . # . . . . ? . . . . . . . . # . # -dnl glibc 2.35 . # . . . . . . . . . . . . . . . . . . . . . . . . . . -dnl glibc 2.5 . # . . . . # # . . . . . . . . . . . . . . . . . . # . -dnl glibc 2.3.6 . # . . . # # # . . . . . . . . . . . . . . . . . . # . -dnl FreeBSD 14.0 . . . . . # . . . . . . . . . . . . # . . . . . . # . # -dnl FreeBSD 13.0 . # . . . # # # . . . . . . . . . . # . . . . . . # . # -dnl FreeBSD 5.4, 6.1 . # . . . # # # . . . . . . . # ? . # . . . . . . # ? ? -dnl Mac OS X 10.13.5 . # . . # # # # . # . . . . . . . . . . . . # . . # ? ? -dnl Mac OS X 10.5.8 . # . . # # # # . . . . . . . # # . . . . . . . . # ? ? -dnl Mac OS X 10.3.9 . # . . . # # # . . . . . . . # # . # . . . . . . # ? ? -dnl OpenBSD 6.0, 6.7 . # . . . # # # . . . . . . . . . . # . . . . . . # . # -dnl OpenBSD 3.9, 4.0 . # . # # # # # # . # . . # . # ? . # . . . . . . # ? ? -dnl Cygwin 1.7.0 (2009) . # . . # . # # . . ? ? . . . . ? . ? . . . . . . ? ? ? -dnl Cygwin 1.5.25 (2008) . # . . # # # # . . # ? . . . . ? . # . . . . . . ? ? ? -dnl Cygwin 1.5.19 (2006) # # . . # # # # # . # ? . # . # ? # # . . . . . . ? ? ? -dnl Solaris 11.4 . # . # # # # # . . # . . . . # . . . . . . . . . . # . -dnl Solaris 11.3 . # . . . # # # . . # . . . . . . . . . . . . . . . # . -dnl Solaris 11.0 . # . # # # # # . . # . . . . # . . . . . . . . . ? ? ? -dnl Solaris 10 . # . # # # # # . . # . . . . # . # . . . . . . . . # . -dnl Solaris 2.6 ... 9 # # . # # # # # # . # . . . . # ? # . . . # . . . ? ? ? -dnl Solaris 2.5.1 # # . # # # # # # . # . . . . # ? . . # # # # # # ? ? ? -dnl AIX 7.1 . # . # # # # # . . . . . . . # . # . . . . . . . # . . -dnl AIX 5.2 . # . # # # # # . . . . . . . # ? . . . . . . . . # ? ? -dnl AIX 4.3.2, 5.1 # # . # # # # # # . . . . . . # ? . . . . # . . . # ? ? -dnl HP-UX 11.31 . # . . . # # # . . . ? . . . # ? . . . . # # . . ? ? ? -dnl HP-UX 11.{00,11,23} # # . . . # # # # . . ? . . . # ? . . . . # # . # ? ? ? -dnl HP-UX 10.20 # # . # . # # # # . ? ? . . # # ? . . . . # # ? # ? ? ? -dnl IRIX 6.5 # # . # # # # # # . # . . . . # ? . . . . # . . . # ? ? -dnl OSF/1 5.1 # # . # # # # # # . . ? . . . # ? . . . . # . . # ? ? ? -dnl OSF/1 4.0d # # . # # # # # # . . ? . . . # ? . . # # # # # # ? ? ? -dnl NetBSD 9.0 . # . . . # # # . . . . . . . . . . . . . . . . . # . # -dnl NetBSD 5.0 . # . . # # # # . . . . . . . # ? . # . . . . . . # ? ? -dnl NetBSD 4.0 . # ? ? ? ? # # ? . ? . . ? ? ? ? ? ? . . . ? ? ? # ? ? -dnl NetBSD 3.0 . # . . . # # # # . ? . # # ? # ? . # . . . . . . # ? ? -dnl Haiku . # . . # # # # # . # ? . . . . ? . ? . . ? . . . . # . -dnl BeOS # # # . # # # # # . ? ? # . ? . ? # ? . . ? . . . ? ? ? -dnl Android 4.3 . # . # # # # # # # # ? . # . # ? . # . . . # . . ? ? ? -dnl old mingw / msvcrt # # # # # # # # # . . ? # # . # ? # ? . # # # . . # ? ? -dnl MSVC 9 # # # # # # # # # # . ? # # . # ? # ? # # # # . . # ? ? -dnl mingw 2009-2011 . # # . # . # # . . . ? # # . . ? . ? . . . . . . # ? ? -dnl mingw-w64 2011 # # # # # # # # # . . ? # # . # ? # ? . # # # . . # ? ? +dnl 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 +dnl musl libc 1.2.3 . # . . . . # # . . . # . . . . . . . . . . . . . . . # . # +dnl glibc 2.35 . # . . . . . . . . . . . . # . . . . . . . . . . . . . . . +dnl glibc 2.5 . # . . . . # # . . . . . . # . . . . . . . . . . . . . # . +dnl glibc 2.3.6 . # . . . # # # . . . . . . . . . . . . . . . . . . . . # . +dnl FreeBSD 14.0 . . . . . # . . . . . . . . # . . . . . # . . . . . . # . # +dnl FreeBSD 13.0 . # . . . # # # . . . . . . # . . . . . # . . . . . . # . # +dnl FreeBSD 5.4, 6.1 . # . . . # # # . . . . . . . . . # ? . # . . . . . . # ? ? +dnl Mac OS X 10.13.5 . # . . # # # # . # . . . . ? ? . . . . . . . . # . . # ? ? +dnl Mac OS X 10.5.8 . # . . # # # # . . . . . . ? ? . # # . . . . . . . . # ? ? +dnl Mac OS X 10.3.9 . # . . . # # # . . . . . . ? ? . # # . # . . . . . . # ? ? +dnl OpenBSD 6.0, 6.7 . # . . . # # # . . . . . . . . . . . . # . . . . . . # . # +dnl OpenBSD 3.9, 4.0 . # . # # # # # # . # . . # ? ? . # ? . # . . . . . . # ? ? +dnl Cygwin 1.7.0 (2009) . # . . # . # # . . ? ? . . ? ? . . ? . ? . . . . . . ? ? ? +dnl Cygwin 1.5.25 (2008) . # . . # # # # . . # ? . . ? ? . . ? . # . . . . . . ? ? ? +dnl Cygwin 1.5.19 (2006) # # . . # # # # # . # ? . # ? ? . # ? # # . . . . . . ? ? ? +dnl Solaris 11.4 . # . # # # # # . . # . . . # # . # . . . . . . . . . . # . +dnl Solaris 11.3 . # . . . # # # . . # . . . ? ? . . . . . . . . . . . . # . +dnl Solaris 11.0 . # . # # # # # . . # . . . ? ? . # . . . . . . . . . ? ? ? +dnl Solaris 10 . # . # # # # # . . # . . . ? ? . # . # . . . . . . . . # . +dnl Solaris 2.6 ... 9 # # . # # # # # # . # . . . ? ? . # ? # . . . # . . . ? ? ? +dnl Solaris 2.5.1 # # . # # # # # # . # . . . ? ? . # ? . . # # # # # # ? ? ? +dnl AIX 7.1 . # . # # # # # . . . . . . ? ? . # . # . . . . . . . # . . +dnl AIX 5.2 . # . # # # # # . . . . . . ? ? . # ? . . . . . . . . # ? ? +dnl AIX 4.3.2, 5.1 # # . # # # # # # . . . . . ? ? . # ? . . . . # . . . # ? ? +dnl HP-UX 11.31 . # . . . # # # . . . ? . . ? ? . # ? . . . . # # . . ? ? ? +dnl HP-UX 11.{00,11,23} # # . . . # # # # . . ? . . ? ? . # ? . . . . # # . # ? ? ? +dnl HP-UX 10.20 # # . # . # # # # . ? ? . . ? ? # # ? . . . . # # ? # ? ? ? +dnl IRIX 6.5 # # . # # # # # # . # . . . ? ? . # ? . . . . # . . . # ? ? +dnl OSF/1 5.1 # # . # # # # # # . . ? . . ? ? . # ? . . . . # . . # ? ? ? +dnl OSF/1 4.0d # # . # # # # # # . . ? . . ? ? . # ? . . # # # # # # ? ? ? +dnl NetBSD 9.0 . # . . . # # # . . . . . . # # . . . . . . . . . . . # . # +dnl NetBSD 5.0 . # . . # # # # . . . . . . ? ? . # ? . # . . . . . . # ? ? +dnl NetBSD 4.0 . # ? ? ? ? # # ? . ? . . ? ? ? ? ? ? ? ? . . . ? ? ? # ? ? +dnl NetBSD 3.0 . # . . . # # # # . ? . # # ? ? ? # ? . # . . . . . . # ? ? +dnl Haiku . # . . # # # # # . # ? . . # . . . ? . ? . . ? . . . . # . +dnl BeOS # # # . # # # # # . ? ? # . ? ? ? . ? # ? . . ? . . . ? ? ? +dnl Android 4.3 . # . # # # # # # # # ? . # ? ? . # ? . # . . . # . . ? ? ? +dnl old mingw / msvcrt # # # # # # # # # . . ? # # ? ? . # ? # ? . # # # . . # ? ? +dnl MSVC 9 # # # # # # # # # # . ? # # ? ? . # ? # ? # # # # . . # ? ? +dnl mingw 2009-2011 . # # . # . # # . . . ? # # ? ? . . ? . ? . . . . . . # ? ? +dnl mingw-w64 2011 # # # # # # # # # . . ? # # ? ? . # ? # ? . # # # . . # ? ? diff --git a/m4/snprintf-posix.m4 b/m4/snprintf-posix.m4 index b8d238b637..4794cc3ae2 100644 --- a/m4/snprintf-posix.m4 +++ b/m4/snprintf-posix.m4 @@ -1,5 +1,5 @@ # snprintf-posix.m4 -# serial 22 +# serial 23 dnl Copyright (C) 2007-2025 Free Software Foundation, Inc. dnl This file is free software; the Free Software Foundation dnl gives unlimited permission to copy and/or distribute it, @@ -33,6 +33,8 @@ AC_DEFUN([gl_FUNC_SNPRINTF_IS_POSIX], AC_REQUIRE([gl_PRINTF_DIRECTIVE_LC]) AC_REQUIRE([gl_PRINTF_POSITIONS]) AC_REQUIRE([gl_PRINTF_FLAG_GROUPING]) + AC_REQUIRE([gl_PRINTF_FLAG_GROUPING_INT_PRECISION]) + AC_REQUIRE([gl_PRINTF_FLAG_GROUPING_MULTIBYTE]) AC_REQUIRE([gl_PRINTF_FLAG_LEFTADJUST]) AC_REQUIRE([gl_PRINTF_FLAG_ZERO]) AC_REQUIRE([gl_PRINTF_FLAG_ALT_PRECISION_ZERO]) @@ -72,29 +74,37 @@ AC_DEFUN([gl_FUNC_SNPRINTF_IS_POSIX], *yes) case "$gl_cv_func_printf_flag_grouping" in *yes) - case "$gl_cv_func_printf_flag_leftadjust" in + case "$gl_cv_func_printf_flag_grouping_int_precision" in *yes) - case "$gl_cv_func_printf_flag_zero" in + case "$gl_cv_func_printf_flag_grouping_multibyte" in *yes) - case "$gl_cv_func_printf_flag_alt_precision_zero" in + case "$gl_cv_func_printf_flag_leftadjust" in *yes) - case "$gl_cv_func_printf_precision" in + case "$gl_cv_func_printf_flag_zero" in *yes) - case "$gl_cv_func_printf_enomem" in + case "$gl_cv_func_printf_flag_alt_precision_zero" in *yes) - case "$gl_cv_func_snprintf_truncation_c99" in + case "$gl_cv_func_printf_precision" in *yes) - case "$gl_cv_func_snprintf_retval_c99" in + case "$gl_cv_func_printf_enomem" in *yes) - case m4_ifdef([gl_PRINTF_SUPPORT_N_DIRECTIVE],["$gl_cv_func_snprintf_directive_n"],["yes"]) in + case "$gl_cv_func_snprintf_truncation_c99" in *yes) - case "$gl_cv_func_snprintf_size1" in + case "$gl_cv_func_snprintf_retval_c99" in *yes) - case "$gl_cv_func_vsnprintf_zerosize_c99" in + case m4_ifdef([gl_PRINTF_SUPPORT_N_DIRECTIVE],["$gl_cv_func_snprintf_directive_n"],["yes"]) in *yes) - # snprintf exists and is - # already POSIX compliant. - gl_cv_func_snprintf_posix=yes + case "$gl_cv_func_snprintf_size1" in + *yes) + case "$gl_cv_func_vsnprintf_zerosize_c99" in + *yes) + # snprintf exists and is + # already POSIX compliant. + gl_cv_func_snprintf_posix=yes + ;; + esac + ;; + esac ;; esac ;; diff --git a/m4/sprintf-posix.m4 b/m4/sprintf-posix.m4 index fe07f061fa..1bd9f9cbf1 100644 --- a/m4/sprintf-posix.m4 +++ b/m4/sprintf-posix.m4 @@ -1,5 +1,5 @@ # sprintf-posix.m4 -# serial 19 +# serial 20 dnl Copyright (C) 2007-2025 Free Software Foundation, Inc. dnl This file is free software; the Free Software Foundation dnl gives unlimited permission to copy and/or distribute it, @@ -33,6 +33,8 @@ AC_DEFUN([gl_FUNC_SPRINTF_IS_POSIX], AC_REQUIRE([gl_PRINTF_DIRECTIVE_LC]) AC_REQUIRE([gl_PRINTF_POSITIONS]) AC_REQUIRE([gl_PRINTF_FLAG_GROUPING]) + AC_REQUIRE([gl_PRINTF_FLAG_GROUPING_INT_PRECISION]) + AC_REQUIRE([gl_PRINTF_FLAG_GROUPING_MULTIBYTE]) AC_REQUIRE([gl_PRINTF_FLAG_LEFTADJUST]) AC_REQUIRE([gl_PRINTF_FLAG_ZERO]) AC_REQUIRE([gl_PRINTF_FLAG_ALT_PRECISION_ZERO]) @@ -65,19 +67,27 @@ AC_DEFUN([gl_FUNC_SPRINTF_IS_POSIX], *yes) case "$gl_cv_func_printf_flag_grouping" in *yes) - case "$gl_cv_func_printf_flag_leftadjust" in + case "$gl_cv_func_printf_flag_grouping_int_precision" in *yes) - case "$gl_cv_func_printf_flag_zero" in + case "$gl_cv_func_printf_flag_grouping_multibyte" in *yes) - case "$gl_cv_func_printf_flag_alt_precision_zero" in + case "$gl_cv_func_printf_flag_leftadjust" in *yes) - case "$gl_cv_func_printf_precision" in + case "$gl_cv_func_printf_flag_zero" in *yes) - case "$gl_cv_func_printf_enomem" in + case "$gl_cv_func_printf_flag_alt_precision_zero" in *yes) - # sprintf exists and is - # already POSIX compliant. - gl_cv_func_sprintf_posix=yes + case "$gl_cv_func_printf_precision" in + *yes) + case "$gl_cv_func_printf_enomem" in + *yes) + # sprintf exists and is + # already POSIX compliant. + gl_cv_func_sprintf_posix=yes + ;; + esac + ;; + esac ;; esac ;; diff --git a/m4/vasnprintf-posix.m4 b/m4/vasnprintf-posix.m4 index ba8a152010..ae073fda37 100644 --- a/m4/vasnprintf-posix.m4 +++ b/m4/vasnprintf-posix.m4 @@ -1,5 +1,5 @@ # vasnprintf-posix.m4 -# serial 20 +# serial 21 dnl Copyright (C) 2007-2025 Free Software Foundation, Inc. dnl This file is free software; the Free Software Foundation dnl gives unlimited permission to copy and/or distribute it, @@ -32,6 +32,8 @@ AC_DEFUN_ONCE([gl_FUNC_VASNPRINTF_IS_POSIX], AC_REQUIRE([gl_PRINTF_DIRECTIVE_LC]) AC_REQUIRE([gl_PRINTF_POSITIONS]) AC_REQUIRE([gl_PRINTF_FLAG_GROUPING]) + AC_REQUIRE([gl_PRINTF_FLAG_GROUPING_INT_PRECISION]) + AC_REQUIRE([gl_PRINTF_FLAG_GROUPING_MULTIBYTE]) AC_REQUIRE([gl_PRINTF_FLAG_LEFTADJUST]) AC_REQUIRE([gl_PRINTF_FLAG_ZERO]) AC_REQUIRE([gl_PRINTF_FLAG_ALT_PRECISION_ZERO]) @@ -65,21 +67,29 @@ AC_DEFUN_ONCE([gl_FUNC_VASNPRINTF_IS_POSIX], *yes) case "$gl_cv_func_printf_flag_grouping" in *yes) - case "$gl_cv_func_printf_flag_leftadjust" in + case "$gl_cv_func_printf_flag_grouping_int_precision" in *yes) - case "$gl_cv_func_printf_flag_zero" in + case "$gl_cv_func_printf_flag_grouping_multibyte" in *yes) - case "$gl_cv_func_printf_flag_alt_precision_zero" in + case "$gl_cv_func_printf_flag_leftadjust" in *yes) - case "$gl_cv_func_printf_precision" in + case "$gl_cv_func_printf_flag_zero" in *yes) - case "$gl_cv_func_printf_enomem" in + case "$gl_cv_func_printf_flag_alt_precision_zero" in *yes) - if test $ac_cv_func_vasnprintf = yes; then - # vasnprintf exists and is - # already POSIX compliant. - gl_cv_func_vasnprintf_posix=yes - fi + case "$gl_cv_func_printf_precision" in + *yes) + case "$gl_cv_func_printf_enomem" in + *yes) + if test $ac_cv_func_vasnprintf = yes; then + # vasnprintf exists and is + # already POSIX compliant. + gl_cv_func_vasnprintf_posix=yes + fi + ;; + esac + ;; + esac ;; esac ;; diff --git a/m4/vasnprintf.m4 b/m4/vasnprintf.m4 index ab1b871eda..35fdbc5e94 100644 --- a/m4/vasnprintf.m4 +++ b/m4/vasnprintf.m4 @@ -1,5 +1,5 @@ # vasnprintf.m4 -# serial 54 +# serial 55 dnl Copyright (C) 2002-2004, 2006-2025 Free Software Foundation, Inc. dnl This file is free software; the Free Software Foundation dnl gives unlimited permission to copy and/or distribute it, @@ -358,8 +358,19 @@ AC_DEFUN([gl_PREREQ_VASNPRINTF_DIRECTIVE_LC], AC_DEFUN([gl_PREREQ_VASNPRINTF_FLAG_GROUPING], [ AC_REQUIRE([gl_PRINTF_FLAG_GROUPING]) - case "$gl_cv_func_printf_flag_grouping" in - *yes) + AC_REQUIRE([gl_PRINTF_FLAG_GROUPING_INT_PRECISION]) + AC_REQUIRE([gl_PRINTF_FLAG_GROUPING_MULTIBYTE]) + case "$gl_cv_func_printf_flag_grouping,$gl_cv_func_printf_flag_grouping_multibyte" in + *yes,*yes) + case "$gl_cv_func_printf_flag_grouping_int_precision" in + *yes) + ;; + *) + AC_DEFINE([NEED_PRINTF_FLAG_GROUPING_INT], [1], + [Define if the vasnprintf implementation needs special code for the + ' flag, for integer directives only.]) + ;; + esac ;; *) AC_DEFINE([NEED_PRINTF_FLAG_GROUPING], [1], diff --git a/m4/vasprintf-posix.m4 b/m4/vasprintf-posix.m4 index edd5832fd0..692729c66f 100644 --- a/m4/vasprintf-posix.m4 +++ b/m4/vasprintf-posix.m4 @@ -1,5 +1,5 @@ # vasprintf-posix.m4 -# serial 20 +# serial 21 dnl Copyright (C) 2007-2025 Free Software Foundation, Inc. dnl This file is free software; the Free Software Foundation dnl gives unlimited permission to copy and/or distribute it, @@ -33,6 +33,8 @@ AC_DEFUN([gl_FUNC_VASPRINTF_IS_POSIX], AC_REQUIRE([gl_PRINTF_DIRECTIVE_LC]) AC_REQUIRE([gl_PRINTF_POSITIONS]) AC_REQUIRE([gl_PRINTF_FLAG_GROUPING]) + AC_REQUIRE([gl_PRINTF_FLAG_GROUPING_INT_PRECISION]) + AC_REQUIRE([gl_PRINTF_FLAG_GROUPING_MULTIBYTE]) AC_REQUIRE([gl_PRINTF_FLAG_LEFTADJUST]) AC_REQUIRE([gl_PRINTF_FLAG_ZERO]) AC_REQUIRE([gl_PRINTF_FLAG_ALT_PRECISION_ZERO]) @@ -66,21 +68,29 @@ AC_DEFUN([gl_FUNC_VASPRINTF_IS_POSIX], *yes) case "$gl_cv_func_printf_flag_grouping" in *yes) - case "$gl_cv_func_printf_flag_leftadjust" in + case "$gl_cv_func_printf_flag_grouping_int_precision" in *yes) - case "$gl_cv_func_printf_flag_zero" in + case "$gl_cv_func_printf_flag_grouping_multibyte" in *yes) - case "$gl_cv_func_printf_flag_alt_precision_zero" in + case "$gl_cv_func_printf_flag_leftadjust" in *yes) - case "$gl_cv_func_printf_precision" in + case "$gl_cv_func_printf_flag_zero" in *yes) - case "$gl_cv_func_printf_enomem" in + case "$gl_cv_func_printf_flag_alt_precision_zero" in *yes) - if test $ac_cv_func_vasprintf = yes; then - # vasprintf exists and is - # already POSIX compliant. - gl_cv_func_vasprintf_posix=yes - fi + case "$gl_cv_func_printf_precision" in + *yes) + case "$gl_cv_func_printf_enomem" in + *yes) + if test $ac_cv_func_vasprintf = yes; then + # vasprintf exists and is + # already POSIX compliant. + gl_cv_func_vasprintf_posix=yes + fi + ;; + esac + ;; + esac ;; esac ;; diff --git a/m4/vdprintf-posix.m4 b/m4/vdprintf-posix.m4 index ceec8523e5..063bbf5ebc 100644 --- a/m4/vdprintf-posix.m4 +++ b/m4/vdprintf-posix.m4 @@ -1,5 +1,5 @@ # vdprintf-posix.m4 -# serial 10 +# serial 11 dnl Copyright (C) 2007-2025 Free Software Foundation, Inc. dnl This file is free software; the Free Software Foundation dnl gives unlimited permission to copy and/or distribute it, @@ -33,6 +33,8 @@ AC_DEFUN([gl_FUNC_VDPRINTF_IS_POSIX], AC_REQUIRE([gl_PRINTF_DIRECTIVE_LC]) AC_REQUIRE([gl_PRINTF_POSITIONS]) AC_REQUIRE([gl_PRINTF_FLAG_GROUPING]) + AC_REQUIRE([gl_PRINTF_FLAG_GROUPING_INT_PRECISION]) + AC_REQUIRE([gl_PRINTF_FLAG_GROUPING_MULTIBYTE]) AC_REQUIRE([gl_PRINTF_FLAG_LEFTADJUST]) AC_REQUIRE([gl_PRINTF_FLAG_ZERO]) AC_REQUIRE([gl_PRINTF_FLAG_ALT_PRECISION_ZERO]) @@ -67,19 +69,27 @@ AC_DEFUN([gl_FUNC_VDPRINTF_IS_POSIX], *yes) case "$gl_cv_func_printf_flag_grouping" in *yes) - case "$gl_cv_func_printf_flag_leftadjust" in + case "$gl_cv_func_printf_flag_grouping_int_precision" in *yes) - case "$gl_cv_func_printf_flag_zero" in + case "$gl_cv_func_printf_flag_grouping_multibyte" in *yes) - case "$gl_cv_func_printf_flag_alt_precision_zero" in + case "$gl_cv_func_printf_flag_leftadjust" in *yes) - case "$gl_cv_func_printf_precision" in + case "$gl_cv_func_printf_flag_zero" in *yes) - case "$gl_cv_func_printf_enomem" in + case "$gl_cv_func_printf_flag_alt_precision_zero" in *yes) - # vdprintf exists and is - # already POSIX compliant. - gl_cv_func_vdprintf_posix=yes + case "$gl_cv_func_printf_precision" in + *yes) + case "$gl_cv_func_printf_enomem" in + *yes) + # vdprintf exists and is + # already POSIX compliant. + gl_cv_func_vdprintf_posix=yes + ;; + esac + ;; + esac ;; esac ;; diff --git a/m4/vfprintf-posix.m4 b/m4/vfprintf-posix.m4 index 6d9ed3a18a..083cb42e9c 100644 --- a/m4/vfprintf-posix.m4 +++ b/m4/vfprintf-posix.m4 @@ -1,5 +1,5 @@ # vfprintf-posix.m4 -# serial 21 +# serial 22 dnl Copyright (C) 2007-2025 Free Software Foundation, Inc. dnl This file is free software; the Free Software Foundation dnl gives unlimited permission to copy and/or distribute it, @@ -33,6 +33,8 @@ AC_DEFUN([gl_FUNC_VFPRINTF_IS_POSIX], AC_REQUIRE([gl_PRINTF_DIRECTIVE_LC]) AC_REQUIRE([gl_PRINTF_POSITIONS]) AC_REQUIRE([gl_PRINTF_FLAG_GROUPING]) + AC_REQUIRE([gl_PRINTF_FLAG_GROUPING_INT_PRECISION]) + AC_REQUIRE([gl_PRINTF_FLAG_GROUPING_MULTIBYTE]) AC_REQUIRE([gl_PRINTF_FLAG_LEFTADJUST]) AC_REQUIRE([gl_PRINTF_FLAG_ZERO]) AC_REQUIRE([gl_PRINTF_FLAG_ALT_PRECISION_ZERO]) @@ -65,19 +67,27 @@ AC_DEFUN([gl_FUNC_VFPRINTF_IS_POSIX], *yes) case "$gl_cv_func_printf_flag_grouping" in *yes) - case "$gl_cv_func_printf_flag_leftadjust" in + case "$gl_cv_func_printf_flag_grouping_int_precision" in *yes) - case "$gl_cv_func_printf_flag_zero" in + case "$gl_cv_func_printf_flag_grouping_multibyte" in *yes) - case "$gl_cv_func_printf_flag_alt_precision_zero" in + case "$gl_cv_func_printf_flag_leftadjust" in *yes) - case "$gl_cv_func_printf_precision" in + case "$gl_cv_func_printf_flag_zero" in *yes) - case "$gl_cv_func_printf_enomem" in + case "$gl_cv_func_printf_flag_alt_precision_zero" in *yes) - # vfprintf exists and is - # already POSIX compliant. - gl_cv_func_vfprintf_posix=yes + case "$gl_cv_func_printf_precision" in + *yes) + case "$gl_cv_func_printf_enomem" in + *yes) + # vfprintf exists and is + # already POSIX compliant. + gl_cv_func_vfprintf_posix=yes + ;; + esac + ;; + esac ;; esac ;; diff --git a/m4/vsnprintf-posix.m4 b/m4/vsnprintf-posix.m4 index c01c4c462f..405fa19b69 100644 --- a/m4/vsnprintf-posix.m4 +++ b/m4/vsnprintf-posix.m4 @@ -1,5 +1,5 @@ # vsnprintf-posix.m4 -# serial 23 +# serial 24 dnl Copyright (C) 2007-2025 Free Software Foundation, Inc. dnl This file is free software; the Free Software Foundation dnl gives unlimited permission to copy and/or distribute it, @@ -33,6 +33,8 @@ AC_DEFUN([gl_FUNC_VSNPRINTF_IS_POSIX], AC_REQUIRE([gl_PRINTF_DIRECTIVE_LC]) AC_REQUIRE([gl_PRINTF_POSITIONS]) AC_REQUIRE([gl_PRINTF_FLAG_GROUPING]) + AC_REQUIRE([gl_PRINTF_FLAG_GROUPING_INT_PRECISION]) + AC_REQUIRE([gl_PRINTF_FLAG_GROUPING_MULTIBYTE]) AC_REQUIRE([gl_PRINTF_FLAG_LEFTADJUST]) AC_REQUIRE([gl_PRINTF_FLAG_ZERO]) AC_REQUIRE([gl_PRINTF_FLAG_ALT_PRECISION_ZERO]) @@ -73,29 +75,37 @@ AC_DEFUN([gl_FUNC_VSNPRINTF_IS_POSIX], *yes) case "$gl_cv_func_printf_flag_grouping" in *yes) - case "$gl_cv_func_printf_flag_leftadjust" in + case "$gl_cv_func_printf_flag_grouping_int_precision" in *yes) - case "$gl_cv_func_printf_flag_zero" in + case "$gl_cv_func_printf_flag_grouping_multibyte" in *yes) - case "$gl_cv_func_printf_flag_alt_precision_zero" in + case "$gl_cv_func_printf_flag_leftadjust" in *yes) - case "$gl_cv_func_printf_precision" in + case "$gl_cv_func_printf_flag_zero" in *yes) - case "$gl_cv_func_printf_enomem" in + case "$gl_cv_func_printf_flag_alt_precision_zero" in *yes) - case "$gl_cv_func_snprintf_truncation_c99" in + case "$gl_cv_func_printf_precision" in *yes) - case "$gl_cv_func_snprintf_retval_c99" in + case "$gl_cv_func_printf_enomem" in *yes) - case m4_ifdef([gl_PRINTF_SUPPORT_N_DIRECTIVE],["$gl_cv_func_snprintf_directive_n"],["yes"]) in + case "$gl_cv_func_snprintf_truncation_c99" in *yes) - case "$gl_cv_func_snprintf_size1" in + case "$gl_cv_func_snprintf_retval_c99" in *yes) - case "$gl_cv_func_vsnprintf_zerosize_c99" in + case m4_ifdef([gl_PRINTF_SUPPORT_N_DIRECTIVE],["$gl_cv_func_snprintf_directive_n"],["yes"]) in *yes) - # vsnprintf exists and is - # already POSIX compliant. - gl_cv_func_vsnprintf_posix=yes + case "$gl_cv_func_snprintf_size1" in + *yes) + case "$gl_cv_func_vsnprintf_zerosize_c99" in + *yes) + # vsnprintf exists and is + # already POSIX compliant. + gl_cv_func_vsnprintf_posix=yes + ;; + esac + ;; + esac ;; esac ;; diff --git a/m4/vsprintf-posix.m4 b/m4/vsprintf-posix.m4 index 9c42bd775d..6a8ad623c1 100644 --- a/m4/vsprintf-posix.m4 +++ b/m4/vsprintf-posix.m4 @@ -1,5 +1,5 @@ # vsprintf-posix.m4 -# serial 19 +# serial 20 dnl Copyright (C) 2007-2025 Free Software Foundation, Inc. dnl This file is free software; the Free Software Foundation dnl gives unlimited permission to copy and/or distribute it, @@ -33,6 +33,8 @@ AC_DEFUN([gl_FUNC_VSPRINTF_IS_POSIX], AC_REQUIRE([gl_PRINTF_DIRECTIVE_LC]) AC_REQUIRE([gl_PRINTF_POSITIONS]) AC_REQUIRE([gl_PRINTF_FLAG_GROUPING]) + AC_REQUIRE([gl_PRINTF_FLAG_GROUPING_INT_PRECISION]) + AC_REQUIRE([gl_PRINTF_FLAG_GROUPING_MULTIBYTE]) AC_REQUIRE([gl_PRINTF_FLAG_LEFTADJUST]) AC_REQUIRE([gl_PRINTF_FLAG_ZERO]) AC_REQUIRE([gl_PRINTF_FLAG_ALT_PRECISION_ZERO]) @@ -65,19 +67,27 @@ AC_DEFUN([gl_FUNC_VSPRINTF_IS_POSIX], *yes) case "$gl_cv_func_printf_flag_grouping" in *yes) - case "$gl_cv_func_printf_flag_leftadjust" in + case "$gl_cv_func_printf_flag_grouping_int_precision" in *yes) - case "$gl_cv_func_printf_flag_zero" in + case "$gl_cv_func_printf_flag_grouping_multibyte" in *yes) - case "$gl_cv_func_printf_flag_alt_precision_zero" in + case "$gl_cv_func_printf_flag_leftadjust" in *yes) - case "$gl_cv_func_printf_precision" in + case "$gl_cv_func_printf_flag_zero" in *yes) - case "$gl_cv_func_printf_enomem" in + case "$gl_cv_func_printf_flag_alt_precision_zero" in *yes) - # vsprintf exists and is - # already POSIX compliant. - gl_cv_func_vsprintf_posix=yes + case "$gl_cv_func_printf_precision" in + *yes) + case "$gl_cv_func_printf_enomem" in + *yes) + # vsprintf exists and is + # already POSIX compliant. + gl_cv_func_vsprintf_posix=yes + ;; + esac + ;; + esac ;; esac ;; diff --git a/modules/vasnprintf-posix-tests b/modules/vasnprintf-posix-tests index 5e2c388fec..43d57264e7 100644 --- a/modules/vasnprintf-posix-tests +++ b/modules/vasnprintf-posix-tests @@ -16,6 +16,7 @@ float-h nan snan setlocale +localeconv configure.ac: AC_REQUIRE([gl_LONG_DOUBLE_VS_DOUBLE]) diff --git a/modules/vasnwprintf-posix-tests b/modules/vasnwprintf-posix-tests index 3d47d8893b..d62437f2f5 100644 --- a/modules/vasnwprintf-posix-tests +++ b/modules/vasnwprintf-posix-tests @@ -17,6 +17,7 @@ float-h nan snan setlocale +localeconv wcscmp wcsspn wmemcmp diff --git a/tests/test-vasnprintf-posix2.c b/tests/test-vasnprintf-posix2.c index 221292d709..1f43aa5e6e 100644 --- a/tests/test-vasnprintf-posix2.c +++ b/tests/test-vasnprintf-posix2.c @@ -59,5 +59,375 @@ main (int argc, char *argv[]) free (result); } + /* Test that a locale dependent grouping separator is used correctly. */ + { + const char *separator = + localeconv ()->thousands_sep; /* = nl_langinfo (THOUSEP) */ + size_t separator_len = strlen (separator); + + /* Tests of %d directive with grouping separator. */ + { + size_t length; + char *result = asnprintf (NULL, &length, "%'d", 1000); + ASSERT (result != NULL); + ASSERT (length == 4 + separator_len); + ASSERT (memcmp (result, "1", 1) == 0 + && memcmp (result + 1, separator, separator_len) == 0 + && strcmp (result + 1 + separator_len, "000") == 0); + free (result); + } + { + size_t length; + char *result = asnprintf (NULL, &length, "%'d", -1000); + ASSERT (result != NULL); + ASSERT (length == 5 + separator_len); + ASSERT (memcmp (result, "-1", 2) == 0 + && memcmp (result + 2, separator, separator_len) == 0 + && strcmp (result + 2 + separator_len, "000") == 0); + free (result); + } + { + size_t length; + char *result = asnprintf (NULL, &length, "%'d", -142857); + ASSERT (result != NULL); + ASSERT (length == 7 + separator_len); + ASSERT (memcmp (result, "-142", 4) == 0 + && memcmp (result + 4, separator, separator_len) == 0 + && strcmp (result + 4 + separator_len, "857") == 0); + free (result); + } + { + size_t length; + char *result = asnprintf (NULL, &length, "%'d", 2147483647); + ASSERT (result != NULL); + ASSERT (length == 10 + 3 * separator_len); + ASSERT (memcmp (result, "2", 1) == 0 + && memcmp (result + 1, separator, separator_len) == 0 + && memcmp (result + 1 + separator_len, "147", 3) == 0 + && memcmp (result + 4 + separator_len, separator, separator_len) == 0 + && memcmp (result + 4 + 2 * separator_len, "483", 3) == 0 + && memcmp (result + 7 + 2 * separator_len, separator, separator_len) == 0 + && strcmp (result + 7 + 3 * separator_len, "647") == 0); + free (result); + } + { + size_t length; + char *result = asnprintf (NULL, &length, "%'d", -2147483647); + ASSERT (result != NULL); + ASSERT (length == 11 + 3 * separator_len); + ASSERT (memcmp (result, "-2", 2) == 0 + && memcmp (result + 2, separator, separator_len) == 0 + && memcmp (result + 2 + separator_len, "147", 3) == 0 + && memcmp (result + 5 + separator_len, separator, separator_len) == 0 + && memcmp (result + 5 + 2 * separator_len, "483", 3) == 0 + && memcmp (result + 8 + 2 * separator_len, separator, separator_len) == 0 + && strcmp (result + 8 + 3 * separator_len, "647") == 0); + free (result); + } + { + size_t length; + char *result = asnprintf (NULL, &length, "%'.10d", 1000); + ASSERT (result != NULL); + if (length == 10 + separator_len) + { + /* Most implementations don't add the grouping character within the + leading zeroes. */ + ASSERT (length == 10 + separator_len); + ASSERT (memcmp (result, "0000001", 7) == 0 + && memcmp (result + 7, separator, separator_len) == 0 + && strcmp (result + 7 + separator_len, "000") == 0); + } + else + { + /* But there are good arguments for adding the grouping character + within the leading zeroes. + */ + ASSERT (length == 10 + 3 * separator_len); + ASSERT (memcmp (result, "0", 1) == 0 + && memcmp (result + 1, separator, separator_len) == 0 + && memcmp (result + 1 + separator_len, "000", 3) == 0 + && memcmp (result + 4 + separator_len, separator, separator_len) == 0 + && memcmp (result + 4 + 2 * separator_len, "001", 3) == 0 + && memcmp (result + 7 + 2 * separator_len, separator, separator_len) == 0 + && strcmp (result + 7 + 3 * separator_len, "000") == 0); + } + free (result); + } + { + size_t length; + char *result = asnprintf (NULL, &length, "%'.10d", -1000); + ASSERT (result != NULL); + if (length == 11 + separator_len) + { + /* Most implementations don't add the grouping character within the + leading zeroes. */ + ASSERT (length == 11 + separator_len); + ASSERT (memcmp (result, "-0000001", 8) == 0 + && memcmp (result + 8, separator, separator_len) == 0 + && strcmp (result + 8 + separator_len, "000") == 0); + } + else + { + /* But there are good arguments for adding the grouping character + within the leading zeroes. + */ + ASSERT (length == 11 + 3 * separator_len); + ASSERT (memcmp (result, "-0", 2) == 0 + && memcmp (result + 2, separator, separator_len) == 0 + && memcmp (result + 2 + separator_len, "000", 3) == 0 + && memcmp (result + 5 + separator_len, separator, separator_len) == 0 + && memcmp (result + 5 + 2 * separator_len, "001", 3) == 0 + && memcmp (result + 8 + 2 * separator_len, separator, separator_len) == 0 + && strcmp (result + 8 + 3 * separator_len, "000") == 0); + } + free (result); + } + { + size_t length; + char *result = asnprintf (NULL, &length, "%'10d", 1000); + ASSERT (result != NULL); + if (separator_len > 0) + { + if (length == 10) + { + /* Result according to POSIX, which says that the width is + measured in "bytes". + + */ + ASSERT (length == 10); + ASSERT (memcmp (result, " 1" + separator_len, 7 - separator_len) == 0 + && memcmp (result + 7 - separator_len, separator, separator_len) == 0 + && strcmp (result + 7, "000") == 0); + } + else + { + /* Result according to glibc, which measures the width in + multibyte characters or column positions. + + + */ + ASSERT (length == 9 + separator_len); + ASSERT (memcmp (result, " 1", 6) == 0 + && memcmp (result + 6, separator, separator_len) == 0 + && strcmp (result + 6 + separator_len, "000") == 0); + } + } + else + { + ASSERT (length == 10); + ASSERT (strcmp (result, " 1000") == 0); + } + free (result); + } + { + size_t length; + char *result = asnprintf (NULL, &length, "%'10d", -1000); + ASSERT (result != NULL); + if (separator_len > 0) + { + if (length == 10) + { + /* Result according to POSIX, which says that the width is + measured in "bytes". + + */ + ASSERT (length == 10); + ASSERT (memcmp (result, " -1" + separator_len, 7 - separator_len) == 0 + && memcmp (result + 7 - separator_len, separator, separator_len) == 0 + && strcmp (result + 7, "000") == 0); + } + else + { + /* Result according to glibc, which measures the width in + multibyte characters or column positions. + + + */ + ASSERT (length == 9 + separator_len); + ASSERT (memcmp (result, " -1", 6) == 0 + && memcmp (result + 6, separator, separator_len) == 0 + && strcmp (result + 6 + separator_len, "000") == 0); + } + } + else + { + ASSERT (length == 10); + ASSERT (strcmp (result, " -1000") == 0); + } + free (result); + } + + /* Tests of %f directive with grouping separator. */ + { + size_t length; + char *result = asnprintf (NULL, &length, "%'.0f", 1000.0); + ASSERT (result != NULL); + ASSERT (length == 4 + separator_len); + ASSERT (memcmp (result, "1", 1) == 0 + && memcmp (result + 1, separator, separator_len) == 0 + && strcmp (result + 1 + separator_len, "000") == 0); + free (result); + } + { + size_t length; + char *result = asnprintf (NULL, &length, "%'.5f", 3141592653.5897932386); + ASSERT (result != NULL); + ASSERT (length == 16 + 3 * separator_len); + ASSERT (memcmp (result, "3", 1) == 0 + && memcmp (result + 1, separator, separator_len) == 0 + && memcmp (result + 1 + separator_len, "141", 3) == 0 + && memcmp (result + 4 + separator_len, separator, separator_len) == 0 + && memcmp (result + 4 + 2 * separator_len, "592", 3) == 0 + && memcmp (result + 7 + 2 * separator_len, separator, separator_len) == 0 + && strcmp (result + 7 + 3 * separator_len, "653,58979") == 0); + free (result); + } + { + size_t length; + char *result = asnprintf (NULL, &length, "%'.5f", -3141592653.5897932386); + ASSERT (result != NULL); + ASSERT (length == 17 + 3 * separator_len); + ASSERT (memcmp (result, "-3", 2) == 0 + && memcmp (result + 2, separator, separator_len) == 0 + && memcmp (result + 2 + separator_len, "141", 3) == 0 + && memcmp (result + 5 + separator_len, separator, separator_len) == 0 + && memcmp (result + 5 + 2 * separator_len, "592", 3) == 0 + && memcmp (result + 8 + 2 * separator_len, separator, separator_len) == 0 + && strcmp (result + 8 + 3 * separator_len, "653,58979") == 0); + free (result); + } + { + size_t length; + char *result = asnprintf (NULL, &length, "%'010.0f", 1000.0); + ASSERT (result != NULL); + if (separator_len > 0) + { + if (length == 10) + { + /* Result according to POSIX, which says that the width is + measured in "bytes". + + */ + ASSERT (length == 10); + ASSERT (memcmp (result, "0000001" + separator_len, 7 - separator_len) == 0 + && memcmp (result + 7 - separator_len, separator, separator_len) == 0 + && strcmp (result + 7, "000") == 0); + } + else + { + /* Result according to glibc, which measures the width in + multibyte characters or column positions. + + + */ + ASSERT (length == 9 + separator_len); + ASSERT (memcmp (result, "000001", 6) == 0 + && memcmp (result + 6, separator, separator_len) == 0 + && strcmp (result + 6 + separator_len, "000") == 0); + } + } + else + { + ASSERT (length == 10); + ASSERT (strcmp (result, "0000001000") == 0); + } + free (result); + } + + /* Tests of %g directive with grouping separator. */ + { + size_t length; + char *result = asnprintf (NULL, &length, "%'g", 1000.0); + ASSERT (result != NULL); + ASSERT (length == 4 + separator_len); + ASSERT (memcmp (result, "1", 1) == 0 + && memcmp (result + 1, separator, separator_len) == 0 + && strcmp (result + 1 + separator_len, "000") == 0); + free (result); + } + { + size_t length; + char *result = asnprintf (NULL, &length, "%'g", -142857.0); + ASSERT (result != NULL); + ASSERT (length == 7 + separator_len); + ASSERT (memcmp (result, "-142", 4) == 0 + && memcmp (result + 4, separator, separator_len) == 0 + && strcmp (result + 4 + separator_len, "857") == 0); + free (result); + } + { + size_t length; + char *result = asnprintf (NULL, &length, "%'.5g", 3141592653.5897932386); + ASSERT (result != NULL); + ASSERT (length == 10); + ASSERT (strcmp (result, "3,1416e+09") == 0); + free (result); + } + { + size_t length; + char *result = asnprintf (NULL, &length, "%'.5g", -3141592653.5897932386); + ASSERT (result != NULL); + ASSERT (length == 11); + ASSERT (strcmp (result, "-3,1416e+09") == 0); + free (result); + } + { + size_t length; + char *result = + asnprintf (NULL, &length, "%'.50g", + 42351647362715016953416125033982098102569580078125.0); + ASSERT (result != NULL); + ASSERT (length == 50 + 16 * separator_len); + ASSERT (memcmp (result, "42", 2) == 0 + && memcmp (result + 2, separator, separator_len) == 0 + && memcmp (result + 2 + separator_len, "351", 3) == 0 + && memcmp (result + 5 + separator_len, separator, separator_len) == 0 + && memcmp (result + 5 + 2 * separator_len, "647", 3) == 0 + && memcmp (result + 8 + 2 * separator_len, separator, separator_len) == 0 + && memcmp (result + 8 + 3 * separator_len, "362", 3) == 0 + && memcmp (result + 11 + 3 * separator_len, separator, separator_len) == 0 + && memcmp (result + 11 + 4 * separator_len, "715", 3) == 0 + && memcmp (result + 14 + 4 * separator_len, separator, separator_len) == 0); + free (result); + } + { + size_t length; + char *result = asnprintf (NULL, &length, "%'010g", 1000.0); + ASSERT (result != NULL); + if (separator_len > 0) + { + if (length == 10) + { + /* Result according to POSIX, which says that the width is + measured in "bytes". + + */ + ASSERT (length == 10); + ASSERT (memcmp (result, "0000001" + separator_len, 7 - separator_len) == 0 + && memcmp (result + 7 - separator_len, separator, separator_len) == 0 + && strcmp (result + 7, "000") == 0); + } + else + { + /* Result according to glibc, which measures the width in + multibyte characters or column positions. + + + */ + ASSERT (length == 9 + separator_len); + ASSERT (memcmp (result, "000001", 6) == 0 + && memcmp (result + 6, separator, separator_len) == 0 + && strcmp (result + 6 + separator_len, "000") == 0); + } + } + else + { + ASSERT (length == 10); + ASSERT (strcmp (result, "0000001000") == 0); + } + free (result); + } + } + return test_exit_status; } diff --git a/tests/test-vasnprintf-posix2.sh b/tests/test-vasnprintf-posix2.sh index e488e94e97..90c2fc180c 100755 --- a/tests/test-vasnprintf-posix2.sh +++ b/tests/test-vasnprintf-posix2.sh @@ -3,19 +3,13 @@ # Test whether a french locale is installed. : "${LOCALE_FR=fr_FR}" : "${LOCALE_FR_UTF8=fr_FR.UTF-8}" -if test $LOCALE_FR != none; then - testlocale=$LOCALE_FR -else - if test $LOCALE_FR_UTF8 != none; then - testlocale=$LOCALE_FR_UTF8 +if test $LOCALE_FR = none && test $LOCALE_FR_UTF8 = none; then + if test -f /usr/bin/localedef; then + echo "Skipping test: no french locale is installed" else - if test -f /usr/bin/localedef; then - echo "Skipping test: no french locale is installed" - else - echo "Skipping test: no french locale is supported" - fi - exit 77 + echo "Skipping test: no french locale is supported" fi + exit 77 fi if $LC_NUMERIC_IMPLEMENTED; then @@ -25,5 +19,11 @@ else exit 77 fi -LC_ALL=$testlocale \ -${CHECKER} ./test-vasnprintf-posix2${EXEEXT} +if test $LOCALE_FR != none; then + LC_ALL=$LOCALE_FR ${CHECKER} ./test-vasnprintf-posix2${EXEEXT} || exit 1 +fi +if test $LOCALE_FR_UTF8 != none; then + LC_ALL=$LOCALE_FR_UTF8 ${CHECKER} ./test-vasnprintf-posix2${EXEEXT} || exit 1 +fi + +exit 0 diff --git a/tests/test-vasnwprintf-posix2.c b/tests/test-vasnwprintf-posix2.c index fde409376e..3794d5f7c4 100644 --- a/tests/test-vasnwprintf-posix2.c +++ b/tests/test-vasnwprintf-posix2.c @@ -24,6 +24,10 @@ #include #include #include +#if __GLIBC__ >= 2 || defined __CYGWIN__ +# include +# include +#endif #include "macros.h" @@ -60,5 +64,276 @@ main (int argc, char *argv[]) free (result); } + /* Test that a locale dependent grouping separator is used correctly. */ + { + wchar_t separator[10]; +#if __GLIBC__ >= 2 || defined __CYGWIN__ + /* On glibc, in the unibyte locale fr_FR, the *wprintf routines use U+202F + as separator, which cannot be represented in the locale encoding. */ + separator[0] = (wchar_t) (uintptr_t) nl_langinfo (_NL_NUMERIC_THOUSANDS_SEP_WC); + separator[1] = L'\0'; +#else + const char *separator_mb = + localeconv ()->thousands_sep; /* = nl_langinfo (THOUSEP) */ + mbstowcs (separator, separator_mb, SIZEOF (separator)); +#endif + size_t separator_len = wcslen (separator); + + /* Tests of %d directive with grouping separator. */ + { + size_t length; + wchar_t *result = asnwprintf (NULL, &length, L"%'d", 1000); + ASSERT (result != NULL); + ASSERT (length == 4 + separator_len); + ASSERT (wmemcmp (result, L"1", 1) == 0 + && wmemcmp (result + 1, separator, separator_len) == 0 + && wcscmp (result + 1 + separator_len, L"000") == 0); + free (result); + } + { + size_t length; + wchar_t *result = asnwprintf (NULL, &length, L"%'d", -1000); + ASSERT (result != NULL); + ASSERT (length == 5 + separator_len); + ASSERT (wmemcmp (result, L"-1", 2) == 0 + && wmemcmp (result + 2, separator, separator_len) == 0 + && wcscmp (result + 2 + separator_len, L"000") == 0); + free (result); + } + { + size_t length; + wchar_t *result = asnwprintf (NULL, &length, L"%'d", -142857); + ASSERT (result != NULL); + ASSERT (length == 7 + separator_len); + ASSERT (wmemcmp (result, L"-142", 4) == 0 + && wmemcmp (result + 4, separator, separator_len) == 0 + && wcscmp (result + 4 + separator_len, L"857") == 0); + free (result); + } + { + size_t length; + wchar_t *result = asnwprintf (NULL, &length, L"%'d", 2147483647); + ASSERT (result != NULL); + ASSERT (length == 10 + 3 * separator_len); + ASSERT (wmemcmp (result, L"2", 1) == 0 + && wmemcmp (result + 1, separator, separator_len) == 0 + && wmemcmp (result + 1 + separator_len, L"147", 3) == 0 + && wmemcmp (result + 4 + separator_len, separator, separator_len) == 0 + && wmemcmp (result + 4 + 2 * separator_len, L"483", 3) == 0 + && wmemcmp (result + 7 + 2 * separator_len, separator, separator_len) == 0 + && wcscmp (result + 7 + 3 * separator_len, L"647") == 0); + free (result); + } + { + size_t length; + wchar_t *result = asnwprintf (NULL, &length, L"%'d", -2147483647); + ASSERT (result != NULL); + ASSERT (length == 11 + 3 * separator_len); + ASSERT (wmemcmp (result, L"-2", 2) == 0 + && wmemcmp (result + 2, separator, separator_len) == 0 + && wmemcmp (result + 2 + separator_len, L"147", 3) == 0 + && wmemcmp (result + 5 + separator_len, separator, separator_len) == 0 + && wmemcmp (result + 5 + 2 * separator_len, L"483", 3) == 0 + && wmemcmp (result + 8 + 2 * separator_len, separator, separator_len) == 0 + && wcscmp (result + 8 + 3 * separator_len, L"647") == 0); + free (result); + } + { + size_t length; + wchar_t *result = asnwprintf (NULL, &length, L"%'.10d", 1000); + ASSERT (result != NULL); + if (length == 10 + separator_len) + { + /* Most implementations don't add the grouping character within the + leading zeroes. */ + ASSERT (length == 10 + separator_len); + ASSERT (wmemcmp (result, L"0000001", 7) == 0 + && wmemcmp (result + 7, separator, separator_len) == 0 + && wcscmp (result + 7 + separator_len, L"000") == 0); + } + else + { + /* But there are good arguments for adding the grouping character + within the leading zeroes too. + */ + ASSERT (length == 10 + 3 * separator_len); + ASSERT (wmemcmp (result, L"0", 1) == 0 + && wmemcmp (result + 1, separator, separator_len) == 0 + && wmemcmp (result + 1 + separator_len, L"000", 3) == 0 + && wmemcmp (result + 4 + separator_len, separator, separator_len) == 0 + && wmemcmp (result + 4 + 2 * separator_len, L"001", 3) == 0 + && wmemcmp (result + 7 + 2 * separator_len, separator, separator_len) == 0 + && wcscmp (result + 7 + 3 * separator_len, L"000") == 0); + } + free (result); + } + { + size_t length; + wchar_t *result = asnwprintf (NULL, &length, L"%'.10d", -1000); + ASSERT (result != NULL); + if (length == 11 + separator_len) + { + /* Most implementations don't add the grouping character within the + leading zeroes. */ + ASSERT (length == 11 + separator_len); + ASSERT (wmemcmp (result, L"-0000001", 8) == 0 + && wmemcmp (result + 8, separator, separator_len) == 0 + && wcscmp (result + 8 + separator_len, L"000") == 0); + } + else + { + /* But there are good arguments for adding the grouping character + within the leading zeroes too. + */ + ASSERT (length == 11 + 3 * separator_len); + ASSERT (wmemcmp (result, L"-0", 2) == 0 + && wmemcmp (result + 2, separator, separator_len) == 0 + && wmemcmp (result + 2 + separator_len, L"000", 3) == 0 + && wmemcmp (result + 5 + separator_len, separator, separator_len) == 0 + && wmemcmp (result + 5 + 2 * separator_len, L"001", 3) == 0 + && wmemcmp (result + 8 + 2 * separator_len, separator, separator_len) == 0 + && wcscmp (result + 8 + 3 * separator_len, L"000") == 0); + } + free (result); + } + { + size_t length; + wchar_t *result = asnwprintf (NULL, &length, L"%'10d", 1000); + ASSERT (result != NULL); + ASSERT (length == 10); + ASSERT (wmemcmp (result, L" 1" + separator_len, 7 - separator_len) == 0 + && wmemcmp (result + 7 - separator_len, separator, separator_len) == 0 + && wcscmp (result + 7, L"000") == 0); + free (result); + } + { + size_t length; + wchar_t *result = asnwprintf (NULL, &length, L"%'10d", -1000); + ASSERT (result != NULL); + ASSERT (length == 10); + ASSERT (wmemcmp (result, L" -1" + separator_len, 7 - separator_len) == 0 + && wmemcmp (result + 7 - separator_len, separator, separator_len) == 0 + && wcscmp (result + 7, L"000") == 0); + free (result); + } + + /* Tests of %f directive with grouping separator. */ + { + size_t length; + wchar_t *result = asnwprintf (NULL, &length, L"%'.0f", 1000.0); + ASSERT (result != NULL); + ASSERT (length == 4 + separator_len); + ASSERT (wmemcmp (result, L"1", 1) == 0 + && wmemcmp (result + 1, separator, separator_len) == 0 + && wcscmp (result + 1 + separator_len, L"000") == 0); + free (result); + } + { + size_t length; + wchar_t *result = asnwprintf (NULL, &length, L"%'.5f", 3141592653.5897932386); + ASSERT (result != NULL); + ASSERT (length == 16 + 3 * separator_len); + ASSERT (wmemcmp (result, L"3", 1) == 0 + && wmemcmp (result + 1, separator, separator_len) == 0 + && wmemcmp (result + 1 + separator_len, L"141", 3) == 0 + && wmemcmp (result + 4 + separator_len, separator, separator_len) == 0 + && wmemcmp (result + 4 + 2 * separator_len, L"592", 3) == 0 + && wmemcmp (result + 7 + 2 * separator_len, separator, separator_len) == 0 + && wcscmp (result + 7 + 3 * separator_len, L"653,58979") == 0); + free (result); + } + { + size_t length; + wchar_t *result = asnwprintf (NULL, &length, L"%'.5f", -3141592653.5897932386); + ASSERT (result != NULL); + ASSERT (length == 17 + 3 * separator_len); + ASSERT (wmemcmp (result, L"-3", 2) == 0 + && wmemcmp (result + 2, separator, separator_len) == 0 + && wmemcmp (result + 2 + separator_len, L"141", 3) == 0 + && wmemcmp (result + 5 + separator_len, separator, separator_len) == 0 + && wmemcmp (result + 5 + 2 * separator_len, L"592", 3) == 0 + && wmemcmp (result + 8 + 2 * separator_len, separator, separator_len) == 0 + && wcscmp (result + 8 + 3 * separator_len, L"653,58979") == 0); + free (result); + } + { + size_t length; + wchar_t *result = asnwprintf (NULL, &length, L"%'010.0f", 1000.0); + ASSERT (result != NULL); + ASSERT (length == 10); + ASSERT (wmemcmp (result, L"0000001" + separator_len, 7 - separator_len) == 0 + && wmemcmp (result + 7 - separator_len, separator, separator_len) == 0 + && wcscmp (result + 7, L"000") == 0); + free (result); + } + + /* Tests of %g directive with grouping separator. */ + { + size_t length; + wchar_t *result = asnwprintf (NULL, &length, L"%'g", 1000.0); + ASSERT (result != NULL); + ASSERT (length == 4 + separator_len); + ASSERT (wmemcmp (result, L"1", 1) == 0 + && wmemcmp (result + 1, separator, separator_len) == 0 + && wcscmp (result + 1 + separator_len, L"000") == 0); + free (result); + } + { + size_t length; + wchar_t *result = asnwprintf (NULL, &length, L"%'g", -142857.0); + ASSERT (result != NULL); + ASSERT (length == 7 + separator_len); + ASSERT (wmemcmp (result, L"-142", 4) == 0 + && wmemcmp (result + 4, separator, separator_len) == 0 + && wcscmp (result + 4 + separator_len, L"857") == 0); + free (result); + } + { + size_t length; + wchar_t *result = asnwprintf (NULL, &length, L"%'.5g", 3141592653.5897932386); + ASSERT (result != NULL); + ASSERT (length == 10); + ASSERT (wcscmp (result, L"3,1416e+09") == 0); + free (result); + } + { + size_t length; + wchar_t *result = asnwprintf (NULL, &length, L"%'.5g", -3141592653.5897932386); + ASSERT (result != NULL); + ASSERT (length == 11); + ASSERT (wcscmp (result, L"-3,1416e+09") == 0); + free (result); + } + { + size_t length; + wchar_t *result = + asnwprintf (NULL, &length, L"%'.50g", + 42351647362715016953416125033982098102569580078125.0); + ASSERT (result != NULL); + ASSERT (length == 50 + 16 * separator_len); + ASSERT (wmemcmp (result, L"42", 2) == 0 + && wmemcmp (result + 2, separator, separator_len) == 0 + && wmemcmp (result + 2 + separator_len, L"351", 3) == 0 + && wmemcmp (result + 5 + separator_len, separator, separator_len) == 0 + && wmemcmp (result + 5 + 2 * separator_len, L"647", 3) == 0 + && wmemcmp (result + 8 + 2 * separator_len, separator, separator_len) == 0 + && wmemcmp (result + 8 + 3 * separator_len, L"362", 3) == 0 + && wmemcmp (result + 11 + 3 * separator_len, separator, separator_len) == 0 + && wmemcmp (result + 11 + 4 * separator_len, L"715", 3) == 0 + && wmemcmp (result + 14 + 4 * separator_len, separator, separator_len) == 0); + free (result); + } + { + size_t length; + wchar_t *result = asnwprintf (NULL, &length, L"%'010g", 1000.0); + ASSERT (result != NULL); + ASSERT (length == 10); + ASSERT (wmemcmp (result, L"0000001" + separator_len, 7 - separator_len) == 0 + && wmemcmp (result + 7 - separator_len, separator, separator_len) == 0 + && wcscmp (result + 7, L"000") == 0); + free (result); + } + } + return test_exit_status; } diff --git a/tests/test-vasnwprintf-posix2.sh b/tests/test-vasnwprintf-posix2.sh index ecff3c9f65..ceddec7ab6 100755 --- a/tests/test-vasnwprintf-posix2.sh +++ b/tests/test-vasnwprintf-posix2.sh @@ -3,19 +3,13 @@ # Test whether a french locale is installed. : "${LOCALE_FR=fr_FR}" : "${LOCALE_FR_UTF8=fr_FR.UTF-8}" -if test $LOCALE_FR != none; then - testlocale=$LOCALE_FR -else - if test $LOCALE_FR_UTF8 != none; then - testlocale=$LOCALE_FR_UTF8 +if test $LOCALE_FR = none && test $LOCALE_FR_UTF8 = none; then + if test -f /usr/bin/localedef; then + echo "Skipping test: no french locale is installed" else - if test -f /usr/bin/localedef; then - echo "Skipping test: no french locale is installed" - else - echo "Skipping test: no french locale is supported" - fi - exit 77 + echo "Skipping test: no french locale is supported" fi + exit 77 fi if $LC_NUMERIC_IMPLEMENTED; then @@ -25,5 +19,11 @@ else exit 77 fi -LC_ALL=$testlocale \ -${CHECKER} ./test-vasnwprintf-posix2${EXEEXT} +if test $LOCALE_FR != none; then + LC_ALL=$LOCALE_FR ${CHECKER} ./test-vasnwprintf-posix2${EXEEXT} || exit 1 +fi +if test $LOCALE_FR_UTF8 != none; then + LC_ALL=$LOCALE_FR_UTF8 ${CHECKER} ./test-vasnwprintf-posix2${EXEEXT} || exit 1 +fi + +exit 0 -- 2.39.5