Adopt use of gnulib for portability.
[pspp-builds.git] / src / data-out.c
index d54ebd1f0d43ec5fb1eafbc70606e157190ab011..2600ccb8037dfac35af8494b60b6c5b23a6eaa9f 100644 (file)
 
    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
-   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
-   02111-1307, USA. */
+   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, USA. */
 
 #include <config.h>
-#include <assert.h>
+#include "error.h"
 #include <ctype.h>
 #include <math.h>
 #include <float.h>
 #include <stdlib.h>
 #include <time.h>
-#include "approx.h"
+#include "calendar.h"
 #include "error.h"
 #include "format.h"
-#include "julcal/julcal.h"
 #include "magic.h"
 #include "misc.h"
 #include "misc.h"
 #include "str.h"
 #include "var.h"
 
-#undef DEBUGGING
-/*#define DEBUGGING 1*/
-#include "debug-print.h"
+#include "gettext.h"
+#define _(msgid) gettext (msgid)
 
-/* 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
+#include "debug-print.h"
 \f
 /* Public functions. */
 
-typedef int convert_func (char *, const struct fmt_spec *,
-                         const union value *);
-
-static convert_func convert_F, convert_N, convert_E, convert_F_plus;
-static convert_func convert_Z, convert_A, convert_AHEX, convert_IB;
-static convert_func convert_P, convert_PIB, convert_PIBHEX, convert_PK;
-static convert_func convert_RB, convert_RBHEX, convert_CCx, convert_date;
-static convert_func convert_time, convert_WKDAY, convert_MONTH;
-static convert_func try_F;
-
-/* Converts binary value V into printable form in string S according
-   to format specification FP.  The string as written has exactly
-   FP->W characters.  It is not null-terminated.  Returns 1 on
-   success, 0 on failure. */
-int
-data_out (char *s, const struct fmt_spec *fp, const union value *v)
-{
-  union value tmp_val;
-  
-  {
-    int cat = formats[fp->type].cat;
-    if ((cat & FCAT_BLANKS_SYSMIS) && v->f == SYSMIS)
-      {
-       memset (s, ' ', fp->w);
-       s[fp->w - fp->d - 1] = '.';
-       return 1;
-      }
-    if ((cat & FCAT_SHIFT_DECIMAL) && v->f != SYSMIS && fp->d)
-      {
-       tmp_val.f = v->f * pow (10.0, fp->d);
-       v = &tmp_val;
-      }
-  }
-  
-  {
-    static convert_func *const handlers[FMT_NUMBER_OF_FORMATS] =
-      {
-       convert_F, convert_N, convert_E, convert_F_plus,
-       convert_F_plus, convert_F_plus, convert_F_plus,
-       convert_Z, convert_A, convert_AHEX, convert_IB, convert_P, convert_PIB,
-       convert_PIBHEX, convert_PK, convert_RB, convert_RBHEX,
-       convert_CCx, convert_CCx, convert_CCx, convert_CCx, convert_CCx,
-       convert_date, convert_date, convert_date, convert_date, convert_date,
-       convert_date, convert_date, convert_date, convert_date,
-       convert_time, convert_time,
-       convert_WKDAY, convert_MONTH,
-      };
-
-    return handlers[fp->type] (s, fp, v);
-  }
-}
+typedef int numeric_converter (char *, const struct fmt_spec *, double);
+static numeric_converter convert_F, convert_N, convert_E, convert_F_plus;
+static numeric_converter convert_Z, convert_IB, convert_P, convert_PIB;
+static numeric_converter convert_PIBHEX, convert_PK, convert_RB;
+static numeric_converter convert_RBHEX, convert_CCx, convert_date;
+static numeric_converter convert_time, convert_WKDAY, convert_MONTH;
 
-/* Converts V into S in F format with width W and D decimal places,
-   then deletes trailing zeros.  S is not null-terminated. */
-void
-num_to_string (double v, char *s, int w, int d)
-{
-  /* Dummies to pass to convert_F. */
-  union value val;
-  struct fmt_spec f;
-
-#if !NEW_STYLE
-  /* Pointer to `.' in S. */
-  char *decp;
-
-  /* Pointer to `E' in S. */
-  char *expp;
+static numeric_converter try_F, convert_infinite;
 
-  /* Number of characters to delete. */
-  int n = 0;
-#endif
+typedef int string_converter (char *, const struct fmt_spec *, const char *);
+static string_converter convert_A, convert_AHEX;
 
-  f.w = w;
-  f.d = d;
-  val.f = v;
+/* Converts binary value V into printable form in the exactly
+   FP->W character in buffer S according to format specification
+   FP.  No null terminator is appended to the buffer.  */
+void
+data_out (char *s, const struct fmt_spec *fp, const union value *v)
+{
+  int cat = formats[fp->type].cat;
+  int ok;
 
-  /* Cut out the jokers. */
-  if (!finite (v))
+  assert (check_output_specifier (fp, 0));
+  if (!(cat & FCAT_STRING)) 
     {
-      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;
+      /* Numeric formatting. */
+      double number = v->f;
+
+      /* Handle SYSMIS turning into blanks. */
+      if ((cat & FCAT_BLANKS_SYSMIS) && number == SYSMIS)
+        {
+          memset (s, ' ', fp->w);
+          s[fp->w - fp->d - 1] = '.';
+          return;
+        }
+
+      /* Handle decimal shift. */
+      if ((cat & FCAT_SHIFT_DECIMAL) && number != SYSMIS && fp->d)
+        number *= pow (10.0, fp->d);
+
+      switch (fp->type) 
+        {
+        case FMT_F:
+          ok = convert_F (s, fp, number);
+          break;
+
+        case FMT_N:
+          ok = convert_N (s, fp, number);
+          break;
+
+        case FMT_E:
+          ok = convert_E (s, fp, number);
+          break;
+
+        case FMT_COMMA: case FMT_DOT: case FMT_DOLLAR: case FMT_PCT:
+          ok = convert_F_plus (s, fp, number);
+          break;
+
+        case FMT_Z:
+          ok = convert_Z (s, fp, number);
+          break;
+
+        case FMT_A:
+          assert (0);
+          abort ();
+
+        case FMT_AHEX:
+          assert (0);
+          abort ();
+
+        case FMT_IB:
+          ok = convert_IB (s, fp, number);
+          break;
+
+        case FMT_P:
+          ok = convert_P (s, fp, number);
+          break;
+
+        case FMT_PIB:
+          ok = convert_PIB (s, fp, number);
+          break;
+
+        case FMT_PIBHEX:
+          ok = convert_PIBHEX (s, fp, number);
+          break;
+
+        case FMT_PK:
+          ok = convert_PK (s, fp, number);
+          break;
+
+        case FMT_RB:
+          ok = convert_RB (s, fp, number);
+          break;
+
+        case FMT_RBHEX:
+          ok = convert_RBHEX (s, fp, number);
+          break;
+
+        case FMT_CCA: case FMT_CCB: case FMT_CCC: case FMT_CCD: case FMT_CCE:
+          ok = convert_CCx (s, fp, number);
+          break;
+
+        case FMT_DATE: case FMT_EDATE: case FMT_SDATE: case FMT_ADATE:
+        case FMT_JDATE: case FMT_QYR: case FMT_MOYR: case FMT_WKYR:
+        case FMT_DATETIME: 
+          ok = convert_date (s, fp, number);
+          break;
+
+        case FMT_TIME: case FMT_DTIME:
+          ok = convert_time (s, fp, number);
+          break;
+
+        case FMT_WKDAY:
+          ok = convert_WKDAY (s, fp, number);
+          break;
+
+        case FMT_MONTH:
+          ok = convert_MONTH (s, fp, number);
+          break;
+
+        default:
+          assert (0);
+          abort ();
+        }
     }
-
-  try_F (s, &f, &val);
-
-#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)
+  else 
     {
-      while (expp[-n - 1] == '0')
-       n++;
-      if (expp[-n - 1] == set_decimal)
-       n++;
-      memmove (&s[n], s, expp - s - n);
-      memset (s, ' ', n);
-      return;
+      /* String formatting. */
+      const char *string = v->s;
+
+      switch (fp->type) 
+        {
+        case FMT_A:
+          ok = convert_A (s, fp, string);
+          break;
+
+        case FMT_AHEX:
+          ok = convert_AHEX (s, fp, string);
+          break;
+
+        default:
+          assert (0);
+          abort ();
+        }
     }
 
-  /* 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
+  /* Error handling. */
+  if (!ok)
+    strncpy (s, "ERROR", fp->w);
+}
+
+/* Converts V into S in F format with width W and D decimal places,
+   then deletes trailing zeros.  S is not null-terminated. */
+void
+num_to_string (double v, char *s, int w, int d)
+{
+  struct fmt_spec f = make_output_format (FMT_F, w, d);
+  convert_F (s, &f, v);
 }
 \f
 /* Main conversion functions. */
@@ -204,21 +211,6 @@ static int try_CCx (char *s, const struct fmt_spec *fp, double v);
 #error Write your own floating-point output routines.
 #endif
 
-/* 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".  */
-
 /* Converts a number between 0 and 15 inclusive to a `hexit'
    [0-9A-F]. */
 #define MAKE_HEXIT(X) ("0123456789ABCDEF"[X])
@@ -235,18 +227,18 @@ static const double power10[] =
 
 /* Handles F format. */
 static int
-convert_F (char *dst, const struct fmt_spec *fp, const union value *v)
+convert_F (char *dst, const struct fmt_spec *fp, double number)
 {
-  if (!try_F (dst, fp, v))
-    convert_E (dst, fp, v);
+  if (!try_F (dst, fp, number))
+    convert_E (dst, fp, number);
   return 1;
 }
 
 /* Handles N format. */
 static int
-convert_N (char *dst, const struct fmt_spec *fp, const union value *v)
+convert_N (char *dst, const struct fmt_spec *fp, double number)
 {
-  double d = floor (v->f);
+  double d = floor (number);
 
   if (d < 0 || d == SYSMIS)
     {
@@ -258,7 +250,7 @@ convert_N (char *dst, const struct fmt_spec *fp, const union value *v)
   if (d < power10[fp->w])
     {
       char buf[128];
-      sprintf (buf, "%0*.0f", fp->w, v->f);
+      sprintf (buf, "%0*.0f", fp->w, number);
       memcpy (dst, buf, fp->w);
     }
   else
@@ -270,7 +262,7 @@ convert_N (char *dst, const struct fmt_spec *fp, const union value *v)
 /* Handles E format.  Also operates as fallback for some other
    formats. */
 static int
-convert_E (char *dst, const struct fmt_spec *fp, const union value *v)
+convert_E (char *dst, const struct fmt_spec *fp, double number)
 {
   /* Temporary buffer. */
   char buf[128];
@@ -278,7 +270,10 @@ convert_E (char *dst, const struct fmt_spec *fp, const union value *v)
   /* Ranged number of decimal places. */
   int d;
 
-  /* Check that the format is width enough.
+  if (!finite (number))
+    return convert_infinite (dst, fp, number);
+
+  /* Check that the format is wide enough.
      Although PSPP generally checks this, convert_E() can be called as
      a fallback from other formats which do not check. */
   if (fp->w < 6)
@@ -289,11 +284,11 @@ convert_E (char *dst, const struct fmt_spec *fp, const union value *v)
 
   /* Put decimal places in usable range. */
   d = min (fp->d, fp->w - 6);
-  if (v->f < 0)
+  if (number < 0)
     d--;
   if (d < 0)
     d = 0;
-  sprintf (buf, "%*.*E", fp->w, d, v->f);
+  sprintf (buf, "%*.*E", fp->w, d, number);
 
   /* What we do here is force the exponent part to have four
      characters whenever possible.  That is, 1.00E+99 is okay (`E+99')
@@ -301,7 +296,7 @@ convert_E (char *dst, const struct fmt_spec *fp, const union value *v)
      the other hand, 1.00E1000 (`E+100') cannot be canonicalized.
      Note that ANSI C guarantees at least two digits in the
      exponent. */
-  if (fabs (v->f) > 1e99)
+  if (fabs (number) > 1e99)
     {
       /* Pointer to the `E' in buf. */
       char *cp;
@@ -327,8 +322,8 @@ convert_E (char *dst, const struct fmt_spec *fp, const union value *v)
 
   /* The C locale always uses a period `.' as a decimal point.
      Translate to comma if necessary. */
-  if ((set_decimal == ',' && fp->type != FMT_DOT)
-      || (set_decimal == '.' && fp->type == FMT_DOT))
+  if ((get_decimal() == ',' && fp->type != FMT_DOT)
+      || (get_decimal() == '.' && fp->type == FMT_DOT))
     {
       char *cp = strchr (buf, '.');
       if (cp)
@@ -341,31 +336,33 @@ convert_E (char *dst, const struct fmt_spec *fp, const union value *v)
 
 /* Handles COMMA, DOT, DOLLAR, and PCT formats. */
 static int
-convert_F_plus (char *dst, const struct fmt_spec *fp, const union value *v)
+convert_F_plus (char *dst, const struct fmt_spec *fp, double number)
 {
   char buf[40];
   
-  if (try_F (buf, fp, v))
+  if (try_F (buf, fp, number))
     insert_commas (dst, buf, fp);
   else
-    convert_E (dst, fp, v);
+    convert_E (dst, fp, number);
 
   return 1;
 }
 
 static int
-convert_Z (char *dst, const struct fmt_spec *fp, const union value *v)
+convert_Z (char *dst, const struct fmt_spec *fp, double number)
 {
   static int warned = 0;
 
   if (!warned)
     {
-      msg (MW, _("Quality of zoned decimal (Z) output format code is "
-                "suspect.  Check your results, report bugs to author."));
+      msg (MW, 
+       _("Quality of zoned decimal (Z) output format code is "
+          "suspect.  Check your results. Report bugs to %s."),
+       PACKAGE_BUGREPORT);
       warned = 1;
     }
 
-  if (v->f == SYSMIS)
+  if (number == SYSMIS)
     {
       msg (ME, _("The system-missing value cannot be output as a zoned "
                 "decimal number."));
@@ -377,18 +374,18 @@ convert_Z (char *dst, const struct fmt_spec *fp, const union value *v)
     double d;
     int i;
     
-    d = fabs (floor (v->f));
+    d = fabs (floor (number));
     if (d >= power10[fp->w])
       {
        msg (ME, _("Number %g too big to fit in field with format Z%d.%d."),
-            v->f, fp->w, fp->d);
+            number, fp->w, fp->d);
        return 0;
       }
 
-    sprintf (buf, "%*.0f", fp->w, v->f);
+    sprintf (buf, "%*.0f", fp->w, number);
     for (i = 0; i < fp->w; i++)
       dst[i] = (buf[i] - '0') | 0xf0;
-    if (v->f < 0)
+    if (number < 0)
       dst[fp->w - 1] &= 0xdf;
   }
 
@@ -396,32 +393,32 @@ convert_Z (char *dst, const struct fmt_spec *fp, const union value *v)
 }
 
 static int
-convert_A (char *dst, const struct fmt_spec *fp, const union value *v)
+convert_A (char *dst, const struct fmt_spec *fp, const char *string)
 {
-  memcpy (dst, v->c, fp->w);
+  memcpy (dst, string, fp->w);
   return 1;
 }
 
 static int
-convert_AHEX (char *dst, const struct fmt_spec *fp, const union value *v)
+convert_AHEX (char *dst, const struct fmt_spec *fp, const char *string)
 {
   int i;
 
   for (i = 0; i < fp->w / 2; i++)
     {
-      ((unsigned char *) dst)[i * 2] = MAKE_HEXIT ((v->c[i]) >> 4);
-      ((unsigned char *) dst)[i * 2 + 1] = MAKE_HEXIT ((v->c[i]) & 0xf);
+      *dst++ = MAKE_HEXIT ((string[i]) >> 4);
+      *dst++ = MAKE_HEXIT ((string[i]) & 0xf);
     }
 
   return 1;
 }
 
 static int
-convert_IB (char *dst, const struct fmt_spec *fp, const union value *v)
+convert_IB (char *dst, const struct fmt_spec *fp, double number)
 {
-  /* Strategy: Basically the same as convert_PIBHEX() but with base
-     256. Then it's necessary to negate the two's-complement result if
-     v->f is negative. */
+  /* Strategy: Basically the same as convert_PIBHEX() but with
+     base 256. Then negate the two's-complement result if number
+     is negative. */
 
   /* Used for constructing the two's-complement result. */
   unsigned temp[8];
@@ -439,7 +436,7 @@ convert_IB (char *dst, const struct fmt_spec *fp, const union value *v)
   int i;
 
   /* Make the exponent (-8*fp->w-1). */
-  frac = frexp (fabs (v->f), &exp);
+  frac = frexp (fabs (number), &exp);
   diff = exp - (-8 * fp->w - 1);
   exp -= diff;
   frac *= ldexp (1.0, diff);
@@ -452,8 +449,8 @@ convert_IB (char *dst, const struct fmt_spec *fp, const union value *v)
       temp[i] = floor (frac);
     }
 
-  /* Perform two's-complement negation if v->f is negative. */
-  if (v->f < 0)
+  /* Perform two's-complement negation if number is negative. */
+  if (number < 0)
     {
       /* Perform NOT operation. */
       for (i = 0; i < fp->w; i++)
@@ -468,16 +465,16 @@ convert_IB (char *dst, const struct fmt_spec *fp, const union value *v)
     }
   memcpy (dst, temp, fp->w);
 #ifndef WORDS_BIGENDIAN
-  mm_reverse (dst, fp->w);
+  buf_reverse (dst, fp->w);
 #endif
 
   return 1;
 }
 
 static int
-convert_P (char *dst, const struct fmt_spec *fp, const union value *v)
+convert_P (char *dst, const struct fmt_spec *fp, double number)
 {
-  /* Buffer for v->f*2-1 characters + a decimal point if library is
+  /* Buffer for fp->w*2-1 characters + a decimal point if library is
      not quite compliant + a null. */
   char buf[17];
 
@@ -485,7 +482,7 @@ convert_P (char *dst, const struct fmt_spec *fp, const union value *v)
   int i;
 
   /* Main extraction. */
-  sprintf (buf, "%0*.0f", fp->w * 2 - 1, floor (fabs (v->f)));
+  sprintf (buf, "%0*.0f", fp->w * 2 - 1, floor (fabs (number)));
 
   for (i = 0; i < fp->w; i++)
     ((unsigned char *) dst)[i]
@@ -493,7 +490,7 @@ convert_P (char *dst, const struct fmt_spec *fp, const union value *v)
 
   /* Set sign. */
   dst[fp->w - 1] &= 0xf0;
-  if (v->f >= 0.0)
+  if (number >= 0.0)
     dst[fp->w - 1] |= 0xf;
   else
     dst[fp->w - 1] |= 0xd;
@@ -502,7 +499,7 @@ convert_P (char *dst, const struct fmt_spec *fp, const union value *v)
 }
 
 static int
-convert_PIB (char *dst, const struct fmt_spec *fp, const union value *v)
+convert_PIB (char *dst, const struct fmt_spec *fp, double number)
 {
   /* Strategy: Basically the same as convert_IB(). */
 
@@ -519,7 +516,7 @@ convert_PIB (char *dst, const struct fmt_spec *fp, const union value *v)
   int i;
 
   /* Make the exponent (-8*fp->w). */
-  frac = frexp (fabs (v->f), &exp);
+  frac = frexp (fabs (number), &exp);
   diff = exp - (-8 * fp->w);
   exp -= diff;
   frac *= ldexp (1.0, diff);
@@ -532,14 +529,14 @@ convert_PIB (char *dst, const struct fmt_spec *fp, const union value *v)
       ((unsigned char *) dst)[i] = floor (frac);
     }
 #ifndef WORDS_BIGENDIAN
-  mm_reverse (dst, fp->w);
+  buf_reverse (dst, fp->w);
 #endif
 
   return 1;
 }
 
 static int
-convert_PIBHEX (char *dst, const struct fmt_spec *fp, const union value *v)
+convert_PIBHEX (char *dst, const struct fmt_spec *fp, double number)
 {
   /* Strategy: Use frexp() to create a normalized result (but mostly
      to find the base-2 exponent), then change the base-2 exponent to
@@ -559,7 +556,7 @@ convert_PIBHEX (char *dst, const struct fmt_spec *fp, const union value *v)
   int i;
 
   /* Make the exponent (-4*fp->w). */
-  frac = frexp (fabs (v->f), &exp);
+  frac = frexp (fabs (number), &exp);
   diff = exp - (-4 * fp->w);
   exp -= diff;
   frac *= ldexp (1.0, diff);
@@ -576,9 +573,9 @@ convert_PIBHEX (char *dst, const struct fmt_spec *fp, const union value *v)
 }
 
 static int
-convert_PK (char *dst, const struct fmt_spec *fp, const union value *v)
+convert_PK (char *dst, const struct fmt_spec *fp, double number)
 {
-  /* Buffer for v->f*2 characters + a decimal point if library is not
+  /* Buffer for fp->w*2 characters + a decimal point if library is not
      quite compliant + a null. */
   char buf[18];
 
@@ -586,7 +583,7 @@ convert_PK (char *dst, const struct fmt_spec *fp, const union value *v)
   int i;
 
   /* Main extraction. */
-  sprintf (buf, "%0*.0f", fp->w * 2, floor (fabs (v->f)));
+  sprintf (buf, "%0*.0f", fp->w * 2, floor (fabs (number)));
 
   for (i = 0; i < fp->w; i++)
     ((unsigned char *) dst)[i]
@@ -596,7 +593,7 @@ convert_PK (char *dst, const struct fmt_spec *fp, const union value *v)
 }
 
 static int
-convert_RB (char *dst, const struct fmt_spec *fp, const union value *v)
+convert_RB (char *dst, const struct fmt_spec *fp, double number)
 {
   union
     {
@@ -605,14 +602,14 @@ convert_RB (char *dst, const struct fmt_spec *fp, const union value *v)
     }
   u;
 
-  u.d = v->f;
+  u.d = number;
   memcpy (dst, u.c, fp->w);
 
   return 1;
 }
 
 static int
-convert_RBHEX (char *dst, const struct fmt_spec *fp, const union value *v)
+convert_RBHEX (char *dst, const struct fmt_spec *fp, double number)
 {
   union
   {
@@ -623,7 +620,7 @@ convert_RBHEX (char *dst, const struct fmt_spec *fp, const union value *v)
 
   int i;
 
-  u.d = v->f;
+  u.d = number;
   for (i = 0; i < fp->w / 2; i++)
     {
       *dst++ = MAKE_HEXIT (u.c[i] >> 4);
@@ -634,9 +631,9 @@ convert_RBHEX (char *dst, const struct fmt_spec *fp, const union value *v)
 }
 
 static int
-convert_CCx (char *dst, const struct fmt_spec *fp, const union value *v)
+convert_CCx (char *dst, const struct fmt_spec *fp, double number)
 {
-  if (try_CCx (dst, fp, v->f))
+  if (try_CCx (dst, fp, number))
     return 1;
   else
     {
@@ -646,12 +643,12 @@ convert_CCx (char *dst, const struct fmt_spec *fp, const union value *v)
       f.w = fp->w;
       f.d = fp->d;
   
-      return convert_F (dst, &f, v);
+      return convert_F_plus (dst, &f, number);
     }
 }
 
 static int
-convert_date (char *dst, const struct fmt_spec *fp, const union value *v)
+convert_date (char *dst, const struct fmt_spec *fp, double number)
 {
   static const char *months[12] =
     {
@@ -660,9 +657,13 @@ convert_date (char *dst, const struct fmt_spec *fp, const union value *v)
     };
 
   char buf[64] = {0};
+  int ofs = number / 86400.;
   int month, day, year;
 
-  julian_to_calendar (v->f / 86400., &year, &month, &day);
+  if (ofs < 1)
+    return 0;
+
+  calendar_offset_to_gregorian (ofs, &year, &month, &day);
   switch (fp->type)
     {
     case FMT_DATE:
@@ -691,15 +692,13 @@ convert_date (char *dst, const struct fmt_spec *fp, const union value *v)
       break;
     case FMT_JDATE:
       {
-       int yday = (v->f / 86400.) - calendar_to_julian (year, 1, 1) + 1;
+        int yday = calendar_offset_to_yday (ofs);
        
-       if (fp->w >= 7)
-         {
-           if (year4 (year))
-             sprintf (buf, "%04d%03d", year, yday);
-         }
-       else
-         sprintf (buf, "%02d%03d", year % 100, yday);
+        if (fp->w < 7)
+          sprintf (buf, "%02d%03d", year % 100, yday); 
+        else if (year4 (year))
+          sprintf (buf, "%04d%03d", year, yday);
+        else
        break;
       }
     case FMT_QYR:
@@ -716,7 +715,7 @@ convert_date (char *dst, const struct fmt_spec *fp, const union value *v)
       break;
     case FMT_WKYR:
       {
-       int yday = (v->f / 86400.) - calendar_to_julian (year, 1, 1) + 1;
+       int yday = calendar_offset_to_yday (ofs);
        
        if (fp->w >= 10)
          sprintf (buf, "%02d WK% 04d", (yday - 1) / 7 + 1, year);
@@ -730,8 +729,8 @@ convert_date (char *dst, const struct fmt_spec *fp, const union value *v)
 
        cp = spprintf (buf, "%02d-%s-%04d %02d:%02d",
                       day, months[month - 1], year,
-                      (int) fmod (floor (v->f / 60. / 60.), 24.),
-                      (int) fmod (floor (v->f / 60.), 60.));
+                      (int) fmod (floor (number / 60. / 60.), 24.),
+                      (int) fmod (floor (number / 60.), 60.));
        if (fp->w >= 20)
          {
            int w, d;
@@ -747,7 +746,7 @@ convert_date (char *dst, const struct fmt_spec *fp, const union value *v)
                d = 0;
              }
 
-           cp = spprintf (cp, ":%0*.*f", w, d, fmod (v->f, 60.));
+           cp = spprintf (cp, ":%0*.*f", w, d, fmod (number, 60.));
          }
       }
       break;
@@ -757,12 +756,12 @@ convert_date (char *dst, const struct fmt_spec *fp, const union value *v)
 
   if (buf[0] == 0)
     return 0;
-  st_bare_pad_copy (dst, buf, fp->w);
+  buf_copy_str_rpad (dst, fp->w, buf);
   return 1;
 }
 
 static int
-convert_time (char *dst, const struct fmt_spec *fp, const union value *v)
+convert_time (char *dst, const struct fmt_spec *fp, double number)
 {
   char temp_buf[40];
   char *cp;
@@ -770,14 +769,14 @@ convert_time (char *dst, const struct fmt_spec *fp, const union value *v)
   double time;
   int width;
 
-  if (fabs (v->f) > 1e20)
+  if (fabs (number) > 1e20)
     {
       msg (ME, _("Time value %g too large in magnitude to convert to "
-          "alphanumeric time."), v->f);
+          "alphanumeric time."), number);
       return 0;
     }
 
-  time = v->f;
+  time = number;
   width = fp->w;
   cp = temp_buf;
   if (time < 0)
@@ -807,13 +806,13 @@ convert_time (char *dst, const struct fmt_spec *fp, const union value *v)
 
       cp = spprintf (cp, ":%0*.*f", w, d, fmod (time, 60.));
     }
-  st_bare_pad_copy (dst, temp_buf, fp->w);
+  buf_copy_str_rpad (dst, fp->w, temp_buf);
 
   return 1;
 }
 
 static int
-convert_WKDAY (char *dst, const struct fmt_spec *fp, const union value *v)
+convert_WKDAY (char *dst, const struct fmt_spec *fp, double wkday)
 {
   static const char *weekdays[7] =
     {
@@ -821,20 +820,19 @@ convert_WKDAY (char *dst, const struct fmt_spec *fp, const union value *v)
       "THURSDAY", "FRIDAY", "SATURDAY",
     };
 
-  int x = v->f;
-
-  if (x < 1 || x > 7)
+  if (wkday < 1 || wkday > 7)
     {
-      msg (ME, _("Weekday index %d does not lie between 1 and 7."), x);
+      msg (ME, _("Weekday index %f does not lie between 1 and 7."),
+           (double) wkday);
       return 0;
     }
-  st_bare_pad_copy (dst, weekdays[x - 1], fp->w);
+  buf_copy_str_rpad (dst, fp->w, weekdays[(int) wkday - 1]);
 
   return 1;
 }
 
 static int
-convert_MONTH (char *dst, const struct fmt_spec *fp, const union value *v)
+convert_MONTH (char *dst, const struct fmt_spec *fp, double month)
 {
   static const char *months[12] =
     {
@@ -842,15 +840,14 @@ convert_MONTH (char *dst, const struct fmt_spec *fp, const union value *v)
       "JULY", "AUGUST", "SEPTEMBER", "OCTOBER", "NOVEMBER", "DECEMBER",
     };
 
-  int x = v->f;
-
-  if (x < 1 || x > 12)
+  if (month < 1 || month > 12)
     {
-      msg (ME, _("Month index %d does not lie between 1 and 12."), x);
+      msg (ME, _("Month index %f does not lie between 1 and 12."),
+           month);
       return 0;
     }
   
-  st_bare_pad_copy (dst, months[x - 1], fp->w);
+  buf_copy_str_rpad (dst, fp->w, months[(int) month - 1]);
 
   return 1;
 }
@@ -940,7 +937,7 @@ insert_commas (char *dst, const char *src, const struct fmt_spec *fp)
       if (i % 3 == 0 && n_digits > i && n_items > n_reserved)
        {
          n_items--;
-         *dst++ = fp->type == FMT_COMMA ? set_grouping : set_decimal;
+         *dst++ = fp->type == FMT_COMMA ? get_grouping() : get_decimal();
        }
       *dst++ = *sp++;
     }
@@ -964,9 +961,9 @@ year4 (int year)
 }
 
 static int
-try_CCx (char *dst, const struct fmt_spec *fp, double v)
+try_CCx (char *dst, const struct fmt_spec *fp, double number)
 {
-  struct set_cust_currency *cc = &set_cc[fp->type - FMT_CCA];
+  const struct set_cust_currency *cc = get_cc(fp->type - FMT_CCA);
 
   struct fmt_spec f;
 
@@ -976,13 +973,13 @@ try_CCx (char *dst, const struct fmt_spec *fp, double v)
 
   /* Determine length available, decimal character for number
      proper. */
-  f.type = cc->decimal == set_decimal ? FMT_COMMA : FMT_DOT;
+  f.type = cc->decimal == get_decimal() ? FMT_COMMA : FMT_DOT;
   f.w = fp->w - strlen (cc->prefix) - strlen (cc->suffix);
-  if (v < 0)
+  if (number < 0)
     f.w -= strlen (cc->neg_prefix) + strlen (cc->neg_suffix) - 1;
   else
     /* Convert -0 to +0. */
-    v = fabs (v);
+    number = fabs (number);
   f.d = fp->d;
 
   if (f.w <= 0)
@@ -990,13 +987,13 @@ try_CCx (char *dst, const struct fmt_spec *fp, double v)
 
   /* There's room for all that currency crap.  Let's do the F
      conversion first. */
-  if (!convert_F (buf, &f, (union value *) &v) || *buf == '*')
+  if (!convert_F (buf, &f, number) || *buf == '*')
     return 0;
   insert_commas (buf2, buf, &f);
 
   /* Postprocess back into buf. */
   cp = buf;
-  if (v < 0)
+  if (number < 0)
     cp = stpcpy (cp, cc->neg_prefix);
   cp = stpcpy (cp, cc->prefix);
   {
@@ -1004,15 +1001,15 @@ try_CCx (char *dst, const struct fmt_spec *fp, double v)
     while (*bp == ' ')
       bp++;
 
-    assert ((v >= 0) ^ (*bp == '-'));
-    if (v < 0)
+    assert ((number >= 0) ^ (*bp == '-'));
+    if (number < 0)
       bp++;
 
     memcpy (cp, bp, f.w - (bp - buf2));
     cp += f.w - (bp - buf2);
   }
   cp = stpcpy (cp, cc->suffix);
-  if (v < 0)
+  if (number < 0)
     cp = stpcpy (cp, cc->neg_suffix);
 
   /* Copy into dst. */
@@ -1028,202 +1025,230 @@ try_CCx (char *dst, const struct fmt_spec *fp, double v)
   return 1;
 }
 
-/* This routine relies on the underlying implementation of sprintf:
-
-   If the number has a magnitude 1e40 or greater, then we needn't
-   bother with it, since it's guaranteed to need processing in
-   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).
+static int
+format_and_round (char *dst, double number, const struct fmt_spec *fp,
+                  int decimals);
 
-   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! */
+/* Tries to format NUMBER into DST as the F format specified in
+   *FP.  Return true if successful, false on failure. */
 static int
-try_F (char *dst, const struct fmt_spec *fp, const union value *value)
+try_F (char *dst, const struct fmt_spec *fp, double number)
 {
-  /* This is the DELTA array from Knuth.
-     DELTA[j] = floor((40+2**(j-1))/(2**j)). */
-  static const int delta[8] =
-  {
-    0, (40 + 1) / 2, (40 + 2) / 4, (40 + 4) / 8, (40 + 8) / 16,
-    (40 + 16) / 32, (40 + 32) / 64, (40 + 64) / 128,
-  };
+  assert (fp->w <= 40);
+  if (finite (number)) 
+    {
+      if (fabs (number) < power10[fp->w])
+        {
+          /* The value may fit in the field. */
+          if (fp->d == 0) 
+            {
+              /* There are no decimal places, so there's no way
+                 that the value can be shortened.  Either it fits
+                 or it doesn't. */
+              char buf[41];
+              sprintf (buf, "%*.0f", fp->w, number);
+              if (strlen (buf) <= fp->w) 
+                {
+                  buf_copy_str_lpad (dst, fp->w, buf);
+                  return true; 
+                }
+              else 
+                return false;
+            }
+          else 
+            {
+              /* First try to format it with 2 extra decimal
+                 places.  This gives us a good chance of not
+                 needing even more decimal places, but it also
+                 avoids wasting too much time formatting more
+                 decimal places on the first try. */
+              int result = format_and_round (dst, number, fp, fp->d + 2);
+              if (result >= 0)
+                return result;
+
+              /* 2 extra decimal places weren't enough to
+                 correctly round.  Try again with the maximum
+                 number of places. */
+              return format_and_round (dst, number, fp, LDBL_DIG + 1);
+            }
+        }
+      else 
+        {
+          /* The value is too big to fit in the field. */
+          return false;
+        }
+    }
+  else
+    return convert_infinite (dst, fp, number);
+}
 
-  /* The number of digits in floor(v), including sign.  This is `i'
-     from Knuth. */
-  int n_int = (40 + 1) / 2;
+/* Tries to compose NUMBER into DST in format FP by first
+   formatting it with DECIMALS decimal places, then rounding off
+   to as many decimal places will fit or the number specified in
+   FP, whichever is fewer.
 
-  /* Used to step through delta[].  This is `j' from Knuth. */
-  int j = 2;
+   Returns 1 if conversion succeeds, 0 if this try at conversion
+   failed and so will any other tries (because the integer part
+   of the number is too long), or -1 if this try failed but
+   another with higher DECIMALS might succeed (because we'd be
+   able to properly round). */
+static int
+format_and_round (char *dst, double number, const struct fmt_spec *fp,
+                  int decimals)
+{
+  /* Number of characters before the decimal point,
+     which includes digits and possibly a minus sign. */
+  int predot_chars;
 
-  /* Value. */
-  double v = value->f;
+  /* Number of digits in the output fraction,
+     which may be smaller than fp->d if there's not enough room. */
+  int fraction_digits;
 
-  /* Magnitude of v.  This is `K' from Knuth. */
-  double mag;
+  /* Points to last digit that will remain in the fraction after
+     rounding. */
+  char *final_frac_dig;
 
-  /* Number of characters for the fractional part, including the
-     decimal point. */
-  int n_dec;
+  /* Round up? */
+  bool round_up;
+  
+  char buf[128];
+  
+  assert (decimals > fp->d);
+  if (decimals > LDBL_DIG)
+    decimals = LDBL_DIG + 1;
 
-  /* Pointer into buf used for formatting. */
-  char *cp;
+  sprintf (buf, "%.*f", decimals, number);
 
-  /* Used to count characters formatted by nsprintf(). */
-  int n;
+  /* Omit integer part if it's 0. */
+  if (!memcmp (buf, "0.", 2))
+    memmove (buf, buf + 1, strlen (buf));
+  else if (!memcmp (buf, "-0.", 3))
+    memmove (buf + 1, buf + 2, strlen (buf + 1));
 
-  /* Temporary buffer. */
-  char buf[128];
-
-  /* First check for infinities and NaNs.  12/13/96. */
-  if (!finite (v))
+  predot_chars = strcspn (buf, ".");
+  if (predot_chars > fp->w) 
     {
-      n = nsprintf (buf, "%f", v);
-      if (n > fp->w)
-       memset (buf, '*', fp->w);
-      else if (n < fp->w)
-       {
-         memmove (&buf[fp->w - n], buf, n);
-         memset (buf, ' ', fp->w - n);
-       }
+      /* Can't possibly fit. */
+      return 0; 
+    }
+  else if (predot_chars == fp->w)
+    {
+      /* Exact fit for integer part and sign. */
       memcpy (dst, buf, fp->w);
       return 1;
     }
+  else if (predot_chars + 1 == fp->w) 
+    {
+      /* There's room for the decimal point, but not for any
+         digits of the fraction.
+         Right-justify the integer part and sign. */
+      dst[0] = ' ';
+      memcpy (dst + 1, buf, fp->w);
+      return 1;
+    }
 
-  /* Then check for radically out-of-range values. */
-  mag = fabs (v);
-  if (mag >= power10[fp->w])
-    return 0;
+  /* It looks like we have room for at least one digit of the
+     fraction.  Figure out how many. */
+  fraction_digits = fp->w - predot_chars - 1;
+  if (fraction_digits > fp->d)
+    fraction_digits = fp->d;
+  final_frac_dig = buf + predot_chars + fraction_digits;
 
-  if (mag < 1.0)
+  /* Decide rounding direction and truncate string. */
+  if (final_frac_dig[1] == '5'
+      && strspn (final_frac_dig + 2, "0") == strlen (final_frac_dig + 2)) 
     {
-      n_int = 0;
-
-      /* Avoid printing `-.000'. 7/6/96. */
-      if (approx_eq (v, 0.0))
-       v = 0.0;
+      /* Exactly 1/2. */
+      if (decimals <= LDBL_DIG)
+        {
+          /* Don't have enough fractional digits to know which way to
+             round.  We can format with more decimal places, so go
+             around again. */
+          return -1;
+        }
+      else 
+        {
+          /* We used up all our fractional digits and still don't
+             know.  Round to even. */
+          round_up = (final_frac_dig[0] - '0') % 2 != 0;
+        }
     }
   else
-    /* Now perform a `uniform binary search' based on the tables
-       power10[] and delta[].  After this step, nint is the number of
-       digits in floor(v), including any sign.  */
-    for (;;)
-      {
-       if (mag >= power10[n_int])      /* Should this be approx_ge()? */
-         {
-           assert (delta[j]);
-           n_int += delta[j++];
-         }
-       else if (mag < power10[n_int - 1])
-         {
-           assert (delta[j]);
-           n_int -= delta[j++];
-         }
-       else
-         break;
-      }
+    round_up = final_frac_dig[1] >= '5';
+  final_frac_dig[1] = '\0';
 
-  /* If we have any decimal places, then there is a decimal point,
-     too. */
-  n_dec = fp->d;
-  if (n_dec)
-    n_dec++;
+  /* Do rounding. */
+  if (round_up) 
+    {
+      char *cp = final_frac_dig;
+      for (;;) 
+        {
+          if (*cp >= '0' && *cp <= '8')
+            {
+              (*cp)++;
+              break; 
+            }
+          else if (*cp == '9') 
+            *cp = '0';
+          else
+            assert (*cp == '.');
+
+          if (cp == buf || *--cp == '-')
+            {
+              size_t length;
+              
+              /* Tried to go past the leftmost digit.  Insert a 1. */
+              memmove (cp + 1, cp, strlen (cp) + 1);
+              *cp = '1';
+
+              length = strlen (buf);
+              if (length > fp->w) 
+                {
+                  /* Inserting the `1' overflowed our space.
+                     Drop a decimal place. */
+                  buf[--length] = '\0';
+
+                  /* If that was the last decimal place, drop the
+                     decimal point too. */
+                  if (buf[length - 1] == '.')
+                    buf[length - 1] = '\0';
+                }
+              
+              break;
+            }
+        }
+    }
 
-  /* 1/10/96: If there aren't any digits at all, add one.  This occurs
-     only when fabs(v) < 1.0. */
-  if (n_int + n_dec == 0)
-    n_int++;
+  /* Omit `-' if value output is zero. */
+  if (buf[0] == '-' && buf[strspn (buf, "-.0")] == '\0')
+    memmove (buf, buf + 1, strlen (buf));
 
-  /* Give space for a minus sign.  Moved 1/10/96. */
-  if (v < 0)
-    n_int++;
+  buf_copy_str_lpad (dst, fp->w, buf);
+  return 1;
+}
 
-  /* Normally we only go through the loop once; occasionally twice.
-     Three times or more indicates a very serious bug somewhere. */
-  for (;;)
+/* Formats non-finite NUMBER into DST according to the width
+   given in FP. */
+static int
+convert_infinite (char *dst, const struct fmt_spec *fp, double number)
+{
+  assert (!finite (number));
+  
+  if (fp->w >= 3)
     {
-      /* Check out the total length of the string. */
-      cp = buf;
-      if (n_int + n_dec > fp->w)
-       {
-         /* The string is too long.  Let's see what can be done. */
-         if (n_int <= fp->w)
-           /* 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;
-       }
+      const char *s;
 
-      /* Finally, format the number. */
-      if (n_dec)
-       n = nsprintf (cp, "%.*f", n_dec - 1, v);
+      if (isnan (number))
+        s = "NaN";
+      else if (isinf (number))
+        s = number > 0 ? "+Infinity" : "-Infinity";
       else
-       n = nsprintf (cp, "%.0f", v);
+        s = "Unknown";
 
-      /* If v is positive and its magnitude is less than 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 v is negative and its magnitude is less than 1...  */
-      else if (v < 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 ((set_decimal == ',' && fp->type != FMT_DOT)
-             || (set_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? */
+      buf_copy_str_lpad (dst, fp->w, s);
     }
+  else 
+    memset (dst, '*', fp->w);
+
+  return true;
 }