From 22b8fd6da931657ec930ba2625179c704de3e830 Mon Sep 17 00:00:00 2001 From: Peter Stephenson Date: Thu, 23 Jan 2014 10:32:59 +0000 Subject: 32299: add use of underscores on arithmetic output for spacing --- ChangeLog | 7 +++ Doc/Zsh/arith.yo | 21 +++++++ Functions/Misc/zcalc | 2 +- Src/math.c | 35 ++++++++---- Src/params.c | 151 +++++++++++++++++++++++++++++++++++++++++++++++++-- Src/subst.c | 8 +-- Test/C01arith.ztst | 16 ++++++ 7 files changed, 221 insertions(+), 19 deletions(-) diff --git a/ChangeLog b/ChangeLog index ac5759e46..a232fa6d7 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,10 @@ +2014-01-23 Peter Stephenson + + * 32299: Doc/Zsh/arith.yo, Functions/Misc/zcalc, Src/math.c, + Src/params.c, Src/subst.c, Test/C01arith.ztst: add + ability to use "_" at the end of a [#] arithmetic expression + to get underscores to space numeric output. + 2014-01-22 Barton E. Schaefer * unposted: Src/mem.c: reformulate 32285 to lift the fheap->sp diff --git a/Doc/Zsh/arith.yo b/Doc/Zsh/arith.yo index 2674c7817..4fff28fe2 100644 --- a/Doc/Zsh/arith.yo +++ b/Doc/Zsh/arith.yo @@ -76,6 +76,27 @@ have output base 16, while tt(x) (assuming it does not already exist) is implicitly typed by the arithmetic evaluation, where it acquires the output base 8. +The var(base) may be replaced or followed by an underscore, which may +itself be followed by a positive integer (if it is missing the value 3 +is used). This indicates that underscores should be inserted into the +output string, grouping the number for visual clarity. The following +integer specifies the number of digits to group together. For example: + +example(setopt cbases +print $(( [#16_4] 65536 ** 2 ))) + +outputs `tt(0x1_0000_0000)'. + +The feature can be used with floating +point numbers, in which case the base must be omitted; grouping +is away from the decimal point. For example, + +example(zmodload zsh/mathfunc +print $(( [#_] sqrt(1e7) ))) + +outputs `tt(3_162.277_660_168_379_5)' (the number of decimal places +shown may vary). + pindex(C_BASES, use of) pindex(OCTAL_ZEROES, use of) If the tt(C_BASES) option is set, hexadecimal numbers in the standard C diff --git a/Functions/Misc/zcalc b/Functions/Misc/zcalc index 1f3392d92..6a56d4782 100644 --- a/Functions/Misc/zcalc +++ b/Functions/Misc/zcalc @@ -196,7 +196,7 @@ while (( expression_mode )) || # special cases # Set default base if `[#16]' or `[##16]' etc. on its own. # Unset it if `[#]' or `[##]'. - if [[ $line = (#b)[[:blank:]]#('[#'(\#|)(<->|)']')[[:blank:]]#(*) ]]; then + if [[ $line = (#b)[[:blank:]]#('[#'(\#|)((<->|)(|_|_<->))']')[[:blank:]]#(*) ]]; then if [[ -z $match[4] ]]; then if [[ -z $match[3] ]]; then defbase= diff --git a/Src/math.c b/Src/math.c index 42355f885..266569827 100644 --- a/Src/math.c +++ b/Src/math.c @@ -555,6 +555,9 @@ lexconstant(void) /**/ int outputradix; +/**/ +int outputunderscore; + /**/ static int zzlex(void) @@ -713,7 +716,7 @@ zzlex(void) return EOI; case '[': { - int n; + int n, checkradix = 0; if (idigit(*ptr)) { n = zstrtol(ptr, &ptr, 10); @@ -730,9 +733,19 @@ zzlex(void) n = -1; ptr++; } - if (!idigit(*ptr)) + if (!idigit(*ptr) && *ptr != '_') goto bofs; - outputradix = n * zstrtol(ptr, &ptr, 10); + if (idigit(*ptr)) { + outputradix = n * zstrtol(ptr, &ptr, 10); + checkradix = 1; + } + if (*ptr == '_') { + ptr++; + if (idigit(*ptr)) + outputunderscore = zstrtol(ptr, &ptr, 10); + else + outputunderscore = 3; + } } else { bofs: zerr("bad output format specification"); @@ -740,11 +753,13 @@ zzlex(void) } if(*ptr != ']') goto bofs; - n = (outputradix < 0) ? -outputradix : outputradix; - if (n < 2 || n > 36) { - zerr("invalid base (must be 2 to 36 inclusive): %d", - outputradix); - return EOI; + if (checkradix) { + n = (outputradix < 0) ? -outputradix : outputradix; + if (n < 2 || n > 36) { + zerr("invalid base (must be 2 to 36 inclusive): %d", + outputradix); + return EOI; + } } ptr++; break; @@ -1337,9 +1352,9 @@ matheval(char *s) char *junk; mnumber x; int xmtok = mtok; - /* maintain outputradix across levels of evaluation */ + /* maintain outputradix and outputunderscore across levels of evaluation */ if (!mlevel) - outputradix = 0; + outputradix = outputunderscore = 0; if (!*s) { x.type = MN_INTEGER; diff --git a/Src/params.c b/Src/params.c index ad9e3470b..dc41c6c92 100644 --- a/Src/params.c +++ b/Src/params.c @@ -2416,9 +2416,10 @@ setnumvalue(Value v, mnumber val) if ((val.type & MN_INTEGER) || outputradix) { if (!(val.type & MN_INTEGER)) val.u.l = (zlong) val.u.d; - convbase(p = buf, val.u.l, outputradix); + p = convbase_underscore(buf, val.u.l, outputradix, + outputunderscore); } else - p = convfloat(val.u.d, 0, 0, NULL); + p = convfloat_underscore(val.u.d, outputunderscore); setstrvalue(v, ztrdup(p)); break; case PM_INTEGER: @@ -4555,9 +4556,14 @@ delenv(Param pm) */ } +/* + * Guts of convbase: this version can return the number of digits + * sans any base discriminator. + */ + /**/ -mod_export void -convbase(char *s, zlong v, int base) +void +convbase_ptr(char *s, zlong v, int base, int *ndigits) { int digs = 0; zulong x; @@ -4583,6 +4589,8 @@ convbase(char *s, zlong v, int base) x /= base; if (!digs) digs = 1; + if (ndigits) + *ndigits = digs; s[digs--] = '\0'; x = v; while (digs >= 0) { @@ -4593,6 +4601,64 @@ convbase(char *s, zlong v, int base) } } +/* + * Basic conversion of integer to a string given a base. + * If 0 base is 10. + * If negative no base discriminator is output. + */ + +/**/ +mod_export void +convbase(char *s, zlong v, int base) +{ + convbase_ptr(s, v, base, NULL); +} + +/* + * Add underscores to converted integer for readability with given spacing. + * s is as for convbase: at least BDIGBUFSIZE. + * If underscores were added, returned value with underscores comes from + * heap, else the returned value is s. + */ + +/**/ +char * +convbase_underscore(char *s, zlong v, int base, int underscore) +{ + char *retptr, *sptr, *dptr; + int ndigits, nunderscore, mod, len; + + convbase_ptr(s, v, base, &ndigits); + + if (underscore <= 0) + return s; + + nunderscore = (ndigits - 1) / underscore; + if (!nunderscore) + return s; + len = strlen(s); + retptr = zhalloc(len + nunderscore + 1); + mod = 0; + memcpy(retptr, s, len - ndigits); + sptr = s + len; + dptr = retptr + len + nunderscore; + /* copy the null */ + *dptr-- = *sptr--; + for (;;) { + *dptr = *sptr; + if (!--ndigits) + break; + dptr--; + sptr--; + if (++mod == underscore) { + mod = 0; + *dptr-- = '_'; + } + } + + return retptr; +} + /* * Convert a floating point value for output. * Unlike convbase(), this has its own internal storage and returns @@ -4659,6 +4725,83 @@ convfloat(double dval, int digits, int flags, FILE *fout) return ret; } +/* + * convert float to string with basic options but inserting underscores + * for readability. + */ + +/**/ +char *convfloat_underscore(double dval, int underscore) +{ + int ndigits_int = 0, ndigits_frac = 0, nunderscore, len; + char *s, *retptr, *sptr, *dptr; + + s = convfloat(dval, 0, 0, NULL); + if (underscore <= 0) + return s; + + /* + * Count the number of digits before and after the decimal point, if any. + */ + sptr = s; + if (*sptr == '-') + sptr++; + while (idigit(*sptr)) { + ndigits_int++; + sptr++; + } + if (*sptr == '.') { + sptr++; + while (idigit(*sptr)) { + ndigits_frac++; + sptr++; + } + } + + /* + * Work out how many underscores to insert --- remember we + * put them in integer and fractional parts separately. + */ + nunderscore = (ndigits_int-1) / underscore + (ndigits_frac-1) / underscore; + if (!nunderscore) + return s; + len = strlen(s); + dptr = retptr = zhalloc(len + nunderscore + 1); + + /* + * Insert underscores in integer part. + * Grouping starts from the point in both directions. + */ + sptr = s; + if (*sptr == '-') + *dptr++ = *sptr++; + while (ndigits_int) { + *dptr++ = *sptr++; + if (--ndigits_int && !(ndigits_int % underscore)) + *dptr++ = '_'; + } + if (ndigits_frac) { + /* + * Insert underscores in the fractional part. + */ + int mod = 0; + /* decimal point, we already checked */ + *dptr++ = *sptr++; + while (ndigits_frac) { + *dptr++ = *sptr++; + mod++; + if (--ndigits_frac && mod == underscore) { + *dptr++ = '_'; + mod = 0; + } + } + } + /* Copy exponent and anything else up to null */ + while ((*dptr++ = *sptr++)) + ; + return retptr; +} + /* Start a parameter scope */ /**/ diff --git a/Src/subst.c b/Src/subst.c index 1059508ef..cc5df3fba 100644 --- a/Src/subst.c +++ b/Src/subst.c @@ -3754,19 +3754,19 @@ static char * arithsubst(char *a, char **bptr, char *rest) { char *s = *bptr, *t; - char buf[BDIGBUFSIZE], *b = buf; + char buf[BDIGBUFSIZE], *b; mnumber v; singsub(&a); v = matheval(a); if ((v.type & MN_FLOAT) && !outputradix) - b = convfloat(v.u.d, 0, 0, NULL); + b = convfloat_underscore(v.u.d, outputunderscore); else { if (v.type & MN_FLOAT) v.u.l = (zlong) v.u.d; - convbase(buf, v.u.l, outputradix); + b = convbase_underscore(buf, v.u.l, outputradix, outputunderscore); } - t = *bptr = (char *) hcalloc(strlen(*bptr) + strlen(b) + + t = *bptr = (char *) hcalloc(strlen(*bptr) + strlen(b) + strlen(rest) + 1); t--; while ((*++t = *s++)); diff --git a/Test/C01arith.ztst b/Test/C01arith.ztst index 7b005c2ab..25cd8b83a 100644 --- a/Test/C01arith.ztst +++ b/Test/C01arith.ztst @@ -266,3 +266,19 @@ >48.5 >77.5 >63.5 + + underscore_integer() { + setopt cbases localoptions + print $(( [#_] 1000000 )) + print $(( [#16_] 65536 )) + print $(( [#16_4] 65536 * 32768 )) + } + underscore_integer +0:Grouping output with underscores: integers +>1_000_000 +>0x10_000 +>0x8000_0000 + + print $(( [#_] (5. ** 10) / 16. )) +0:Grouping output with underscores: floating point +>610_351.562_5 -- cgit 1.4.1