1 /*****************************************************************************
2 *
3 * Mark Nagel <nagel@ics.uci.edu>
4 * 20 July 1989
5 *
6 * $Revision: 1.1 $
7 * Tom Kloos <tk@sequent.com> Fixed a number of problems that actually
8 * caused bad output conversions for certain data values with certain
9 * valid format strings. Note that there still isn't good (any?) error
10 * checking of the format string -- A bad format may print wrong values
11 * without warning (ie. ".#" won't show overflows). 28 Sep 92
12 *
13 * bool
14 * format(fmt, num, buf, buflen)
15 * char *fmt;
16 * double num;
17 * char buf[];
18 * int buflen;
19 *
20 * The format function will produce a string representation of a number
21 * given a _format_ (described below) and a double value. The result is
22 * written into the passed buffer -- if the resulting string is too
23 * long to fit into the passed buffer, the function returns FALSE.
24 * Otherwise the function returns TRUE.
25 *
26 * The fmt parameter contains the format to use to convert the number.
27 *
28 * # Digit placeholder. If the number has fewer digits on either
29 * side of the decimal point than there are '#' characters in
30 * the format, the extra '#' characters are ignored. The number
31 * is rounded to the number of digit placeholders as there are
32 * to the right of the decimal point. If there are more digits
33 * in the number than there are digit placeholders on the left
34 * side of the decimal point, then those digits are displayed.
35 *
36 * 0 Digit placeholder. Same as for '#' except that the number
37 * is padded with zeroes on either side of the decimal point.
38 * The number of zeroes used in padding is determined by the
39 * number of digit placeholders after the '0' for digits on
40 * the left side of the decimal point and by the number of
41 * digit placeholders before the '0' for digits on the right
42 * side of the decimal point.
43 *
44 * . Decimal point. Determines how many digits are placed on
45 * the right and left sides of the decimal point in the number.
46 * Note that numbers smaller than 1 will begin with a decimal
47 * point if the left side of the decimal point contains only
48 * a '#' digit placeholder. Use a '0' placeholder to get a
49 * leading zero in decimal formats.
50 *
51 * % Percentage. For each '%' character in the format, the actual
52 * number gets multiplied by 100 (only for purposes of formatting
53 * -- the original number is left unmodified) and the '%' character
54 * is placed in the same position as it is in the format.
55 *
56 * , Thousands separator. The presence of a ',' in the format
57 * (multiple commas are treated as one) will cause the number
58 * to be formatted with a ',' separating each set of three digits
59 * in the integer part of the number with numbering beginning
60 * from the right end of the integer.
61 *
62 * \ Quote. This character causes the next character to be
63 * inserted into the formatted string directly with no
64 * special interpretation.
65 *
66 * E- E+ e- e+
67 * Scientific format. Causes the number to formatted in scientific
68 * notation. The case of the 'E' or 'e' given is preserved. If
69 * the format uses a '+', then the sign is always given for the
70 * exponent value. If the format uses a '-', then the sign is
71 * only given when the exponent value is negative. Note that if
72 * there is no digit placeholder following the '+' or '-', then
73 * that part of the formatted number is left out. In general,
74 * there should be one or more digit placeholders after the '+'
75 * or '-'.
76 *
77 * ; Format selector. Use this character to separate the format
78 * into two distinct formats. The format to the left of the
79 * ';' character will be used if the number given is zero or
80 * positive. The format to the right of the ';' character is
81 * used if the number given is negative.
82 *
83 * Any
84 * Self insert. Any other character will be inserted directly
85 * into the formatted number with no change made to the actual
86 * number.
87 *
88 *****************************************************************************/
89
90 /*****************************************************************************/
91
92 #include <config.h>
93
94 #include <stdio.h>
95 #include <sys/types.h>
96 #include <math.h>
97 #include "sc.h"
98
99 #define bool int
100 #define EOS '\0'
101 #define MAXBUF 256
102
103
104 static char * fmt_int PROTO((char *, char *, bool, bool));
105 static char * fmt_frac PROTO((char *, char *));
106 static char * fmt_exp PROTO((int, char *));
107 static void reverse PROTO((char *));
108
109 #ifndef __STDC__
110 extern char * strcpy();
111 extern char * strcat();
112 #endif /* __STDC__ */
113
114 /*****************************************************************************/
115
116 bool
format(fmt,val,buf,buflen)117 format(fmt, val, buf, buflen)
118 char *fmt;
119 double val;
120 char *buf;
121 int buflen;
122 {
123 register char *cp;
124 char *tmp, *tp;
125 bool comma = FALSE, negative = FALSE;
126 char *integer = NULL, *decimal = NULL;
127 char *exponent = NULL;
128 int exp_val = 0;
129 int width;
130 char prtfmt[32];
131 static char *mantissa = NULL;
132 static char *tmpfmt1 = NULL, *tmpfmt2 = NULL, *exptmp = NULL;
133 static unsigned mantlen = 0, fmtlen = 0;
134 char *fraction = NULL;
135 int zero_pad = 0;
136
137 if (fmt == NULL)
138 return(TRUE);
139
140 if (strlen(fmt) + 1 > fmtlen)
141 { fmtlen = strlen(fmt) + 40;
142 tmpfmt1 = scxrealloc(tmpfmt1, fmtlen);
143 tmpfmt2 = scxrealloc(tmpfmt2, fmtlen);
144 exptmp = scxrealloc(exptmp, fmtlen);
145 }
146 fmt = strcpy(tmpfmt1, fmt);
147 if (buflen + 1 > mantlen)
148 { mantlen = buflen + 40;
149 mantissa = scxrealloc(mantissa, mantlen);
150 }
151
152 /*
153 * select positive or negative format if necessary
154 */
155 for (cp = fmt; *cp != ';' && *cp != EOS; cp++)
156 {
157 if (*cp == '\\')
158 cp++;
159 }
160 if (*cp == ';')
161 {
162 if (val < 0.0)
163 {
164 val = -val; /* format should provide sign if desired */
165 fmt = cp + 1;
166 }
167 else
168 {
169 *cp = EOS;
170 }
171 }
172
173 /*
174 * extract other information from format and produce a
175 * format string stored in tmpfmt2 also scxmalloc()'d above
176 */
177 tmp = tmpfmt2;
178 for (cp = fmt, tp = tmp; *cp != EOS; cp++)
179 {
180 switch (*cp)
181 {
182 case '\\':
183 *tp++ = *cp++;
184 *tp++ = *cp;
185 break;
186
187 case ',':
188 comma = TRUE;
189 break;
190
191 case '.':
192 if (decimal == NULL)
193 decimal = tp;
194 *tp++ = *cp;
195 break;
196
197 case '%':
198 val *= 100.0;
199 *tp++ = *cp;
200 break;
201
202 default:
203 *tp++ = *cp;
204 break;
205 }
206 }
207 *tp = EOS;
208 fmt = tmpfmt2;
209
210 if (val < 0.0)
211 { negative = TRUE;
212 val = -val;
213 }
214 /*
215 * extract the exponent from the format if present
216 */
217 for (cp = fmt; *cp != EOS; cp++)
218 { if (*cp == '\\')
219 {
220 cp++;
221 }
222 else if (*cp == 'e' || *cp == 'E')
223 {
224 if (cp[1] == '+' || cp[1] == '-')
225 {
226 exponent = strcpy(exptmp, cp);
227 *cp = EOS;
228 exp_val = 0;
229 if (val!=0.0) {
230 while (val < 1.0)
231 {
232 val *= 10.0;
233 exp_val--;
234 }
235 while (val >= 10.0)
236 {
237 val /= 10.0;
238 exp_val++;
239 }
240 }
241 break;
242 }
243 }
244 }
245
246 /*
247 * determine maximum decimal places and use sprintf
248 * to build initial character form of formatted value.
249 */
250 width = 0;
251 if (decimal)
252 {
253 *decimal++ = EOS;
254 for (cp = decimal; *cp != EOS; cp++)
255 {
256 if (*cp == '\\')
257 cp++;
258 else
259 if (*cp == '#')
260 width++;
261 else
262 if (*cp == '0')
263 zero_pad = ++width;
264 else
265 break;
266 }
267 zero_pad = width - zero_pad; /* Now really zeros to zap! */
268 }
269 (void) sprintf(prtfmt, "%%.%dlf", width);
270 (void) sprintf(mantissa, prtfmt, val);
271 for (cp = integer = mantissa; *cp != '.' && *cp != EOS; cp++)
272 {
273 if (*integer == '0')
274 integer++;
275 }
276 if (*cp == '.')
277 {
278 fraction = cp + 1;
279 *cp = EOS;
280 cp = fraction + strlen(fraction) - 1;
281 while ((zero_pad-- > 0) && (*cp == '0'))
282 *cp-- = EOS;
283 }
284
285 /*
286 * format the puppy
287 */
288 {
289 static char *citmp = NULL, *cftmp = NULL;
290 static unsigned cilen = 0, cflen = 0;
291 char *ci, *cf, *ce;
292 int len_ci, len_cf, len_ce;
293 bool ret = FALSE;
294
295 ci = fmt_int(integer, fmt, comma, negative);
296 len_ci = strlen(ci);
297 if (len_ci >= cilen)
298 { cilen = len_ci + 40;
299 citmp = scxrealloc(citmp, cilen);
300 }
301 ci = strcpy(citmp, ci);
302
303 cf = (decimal) ? fmt_frac(fraction, decimal) : "";
304 len_cf = strlen(cf);
305 if (len_cf >= cflen)
306 { cflen = len_cf + 40;
307 cftmp = scxrealloc(cftmp, cilen);
308 }
309 cf = strcpy(cftmp, cf);
310
311 ce = (exponent) ? fmt_exp(exp_val, exponent) : "";
312 len_ce = strlen(ce);
313 /*
314 * Skip copy assuming sprintf doesn't call our format functions
315 * ce = strcpy(scxmalloc((unsigned)((len_ce = strlen(ce)) + 1)), ce);
316 */
317 if (len_ci + len_cf + len_ce < buflen)
318 {
319 (void) sprintf(buf, "%s%s%s", ci, cf, ce);
320 ret = TRUE;
321 }
322
323 return (ret);
324 }
325 }
326
327 /*****************************************************************************/
328
329 static char *
fmt_int(val,fmt,comma,negative)330 fmt_int(val, fmt, comma, negative)
331 char *val; /* integer part of the value to be formatted */
332 char *fmt; /* integer part of the format */
333 bool comma; /* TRUE if we should comma-ify the value */
334 bool negative; /* TRUE if the value is actually negative */
335 {
336 int digit, f, v;
337 int thousands = 0;
338 char *cp;
339 static char buf[MAXBUF];
340 char *bufptr = buf;
341
342 /*
343 * locate the leftmost digit placeholder
344 */
345 for (cp = fmt; *cp != EOS; cp++)
346 {
347 if (*cp == '\\')
348 cp++;
349 else if (*cp == '#' || *cp == '0')
350 break;
351 }
352 digit = (*cp == EOS) ? -1 : cp - fmt;
353
354 /*
355 * format the value
356 */
357 f = strlen(fmt) - 1;
358 v = (digit >= 0) ? strlen(val) - 1 : -1;
359 while (f >= 0 || v >= 0)
360 {
361 if (f > 0 && fmt[f-1] == '\\')
362 {
363 *bufptr++ = fmt[f--];
364 }
365 else if (f >= 0 && (fmt[f] == '#' || fmt[f] == '0'))
366 {
367 if (v >= 0 || fmt[f] == '0')
368 {
369 *bufptr++ = v < 0 ? '0' : val[v];
370 if (comma && (thousands = (thousands + 1) % 3) == 0 && v > 0)
371 {
372 *bufptr++ = ',';
373 }
374 v--;
375 }
376 }
377 else if (f >= 0)
378 {
379 *bufptr++ = fmt[f];
380 }
381 if (v >= 0 && f == digit)
382 {
383 continue;
384 }
385 f--;
386 }
387
388 if (negative && digit >= 0)
389 *bufptr++ = '-';
390 *bufptr = EOS;
391 reverse(buf);
392
393 return (buf);
394 }
395
396 /*****************************************************************************/
397
398 static char *
fmt_frac(val,fmt)399 fmt_frac(val, fmt)
400 char *val; /* fractional part of the value to be formatted */
401 char *fmt; /* fractional portion of format */
402 {
403 static char buf[MAXBUF];
404 register char *bufptr = buf;
405 register char *fmtptr = fmt, *valptr = val;
406
407 *bufptr++ = '.';
408 while (*fmtptr != EOS)
409 {
410 if (*fmtptr == '\\')
411 {
412 *bufptr++ = *++fmtptr;
413 }
414 else if (*fmtptr == '#' || *fmtptr == '0')
415 {
416 if (*valptr != EOS || *fmtptr == '0')
417 {
418 *bufptr++ = (*valptr != EOS) ? *valptr++ : *fmtptr;
419 }
420 }
421 else
422 {
423 *bufptr++ = *fmtptr;
424 }
425 fmtptr++;
426 }
427 *bufptr = EOS;
428
429 return (buf);
430 }
431
432 /*****************************************************************************/
433
434 static char *
fmt_exp(val,fmt)435 fmt_exp(val, fmt)
436 int val; /* value of the exponent */
437 char *fmt; /* exponent part of the format */
438 {
439 static char buf[MAXBUF];
440 register char *bufptr = buf;
441 char valbuf[64];
442 bool negative = FALSE;
443
444 *bufptr++ = *fmt++;
445 if (*fmt == '+')
446 *bufptr++ = (val < 0) ? '-' : '+';
447 else if (val < 0)
448 *bufptr++ = '-';
449 fmt++;
450 *bufptr = EOS;
451
452 if (val < 0)
453 {
454 val = -val;
455 negative = FALSE;
456 }
457 (void) sprintf(valbuf, "%d", val);
458
459 (void) strcat(buf, fmt_int(valbuf, fmt, FALSE, negative));
460 return (buf);
461 }
462
463 /*****************************************************************************/
464
465 static void
reverse(buf)466 reverse(buf)
467 register char *buf;
468 {
469 register char *cp = buf + strlen(buf) - 1;
470 register char tmp;
471
472 while (buf < cp)
473 {
474 tmp = *cp;
475 *cp-- = *buf;
476 *buf++ = tmp;
477 }
478 }
479
480 /*****************************************************************************
481 *
482 * Tom Anderson <toma@hpsad.hp.com>
483 * 10/14/90
484 * David Fox - added a date format
485 * Philemon W. Johnson <pjohnson@itd.nrl.navy.mil>
486 * added exponent format, 19 July 92
487 *
488 * This routine takes a value and formats it using fixed, scientific,
489 * engineering notation, date, or exponent (modulo 3). The format command
490 * 'f' determines which format is used.
491 * The formats are: example
492 * 0: Fixed point (default) 0.00010
493 * 1: Scientific 1.00E-04
494 * 2: Engineering 100.00u
495 * 3: Date 05/15/92
496 * 4: Exponent, modulo 3 100.E-06
497 *
498 * The format command 'f' now uses three values. The first two are the
499 * width and precision, and the last one is the format value 0 to 4 as
500 * described above. The format value is passed in the variable fmt.
501 *
502 * This formatted value is written into the passed buffer. if the
503 * resulting string is too long to fit into the passed buffer, the
504 * function returns FALSE. Otherwise the function returns true.
505 *
506 * When a number is formatted as engineering and is outside of the range
507 * of typically used engineering exponents, the format reverts to
508 * scientific.
509 *
510 * To preserve compatability with old spreadsheet files, the third value
511 * may be missing, and the default will be fixed point (format 0).
512 *
513 * When an old style sheet is saved, the third value will be stored.
514 *
515 *****************************************************************************/
516
517 bool
engformat(fmt,width,lprecision,val,buf,buflen)518 engformat (fmt, width, lprecision, val, buf, buflen)
519 int fmt, width, lprecision, buflen;
520 double val;
521 char *buf;
522 {
523 static char engmult[] = "afpnum kMGT";
524
525 int engind = 0;
526 double engabs, engexp, engmant;
527
528 if (buflen >= width)
529 {
530 switch (fmt)
531 {
532 case REFMTFLT:
533 (void) sprintf (buf, "%*.*E", width, lprecision, val);
534 break;
535 case REFMTENG:
536 if (val == 0e0) /* Hack to get zeroes to line up in engr fmt */
537 (void) sprintf (buf, "%*.*f ", width - 1, lprecision, val);
538 else
539 {
540 engabs = (val < 0e0) ? -val : val;
541 if ((1e-18 <= engabs) && (engabs < 1e-15)) engind = 0;
542 if ((1e-15 <= engabs) && (engabs < 1e-12)) engind = 1;
543 if ((1e-12 <= engabs) && (engabs < 1e-9)) engind = 2;
544 if ((1e-9 <= engabs) && (engabs < 1e-6)) engind = 3;
545 if ((1e-6 <= engabs) && (engabs < 1e-3)) engind = 4;
546 if ((1e-3 <= engabs) && (engabs < 1)) engind = 5;
547 if ((1 <= engabs) && (engabs < 1e3)) engind = 6;
548 if ((1e3 <= engabs) && (engabs < 1e6)) engind = 7;
549 if ((1e6 <= engabs) && (engabs < 1e9)) engind = 8;
550 if ((1e9 <= engabs) && (engabs < 1e12)) engind = 9;
551 if ((1e12 <= engabs) && (engabs < 1e15)) engind = 10;
552 if ((engabs < 1e-18) || (1e15 <= engabs))
553 {
554 /* Revert to floating point */
555 (void) sprintf (buf, "%*.*E", width, lprecision, val);
556 }
557 else
558 {
559 engexp = (double) (engind - 6) * 3;
560 engmant = val / pow (10.0e0, engexp);
561 (void) sprintf (buf, "%*.*f%c", width - 1,
562 lprecision, engmant, engmult[engind]);
563 }
564 }
565 break;
566 case REFMTDATE:
567 {
568 int i;
569 char *temp_time;
570 long int secs;
571 /*
572 * sure hope that this is an 8 bit per character machine
573 * a defense against machines which do not cast
574 * double to long properly
575 */
576 secs = ~(1 << ((8 * (long) sizeof (long)) - 1));
577 if (buflen < 9 || val < (double) -secs || (double) secs < val)
578 {
579 for (i = 0; i < width; i++)
580 buf[i] = '*';
581 buf[i] = '\0';
582 }
583 else
584 {
585 secs = (time_t) val;
586 temp_time = ctime (&secs);
587 buf[0] = temp_time[8];
588 buf[1] = temp_time[9];
589 buf[2] = ' ';
590 buf[3] = temp_time[4];
591 buf[4] = temp_time[5];
592 buf[5] = temp_time[6];
593 buf[6] = ' ';
594 buf[7] = temp_time[22];
595 buf[8] = temp_time[23];
596 for (i = 9; i < width; i++)
597 buf[i] = ' ';
598 buf[i] = '\0';
599 }
600 }
601 break;
602 case REFMTEXP:
603 /*
604 * sprintf the value val to the output buffer buf
605 */
606 (void) sprintf (buf, "%*.*E", width, lprecision, val);
607 if (lprecision > 1) /* need 2 signifcant digits for this format */
608 {
609 int exponent;
610 char *pdecimal, *pexponent;
611 /*
612 * locate the decimal point and the exponent in buf
613 */
614 pdecimal = strchr (buf, '.');
615 pexponent = strrchr (buf, 'E');
616 pexponent++;
617 exponent = atoi (pexponent); /* convert pexponent to int */
618 /*
619 * determine how many places to shift the decimal point
620 * handle negative exponents as a special case
621 * exponent will be reduced by 1 for each decimal shift
622 */
623 switch ((exponent < 0) ? -abs (exponent) % 3 : exponent % 3)
624 {
625 case -1:
626 case 2:
627 /*
628 * need to shift decimal two places
629 */
630 *pdecimal = *(pdecimal + 1);
631 pdecimal++;
632 exponent--;
633 case -2:
634 case 1:
635 /*
636 * need to shift decimal one place
637 */
638 *pdecimal = *(pdecimal + 1);
639 *++pdecimal = 056; /* write in a new decimal point */
640 exponent--;
641 /*
642 * write the adjusted exponent to buf
643 * verify if exponent is two or three digits
644 */
645 if (*(pexponent + 3) == '\0') /* JEFFB compiler warning below */
646 (void) sprintf (pexponent, "%0+3.0d", exponent);
647 else
648 (void) sprintf (pexponent, "%0+4.0d", exponent);
649 break;
650 case 0:
651 default:
652 /*
653 * do not need to shift at all, just exit
654 */
655 break;
656 }
657 }
658 break;
659 case REFMTFIX:
660 default:
661 (void) sprintf (buf, "%*.*f", width, lprecision, val);
662 break;
663 }
664 return (TRUE);
665 }
666 else
667 return (FALSE);
668 }
669