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