v10i085: numeric formatting function

allbery at uunet.UU.NET allbery at uunet.UU.NET
Tue Feb 27 14:06:44 AEST 1990


Posting-number: Volume 10, Issue 85
Submitted-by: allbery at 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



More information about the Comp.sources.misc mailing list