[comp.unix.questions] How to separate numbers in three-digit groups in C

rouben@math16.math.umbc.edu (Rouben Rostamian) (06/24/91)

I need help with printing numbers (floating or integer) in C.  I would like
to display the numbers in three-digit comma-separated format.  For instance,
the integer 12345678 should be printed as 12,345,678.  The floating point
number 1234.56789 may be printed as 1,234.5678 or as 1,234.567,8.
I wish the printf function had an option for such formatting, but
as far as I know, it doesn't.  

I guess the ideal solution would be to extend the format options
for the printf command.  Perhaps a Printf function may be defined which
accepts the standard formatting strings of the printf function, but which
also accepts additional conversion characters to print comma-separated
digits.  I do not wish to re-invent the wheel if someone has already done
this.  So please let me know if there is something similar available somewhere.

--
Rouben Rostamian                          Telephone: (301) 455-2458
Department of Mathematics and Statistics  e-mail:
University of Maryland Baltimore County   bitnet: rostamian@umbc.bitnet
Baltimore, MD 21228,  U.S.A.              internet: rouben@math9.math.umbc.edu

angst@fig.ucsb.edu (Hopelessly in love w/Donna Reed) (06/24/91)

[This should have been posted to comp.lang.c; I have directed followups there.]

In article <1991Jun23.174550.14820@umbc3.umbc.edu> rouben@math16.math.umbc.edu (Rouben Rostamian) writes:
>
>I need help with printing numbers (floating or integer) in C.  I would like
>to display the numbers in three-digit comma-separated format.  For instance,
>the integer 12345678 should be printed as 12,345,678.  The floating point
>number 1234.56789 may be printed as 1,234.5678 or as 1,234.567,8.
>I wish the printf function had an option for such formatting, but
>as far as I know, it doesn't.  

This function will work.  It assumes the number is contained in a string
(which you can do by using sprintf()) --

    void commas (char *s)
    {
	char *p = index (s, '.');
	int count;

	if (p)
	    *p = '\0';			/* ignore fractional part for now */

	count = strlen (s);		

	while (count-- > 0) {
	    putchar (*s++);
	    if (count > 0 && count % 3 == 0)
		 putchar (',');
	}
	if (p)
	    printf (".%s", p+1);		/* print out fractional part */
    }

Here's the output of the short program I wrote to test this:

Script started on Sun Jun 23 16:37:37 1991
manray% a.out
enter string: 123
123
enter string: 1234
1,234
enter string: 123456
123,456
enter string: 12345678901
12,345,678,901
enter string: 34.2732
34.2732
enter string: 35276.28321
35,276.28321
enter string: ^C
manray% exit
script done on Sun Jun 23 16:38:03 1991

"Let the fools have their tartar sauce."	|          Dave Stein
  		        - Mr. Burns		|       angst@cs.ucsb.edu
						|    angst%cs@ucsbuxa.bitnet

art@pilikia.pegasus.com (Arthur Neilson) (06/25/91)

In article <1991Jun23.174550.14820@umbc3.umbc.edu> rouben@math16.math.umbc.edu (Rouben Rostamian) writes:
>
>I need help with printing numbers (floating or integer) in C.  I would like
>to display the numbers in three-digit comma-separated format.  For instance,
>the integer 12345678 should be printed as 12,345,678.  The floating point
>number 1234.56789 may be printed as 1,234.5678 or as 1,234.567,8.

I use the function below quite a bit, it formats doubles in a char buffer
according to a picture clause type specification.  I didn't write it, I
got it off the net a long time ago and forget where it came from.

-----8<----- cut here -----8<----- cut here -----8<----- cut here -----8<-----
/*
 *	f o r m a t
 *
 *	Format double value as char string in buffer based
 *	on the picture format passed in the format string.
 *
 *	Recognised format characters:
 *
 *	*	Digit or asterisk prefix
 *	$	Digit or dollar-sign prefix
 *	-	Digit or minus-sign prefix if negative
 *	+	Digit or sign prefix
 *	(	Digit or left-parenthesis prefix if negative
 *	#	Digit or blank prefix
 *	&	Digit or zero prefix
 *	)	Right-parenthesis suffix if negative
 *	.	Decimal point
 *	,	Comma or space prefix
 *	<	Digit or space appended after format (left justification)
 *
 *	This function uses only fabs(), fmod(), and floor(),
 *	it should be compatible with any system that has a
 *	standard C math library.
 */

#include <math.h>

char   *
format(buf, fmt, val)
char   *buf;
char   *fmt;
double  val;
{
	double  decval;
	int     didlead, didsign, pad, signum, overflow;
	register char *fmtp, *bufp, *decp;
	char    tbuf[1024];
	char    *retp = buf;

	signum = (val < 0.0);
	val = fabs(val);
	for (decp = fmt; *decp; decp++)
		if (*decp == '.')
			break;
	/*
	 * Make a first pass to calculate a rounding value.
	 */
	decval = 0.5;
	for (fmtp = decp; *fmtp; fmtp++) {
		switch (*fmtp) {
		case '*':
		case '$':
		case '-':
		case '+':
		case '(':
		case '#':
		case '&':
		case '<':
			decval /= 10.0;
			break;
		}
	}

	val += decval;
	fmtp = decp;
	decval = val - floor(val);
	val = floor(val);
	pad = 0;
	didlead = 0;
	didsign = 0;
	bufp = tbuf;

	while (fmtp != fmt) {
		switch (*--fmtp) {
		case '#':
		case '<':
			if (val < 1.0) {
				if (*fmtp == '<')
					pad++;
				else
					*bufp++ = ' ';
				break;
			}
			/* FALLTHROUGH */
		case '&':
			*bufp++ = (int) fmod(val, 10.0) + '0';
			val /= 10.0;
			break;
		case '*':
			if (val >= 1.0) {
				*bufp++ = (int) fmod(val, 10.0) + '0';
				val /= 10.0;
				break;
			}
			*bufp++ = (didlead ? ' ' : '*');
			didlead = 1;
			break;
		case '$':
			if (val >= 1.0) {
				*bufp++ = (int) fmod(val, 10.0) + '0';
				val /= 10.0;
				break;
			}
			*bufp++ = (didlead ? ' ' : '$');
			didlead = 1;
			break;
		case '-':
			if (val >= 1.0) {
				*bufp++ = (int) fmod(val, 10.0) + '0';
				val /= 10.0;
				break;
			}
			*bufp++ = (didsign ? ' ' : (signum ? '-' : ' '));
			didsign = 1;
			break;
		case '+':
			if (val >= 1.0) {
				*bufp++ = (int) fmod(val, 10.0) + '0';
				val /= 10.0;
				break;
			}
			*bufp++ = (didsign ? ' ' : (signum ? '-' : '+'));
			didsign = 1;
			break;
		case '(':
			if (val >= 1.0) {
				*bufp++ = (int) fmod(val, 10.0) + '0';
				val /= 10.0;
				break;
			}
			*bufp++ = (didsign ? ' ' : (signum ? '(' : ' '));
			didsign = 1;
			break;
		case ')':
			*bufp++ = (signum ? ')' : ' ');
			break;
		case ',':
			*bufp++ = (val < 1.0 ? ' ' : ',');
			break;
		default:
			*bufp++ = *fmtp;
		}
	}

	overflow = (val >= 1.0);
	while (bufp-- != tbuf)
		*buf++ = (overflow ? '*' : *bufp);
	/*
	 * Decimals turn out to be easy, since we can parse forward and all
	 * the potential digit chars can be treated as "&".  Also, extracting
	 * digits is done via (decval *= 10.0; floor(decval)) instead of slow
	 * fmod().
	 */
	while (*decp) {
		if (overflow)
			*buf++ = '*';
		else {
			switch (*decp) {
			case '*':
			case '$':
			case '-':
			case '+':
			case '(':
			case '#':
			case '&':
			case '<':
				decval *= 10.0;
				*buf++ = (int) floor(decval) + '0';
				decval -= floor(decval);
				break;
			case ')':
				*buf++ = (signum ? ')' : ' ');
				break;
			default:
				*buf++ = *decp;
				break;
			}
		}
		decp++;
	}

	while (pad--)
		*buf++ = (overflow ? '*' : ' ');
	*buf = '\0';

	return (retp);
}
-----8<----- cut here -----8<----- cut here -----8<----- cut here -----8<-----
-- 
Arthur W. Neilson III		| INET: art@pilikia.pegasus.com
Bank of Hawaii Tech Support	| UUCP: uunet!ucsd!nosc!pilikia!art