/***************************************************************************** * * Mark Nagel * 20 July 1989 * * $Revision: 6.21 $ * * bool * format(fmt, num, buf, buflen) * char *fmt; * double num; * char buf[]; * int buflen; * * The format function will produce a string representation of a number * given a _format_ (described below) and a double value. The result is * written into the passed buffer -- if the resulting string is too * long to fit into the passed buffer, the function returns false. * Otherwise the function returns true. * * The fmt parameter contains the format to use to convert the number. * * # Digit placeholder. If the number has fewer digits on either * side of the decimal point than there are '#' characters in * the format, the extra '#' characters are ignored. The number * is rounded to the number of digit placeholders as there are * to the right of the decimal point. If there are more digits * in the number than there are digit placeholders on the left * side of the decimal point, then those digits are displayed. * * 0 Digit placeholder. Same as for '#' except that the number * is padded with zeroes on either side of the decimal point. * The number of zeroes used in padding is determined by the * number of digit placeholders after the '0' for digits on * the left side of the decimal point and by the number of * digit placeholders before the '0' for digits on the right * side of the decimal point. * * . Decimal point. Determines how many digits are placed on * the right and left sides of the decimal point in the number. * Note that numbers smaller than 1 will begin with a decimal * point if the left side of the decimal point contains only * a '#' digit placeholder. Use a '0' placeholder to get a * leading zero in decimal formats. * * % Percentage. For each '%' character in the format, the actual * number gets multiplied by 100 (only for purposes of formatting * -- the original number is left unmodified) and the '%' character * is placed in the same position as it is in the format. * * , Thousands separator. The presence of a ',' in the format * (multiple commas are treated as one) will cause the number * to be formatted with a ',' separating each set of three digits * in the integer part of the number with numbering beginning * from the right end of the integer. * * \ Quote. This character causes the next character to be * inserted into the formatted string directly with no * special interpretation. * * E- E+ e- e+ * Scientific format. Causes the number to formatted in scientific * notation. The case of the 'E' or 'e' given is preserved. If * the format uses a '+', then the sign is always given for the * exponent value. If the format uses a '-', then the sign is * only given when the exponent value is negative. Note that if * there is no digit placeholder following the '+' or '-', then * that part of the formatted number is left out. In general, * there should be one or more digit placeholders after the '+' * or '-'. * * ; Format selector. Use this character to separate the format * into two distinct formats. The format to the left of the * ';' character will be used if the number given is zero or * positive. The format to the right of the ';' character is * used if the number given is negative. * * Any * Self insert. Any other character will be inserted directly * into the formatted number with no change made to the actual * number. * *****************************************************************************/ /*****************************************************************************/ #include #include #include #include "sc.h" #define bool int #define true 1 #define false 0 #define EOS '\0' #define MAXBUF 256 extern char *strcpy(), *strcat(); static char *fmt_int(), *fmt_frac(), *fmt_exp(); static void reverse(); /*****************************************************************************/ bool format(fmt, val, buf, buflen) char *fmt; double val; char *buf; int buflen; { register char *cp; char *tmp, *tp; bool comma = false, negative = false; char *integer = NULL, *decimal = NULL; char *exponent = NULL; int exp_val, width; char prtfmt[32]; static char *mantissa = NULL; static char *tmpfmt1 = NULL, *tmpfmt2 = NULL, *exptmp = NULL; static unsigned mantlen = 0, fmtlen = 0; char *fraction = NULL; int zero_pad = 0; if (fmt == NULL) return(true); if (strlen(fmt) + 1 > fmtlen) { fmtlen = strlen(fmt) + 40; tmpfmt1 = scxrealloc(tmpfmt1, fmtlen); tmpfmt2 = scxrealloc(tmpfmt2, fmtlen); exptmp = scxrealloc(exptmp, fmtlen); } fmt = strcpy(tmpfmt1, fmt); if (buflen + 1 > mantlen) { mantlen = buflen + 40; mantissa = scxrealloc(mantissa, mantlen); } /* * select positive or negative format if necessary */ for (cp = fmt; *cp != ';' && *cp != EOS; cp++) { if (*cp == '\\') cp++; } if (*cp == ';') { if (val < 0.0) { val = -val; /* format should provide sign if desired */ fmt = cp + 1; } else { *cp = EOS; } } /* * extract other information from format and produce a * format string stored in tmpfmt2 also scxmalloc()'d above */ tmp = tmpfmt2; for (cp = fmt, tp = tmp; *cp != EOS; cp++) { switch (*cp) { case '\\': *tp++ = *cp++; *tp++ = *cp; break; case ',': comma = true; break; case '.': if (decimal == NULL) decimal = tp; *tp++ = *cp; break; case '%': val *= 100.0; *tp++ = *cp; break; default: *tp++ = *cp; break; } } *tp = EOS; fmt = tmpfmt2; if (val < 0.0) { negative = true; val = -val; } /* * extract the exponent from the format if present */ for (cp = fmt; *cp != EOS; cp++) { if (*cp == '\\') { cp++; } else if (*cp == 'e' || *cp == 'E') { if (cp[1] == '+' || cp[1] == '-') { exponent = strcpy(exptmp, cp); *cp = EOS; exp_val = 0; if (val!=0.0) { while (val < 1.0) { val *= 10.0; exp_val--; } while (val >= 10.0) { val /= 10.0; exp_val++; } } break; } } } /* * determine maximum decimal places and use sprintf * to build initial character form of formatted value. */ width = 0; if (decimal) { *decimal++ = EOS; for (cp = decimal; *cp != EOS; cp++) { switch (*cp) { case '\\': cp++; break; case '#': width++; zero_pad++; break; case '0': width++; break; } } } (void) sprintf(prtfmt, "%%.%dlf", width); (void) sprintf(mantissa, prtfmt, val); for (cp = integer = mantissa; *cp != '.' && *cp != EOS; cp++) { if (*integer == '0') integer++; } if (*cp == '.') { fraction = cp + 1; *cp = EOS; cp = fraction + strlen(fraction) - 1; for (; zero_pad > 0; zero_pad--, cp--) { if (*cp == '0') *cp = EOS; else break; } } /* * format the puppy */ { static char *citmp = NULL, *cftmp = NULL; static unsigned cilen = 0, cflen = 0; char *ci, *cf, *ce; int len_ci, len_cf, len_ce; bool ret = false; ci = fmt_int(integer, fmt, comma, negative); len_ci = strlen(ci); if (len_ci >= cilen) { cilen = len_ci + 40; citmp = scxrealloc(citmp, cilen); } ci = strcpy(citmp, ci); cf = (fraction) ? fmt_frac(fraction, decimal) : ""; len_cf = strlen(cf); if (len_cf >= cflen) { cflen = len_cf + 40; cftmp = scxrealloc(cftmp, cilen); } cf = strcpy(cftmp, cf); ce = (exponent) ? fmt_exp(exp_val, exponent) : ""; len_ce = strlen(ce); /* * Skip copy assuming sprintf doesn't call our format functions * ce = strcpy(scxmalloc((unsigned)((len_ce = strlen(ce)) + 1)), ce); */ if (len_ci + len_cf + len_ce < buflen) { (void) sprintf(buf, "%s%s%s", ci, cf, ce); ret = true; } return (ret); } } /*****************************************************************************/ static char * fmt_int(val, fmt, comma, negative) char *val; /* integer part of the value to be formatted */ char *fmt; /* integer part of the format */ bool comma; /* true if we should comma-ify the value */ bool negative; /* true if the value is actually negative */ { int digit, f, v; int thousands = 0; char *cp; static char buf[MAXBUF]; char *bufptr = buf; /* * locate the leftmost digit placeholder */ for (cp = fmt; *cp != EOS; cp++) { if (*cp == '\\') cp++; else if (*cp == '#' || *cp == '0') break; } digit = (*cp == EOS) ? -1 : cp - fmt; /* * format the value */ f = strlen(fmt) - 1; v = (digit >= 0) ? strlen(val) - 1 : -1; while (f >= 0 || v >= 0) { if (f > 0 && fmt[f-1] == '\\') { *bufptr++ = fmt[f--]; } else if (f >= 0 && (fmt[f] == '#' || fmt[f] == '0')) { if (v >= 0 || fmt[f] == '0') { *bufptr++ = v < 0 ? '0' : val[v]; if (comma && (thousands = (thousands + 1) % 3) == 0 && v > 0) { *bufptr++ = ','; } v--; } } else if (f >= 0) { *bufptr++ = fmt[f]; } if (v >= 0 && f == digit) { continue; } f--; } if (negative && digit >= 0) *bufptr++ = '-'; *bufptr = EOS; reverse(buf); return (buf); } /*****************************************************************************/ static char * fmt_frac(val, fmt) char *val; /* fractional part of the value to be formatted */ char *fmt; /* fractional portion of format */ { static char buf[MAXBUF]; register char *bufptr = buf; register char *fmtptr = fmt, *valptr = val; *bufptr++ = '.'; while (*fmtptr != EOS) { if (*fmtptr == '\\') { *bufptr++ = *++fmtptr; } else if (*fmtptr == '#' || *fmtptr == '0') { if (*valptr != EOS || *fmtptr == '0') { *bufptr++ = (*valptr != EOS) ? *valptr++ : *fmtptr; } } else { *bufptr++ = *fmtptr; } fmtptr++; } *bufptr = EOS; return (buf); } /*****************************************************************************/ static char * fmt_exp(val, fmt) int val; /* value of the exponent */ char *fmt; /* exponent part of the format */ { static char buf[MAXBUF]; register char *bufptr = buf; char valbuf[64]; bool negative = false; *bufptr++ = *fmt++; if (*fmt == '+') *bufptr++ = (val < 0) ? '-' : '+'; else if (val < 0) *bufptr++ = '-'; fmt++; *bufptr = EOS; if (val < 0) { val = -val; negative = false; } (void) sprintf(valbuf, "%d", val); (void) strcat(buf, fmt_int(valbuf, fmt, false, negative)); return (buf); } /*****************************************************************************/ static void reverse(buf) register char *buf; { register char *cp = buf + strlen(buf) - 1; register char tmp; while (buf < cp) { tmp = *cp; *cp-- = *buf; *buf++ = tmp; } } /*****************************************************************************/ /* * Tom Anderson * 10/14/90 * * This routine takes a value and formats it using fixed, scientific, * or engineering notation. The format command 'f' determines which * format is used. The formats are: example * 0: Fixed point (default) 0.00010 * 1: Scientific 1.00E-04 * 2: Engineering 100.00u * * The format command 'f' now uses three values. The first two are the * width and precision, and the last one is the format value 0, 1, or 2 as * described above. The format value is passed in the variable fmt. * * This formatted value is written into the passed buffer. if the * resulting string is too long to fit into the passed buffer, the * function returns false. Otherwise the function returns true. * * When a number is formatted as engineering and is outside of the range * of typically used engineering exponents, the format reverts to * scientific. * * To preserve compatability with old spreadsheet files, the third value * may be missing, and the default will be fixed point (format 0). * * When an old style sheet is saved, the third value will be stored. * */ /* defined in sc.h */ #ifndef REFMTFIX #define REFMTFIX 0 #define REFMTFLT 1 #define REFMTENG 2 #define REFMTDATE 3 #endif char engmult[] = "afpnum kMGT"; bool engformat(fmt, width, lprecision, val, buf, buflen) int fmt; int width; int lprecision; double val; char *buf; int buflen; { int engind = 0; double engmant, pow(), engabs, engexp; if (buflen < width) return (false); if (fmt == REFMTFIX) (void) sprintf(buf,"%*.*f", width, lprecision, val); if (fmt == REFMTFLT) (void) sprintf(buf,"%*.*E", width, lprecision, val); if (fmt == REFMTENG) { if (val == 0e0) /* Hack to get zeroes to line up in engr fmt */ { (void) sprintf((buf-1),"%*.*f ", width, lprecision, val); } else { engabs=(val); if (engabs < 0e0) engabs= -engabs; if ((engabs >= 1e-18) && (engabs < 1e-15 )) engind=0; if ((engabs >= 1e-15) && (engabs < 1e-12 )) engind=1; if ((engabs >= 1e-12) && (engabs < 1e-9 )) engind=2; if ((engabs >= 1e-9) && (engabs < 1e-6 )) engind=3; if ((engabs >= 1e-6) && (engabs < 1e-3 )) engind=4; if ((engabs >= 1e-3) && (engabs < 1 )) engind=5; if ((engabs >= 1) && (engabs < 1e3 )) engind=6; if ((engabs >= 1e3) && (engabs < 1e6 )) engind=7; if ((engabs >= 1e6) && (engabs < 1e9 )) engind=8; if ((engabs >= 1e9) && (engabs < 1e12 )) engind=9; if ((engabs >= 1e12) && (engabs < 1e15 )) engind=10; if ((engabs <1e-18) || (engabs >=1e15)) { /* Revert to floating point */ (void) sprintf(buf,"%*.*E", width, lprecision, val); } else { engexp= (double) (engind-6)*3; engmant= val/pow(10.0e0,engexp); (void) sprintf(buf,"%*.*f%c", width-1, lprecision, engmant, engmult[engind]); } } } if (fmt == REFMTDATE) { int i; char *time; long int secs; if (buflen < 9) { for (i = 0; i < width; i++) buf[i] = '*'; buf[i] = '\0'; } else { secs = (time_t)val; time = ctime(&secs); buf[0] = time[8]; buf[1] = time[9]; buf[2] = ' '; buf[3] = time[4]; buf[4] = time[5]; buf[5] = time[6]; buf[6] = ' '; buf[7] = time[22]; buf[8] = time[23]; for (i = 9; i < width; i++) buf[i] = ' '; buf[i] = '\0'; } } return (true); }