/* Calisto (c) 1998-2000 Peter Howkins, Matthew Howkins, Simon Howkins $Id: msnprintf.c,v 1.1 2000/03/02 20:58:41 peter Exp $ $Log: msnprintf.c,v $ Revision 1.1 2000/03/02 20:58:41 peter Initial revision */ static char rcsid[] = "$Id: msnprintf.c,v 1.1 2000/03/02 20:58:41 peter Exp $"; #include <stdarg.h> #include <stdlib.h> /* for atoi() */ #include <ctype.h> /* * For the FLOATING POINT FORMAT : * the challenge was finding a way to * manipulate the Real numbers without having * to resort to mathematical function (it * would require to link with -lm) and not * going down to the bit pattern (not portable) * * so a number, a real is: real = integral + fraction integral = ... + a(2)*10^2 + a(1)*10^1 + a(0)*10^0 fraction = b(1)*10^-1 + b(2)*10^-2 + ... where: 0 <= a(i) => 9 0 <= b(i) => 9 from then it was simple math */ /* * size of the buffer for the integral part * and the fraction part */ #define MAX_INT 99 + 1 /* 1 for the null */ #define MAX_FRACT 29 + 1 /* * numtoa() uses PRIVATE buffers to store the results, * So this function is not reentrant */ #define itoa(n) (numtoa(n, 10, 0, (char **) 0)) #define otoa(n) (numtoa(n, 8, 0, (char **) 0)) #define htoa(n) (numtoa(n, 16, 0, (char **) 0)) #define dtoa(n, p, f) numtoa(n, 10, p, f) #define SWAP_INT(a,b) {int t; t = (a); (a) = (b); (b) = t;} /* this struct holds everything we need */ struct DATA { int length; char *holder; int counter; const char *pf; /* FLAGS */ int width, precision; int justify; char pad; int square, space, star_w, star_p, a_long ; }; #define PUBLIC /* signature of the functions */ /* the floating point stuff */ static double pow_10(int); static int log_10(double); static double integral(double, double *); static char * numtoa(double, int, int, char **); /* for the format */ static void conv_flag(const char *, struct DATA *); static void floating(struct DATA *, double); static void exponent(struct DATA *, double); static void decimal(struct DATA *, double); static void octal(struct DATA *, double); static void hexa(struct DATA *, double); static void strings(struct DATA *, char *); /* those are defines specific to snprintf to hopefully * make the code clearer :-) */ #define RIGHT 1 #define LEFT 0 #define NOT_FOUND -1 #define FOUND 1 #define MAX_FIELD 15 /* the conversion flags */ #define isflag(c) ((c) == '#' || (c) == ' ' || \ (c) == '*' || (c) == '+' || \ (c) == '-' || (c) == '.' || \ isdigit(c)) /* round off to the precision */ #define ROUND(d, p) \ (d < 0.) ? \ d - pow_10(-(p)->precision) * 0.5 : \ d + pow_10(-(p)->precision) * 0.5 /* set default precision */ #define DEF_PREC(p) \ if ((p)->precision == NOT_FOUND) \ (p)->precision = 6 /* put a char */ #define PUT_CHAR(c, p) \ if ((p)->counter < (p)->length) { \ *(p)->holder++ = (c); \ (p)->counter++; \ } #define PUT_PLUS(d, p) \ if ((d) > 0. && (p)->justify == RIGHT) \ PUT_CHAR('+', p) #define PUT_SPACE(d, p) \ if ((p)->space == FOUND && (d) > 0.) \ PUT_CHAR(' ', p) /* pad right */ #define PAD_RIGHT(p) \ if ((p)->width > 0 && (p)->justify != LEFT) \ for (; (p)->width > 0; (p)->width--) \ PUT_CHAR((p)->pad, p) /* pad left */ #define PAD_LEFT(p) \ if ((p)->width > 0 && (p)->justify == LEFT) \ for (; (p)->width > 0; (p)->width--) \ PUT_CHAR((p)->pad, p) /* if width and prec. in the args */ #define STAR_ARGS(p) \ if ((p)->star_w == FOUND) \ (p)->width = va_arg(args, int); \ if ((p)->star_p == FOUND) \ (p)->precision = va_arg(args, int) #include "msnprintf.h" /* * Find the nth power of 10 */ static double pow_10(int n) { int i; double P; if (n < 0) for (i = 1, P = 1.0, n = -n; i <= n; i++) P *= 0.1; else for (i = 1, P = 1.0; i <= n ; i++) P *= 10.0; return P; } /* * Find the integral part of the log in base 10 * Note: this not a real log10() I just need and approximation(integerpart) of x in: 10^x ~= r * log_10(200) = 2; * log_10(250) = 2; */ static int log_10(double r) { int i = 0; double result = 1.0; if (r < 0.0) r = -r; if (r < 1.0) { while (result >= r) {result *= 0.1; i++;} return (-i); } else { while (result <= r) {result *= 10.0; i++;} return (i - 1); } } /* * This function return the fraction part of a double * and set in ip the integral part. * In many ways it resemble the modf() found on most Un*x */ static double integral(double real, double * ip) { int j; double i, s, p; double real_integral = 0.0; /* take care of the obvious */ /* equal to zero ? */ if (real == 0.0) { *ip = 0.0; return 0.0; } /* negative number ? */ if (real < 0.0) real = -real; /* a fraction ? */ if (real < 1.0) { *ip = 0.0; return real; } /* the real work :-) */ for (j = log_10(real); j >= 0; j--) { p = pow_10(j); s = (real - real_integral) / p; i = 0.0; while (i + 1.0 <= s) i++; real_integral += i * p; } *ip = real_integral; return (real - real_integral); } #define PRECISION 1.e-6 /* * return an ascii representation of the integral part of the number * and set fract to be an ascii representation of the fraction part * the container for the fraction and the integral part or staticly * declare with fix size */ static char * numtoa(double number, int base, int precision, char **fract) { register int i, j; double ip, fp; /* integer and fraction part */ double fraction; int digits = MAX_INT - 1; static char integral_part[MAX_INT]; static char fraction_part[MAX_FRACT]; double sign; int ch; /* taking care of the obvious case: 0.0 */ if (number == 0.0) { integral_part[0] = '0'; integral_part[1] = '\0'; fraction_part[0] = '0'; fraction_part[1] = '\0'; return integral_part; } /* for negative numbers */ if ((sign = number) < 0.0) { number = -number; digits--; /* sign consume one digit */ } fraction = integral(number, &ip); number = ip; /* do the integral part */ if (ip == 0.0) { integral_part[0] = '0'; i = 1; } else { for ( i = 0; i < digits && number != 0.0; ++i) { number /= base; fp = integral(number, &ip); ch = (int)((fp + PRECISION)*base); /* force to round */ integral_part[i] = (ch <= 9) ? (ch + '0') : (ch + 'a' - 10); if (! isxdigit(integral_part[i])) /* bail out overflow !! */ break; number = ip; } } /* Oh No !! out of bound, ho well fill it up ! */ if (number != 0.0) for (i = 0; i < digits; ++i) integral_part[i] = '9'; /* put the sign ? */ if (sign < 0.0) integral_part[i++] = '-'; integral_part[i] = '\0'; /* reverse every thing */ for ( i--, j = 0; j < i; j++, i--) SWAP_INT(integral_part[i], integral_part[j]); /* the fractional part */ for (i=0, fp=fraction; precision > 0 && i < MAX_FRACT ; i++, precision--) { fraction_part[i] = (int) ((fp + PRECISION) * 10.0 + '0'); if (!isdigit(fraction_part[i])) /* underflow ? */ break; fp = (fp * 10.0) - (double) (long) ((fp + PRECISION) * 10.0); } fraction_part[i] = '\0'; if (fract != (char **) 0) *fract = fraction_part; return integral_part; } /* for %d and friends, it puts in holder * the representation with the right padding */ static void decimal(struct DATA *p, double d) { const char *tmp = itoa(d); p->width -= strlen(tmp); PAD_RIGHT(p); PUT_PLUS(d, p); PUT_SPACE(d, p); while (*tmp) { /* the integral */ PUT_CHAR(*tmp, p); tmp++; } PAD_LEFT(p); } /* for %o octal representation */ static void octal(struct DATA *p, double d) { const char *tmp = otoa(d); p->width -= strlen(tmp); PAD_RIGHT(p); if (p->square == FOUND) /* had prefix '0' for octal */ PUT_CHAR('0', p); while (*tmp) { /* octal */ PUT_CHAR(*tmp, p); tmp++; } PAD_LEFT(p); } /* for %x %X hexadecimal representation */ static void hexa(struct DATA *p, double d) { const char *tmp = htoa(d); p->width -= strlen(tmp); PAD_RIGHT(p); if (p->square == FOUND) { /* prefix '0x' for hexa */ PUT_CHAR('0', p); PUT_CHAR(*p->pf, p); } while (*tmp) { /* hexa */ PUT_CHAR((*p->pf == 'X' ? toupper(*tmp) : *tmp), p); tmp++; } PAD_LEFT(p); } /* %s strings */ static void strings(struct DATA *p, char *tmp) { int i; i = strlen(tmp); if (p->precision != NOT_FOUND) /* the smallest number */ i = (i < p->precision ? i : p->precision); p->width -= i; PAD_RIGHT(p); while (i-- > 0) { /* put the sting */ PUT_CHAR(*tmp, p); tmp++; } PAD_LEFT(p); } /* %f or %g floating point representation */ static void floating(struct DATA *p, double d) { char *tmp, *tmp2; int i; DEF_PREC(p); d = ROUND(d, p); tmp = dtoa(d, p->precision, &tmp2); /* calculate the padding. 1 for the dot */ p->width = p->width - ((d > 0.0 && p->justify == RIGHT) ? 1 : 0) - ((p->space == FOUND) ? 1 : 0) - strlen(tmp) - p->precision - 1; PAD_RIGHT(p); PUT_PLUS(d, p); PUT_SPACE(d, p); while (*tmp) { /* the integral */ PUT_CHAR(*tmp, p); tmp++; } if (p->precision != 0 || p->square == FOUND) PUT_CHAR('.', p); /* put the '.' */ if (*p->pf == 'g' || *p->pf == 'G') /* smash the trailing zeros */ for (i = strlen(tmp2) - 1; i >= 0 && tmp2[i] == '0'; i--) tmp2[i] = '\0'; for (; *tmp2; tmp2++) PUT_CHAR(*tmp2, p); /* the fraction */ PAD_LEFT(p); } /* %e %E %g exponent representation */ static void exponent(struct DATA *p, double d) { char *tmp, *tmp2; int j, i; DEF_PREC(p); j = log_10(d); d = d / pow_10(j); /* get the Mantissa */ d = ROUND(d, p); tmp = dtoa(d, p->precision, &tmp2); /* 1 for unit, 1 for the '.', 1 for 'e|E', * 1 for '+|-', 2 for 'exp' */ /* calculate how much padding need */ p->width = p->width - ((d > 0.0 && p->justify == RIGHT) ? 1 : 0) - ((p->space == FOUND) ? 1 : 0) - p->precision - 6; PAD_RIGHT(p); PUT_PLUS(d, p); PUT_SPACE(d, p); while (*tmp) {/* the integral */ PUT_CHAR(*tmp, p); tmp++; } if (p->precision != 0 || p->square == FOUND) PUT_CHAR('.', p); /* the '.' */ if (*p->pf == 'g' || *p->pf == 'G') /* smash the trailing zeros */ for (i = strlen(tmp2) - 1; i >= 0 && tmp2[i] == '0'; i--) tmp2[i] = '\0'; for (; *tmp2; tmp2++) PUT_CHAR(*tmp2, p); /* the fraction */ if (*p->pf == 'g' || *p->pf == 'e') { /* the exponent put the 'e|E' */ PUT_CHAR('e', p); } else PUT_CHAR('E', p); if (j > 0) { /* the sign of the exp */ PUT_CHAR('+', p); } else { PUT_CHAR('-', p); j = -j; } tmp = itoa((double)j); if (j < 9) { /* need to pad the exponent with 0 '000' */ PUT_CHAR('0', p); /*PUT_CHAR('0', p);*/ } /*else if (j < 99) PUT_CHAR('0', p);*/ while (*tmp) { /* the exponent */ PUT_CHAR(*tmp, p); tmp++; } PAD_LEFT(p); } /* initialize the conversion specifiers */ static void conv_flag(const char *s, struct DATA *p) { char number[MAX_FIELD/2]; int i; p->precision = p->width = NOT_FOUND; p->star_w = p->star_p = NOT_FOUND; p->square = p->space = NOT_FOUND; p->a_long = p->justify = NOT_FOUND; p->pad = ' '; for ( ;s && *s ;s++) { switch (*s) { case ' ': p->space = FOUND; break; case '#': p->square = FOUND; break; case '*': if (p->width == NOT_FOUND) p->width = p->star_w = FOUND; else p->precision = p->star_p = FOUND; break; case '+': p->justify = RIGHT; break; case '-': p->justify = LEFT; break; case '.': if (p->width == NOT_FOUND) p->width = 0; break; case '0': p->pad = '0'; break; case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': /* gob all the digits */ for (i = 0; isdigit(*s); i++, s++) if (i < MAX_FIELD/2 - 1) number[i] = *s; number[i] = '\0'; if (p->width == NOT_FOUND) p->width = atoi(number); else p->precision = atoi(number); s--; /* went to far go back */ break; } } } int mvsnprintf(char *string, size_t length, const char *format, va_list args) { struct DATA data; char conv_field[MAX_FIELD]; double d; /* temporary holder */ int state; int i; data.length = length - 1; /* leave room for '\0' */ data.holder = string; data.pf = format; data.counter = 0; /* sanity check, the string must be > 1 */ if (length < 1) return -1; for (; *data.pf && (data.counter < data.length); data.pf++) { if ( *data.pf == '%' ) { /* we got a magic % cookie */ conv_flag((char *)0, &data); /* initialise format flags */ for (state = 1; *data.pf && state;) { switch (*(++data.pf)) { case '\0': /* a NULL here ? ? bail out */ *data.holder = '\0'; return data.counter; break; case 'f': /* float, double */ STAR_ARGS(&data); d = va_arg(args, double); floating(&data, d); state = 0; break; case 'g': case 'G': STAR_ARGS(&data); DEF_PREC(&data); d = va_arg(args, double); i = log_10(d); /* * for '%g|%G' ANSI: use f if exponent * is in the range or [-4,p] exclusively * else use %e|%E */ if (i > -4 && i < data.precision) floating(&data, d); else exponent(&data, d); state = 0; break; case 'e': case 'E': /* Exponent double */ STAR_ARGS(&data); d = va_arg(args, double); exponent(&data, d); state = 0; break; case 'u': /* unsigned decimal */ STAR_ARGS(&data); if (data.a_long == FOUND) d = (double) va_arg(args, unsigned long); else d = (double) va_arg(args, unsigned); decimal(&data, d); state = 0; break; case 'i': case 'd': /* decimal */ STAR_ARGS(&data); if (data.a_long == FOUND) d = va_arg(args, long); else d = va_arg(args, int); decimal(&data, d); state = 0; break; case 'o': /* octal */ STAR_ARGS(&data); if (data.a_long == FOUND) d = va_arg(args, long); else d = va_arg(args, int); octal(&data, d); state = 0; break; case 'x': case 'X': /* hexadecimal */ STAR_ARGS(&data); if (data.a_long == FOUND) d = va_arg(args, long); else d = va_arg(args, int); hexa(&data, d); state = 0; break; case 'c': /* character */ d = va_arg(args, int); PUT_CHAR(d, &data); state = 0; break; case 's': /* string */ STAR_ARGS(&data); strings(&data, va_arg(args, char *)); state = 0; break; case 'n': *(va_arg(args, int *)) = data.counter; /* what's the count ? */ state = 0; break; case 'l': data.a_long = FOUND; break; case 'h': break; case '%': /* nothing just % */ PUT_CHAR('%', &data); state = 0; break; case '#': case ' ': case '+': case '*': case '-': case '.': case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': /* initialize width and precision */ for (i = 0; isflag(*data.pf); i++, data.pf++) if (i < MAX_FIELD - 1) conv_field[i] = *data.pf; conv_field[i] = '\0'; conv_flag(conv_field, &data); data.pf--; /* went to far go back */ break; default: /* is this an error ? maybe bail out */ state = 0; break; } /* end switch */ } /* end of for state */ } else { /* not % */ PUT_CHAR(*data.pf, &data); /* add the char the string */ } } *data.holder = '\0'; /* the end ye ! */ return data.counter; } int msnprintf(char *string, size_t length, const char *format, ...) { int rval; va_list args; va_start(args, format); rval = mvsnprintf(string, length, format, args); va_end(args); return rval; }