[6/7] stdio-common: Keep trailing zeros where %#g rounds into a new decade

Message ID 20260824221612.4132669-7-mattst88@gmail.com (mailing list archive)
State Committed
Delegated to: Adhemerval Zanella Netto
Headers
Series stdio-common: Verify printf format tests with Python rather than AWK |

Checks

Context Check Description
redhat-pt-bot/TryBot-apply_patch success Patch applied to master at the time it was sent
linaro-tcwg-bot/tcwg_glibc_build--master-arm success Build passed
linaro-tcwg-bot/tcwg_glibc_build--master-aarch64 success Build passed
linaro-tcwg-bot/tcwg_glibc_check--master-arm success Test passed
linaro-tcwg-bot/tcwg_glibc_check--master-aarch64 fail Test failed

Commit Message

Matt Turner Aug. 24, 2026, 10:16 p.m. UTC
  The g and G conversions choose between the f and e styles according to
the exponent the value has once rounded to the requested number of
significant digits.  Where the value is small enough for the f style but
rounding then carries into a new decade, printf_fp rewrites the digits
it has already produced into the e style, recomputing along the way how
many fractional digits the leading digit now leaves room for.

FRACDIG_MIN, which the alternative form sets to the number of fractional
digits that have to be retained rather than stripped, was left behind at
the value computed for the f style.  Where the value filled the whole
integer part that is zero, so all the fractional digits were then
stripped from a result the '#' flag requires to keep them:

  printf ("%#.2g", 99.9)    gave "1.e+02" rather than "1.0e+02"
  printf ("%#g", 999999.9)  gave "1.e+06" rather than "1.00000e+06"

Update FRACDIG_MIN along with FRACDIG_MAX.  Without the alternative form
nothing retains trailing zeros, so the outcome is unchanged there.

None of the values the conversion tests iterate over round this way, so
add one that does at a precision they cover.

Tested on x86_64-linux-gnu.
---
 stdio-common/printf_fp.c                          | 5 +++++
 stdio-common/tst-printf-format-skeleton-double.c  | 2 +-
 stdio-common/tst-printf-format-skeleton-ldouble.c | 2 +-
 3 files changed, 7 insertions(+), 2 deletions(-)
  

Comments

Adhemerval Zanella Aug. 28, 2026, 11:50 a.m. UTC | #1
On 24/08/26 19:16, Matt Turner wrote:
> The g and G conversions choose between the f and e styles according to
> the exponent the value has once rounded to the requested number of
> significant digits.  Where the value is small enough for the f style but
> rounding then carries into a new decade, printf_fp rewrites the digits
> it has already produced into the e style, recomputing along the way how
> many fractional digits the leading digit now leaves room for.
> 
> FRACDIG_MIN, which the alternative form sets to the number of fractional
> digits that have to be retained rather than stripped, was left behind at
> the value computed for the f style.  Where the value filled the whole
> integer part that is zero, so all the fractional digits were then
> stripped from a result the '#' flag requires to keep them:
> 
>   printf ("%#.2g", 99.9)    gave "1.e+02" rather than "1.0e+02"
>   printf ("%#g", 999999.9)  gave "1.e+06" rather than "1.00000e+06"
> 
> Update FRACDIG_MIN along with FRACDIG_MAX.  Without the alternative form
> nothing retains trailing zeros, so the outcome is unchanged there.
> 
> None of the values the conversion tests iterate over round this way, so
> add one that does at a precision they cover.
> 
> Tested on x86_64-linux-gnu.

LGTM, thanks. I think this can be installed independently of the set and
I think we should open a bug report to track it.

Reviewed-by: Adhemerval Zanella  <adhemerval.zanella@linaro.org>

> ---
>  stdio-common/printf_fp.c                          | 5 +++++
>  stdio-common/tst-printf-format-skeleton-double.c  | 2 +-
>  stdio-common/tst-printf-format-skeleton-ldouble.c | 2 +-
>  3 files changed, 7 insertions(+), 2 deletions(-)
> 
> diff --git ./stdio-common/printf_fp.c ./stdio-common/printf_fp.c
> index b8f8c5fc07..97bd6afda3 100644
> --- ./stdio-common/printf_fp.c
> +++ ./stdio-common/printf_fp.c
> @@ -896,6 +896,11 @@ __printf_fp_buffer_1 (struct __printf_buffer *buf, locale_t loc,
>  		    fracdig_no += intdig_no;
>  		    intdig_no = 1;
>  		    fracdig_max = intdig_max - intdig_no;
> +		    if (info->alt)
> +		      /* The alternative form retains trailing zeros, and
> +			 the number of fractional digits it has to retain
> +			 has just changed along with FRACDIG_MAX.  */
> +		      fracdig_min = fracdig_max;
>  		    ++p.exponent;
>  		    /* Now we must print the p.exponent.	*/
>  		    p.type = isupper (info->spec) ? 'E' : 'e';
> diff --git ./stdio-common/tst-printf-format-skeleton-double.c ./stdio-common/tst-printf-format-skeleton-double.c
> index ae3acb00f7..333bd568a2 100644
> --- ./stdio-common/tst-printf-format-skeleton-double.c
> +++ ./stdio-common/tst-printf-format-skeleton-double.c
> @@ -30,7 +30,7 @@
>  typedef double type_t;
>  static const type_t vals[] =
>    { -HUGE_VAL, -DBL_MAX, -DBL_MIN, -0.0, -NAN, NAN, 0, DBL_TRUE_MIN,
> -    DBL_MIN, DBL_MAX, HUGE_VAL };
> +    DBL_MIN, 99.9, DBL_MAX, HUGE_VAL };
>  static const char length[] = "";
>  
>  #include "tst-printf-format-skeleton.c"
> diff --git ./stdio-common/tst-printf-format-skeleton-ldouble.c ./stdio-common/tst-printf-format-skeleton-ldouble.c
> index e323d621bb..0c828ec3b5 100644
> --- ./stdio-common/tst-printf-format-skeleton-ldouble.c
> +++ ./stdio-common/tst-printf-format-skeleton-ldouble.c
> @@ -31,7 +31,7 @@
>  typedef long double type_t;
>  static const type_t vals[] =
>    { -HUGE_VAL, -LDBL_MAX, -LDBL_MIN, -0.0, -NAN, NAN, 0, LDBL_TRUE_MIN,
> -    LDBL_MIN, LDBL_MAX, HUGE_VAL };
> +    LDBL_MIN, 99.9L, LDBL_MAX, HUGE_VAL };
>  static const char length[] = "L";
>  
>  #ifndef TIMEOUT
  
Matt Turner Aug. 31, 2026, 12:26 a.m. UTC | #2
On Fri, Aug 28, 2026 at 7:50 AM Adhemerval Zanella Netto
<adhemerval.zanella@linaro.org> wrote:
>
>
>
> On 24/08/26 19:16, Matt Turner wrote:
> > The g and G conversions choose between the f and e styles according to
> > the exponent the value has once rounded to the requested number of
> > significant digits.  Where the value is small enough for the f style but
> > rounding then carries into a new decade, printf_fp rewrites the digits
> > it has already produced into the e style, recomputing along the way how
> > many fractional digits the leading digit now leaves room for.
> >
> > FRACDIG_MIN, which the alternative form sets to the number of fractional
> > digits that have to be retained rather than stripped, was left behind at
> > the value computed for the f style.  Where the value filled the whole
> > integer part that is zero, so all the fractional digits were then
> > stripped from a result the '#' flag requires to keep them:
> >
> >   printf ("%#.2g", 99.9)    gave "1.e+02" rather than "1.0e+02"
> >   printf ("%#g", 999999.9)  gave "1.e+06" rather than "1.00000e+06"
> >
> > Update FRACDIG_MIN along with FRACDIG_MAX.  Without the alternative form
> > nothing retains trailing zeros, so the outcome is unchanged there.
> >
> > None of the values the conversion tests iterate over round this way, so
> > add one that does at a precision they cover.
> >
> > Tested on x86_64-linux-gnu.
>
> LGTM, thanks. I think this can be installed independently of the set and
> I think we should open a bug report to track it.
>
> Reviewed-by: Adhemerval Zanella  <adhemerval.zanella@linaro.org>

Thanks, feel free to peel off patches from this series and merge them
independently.

I've filed a bug
(https://sourceware.org/bugzilla/show_bug.cgi?id=34578) for this
issue.
  

Patch

diff --git ./stdio-common/printf_fp.c ./stdio-common/printf_fp.c
index b8f8c5fc07..97bd6afda3 100644
--- ./stdio-common/printf_fp.c
+++ ./stdio-common/printf_fp.c
@@ -896,6 +896,11 @@  __printf_fp_buffer_1 (struct __printf_buffer *buf, locale_t loc,
 		    fracdig_no += intdig_no;
 		    intdig_no = 1;
 		    fracdig_max = intdig_max - intdig_no;
+		    if (info->alt)
+		      /* The alternative form retains trailing zeros, and
+			 the number of fractional digits it has to retain
+			 has just changed along with FRACDIG_MAX.  */
+		      fracdig_min = fracdig_max;
 		    ++p.exponent;
 		    /* Now we must print the p.exponent.	*/
 		    p.type = isupper (info->spec) ? 'E' : 'e';
diff --git ./stdio-common/tst-printf-format-skeleton-double.c ./stdio-common/tst-printf-format-skeleton-double.c
index ae3acb00f7..333bd568a2 100644
--- ./stdio-common/tst-printf-format-skeleton-double.c
+++ ./stdio-common/tst-printf-format-skeleton-double.c
@@ -30,7 +30,7 @@ 
 typedef double type_t;
 static const type_t vals[] =
   { -HUGE_VAL, -DBL_MAX, -DBL_MIN, -0.0, -NAN, NAN, 0, DBL_TRUE_MIN,
-    DBL_MIN, DBL_MAX, HUGE_VAL };
+    DBL_MIN, 99.9, DBL_MAX, HUGE_VAL };
 static const char length[] = "";
 
 #include "tst-printf-format-skeleton.c"
diff --git ./stdio-common/tst-printf-format-skeleton-ldouble.c ./stdio-common/tst-printf-format-skeleton-ldouble.c
index e323d621bb..0c828ec3b5 100644
--- ./stdio-common/tst-printf-format-skeleton-ldouble.c
+++ ./stdio-common/tst-printf-format-skeleton-ldouble.c
@@ -31,7 +31,7 @@ 
 typedef long double type_t;
 static const type_t vals[] =
   { -HUGE_VAL, -LDBL_MAX, -LDBL_MIN, -0.0, -NAN, NAN, 0, LDBL_TRUE_MIN,
-    LDBL_MIN, LDBL_MAX, HUGE_VAL };
+    LDBL_MIN, 99.9L, LDBL_MAX, HUGE_VAL };
 static const char length[] = "L";
 
 #ifndef TIMEOUT