>From 9d0af2115eb2a54ee89340ae706b39469b603a34 Mon Sep 17 00:00:00 2001 From: Rich Felker Date: Sat, 3 Oct 2026 20:47:10 -0400 Subject: [PATCH 07/10] printf: implement numeric grouping flag the grouping engine is complicated by the need to be able to process floating point conversions in narrow digit windows at a time. it computes an initial position in the grouping pattern based on the total number of digits to be presented, then allows expansion to be done in arbitrary units as long as they take place in sequential order. the buffer is assumed to have sufficient room to expand downward, so a worst case, N digits need a buffer of size N*(1+MB_LEN_MAX), assuming a single-character separator. this expanding-downward follows the existing approach the fmt_u family uses. POSIX does not clearly specify how precision is intended to interact with grouping, so we take a very literal interpretation, adjusting to produce the specified "minimum number of digits to appear" when padding only with zero digits and no additional grouping characters. other possible implementation choices would be fully grouping the zeros (but this is considerably more work that shares little code with the existing precision-padding code paths) or padding out to the length of a fully-grouped number of the given number of digits (like the POSIX examples for strfmon show it doing). this could be changed if an interpretation requires it, but it seems generally ill-advised to use precision with grouping. --- src/stdio/vfprintf.c | 109 +++++++++++++++++++++++++++++++++++++++---- 1 file changed, 101 insertions(+), 8 deletions(-) diff --git a/src/stdio/vfprintf.c b/src/stdio/vfprintf.c index 059db93f..c2c0d942 100644 --- a/src/stdio/vfprintf.c +++ b/src/stdio/vfprintf.c @@ -172,6 +172,74 @@ static char *fmt_u(uintmax_t x, char *s) return s; } +struct digit_grouper { + const char *g; + int pos; + int rep; + int rem; +}; + +int __count_groupings(struct digit_grouper *dg, int l, const char *g) +{ + int i, cnt = 0; + dg->g = g; + dg->rep = 0; + for (i=0; l>g[i] && g[i]>0 && g[i]!=CHAR_MAX; i++) { + cnt++; + l -= g[i]; + } + if (!g[i]) { + l--; + dg->rep = l/g[i-1]; + l %= g[i-1]; + l++; + cnt += dg->rep; + } + dg->pos = i; + dg->rem = l; + return cnt; +} + +char *__expand_grouping(char *a, char *z, struct digit_grouper *dg, const char *sep, size_t seplen) +{ + int i, l, rep, rem, n=0; + rep = dg->rep; + rem = dg->rem; + i = dg->pos; + l = z-a; + while (i>0 && l>=rem) { + n++; + l -= rem; + rem = dg->g[i-1]; + if (rep>0) rep--; + else i--; + } + if (!n) return a; + char *s = a; + a -= n * seplen; + char *d = a; + while (dg->pos>0 && srem) { + for (i=0; irem = dg->g[dg->pos-1]; + if (dg->rep>0) dg->rep--; + else dg->pos--; + } + } + return a; +} + +static const char *c_get_grouping(const char **psep, size_t *pseplen) +{ + static const char no_grouping[] = { CHAR_MAX, 0 }; + *psep = 0; + *pseplen = 0; + return no_grouping; +} +weak_alias(c_get_grouping, __get_grouping); + /* Do not override this check. The floating point printing code below * depends on the float.h constants being right. If they are wrong, it * may overflow the stack. */ @@ -185,7 +253,7 @@ static int c_radixpt(locale_t loc) } weak_alias(c_radixpt, __radixpt); -static int fmt_fp(FILE *f, long double y, int w, int p, int fl, int t, int ps) +static int fmt_fp(FILE *f, long double y, int w, int p, int fl, int t, int ps, const char *gr, const char *gs, size_t gsl) { int max_mant_dig = (ps==BIGLPRE) ? LDBL_MANT_DIG : DBL_MANT_DIG; int max_exp = (ps==BIGLPRE) ? LDBL_MAX_EXP : DBL_MAX_EXP; @@ -197,10 +265,11 @@ static int fmt_fp(FILE *f, long double y, int w, int p, int fl, int t, int ps) uint32_t big[bufsize]; uint32_t *a, *d, *r, *z; int e2=0, e, i, j, l; - char buf[9+LDBL_MANT_DIG/4], *s; + char buf[MAX(9+LDBL_MANT_DIG/4,9*(1+MB_LEN_MAX))], *s; const char *prefix="-0X+0X 0X-0x+0x 0x"; int pl; char ebuf0[3*sizeof(int)], *ebuf=&ebuf0[3*sizeof(int)], *estr; + struct digit_grouper dg; pl=1; if (signbit(y)) { @@ -374,6 +443,12 @@ static int fmt_fp(FILE *f, long double y, int w, int p, int fl, int t, int ps) if ((t|32)=='f') { if (e > INT_MAX-l) return -1; if (e>0) l+=e; + if (fl & GROUPED) { + int ng = __count_groupings(&dg, e+1, gr); + if (ng * gsl > INT_MAX-l) + return -1; + l += ng * gsl; + } } else { estr=fmt_u(e<0 ? -e : e, ebuf); while(ebuf-estr<2) *--estr='0'; @@ -391,10 +466,13 @@ static int fmt_fp(FILE *f, long double y, int w, int p, int fl, int t, int ps) if ((t|32)=='f') { if (a>r) a=r; for (d=a; d<=r; d++) { - char *s = fmt_u(*d, buf+9); - if (d!=a) while (s>buf) *--s='0'; - else if (s==buf+9) *--s='0'; - out(f, s, buf+9-s); + char *sz = buf + sizeof buf; + char *s = fmt_u(*d, sz); + if (d!=a) while (s>sz-9) *--s='0'; + else if (s==sz) *--s='0'; + if (fl & GROUPED) + s = __expand_grouping(s, sz, &dg, gs, gsl); + out(f, s, sz-s); } if (p || (fl&ALT_FORM)) { int radixpt = __radixpt(0); @@ -450,11 +528,13 @@ static int printf_core(FILE *f, const char *fmt, va_list *ap, union arg *nl_arg, unsigned st, ps; int cnt=0, l=0; size_t i; - char buf[sizeof(uintmax_t)*3]; + char buf[sizeof(uintmax_t)*3*(1+MB_LEN_MAX)]; const char *prefix; int t, pl; wchar_t wc[2], *ws; char mb[4]; + const char *gr=0, *gs=0; + size_t gsl=0; for (;;) { /* This error is only specified for snprintf, but since it's @@ -558,6 +638,9 @@ static int printf_core(FILE *f, const char *fmt, va_list *ap, union arg *nl_arg, /* - and 0 flags are mutually exclusive */ if (fl & LEFT_ADJ) fl &= ~ZERO_PAD; + if ((fl & GROUPED) && !gr) + gr = __get_grouping(&gs, &gsl); + switch(t) { case 'n': switch(ps) { @@ -593,6 +676,16 @@ static int printf_core(FILE *f, const char *fmt, va_list *ap, union arg *nl_arg, } else pl=0; case 'u': a = fmt_u(arg.i, z); + if (fl & GROUPED) { + struct digit_grouper dg; + int ng = __count_groupings(&dg, z-a, gr); + if (p > 0) { + if (ng * gsl > INT_MAX-p) + goto overflow; + p += ng * gsl; + } + a = __expand_grouping(a, z, &dg, gs, gsl); + } ifmt_tail: if (xp && p<0) goto overflow; if (xp) fl &= ~ZERO_PAD; @@ -638,7 +731,7 @@ static int printf_core(FILE *f, const char *fmt, va_list *ap, union arg *nl_arg, case 'e': case 'f': case 'g': case 'a': case 'E': case 'F': case 'G': case 'A': if (xp && p<0) goto overflow; - l = fmt_fp(f, arg.f, w, p, fl, t, ps); + l = fmt_fp(f, arg.f, w, p, fl, t, ps, gr, gs, gsl); if (l<0) goto overflow; continue; } -- 2.21.0