/[pspp]/pspp/src/data-out.c
ViewVC logotype

Diff of /pspp/src/data-out.c

Parent Directory Parent Directory | Revision Log Revision Log | View Patch Patch

revision 1.14 by blp, Wed May 11 03:09:09 2005 UTC revision 1.15 by blp, Mon May 16 07:33:17 2005 UTC
# Line 35  Line 35 
35  #include "var.h"  #include "var.h"
36    
37  #include "debug-print.h"  #include "debug-print.h"
   
 /* In older versions, numbers got their trailing zeros stripped.  
    Newer versions leave them on when there's room.  Comment this next  
    line out for retro styling. */  
 #define NEW_STYLE 1  
38    
39  /* Public functions. */  /* Public functions. */
40    
# Line 48  static numeric_converter convert_F, conv Line 43  static numeric_converter convert_F, conv
43  static numeric_converter convert_Z, convert_IB, convert_P, convert_PIB;  static numeric_converter convert_Z, convert_IB, convert_P, convert_PIB;
44  static numeric_converter convert_PIBHEX, convert_PK, convert_RB;  static numeric_converter convert_PIBHEX, convert_PK, convert_RB;
45  static numeric_converter convert_RBHEX, convert_CCx, convert_date;  static numeric_converter convert_RBHEX, convert_CCx, convert_date;
46  static numeric_converter convert_time, convert_WKDAY, convert_MONTH, try_F;  static numeric_converter convert_time, convert_WKDAY, convert_MONTH;
47    
48    static numeric_converter try_F, convert_infinite;
49    
50  typedef int string_converter (char *, const struct fmt_spec *, const char *);  typedef int string_converter (char *, const struct fmt_spec *, const char *);
51  static string_converter convert_A, convert_AHEX;  static string_converter convert_A, convert_AHEX;
# Line 196  data_out (char *s, const struct fmt_spec Line 193  data_out (char *s, const struct fmt_spec
193  void  void
194  num_to_string (double v, char *s, int w, int d)  num_to_string (double v, char *s, int w, int d)
195  {  {
   /* Dummy to pass to convert_F. */  
196    struct fmt_spec f;    struct fmt_spec f;
197      f.type = FMT_F;
 #if !NEW_STYLE  
   /* Pointer to `.' in S. */  
   char *decp;  
   
   /* Pointer to `E' in S. */  
   char *expp;  
   
   /* Number of characters to delete. */  
   int n = 0;  
 #endif  
   
198    f.w = w;    f.w = w;
199    f.d = d;    f.d = d;
200      convert_F (s, &f, v);
   /* Cut out the jokers. */  
   if (!finite (v))  
     {  
       char temp[9];  
       int len;  
   
       if (isnan (v))  
         {  
           memcpy (temp, "NaN", 3);  
           len = 3;  
         }  
       else if (isinf (v))  
         {  
           memcpy (temp, "+Infinity", 9);  
           if (v < 0)  
             temp[0] = '-';  
           len = 9;  
         }  
       else  
         {  
           memcpy (temp, _("Unknown"), 7);  
           len = 7;  
         }  
       if (w > len)  
         {  
           int pad = w - len;  
           memset (s, ' ', pad);  
           s += pad;  
           w -= pad;  
         }  
       memcpy (s, temp, w);  
       return;  
     }  
   
   try_F (s, &f, v);  
   
 #if !NEW_STYLE  
   decp = memchr (s, set_decimal, w);  
   if (!decp)  
     return;  
   
   /* If there's an `E' we can only delete 0s before the E. */  
   expp = memchr (s, 'E', w);  
   if (expp)  
     {  
       while (expp[-n - 1] == '0')  
         n++;  
       if (expp[-n - 1] == set_decimal)  
         n++;  
       memmove (&s[n], s, expp - s - n);  
       memset (s, ' ', n);  
       return;  
     }  
   
   /* Otherwise delete all trailing 0s. */  
   n++;  
   while (s[w - n] == '0')  
     n++;  
   if (s[w - n] != set_decimal)  
     {  
       /* Avoid stripping `.0' to `'. */  
       if (w == n || !isdigit ((unsigned char) s[w - n - 1]))  
         n -= 2;  
     }  
   else  
     n--;  
   memmove (&s[n], s, w - n);  
   memset (s, ' ', n);  
 #endif  
201  }  }
202    
203  /* Main conversion functions. */  /* Main conversion functions. */
# Line 295  static int try_CCx (char *s, const struc Line 211  static int try_CCx (char *s, const struc
211  #error Write your own floating-point output routines.  #error Write your own floating-point output routines.
212  #endif  #endif
213    
 /* PORTME:  
   
    Some of the routines in this file are likely very specific to  
    base-2 representation of floating-point numbers, most notably the  
    routines that use frexp() or ldexp().  These attempt to extract  
    individual digits by setting the base-2 exponent and  
    multiplying/dividing by powers of 2.  In base-2 numeration systems,  
    this just nudges the exponent up or down, but in base-10 floating  
    point, such multiplications/division can cause catastrophic loss of  
    precision.  
   
    The author has never personally used a machine that didn't use  
    binary floating point formats, so he is unwilling, and perhaps  
    unable, to code around this "problem".  */  
   
214  /* Converts a number between 0 and 15 inclusive to a `hexit'  /* Converts a number between 0 and 15 inclusive to a `hexit'
215     [0-9A-F]. */     [0-9A-F]. */
216  #define MAKE_HEXIT(X) ("0123456789ABCDEF"[X])  #define MAKE_HEXIT(X) ("0123456789ABCDEF"[X])
# Line 369  convert_E (char *dst, const struct fmt_s Line 270  convert_E (char *dst, const struct fmt_s
270    /* Ranged number of decimal places. */    /* Ranged number of decimal places. */
271    int d;    int d;
272    
273    /* Check that the format is width enough.    if (!finite (number))
274        return convert_infinite (dst, fp, number);
275    
276      /* Check that the format is wide enough.
277       Although PSPP generally checks this, convert_E() can be called as       Although PSPP generally checks this, convert_E() can be called as
278       a fallback from other formats which do not check. */       a fallback from other formats which do not check. */
279    if (fp->w < 6)    if (fp->w < 6)
# Line 561  convert_IB (char *dst, const struct fmt_ Line 465  convert_IB (char *dst, const struct fmt_
465      }      }
466    memcpy (dst, temp, fp->w);    memcpy (dst, temp, fp->w);
467  #ifndef WORDS_BIGENDIAN  #ifndef WORDS_BIGENDIAN
468    mm_reverse (dst, fp->w);    buf_reverse (dst, fp->w);
469  #endif  #endif
470    
471    return 1;    return 1;
# Line 625  convert_PIB (char *dst, const struct fmt Line 529  convert_PIB (char *dst, const struct fmt
529        ((unsigned char *) dst)[i] = floor (frac);        ((unsigned char *) dst)[i] = floor (frac);
530      }      }
531  #ifndef WORDS_BIGENDIAN  #ifndef WORDS_BIGENDIAN
532    mm_reverse (dst, fp->w);    buf_reverse (dst, fp->w);
533  #endif  #endif
534    
535    return 1;    return 1;
# Line 852  convert_date (char *dst, const struct fm Line 756  convert_date (char *dst, const struct fm
756    
757    if (buf[0] == 0)    if (buf[0] == 0)
758      return 0;      return 0;
759    st_bare_pad_copy (dst, buf, fp->w);    buf_copy_str_rpad (dst, fp->w, buf);
760    return 1;    return 1;
761  }  }
762    
# Line 902  convert_time (char *dst, const struct fm Line 806  convert_time (char *dst, const struct fm
806    
807        cp = spprintf (cp, ":%0*.*f", w, d, fmod (time, 60.));        cp = spprintf (cp, ":%0*.*f", w, d, fmod (time, 60.));
808      }      }
809    st_bare_pad_copy (dst, temp_buf, fp->w);    buf_copy_str_rpad (dst, fp->w, temp_buf);
810    
811    return 1;    return 1;
812  }  }
# Line 922  convert_WKDAY (char *dst, const struct f Line 826  convert_WKDAY (char *dst, const struct f
826             (double) wkday);             (double) wkday);
827        return 0;        return 0;
828      }      }
829    st_bare_pad_copy (dst, weekdays[(int) wkday - 1], fp->w);    buf_copy_str_rpad (dst, fp->w, weekdays[(int) wkday - 1]);
830    
831    return 1;    return 1;
832  }  }
# Line 943  convert_MONTH (char *dst, const struct f Line 847  convert_MONTH (char *dst, const struct f
847        return 0;        return 0;
848      }      }
849        
850    st_bare_pad_copy (dst, months[(int) month - 1], fp->w);    buf_copy_str_rpad (dst, fp->w, months[(int) month - 1]);
851    
852    return 1;    return 1;
853  }  }
# Line 1121  try_CCx (char *dst, const struct fmt_spe Line 1025  try_CCx (char *dst, const struct fmt_spe
1025    return 1;    return 1;
1026  }  }
1027    
1028  /* This routine relies on the underlying implementation of sprintf:  static int
1029    format_and_round (char *dst, double number, const struct fmt_spec *fp,
1030                      int decimals);
1031    
1032     If the number has a magnitude 1e40 or greater, then we needn't  /* Tries to format NUMBER into DST as the F format specified in
1033     bother with it, since it's guaranteed to need processing in     *FP.  Return true if successful, false on failure. */
    scientific notation.  
   
    Otherwise, do a binary search for the base-10 magnitude of the  
    thing.  log10() is not accurate enough, and the alternatives are  
    frightful.  Besides, we never need as many as 6 (pairs of)  
    comparisons.  The algorithm used for searching is Knuth's Algorithm  
    6.2.1C (Uniform binary search).  
   
    DON'T CHANGE ANYTHING HERE UNLESS YOU'VE THOUGHT ABOUT IT FOR A  
    LONG TIME!  The rest of the program is heavily dependent on  
    specific properties of this routine's output.  LOG ALL CHANGES! */  
1034  static int  static int
1035  try_F (char *dst, const struct fmt_spec *fp, double number)  try_F (char *dst, const struct fmt_spec *fp, double number)
1036  {  {
1037    /* This is the DELTA array from Knuth.    assert (fp->w <= 40);
1038       DELTA[j] = floor((40+2**(j-1))/(2**j)). */    if (finite (number))
1039    static const int delta[8] =      {
1040    {        if (fabs (number) < power10[fp->w])
1041      0, (40 + 1) / 2, (40 + 2) / 4, (40 + 4) / 8, (40 + 8) / 16,          {
1042      (40 + 16) / 32, (40 + 32) / 64, (40 + 64) / 128,            /* The value may fit in the field. */
1043    };            if (fp->d == 0)
1044                {
1045                  /* There are no decimal places, so there's no way
1046                     that the value can be shortened.  Either it fits
1047                     or it doesn't. */
1048                  char buf[40];
1049                  sprintf (buf, "%*.0f", fp->w, number);
1050                  if (strlen (buf) <= fp->w)
1051                    {
1052                      buf_copy_str_lpad (dst, fp->w, buf);
1053                      return true;
1054                    }
1055                  else
1056                    return false;
1057                }
1058              else
1059                {
1060                  /* First try to format it with 2 extra decimal
1061                     places.  This gives us a good chance of not
1062                     needing even more decimal places, but it also
1063                     avoids wasting too much time formatting more
1064                     decimal places on the first try. */
1065                  int result = format_and_round (dst, number, fp, fp->d + 2);
1066                  if (result >= 0)
1067                    return result;
1068    
1069                  /* 2 extra decimal places weren't enough to
1070                     correctly round.  Try again with the maximum
1071                     number of places. */
1072                  return format_and_round (dst, number, fp, LDBL_DIG + 1);
1073                }
1074            }
1075          else
1076            {
1077              /* The value is too big to fit in the field. */
1078              return false;
1079            }
1080        }
1081      else
1082        return convert_infinite (dst, fp, number);
1083    }
1084    
1085    /* The number of digits in floor(number), including sign.  This  /* Tries to compose NUMBER into DST in format FP by first
1086       is `i' from Knuth. */     formatting it with DECIMALS decimal places, then rounding off
1087    int n_int = (40 + 1) / 2;     to as many decimal places will fit or the number specified in
1088       FP, whichever is fewer.
1089    
1090    /* Used to step through delta[].  This is `j' from Knuth. */     Returns 1 if conversion succeeds, 0 if this try at conversion
1091    int j = 2;     failed and so will any other tries (because the integer part
1092       of the number is too long), or -1 if this try failed but
1093       another with higher DECIMALS might succeed (because we'd be
1094       able to properly round). */
1095    static int
1096    format_and_round (char *dst, double number, const struct fmt_spec *fp,
1097                      int decimals)
1098    {
1099      /* Number of characters before the decimal point,
1100         which includes digits and possibly a minus sign. */
1101      int predot_chars;
1102    
1103    /* Magnitude of number.  This is `K' from Knuth. */    /* Number of digits in the output fraction,
1104    double mag;       which may be smaller than fp->d if there's not enough room. */
1105      int fraction_digits;
1106    
1107    /* Number of characters for the fractional part, including the    /* Points to last digit that will remain in the fraction after
1108       decimal point. */       rounding. */
1109    int n_dec;    char *final_frac_dig;
1110    
1111    /* Pointer into buf used for formatting. */    /* Round up? */
1112    char *cp;    bool round_up;
1113      
1114      char buf[128];
1115      
1116      assert (decimals > fp->d);
1117      if (decimals > LDBL_DIG)
1118        decimals = LDBL_DIG + 1;
1119    
1120    /* Used to count characters formatted by nsprintf(). */    sprintf (buf, "%.*f", decimals, number);
   int n;  
1121    
1122    /* Temporary buffer. */    if (!memcmp (buf, "0.", 2))
1123    char buf[128];      memmove (buf, buf + 1, strlen (buf));
1124      else if (!memcmp (buf, "-0.", 3))
1125        memmove (buf + 1, buf + 2, strlen (buf + 1));
1126    
1127    /* First check for infinities and NaNs.  12/13/96. */    predot_chars = strcspn (buf, ".");
1128    if (!finite (number))    if (predot_chars > fp->w)
1129      {      {
1130        n = nsprintf (buf, "%f", number);        /* Can't possibly fit. */
1131        if (n > fp->w)        return 0;
1132          memset (buf, '*', fp->w);      }
1133        else if (n < fp->w)    else if (predot_chars == fp->w)
1134          {      {
1135            memmove (&buf[fp->w - n], buf, n);        /* Exact fit for integer part and sign. */
           memset (buf, ' ', fp->w - n);  
         }  
1136        memcpy (dst, buf, fp->w);        memcpy (dst, buf, fp->w);
1137        return 1;        return 1;
1138      }      }
1139      else if (predot_chars + 1 == fp->w)
   /* Then check for radically out-of-range values. */  
   mag = fabs (number);  
   if (mag >= power10[fp->w])  
     return 0;  
   
   if (mag < 1.0)  
1140      {      {
1141        n_int = 0;        /* There's room for the decimal point, but not for any
1142             digits of the fraction.
1143             Right-justify the integer part and sign. */
1144          dst[0] = ' ';
1145          memcpy (dst + 1, buf, fp->w);
1146          return 1;
1147        }
1148    
1149        /* Avoid printing `-.000'. 7/6/96. */    /* It looks like we have room for at least one digit of the
1150        if (mag < EPSILON)       fraction.  Figure out how many. */
1151          number = 0.0;    fraction_digits = fp->w - predot_chars - 1;
1152      if (fraction_digits > fp->d)
1153        fraction_digits = fp->d;
1154      final_frac_dig = buf + predot_chars + fraction_digits;
1155    
1156      /* Decide rounding direction and truncate string. */
1157      if (final_frac_dig[1] == '5'
1158          && strspn (final_frac_dig + 2, "0") == strlen (final_frac_dig + 2))
1159        {
1160          /* Exactly 1/2. */
1161          if (decimals <= LDBL_DIG)
1162            {
1163              /* Don't have enough fractional digits to know which way to
1164                 round.  We can format with more decimal places, so go
1165                 around again. */
1166              return -1;
1167            }
1168          else
1169            {
1170              /* We used up all our fractional digits and still don't
1171                 know.  Round to even. */
1172              round_up = (final_frac_dig[0] - '0') % 2 != 0;
1173            }
1174      }      }
1175    else    else
1176      /* Now perform a `uniform binary search' based on the tables      round_up = final_frac_dig[1] >= '5';
1177         power10[] and delta[].  After this step, nint is the number of    final_frac_dig[1] = '\0';
        digits in floor(number), including any sign.  */  
     for (;;)  
       {  
         if (mag >= power10[n_int])  
           {  
             assert (delta[j]);  
             n_int += delta[j++];  
           }  
         else if (mag < power10[n_int - 1])  
           {  
             assert (delta[j]);  
             n_int -= delta[j++];  
           }  
         else  
           break;  
       }  
1178    
1179    /* If we have any decimal places, then there is a decimal point,    /* Do rounding. */
1180       too. */    if (round_up)
1181    n_dec = fp->d;      {
1182    if (n_dec)        char *cp = final_frac_dig;
1183      n_dec++;        for (;;)
1184            {
1185    /* 1/10/96: If there aren't any digits at all, add one.  This occurs            if (*cp >= '0' && *cp <= '8')
1186       only when fabs(number) < 1.0. */              {
1187    if (n_int + n_dec == 0)                (*cp)++;
1188      n_int++;                break;
1189                }
1190              else if (*cp == '9')
1191                *cp = '0';
1192              else
1193                assert (*cp == '.');
1194    
1195              if (cp == buf || *--cp == '-')
1196                {
1197                  size_t length;
1198                  
1199                  /* Tried to go past the leftmost digit.  Insert a 1. */
1200                  memmove (cp + 1, cp, strlen (cp) + 1);
1201                  *cp = '1';
1202    
1203                  length = strlen (buf);
1204                  if (length > fp->w)
1205                    {
1206                      /* Inserting the `1' overflowed our space.
1207                         Drop a decimal place. */
1208                      buf[--length] = '\0';
1209    
1210                      /* If that was the last decimal place, drop the
1211                         decimal point too. */
1212                      if (buf[length - 1] == '.')
1213                        buf[length - 1] = '\0';
1214                    }
1215                  
1216                  break;
1217                }
1218            }
1219        }
1220    
1221    /* Give space for a minus sign.  Moved 1/10/96. */    buf_copy_str_lpad (dst, fp->w, buf);
1222    if (number < 0)    return 1;
1223      n_int++;  }
1224    
1225    /* Normally we only go through the loop once; occasionally twice.  /* Formats non-finite NUMBER into DST according to the width
1226       Three times or more indicates a very serious bug somewhere. */     given in FP. */
1227    for (;;)  static int
1228      {  convert_infinite (char *dst, const struct fmt_spec *fp, double number)
1229        /* Check out the total length of the string. */  {
1230        cp = buf;    assert (!finite (number));
1231        if (n_int + n_dec > fp->w)    
1232          {    if (fp->w >= 3)
1233            /* The string is too long.  Let's see what can be done. */      {
1234            if (n_int <= fp->w)        const char *s;
             /* If we can, just reduce the number of decimal places. */  
             n_dec = fp->w - n_int;  
           else  
             return 0;  
         }  
       else if (n_int + n_dec < fp->w)  
         {  
           /* The string is too short.  Left-pad with spaces. */  
           int n_spaces = fp->w - n_int - n_dec;  
           memset (cp, ' ', n_spaces);  
           cp += n_spaces;  
         }  
1235    
1236        /* Finally, format the number. */        if (isnan (number))
1237        if (n_dec)          s = "NaN";
1238          n = nsprintf (cp, "%.*f", n_dec - 1, number);        else if (isinf (number))
1239            s = number > 0 ? "+Infinity" : "-Infinity";
1240        else        else
1241          n = nsprintf (cp, "%.0f", number);          s = "Unknown";
1242    
1243        /* If number is positive and its magnitude is less than        buf_copy_str_lpad (dst, fp->w, s);
          1...  */  
       if (n_int == 0)  
         {  
           if (*cp == '0')  
             {  
               /* The value rounds to `.###'. */  
               memmove (cp, &cp[1], n - 1);  
               n--;  
             }  
           else  
             {  
               /* The value rounds to `1.###'. */  
               n_int = 1;  
               continue;  
             }  
         }  
       /* Else if number is negative and its magnitude is less  
          than 1...  */  
       else if (number < 0 && n_int == 1)  
         {  
           if (cp[1] == '0')  
             {  
               /* The value rounds to `-.###'. */  
               memmove (&cp[1], &cp[2], n - 2);  
               n--;  
             }  
           else  
             {  
               /* The value rounds to `-1.###'. */  
               n_int = 2;  
               continue;  
             }  
         }  
   
       /* Check for a correct number of digits & decimal places & stuff.  
          This is just a desperation check.  Hopefully it won't fail too  
          often, because then we have to run through the whole loop again:  
          sprintf() is not a fast operation with floating-points! */  
       if (n == n_int + n_dec)  
         {  
           /* Convert periods `.' to commas `,' for our foreign friends. */  
           if ((get_decimal() == ',' && fp->type != FMT_DOT)  
               || (get_decimal() == '.' && fp->type == FMT_DOT))  
             {  
               cp = strchr (cp, '.');  
               if (cp)  
                 *cp = ',';  
             }  
   
           memcpy (dst, buf, fp->w);  
           return 1;  
         }  
   
       n_int = n - n_dec; /* FIXME?  Need an idiot check on resulting n_int? */  
1244      }      }
1245      else
1246        memset (dst, '*', fp->w);
1247    
1248      return true;
1249  }  }

Legend:
Removed from v.1.14  
changed lines
  Added in v.1.15

savannah-hackers-public@gnu.org
ViewVC Help
Powered by ViewVC 1.1.26