allbery@uunet.UU.NET (02/27/90)
Posting-number: Volume 10, Issue 85 Submitted-by: allbery@uunet.UU.NET Archive-name: uformat Report generators for database managers often use numeric formatting which is based on "pictures" like "($,$$$,$$#.&&)" or "<<<<<#". Here's my version of a function to perform such formatting. To test it, compile with -DTEST to produce the executable "format"; run it with two arguments, a format string (quoted to protect it from the shell) and a floating-point number. Called from a program, it's uformat(buffer, formatstring, doublevalue) and stores its result in buffer. ++Brandon ------------------------------------------------------------------------------- #! /bin/sh # This file was wrapped with "dummyshar". "sh" this file to extract. # Contents: format.c echo extracting 'format.c' if test -f 'format.c' -a -z "$1"; then echo Not overwriting 'format.c'; else sed 's/^X//' << \EOF > 'format.c' X/* X * Process a format, as used by various report generators, to format a value. X * Format characters: X * X * * Digit or asterisk prefix X * $ Digit or dollar-sign prefix X * - Digit or minus-sign prefix if negative X * + Digit or sign prefix X * ( Digit or left-parenthesis prefix if negative X * # Digit or blank prefix X * & Digit or zero prefix X * ) Right-parenthesis suffix if negative X * . Decimal point X * , Comma or space prefix X * < Digit or space appended after format (left justification) X * X * This may not be the fastest possible implementation, but it's plenty fast X * enough for my purposes. X * X * This routine uses only fabs(), fmod(), and floor(); it should be compatible X * with any system that has a standard C math library. X */ X X#ifdef TEST X#include <stdio.h> X#endif X Xextern double fabs(); Xextern double fmod(); Xextern double floor(); X Xvoid Xuformat(buf, val, fmt) X char *fmt, *buf; X double val; X{ X double decval; X int didlead, didsign, pad, signum, overflow; X register char *fmtp, *bufp, *decp; X char tbuf[1024]; X X signum = (val < 0.0); X val = fabs(val); X for (decp = fmt; *decp; decp++) X if (*decp == '.') X break; X /* X * Make a first pass to calculate a rounding value. X */ X decval = 0.5; X for (fmtp = decp; *fmtp; fmtp++) X { X switch (*fmtp) X { X case '*': X case '$': X case '-': X case '+': X case '(': X case '#': X case '&': X case '<': X decval /= 10.0; X break; X } X } X val += decval; X fmtp = decp; X decval = val - floor(val); X val = floor(val); X pad = 0; X didlead = 0; X didsign = 0; X bufp = tbuf; X#ifdef TEST X fprintf(stderr, "fmt = %.*s, decp = %s, val = %s%.14g, decval = %.14g\n", X (decp - fmt), fmt, decp, (signum? "-": ""), val, decval); X#endif X while (fmtp != fmt) X { X switch (*--fmtp) X { X case '#': X case '<': X if (val < 1.0) X { X if (*fmtp == '<') X pad++; X else X *bufp++ = ' '; X break; X } X /*FALLTHROUGH*/ X case '&': X *bufp++ = (int) fmod(val, 10.0) + '0'; X val /= 10.0; X break; X case '*': X if (val >= 1.0) X { X *bufp++ = (int) fmod(val, 10.0) + '0'; X val /= 10.0; X break; X } X *bufp++ = (didlead? ' ': '*'); X didlead = 1; X break; X case '$': X if (val >= 1.0) X { X *bufp++ = (int) fmod(val, 10.0) + '0'; X val /= 10.0; X break; X } X *bufp++ = (didlead? ' ': '$'); X didlead = 1; X break; X case '-': X if (val >= 1.0) X { X *bufp++ = (int) fmod(val, 10.0) + '0'; X val /= 10.0; X break; X } X *bufp++ = (didsign? ' ': (signum? '-': ' ')); X didsign = 1; X break; X case '+': X if (val >= 1.0) X { X *bufp++ = (int) fmod(val, 10.0) + '0'; X val /= 10.0; X break; X } X *bufp++ = (didsign? ' ': (signum? '-': '+')); X didsign = 1; X break; X case '(': X if (val >= 1.0) X { X *bufp++ = (int) fmod(val, 10.0) + '0'; X val /= 10.0; X break; X } X *bufp++ = (didsign? ' ': (signum? '(': ' ')); X didsign = 1; X break; X case ')': X *bufp++ = (signum? ')': ' '); X break; X case ',': X *bufp++ = (val < 1.0? ' ': ','); X break; X default: X *bufp++ = *fmtp; X } X } X overflow = (val >= 1.0); X while (bufp-- != tbuf) X *buf++ = (overflow? '*': *bufp); X /* X * Decimals turn out to be easy, since we can parse forward and all the X * potential digit chars can be treated as "&". Also, extracting digits X * is done via (decval *= 10.0; floor(decval)) instead of slow fmod(). X */ X while (*decp) X { X if (overflow) X *buf++ = '*'; X else X { X switch (*decp) X { X case '*': X case '$': X case '-': X case '+': X case '(': X case '#': X case '&': X case '<': X decval *= 10.0; X *buf++ = (int) floor(decval) + '0'; X decval -= floor(decval); X break; X case ')': X *buf++ = (signum? ')': ' '); X break; X default: X *buf++ = *decp; X break; X } X } X decp++; X } X while (pad--) X *buf++ = (overflow? '*': ' '); X *buf = '\0'; X} X X#ifdef TEST X Xextern double atof(); X Xmain(argc, argv) X char **argv; X{ X char buf[1024]; X X if (argc != 3) X { X fprintf(stderr, "usage: %s format-string value\n", argv[0]); X exit(1); X } X uformat(buf, atof(argv[2]), argv[1]); X puts(buf); X exit(0); X} X X#endif EOF chars=`wc -c < 'format.c'` if test $chars != 4232; then echo 'format.c' is $chars characters, should be 4232 characters!; fi fi exit 0