home *** CD-ROM | disk | FTP | other *** search
/ pc.louisiana.edu/pub/unix/ / Louisiana_UNIX.tar / Louisiana_UNIX / xspread3.0.zoo / format.c < prev    next >
C/C++ Source or Header  |  1994-03-25  |  18KB  |  681 lines

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