changed abs to labs function to avoid warning
[pspp] / src / data / data-out.c
index 8b94da03de39ea527a5ee4e65576a1144839a0c5..3f3611f00da2c032587c53971403e73ff502e35f 100644 (file)
@@ -1,25 +1,22 @@
-/* PSPP - computes sample statistics.
-   Copyright (C) 1997-9, 2000, 2006 Free Software Foundation, Inc.
-   Written by Ben Pfaff <blp@gnu.org>.
+/* PSPP - a program for statistical analysis.
+   Copyright (C) 1997-9, 2000, 2006, 2009, 2011, 2012, 2013, 2014 Free Software Foundation, Inc.
 
-   This program is free software; you can redistribute it and/or
-   modify it under the terms of the GNU General Public License as
-   published by the Free Software Foundation; either version 2 of the
-   License, or (at your option) any later version.
+   This program is free software: you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation, either version 3 of the License, or
+   (at your option) any later version.
 
-   This program is distributed in the hope that it will be useful, but
-   WITHOUT ANY WARRANTY; without even the implied warranty of
-   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-   General Public License for more details.
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
 
    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., 51 Franklin Street, Fifth Floor, Boston, MA
-   02110-1301, USA. */
+   along with this program.  If not, see <http://www.gnu.org/licenses/>. */
 
 #include <config.h>
 
-#include "data-out.h"
+#include "data/data-out.h"
 
 #include <ctype.h>
 #include <float.h>
 #include <stdint.h>
 #include <stdlib.h>
 #include <time.h>
-
-#include "calendar.h"
-#include "format.h"
-#include "settings.h"
-#include "variable.h"
-
-#include <libpspp/assertion.h>
-#include <libpspp/float-format.h>
-#include <libpspp/integer-format.h>
-#include <libpspp/magic.h>
-#include <libpspp/message.h>
-#include <libpspp/misc.h>
-#include <libpspp/misc.h>
-#include <libpspp/str.h>
-
-#include "minmax.h"
+#include <unistr.h>
+
+#include "data/calendar.h"
+#include "data/format.h"
+#include "data/settings.h"
+#include "data/value.h"
+#include "libpspp/assertion.h"
+#include "libpspp/cast.h"
+#include "libpspp/float-format.h"
+#include "libpspp/i18n.h"
+#include "libpspp/integer-format.h"
+#include "libpspp/message.h"
+#include "libpspp/misc.h"
+#include "libpspp/pool.h"
+#include "libpspp/str.h"
+
+#include "gl/minmax.h"
+#include "gl/c-snprintf.h"
 
 #include "gettext.h"
 #define _(msgid) gettext (msgid)
@@ -65,12 +64,6 @@ static int rounder_width (const struct rounder *, int decimals,
 static void rounder_format (const struct rounder *, int decimals,
                             char *output);
 \f
-/* Format of integers in output (SET WIB). */
-static enum integer_format output_integer_format = INTEGER_NATIVE;
-
-/* Format of reals in output (SET WRB). */
-static enum float_format output_float_format = FLOAT_NATIVE_DOUBLE;
-
 typedef void data_out_converter_func (const union value *,
                                       const struct fmt_spec *,
                                       char *);
@@ -94,52 +87,148 @@ static void output_binary_integer (uint64_t, int bytes, enum integer_format,
                                    char *);
 static void output_hex (const void *, size_t bytes, char *);
 \f
-/* Converts the INPUT value into printable form in the exactly
-   FORMAT->W characters in OUTPUT according to format
-   specification FORMAT.  No null terminator is appended to the
-   buffer.  */
-void
-data_out (const union value *input, const struct fmt_spec *format,
-          char *output)
-{
-  static data_out_converter_func *const converters[FMT_NUMBER_OF_FORMATS] = 
+
+static data_out_converter_func *const converters[FMT_NUMBER_OF_FORMATS] =
     {
 #define FMT(NAME, METHOD, IMIN, OMIN, IO, CATEGORY) output_##METHOD,
 #include "format.def"
     };
 
-  assert (fmt_check_output (format));
+/* Converts the INPUT value, encoded in INPUT_ENCODING, according to format
+   specification FORMAT, appending the output to OUTPUT in OUTPUT_ENCODING.
+   However, binary formats (FMT_P, FMT_PK, FMT_IB, FMT_PIB, FMT_RB) yield the
+   binary results, which may not be properly encoded for OUTPUT_ENCODING.
 
-  converters[format->type] (input, format, output);
+   VALUE must be the correct width for FORMAT, that is, its width must be
+   fmt_var_width(FORMAT).
+
+   INPUT_ENCODING can normally be obtained by calling dict_get_encoding() on
+   the dictionary with which INPUT is associated.  ENCODING is only important
+   when FORMAT's type is FMT_A. */
+void
+data_out_recode (const union value *input, const char *input_encoding,
+                 const struct fmt_spec *format,
+                 struct string *output, const char *output_encoding)
+{
+  assert (fmt_check_output (format));
+  if (format->type == FMT_A)
+    {
+      char *in = CHAR_CAST (char *, value_str (input, format->w));
+      char *out = recode_string (output_encoding, input_encoding,
+                                 in, format->w);
+      ds_put_cstr (output, out);
+      free (out);
+    }
+  else if (fmt_get_category (format->type) == FMT_CAT_BINARY)
+    converters[format->type] (input, format,
+                              ds_put_uninit (output, format->w));
+  else
+    {
+      char *utf8_encoded = data_out (input, input_encoding, format);
+      char *output_encoded = recode_string (output_encoding, UTF8,
+                                            utf8_encoded, -1);
+      ds_put_cstr (output, output_encoded);
+      free (output_encoded);
+      free (utf8_encoded);
+    }
 }
 
-/* Returns the current output integer format. */
-enum integer_format
-data_out_get_integer_format (void) 
+static char *
+binary_to_utf8 (const char *in, struct pool *pool)
 {
-  return output_integer_format;
+  uint8_t *out = pool_alloc_unaligned (pool, strlen (in) * 2 + 1);
+  uint8_t *p = out;
+
+  while (*in != '\0')
+    {
+      uint8_t byte = *in++;
+      int mblen = u8_uctomb (p, byte, 2);
+      assert (mblen > 0);
+      p += mblen;
+    }
+  *p = '\0';
+
+  return CHAR_CAST (char *, out);
 }
 
-/* Sets the output integer format to INTEGER_FORMAT. */
-void
-data_out_set_integer_format (enum integer_format integer_format) 
+/* Converts the INPUT value into a UTF-8 encoded string, according to format
+   specification FORMAT.
+
+   VALUE must be the correct width for FORMAT, that is, its width must be
+   fmt_var_width(FORMAT).
+
+   ENCODING must be the encoding of INPUT.  Normally this can be obtained by
+   calling dict_get_encoding() on the dictionary with which INPUT is
+   associated.  ENCODING is only important when FORMAT's type is FMT_A.
+
+   The return value is dynamically allocated, and must be freed by the caller.
+   If POOL is non-null, then the return value is allocated on that pool.  */
+char *
+data_out_pool (const union value *input, const char *encoding,
+              const struct fmt_spec *format, struct pool *pool)
 {
-  output_integer_format = integer_format;
+  assert (fmt_check_output (format));
+  if (format->type == FMT_A)
+    {
+      char *in = CHAR_CAST (char *, value_str (input, format->w));
+      return recode_string_pool (UTF8, encoding, in, format->w, pool);
+    }
+  else if (fmt_get_category (format->type) == FMT_CAT_BINARY)
+    {
+      char tmp[16];
+
+      assert (format->w + 1 <= sizeof tmp);
+      converters[format->type] (input, format, tmp);
+      return binary_to_utf8 (tmp, pool);
+    }
+  else
+    {
+      const struct fmt_number_style *style = settings_get_style (format->type);
+      size_t size = format->w + style->extra_bytes + 1;
+      char *output;
+
+      output = pool_alloc_unaligned (pool, size);
+      converters[format->type] (input, format, output);
+      return output;
+    }
 }
 
-/* Returns the current output float format. */
-enum float_format
-data_out_get_float_format (void) 
+/* Like data_out_pool(), except that for basic numeric formats (F, COMMA, DOT,
+   COLLAR, PCT, E) and custom currency formats are formatted as wide as
+   necessary to fully display the selected number of decimal places. */
+char *
+data_out_stretchy (const union value *input, const char *encoding,
+                   const struct fmt_spec *format, struct pool *pool)
 {
-  return output_float_format;
+
+  if (fmt_get_category (format->type) & (FMT_CAT_BASIC | FMT_CAT_CUSTOM))
+    {
+      const struct fmt_number_style *style = settings_get_style (format->type);
+      struct fmt_spec wide_format;
+      char tmp[128];
+      size_t size;
+
+      wide_format.type = format->type;
+      wide_format.w = 40;
+      wide_format.d = format->d;
+
+      size = format->w + style->extra_bytes + 1;
+      if (size <= sizeof tmp)
+        {
+          output_number (input, &wide_format, tmp);
+          return pool_strdup (pool, tmp + strspn (tmp, " "));
+        }
+    }
+
+  return data_out_pool (input, encoding, format, pool);
 }
 
-/* Sets the output float format to FLOAT_FORMAT. */
-void
-data_out_set_float_format (enum float_format float_format) 
+char *
+data_out (const union value *input, const char *encoding, const struct fmt_spec *format)
 {
-  output_float_format = float_format;
+  return data_out_pool (input, encoding, format, NULL);
 }
+
 \f
 /* Main conversion functions. */
 
@@ -155,9 +244,9 @@ output_number (const union value *input, const struct fmt_spec *format,
     output_missing (format, output);
   else if (!isfinite (number))
     output_infinite (number, format, output);
-  else 
+  else
     {
-      if (format->type != FMT_E && fabs (number) < 1.5 * power10 (format->w)) 
+      if (format->type != FMT_E && fabs (number) < 1.5 * power10 (format->w))
         {
           struct rounder r;
           rounder_init (&r, number, format->d);
@@ -186,11 +275,13 @@ output_N (const union value *input, const struct fmt_spec *format,
       char buf[128];
       number = fabs (round (number));
       if (number < power10 (format->w)
-          && sprintf (buf, "%0*.0f", format->w, number) == format->w)
+          && c_snprintf (buf, 128, "%0*.0f", format->w, number) == format->w)
         memcpy (output, buf, format->w);
       else
         output_overflow (format, output);
     }
+
+  output[format->w] = '\0';
 }
 
 /* Outputs Z format. */
@@ -202,19 +293,20 @@ output_Z (const union value *input, const struct fmt_spec *format,
   char buf[128];
   if (input->f == SYSMIS)
     output_missing (format, output);
-  else if (fabs (number) >= power10 (format->w)
-           || sprintf (buf, "%0*.0f", format->w,
-                       fabs (round (number))) != format->w)
-    output_overflow (format, output);
-  else
+  else if (fabs (number) < power10 (format->w)
+           && c_snprintf (buf, 128, "%0*.0f", format->w,
+                       fabs (round (number))) == format->w)
     {
-      if (number < 0 && strspn (buf, "0") < format->w) 
+      if (number < 0 && strspn (buf, "0") < format->w)
         {
           char *p = &buf[format->w - 1];
           *p = "}JKLMNOPQR"[*p - '0'];
         }
-      memcpy (output, buf, format->w); 
+      memcpy (output, buf, format->w);
+      output[format->w] = '\0';
     }
+  else
+    output_overflow (format, output);
 }
 
 /* Outputs P format. */
@@ -253,9 +345,12 @@ output_IB (const union value *input, const struct fmt_spec *format,
       uint64_t integer = fabs (number);
       if (number < 0)
         integer = -integer;
-      output_binary_integer (integer, format->w, output_integer_format,
+      output_binary_integer (integer, format->w,
+                            settings_get_output_integer_format (),
                              output);
     }
+
+  output[format->w] = '\0';
 }
 
 /* Outputs PIB format. */
@@ -268,7 +363,10 @@ output_PIB (const union value *input, const struct fmt_spec *format,
       || number < 0 || number >= power256 (format->w))
     memset (output, 0, format->w);
   else
-    output_binary_integer (number, format->w, output_integer_format, output);
+    output_binary_integer (number, format->w,
+                          settings_get_output_integer_format (), output);
+
+  output[format->w] = '\0';
 }
 
 /* Outputs PIBHEX format. */
@@ -287,6 +385,7 @@ output_PIBHEX (const union value *input, const struct fmt_spec *format,
       output_binary_integer (number, format->w / 2, INTEGER_MSB_FIRST, tmp);
       output_hex (tmp, format->w / 2, output);
     }
+
 }
 
 /* Outputs RB format. */
@@ -296,6 +395,8 @@ output_RB (const union value *input, const struct fmt_spec *format,
 {
   double d = input->f;
   memcpy (output, &d, format->w);
+
+  output[format->w] = '\0';
 }
 
 /* Outputs RBHEX format. */
@@ -304,6 +405,7 @@ output_RBHEX (const union value *input, const struct fmt_spec *format,
               char *output)
 {
   double d = input->f;
+
   output_hex (&d, format->w / 2, output);
 }
 
@@ -316,18 +418,15 @@ output_date (const union value *input, const struct fmt_spec *format,
   double number = input->f;
   int year, month, day, yday;
 
-  const char *template = fmt_date_template (format->type);
-  size_t template_width = strlen (template);
-  int excess_width = format->w - template_width;
+  const char *template = fmt_date_template (format->type, format->w);
 
   char tmp[64];
   char *p = tmp;
 
-  assert (format->w >= template_width);
   if (number == SYSMIS)
     goto missing;
 
-  if (fmt_get_category (format->type) == FMT_CAT_DATE) 
+  if (fmt_get_category (format->type) == FMT_CAT_DATE)
     {
       if (number <= 0)
         goto missing;
@@ -340,12 +439,14 @@ output_date (const union value *input, const struct fmt_spec *format,
 
   while (*template != '\0')
     {
+      int excess_width;
+
       int ch = *template;
       int count = 1;
-      while (template[count] == ch) 
+      while (template[count] == ch)
         count++;
       template += count;
-      
+
       switch (ch)
         {
         case 'd':
@@ -359,30 +460,31 @@ output_date (const union value *input, const struct fmt_spec *format,
             p += sprintf (p, "%02d", month);
           else
             {
-              static const char *months[12] =
+              static const char *const months[12] =
                 {
                   "JAN", "FEB", "MAR", "APR", "MAY", "JUN",
                   "JUL", "AUG", "SEP", "OCT", "NOV", "DEC",
                 };
-              p = stpcpy (p, months[month - 1]); 
+              p = stpcpy (p, months[month - 1]);
             }
           break;
         case 'y':
-          if (count >= 4 || excess_width >= 2) 
+          if (count >= 4)
             {
               if (year <= 9999)
                 p += sprintf (p, "%04d", year);
               else if (format->type == FMT_DATETIME)
                 p = stpcpy (p, "****");
               else
-                goto overflow; 
+                goto overflow;
             }
-          else 
+          else
             {
-              int offset = year - get_epoch ();
+              int epoch =  settings_get_epoch ();
+              int offset = year - epoch;
               if (offset < 0 || offset > 99)
                 goto overflow;
-              p += sprintf (p, "%02d", abs (year) % 100); 
+              p += sprintf (p, "%02d", abs (year) % 100);
             }
           break;
         case 'q':
@@ -392,24 +494,24 @@ output_date (const union value *input, const struct fmt_spec *format,
           p += sprintf (p, "%2d", (yday - 1) / 7 + 1);
           break;
         case 'D':
-          if (number < 0) 
+          if (number < 0)
             *p++ = '-';
-          number = fabs (number); 
-          p += sprintf (p, "%*.0f", count, floor (number / 60. / 60. / 24.));
+          number = fabs (number);
+          p += c_snprintf (p, 64, "%*.0f", count, floor (number / 60. / 60. / 24.));
           number = fmod (number, 60. * 60. * 24.);
           break;
         case 'H':
           if (number < 0)
             *p++ = '-';
-          number = fabs (number); 
-          p += sprintf (p, "%0*.0f", count, floor (number / 60. / 60.));
+          number = fabs (number);
+          p += c_snprintf (p, 64, "%0*.0f", count, floor (number / 60. / 60.));
           number = fmod (number, 60. * 60.);
           break;
         case 'M':
           p += sprintf (p, "%02d", (int) floor (number / 60.));
           number = fmod (number, 60.);
           excess_width = format->w - (p - tmp);
-          if (excess_width < 0) 
+          if (excess_width < 0)
             goto overflow;
           if (excess_width == 3 || excess_width == 4
               || (excess_width >= 5 && format->d == 0))
@@ -418,27 +520,26 @@ output_date (const union value *input, const struct fmt_spec *format,
             {
               int d = MIN (format->d, excess_width - 4);
               int w = d + 3;
-              sprintf (p, ":%0*.*f", w, d, number);
-              if (fmt_decimal_char (FMT_F) != '.') 
+              c_snprintf (p, 64, ":%0*.*f", w, d, number);
+             if (settings_get_decimal_char (FMT_F) != '.')
                 {
                   char *cp = strchr (p, '.');
                   if (cp != NULL)
-                    *cp = fmt_decimal_char (FMT_F);
+                   *cp = settings_get_decimal_char (FMT_F);
                 }
               p += strlen (p);
             }
-          break;
-        case 'X':
-          *p++ = ' ';
-          break;
+          goto done;
         default:
           assert (count == 1);
           *p++ = ch;
-          break; 
+          break;
         }
     }
 
-  buf_copy_lpad (output, format->w, tmp, p - tmp);
+ done:
+  buf_copy_lpad (output, format->w, tmp, p - tmp, ' ');
+  output[format->w] = '\0';
   return;
 
  overflow:
@@ -455,20 +556,25 @@ static void
 output_WKDAY (const union value *input, const struct fmt_spec *format,
               char *output)
 {
-  static const char *weekdays[7] =
+  static const char *const weekdays[7] =
     {
       "SUNDAY", "MONDAY", "TUESDAY", "WEDNESDAY",
       "THURSDAY", "FRIDAY", "SATURDAY",
     };
 
   if (input->f >= 1 && input->f < 8)
-    buf_copy_str_rpad (output, format->w, weekdays[(int) input->f - 1]);
+    {
+      buf_copy_str_rpad (output, format->w,
+                         weekdays[(int) input->f - 1], ' ');
+      output[format->w] = '\0';
+    }
   else
     {
       if (input->f != SYSMIS)
         msg (ME, _("Weekday number %f is not between 1 and 7."), input->f);
       output_missing (format, output);
     }
+
 }
 
 /* Outputs MONTH format. */
@@ -476,28 +582,32 @@ static void
 output_MONTH (const union value *input, const struct fmt_spec *format,
               char *output)
 {
-  static const char *months[12] =
+  static const char *const months[12] =
     {
       "JANUARY", "FEBRUARY", "MARCH", "APRIL", "MAY", "JUNE",
       "JULY", "AUGUST", "SEPTEMBER", "OCTOBER", "NOVEMBER", "DECEMBER",
     };
 
   if (input->f >= 1 && input->f < 13)
-    buf_copy_str_rpad (output, format->w, months[(int) input->f - 1]);
+    {
+      buf_copy_str_rpad (output, format->w, months[(int) input->f - 1], ' ');
+      output[format->w] = '\0';
+    }
   else
     {
       if (input->f != SYSMIS)
         msg (ME, _("Month number %f is not between 1 and 12."), input->f);
       output_missing (format, output);
     }
+
 }
 
 /* Outputs A format. */
 static void
-output_A (const union value *input, const struct fmt_spec *format,
-          char *output)
+output_A (const union value *input UNUSED,
+          const struct fmt_spec *format UNUSED, char *output UNUSED)
 {
-  memcpy (output, input->s, format->w);
+  NOT_REACHED ();
 }
 
 /* Outputs AHEX format. */
@@ -505,7 +615,7 @@ static void
 output_AHEX (const union value *input, const struct fmt_spec *format,
              char *output)
 {
-  output_hex (input->s, format->w, output);
+  output_hex (value_str (input, format->w / 2), format->w / 2, output);
 }
 \f
 /* Decimal and scientific formatting. */
@@ -517,7 +627,7 @@ static bool
 allocate_space (int request, int max_width, int *width)
 {
   assert (*width <= max_width);
-  if (request + *width <= max_width) 
+  if (request + *width <= max_width)
     {
       *width += request;
       return true;
@@ -536,10 +646,12 @@ static bool
 output_decimal (const struct rounder *r, const struct fmt_spec *format,
                 bool require_affixes, char *output)
 {
-  const struct fmt_number_style *style = fmt_get_style (format->type);
+  const struct fmt_number_style *style =
+    settings_get_style (format->type);
+
   int decimals;
 
-  for (decimals = format->d; decimals >= 0; decimals--) 
+  for (decimals = format->d; decimals >= 0; decimals--)
     {
       /* Formatted version of magnitude of NUMBER. */
       char magnitude[64];
@@ -565,9 +677,9 @@ output_decimal (const struct rounder *r, const struct fmt_spec *format,
          the negative suffix, plus (if negative) the negative
          prefix. */
       width = rounder_width (r, decimals, &integer_digits, &add_neg_prefix);
-      width += ss_length (style->neg_suffix);
+      width += style->neg_suffix.width;
       if (add_neg_prefix)
-        width += ss_length (style->neg_prefix);
+        width += style->neg_prefix.width;
       if (width > format->w)
         continue;
 
@@ -578,7 +690,7 @@ output_decimal (const struct rounder *r, const struct fmt_spec *format,
                                     format->w, &width);
       if (!add_affixes && require_affixes)
         continue;
-      
+
       /* Check whether we should include grouping characters.
          We need room for a complete set or we don't insert any at all.
          We don't include grouping characters if decimal places were
@@ -591,16 +703,15 @@ output_decimal (const struct rounder *r, const struct fmt_spec *format,
 
       /* Format the number's magnitude. */
       rounder_format (r, decimals, magnitude);
-  
+
       /* Assemble number. */
       p = output;
       if (format->w > width)
         p = mempset (p, ' ', format->w - width);
       if (add_neg_prefix)
-        p = mempcpy (p, ss_data (style->neg_prefix),
-                     ss_length (style->neg_prefix));
+        p = stpcpy (p, style->neg_prefix.s);
       if (add_affixes)
-        p = mempcpy (p, ss_data (style->prefix), ss_length (style->prefix));
+        p = stpcpy (p, style->prefix.s);
       if (!add_grouping)
         p = mempcpy (p, magnitude, integer_digits);
       else
@@ -611,7 +722,7 @@ output_decimal (const struct rounder *r, const struct fmt_spec *format,
               if (i > 0 && (integer_digits - i) % 3 == 0)
                 *p++ = style->grouping;
               *p++ = magnitude[i];
-            } 
+            }
         }
       if (decimals > 0)
         {
@@ -619,13 +730,15 @@ output_decimal (const struct rounder *r, const struct fmt_spec *format,
           p = mempcpy (p, &magnitude[integer_digits + 1], decimals);
         }
       if (add_affixes)
-        p = mempcpy (p, ss_data (style->suffix), ss_length (style->suffix));
+        p = stpcpy (p, style->suffix.s);
       if (add_neg_prefix)
-        p = mempcpy (p, ss_data (style->neg_suffix),
-                     ss_length (style->neg_suffix));
+        p = stpcpy (p, style->neg_suffix.s);
       else
-        p = mempset (p, ' ', ss_length (style->neg_suffix));
-      assert (p == output + format->w);
+        p = mempset (p, ' ', style->neg_suffix.width);
+
+      assert (p >= output + format->w);
+      assert (p <= output + format->w + style->extra_bytes);
+      *p = '\0';
 
       return true;
     }
@@ -638,16 +751,17 @@ static bool
 output_scientific (double number, const struct fmt_spec *format,
                    bool require_affixes, char *output)
 {
-  const struct fmt_number_style *style = fmt_get_style (format->type);
+  const struct fmt_number_style *style =
+    settings_get_style (format->type);
   int width;
   int fraction_width;
   bool add_affixes;
-  char buf[64], *p;
+  char *p;
 
   /* Allocate minimum required space. */
-  width = 6 + ss_length (style->neg_suffix);
+  width = 6 + style->neg_suffix.width;
   if (number < 0)
-    width += ss_length (style->neg_prefix);
+    width += style->neg_prefix.width;
   if (width > format->w)
     return false;
 
@@ -659,25 +773,24 @@ output_scientific (double number, const struct fmt_spec *format,
   /* Figure out number of characters we can use for the fraction,
      if any.  (If that turns out to be 1, then we'll output a
      decimal point without any digits following; that's what the
-     # flag does in the call to sprintf, below.) */
+     # flag does in the call to c_snprintf, below.) */
   fraction_width = MIN (MIN (format->d + 1, format->w - width), 16);
   if (format->type != FMT_E && fraction_width == 1)
-    fraction_width = 0; 
+    fraction_width = 0;
   width += fraction_width;
 
   /* Format (except suffix). */
-  p = buf;
+  p = output;
   if (width < format->w)
     p = mempset (p, ' ', format->w - width);
   if (number < 0)
-    p = mempcpy (p, ss_data (style->neg_prefix),
-                 ss_length (style->neg_prefix));
+    p = stpcpy (p, style->neg_prefix.s);
   if (add_affixes)
-    p = mempcpy (p, ss_data (style->prefix), ss_length (style->prefix));
+    p = stpcpy (p, style->prefix.s);
   if (fraction_width > 0)
-    sprintf (p, "%#.*E", fraction_width - 1, fabs (number));
+    c_snprintf (p, 64, "%#.*E", fraction_width - 1, fabs (number));
   else
-    sprintf (p, "%.0E", fabs (number));
+    c_snprintf (p, 64, "%.0E", fabs (number));
 
   /* The C locale always uses a period `.' as a decimal point.
      Translate to comma if necessary. */
@@ -692,7 +805,7 @@ output_scientific (double number, const struct fmt_spec *format,
   {
     char *cp = strchr (p, 'E') + 1;
     long int exponent = strtol (cp, NULL, 10);
-    if (abs (exponent) > 999) 
+    if (labs (exponent) > 999)
       return false;
     sprintf (cp, "%+04ld", exponent);
   }
@@ -700,34 +813,24 @@ output_scientific (double number, const struct fmt_spec *format,
   /* Add suffixes. */
   p = strchr (p, '\0');
   if (add_affixes)
-    p = mempcpy (p, ss_data (style->suffix), ss_length (style->suffix));
+    p = stpcpy (p, style->suffix.s);
   if (number < 0)
-    p = mempcpy (p, ss_data (style->neg_suffix),
-                 ss_length (style->neg_suffix));
+    p = stpcpy (p, style->neg_suffix.s);
   else
-    p = mempset (p, ' ', ss_length (style->neg_suffix));
+    p = mempset (p, ' ', style->neg_suffix.width);
 
-  assert (p == buf + format->w);
+  assert (p >= output + format->w);
+  assert (p <= output + format->w + style->extra_bytes);
+  *p = '\0';
 
-  buf_copy_str_lpad (output, format->w, buf);
   return true;
 }
 \f
-#ifndef HAVE_ROUND
-/* Return X rounded to the nearest integer,
-   rounding ties away from zero. */
-static double
-round (double x) 
-{
-  return x >= 0.0 ? floor (x + .5) : ceil (x - .5);
-}
-#endif /* !HAVE_ROUND */
-
 /* Returns true if the magnitude represented by R should be
    rounded up when chopped off at DECIMALS decimal places, false
    if it should be rounded down. */
 static bool
-should_round_up (const struct rounder *r, int decimals) 
+should_round_up (const struct rounder *r, int decimals)
 {
   int digit = r->string[r->integer_digits + decimals + 1];
   assert (digit >= '0' && digit <= '9');
@@ -741,18 +844,18 @@ rounder_init (struct rounder *r, double number, int max_decimals)
 {
   assert (fabs (number) < 1e41);
   assert (max_decimals >= 0 && max_decimals <= 16);
-  if (max_decimals == 0) 
+  if (max_decimals == 0)
     {
       /* Fast path.  No rounding needed.
 
          We append ".00" to the integer representation because
          round_up assumes that fractional digits are present.  */
-      sprintf (r->string, "%.0f.00", fabs (round (number)));
+      c_snprintf (r->string, 64, "%.0f.00", fabs (round (number)));
     }
-  else 
+  else
     {
       /* Slow path.
-         
+
          This is more difficult than it really should be because
          we have to make sure that numbers that are exactly
          halfway between two representations are always rounded
@@ -760,13 +863,13 @@ rounder_init (struct rounder *r, double number, int max_decimals)
          (usually it rounds to even), so we have to fake it as
          best we can, by formatting with extra precision and then
          doing the rounding ourselves.
-     
+
          We take up to two rounds to format numbers.  In the
          first round, we obtain 2 digits of precision beyond
          those requested by the user.  If those digits are
          exactly "50", then in a second round we format with as
          many digits as are significant in a "double".
-     
+
          It might be better to directly implement our own
          floating-point formatting routine instead of relying on
          the system's sprintf implementation.  But the classic
@@ -774,7 +877,7 @@ rounder_init (struct rounder *r, double number, int max_decimals)
          numbers does not hint how to do what we want, and it's
          not obvious how to change their algorithms to do so.  It
          would also be a lot of work. */
-      sprintf (r->string, "%.*f", max_decimals + 2, fabs (number));
+      c_snprintf (r->string, 64, "%.*f", max_decimals + 2, fabs (number));
       if (!strcmp (r->string + strlen (r->string) - 2, "50"))
         {
           int binary_exponent, decimal_exponent, format_decimals;
@@ -782,16 +885,18 @@ rounder_init (struct rounder *r, double number, int max_decimals)
           decimal_exponent = binary_exponent * 3 / 10;
           format_decimals = (DBL_DIG + 1) - decimal_exponent;
           if (format_decimals > max_decimals + 2)
-            sprintf (r->string, "%.*f", format_decimals, fabs (number));
+            c_snprintf (r->string, 64, "%.*f", format_decimals, fabs (number));
         }
     }
-  
-  if (r->string[0] == '0') 
+
+  if (r->string[0] == '0')
     memmove (r->string, &r->string[1], strlen (r->string));
 
   r->leading_zeros = strspn (r->string, "0.");
   r->leading_nines = strspn (r->string, "9.");
   r->integer_digits = strchr (r->string, '.') - r->string;
+  assert (r->integer_digits < 64);
+  assert (r->integer_digits >= 0);
   r->negative = number < 0;
 }
 
@@ -809,7 +914,7 @@ rounder_init (struct rounder *r, double number, int max_decimals)
    *NEGATIVE is set to true; otherwise, it is set to false. */
 static int
 rounder_width (const struct rounder *r, int decimals,
-               int *integer_digits, bool *negative) 
+               int *integer_digits, bool *negative)
 {
   /* Calculate base measures. */
   int width = r->integer_digits;
@@ -822,28 +927,28 @@ rounder_width (const struct rounder *r, int decimals,
   if (should_round_up (r, decimals))
     {
       /* Rounding up leading 9s adds a new digit (a 1). */
-      if (r->leading_nines >= width) 
+      if (r->leading_nines >= width)
         {
           width++;
-          ++*integer_digits; 
+          ++*integer_digits;
         }
     }
   else
     {
       /* Rounding down. */
-      if (r->leading_zeros >= width) 
+      if (r->leading_zeros >= width)
         {
           /* All digits that remain after rounding are zeros.
              Therefore we drop the negative sign. */
           *negative = false;
-          if (r->integer_digits == 0 && decimals == 0) 
+          if (r->integer_digits == 0 && decimals == 0)
             {
               /* No digits at all are left.  We need to display
                  at least a single digit (a zero). */
               assert (width == 0);
               width++;
               *integer_digits = 1;
-            } 
+            }
         }
     }
   return width;
@@ -854,23 +959,23 @@ rounder_width (const struct rounder *r, int decimals,
    indicated by rounder_width are written.  No terminating null
    is appended. */
 static void
-rounder_format (const struct rounder *r, int decimals, char *output) 
+rounder_format (const struct rounder *r, int decimals, char *output)
 {
   int base_width = r->integer_digits + (decimals > 0 ? decimals + 1 : 0);
-  if (should_round_up (r, decimals)) 
+  if (should_round_up (r, decimals))
     {
-      if (r->leading_nines < base_width) 
+      if (r->leading_nines < base_width)
         {
           /* Rounding up.  This is the common case where rounding
              up doesn't add an extra digit. */
           char *p;
           memcpy (output, r->string, base_width);
-          for (p = output + base_width - 1; ; p--) 
+          for (p = output + base_width - 1; ; p--)
             {
               assert (p >= output);
               if (*p == '9')
                 *p = '0';
-              else if (*p >= '0' && *p <= '8') 
+              else if (*p >= '0' && *p <= '8')
                 {
                   (*p)++;
                   break;
@@ -879,14 +984,14 @@ rounder_format (const struct rounder *r, int decimals, char *output)
                 assert (*p == '.');
             }
         }
-      else 
+      else
         {
           /* Rounding up leading 9s causes the result to be a 1
              followed by a number of 0s, plus a decimal point. */
           char *p = output;
           *p++ = '1';
           p = mempset (p, '0', r->integer_digits);
-          if (decimals > 0) 
+          if (decimals > 0)
             {
               *p++ = '.';
               p = mempset (p, '0', decimals);
@@ -894,18 +999,18 @@ rounder_format (const struct rounder *r, int decimals, char *output)
           assert (p == output + base_width + 1);
         }
     }
-  else 
+  else
     {
       /* Rounding down. */
-      if (r->integer_digits != 0 || decimals != 0) 
+      if (r->integer_digits != 0 || decimals != 0)
         {
           /* Common case: just copy the digits. */
-          memcpy (output, r->string, base_width); 
+          memcpy (output, r->string, base_width);
         }
-      else 
+      else
         {
           /* No digits remain.  The output is just a zero. */
-          output[0] = '0'; 
+          output[0] = '0';
         }
     }
 }
@@ -914,7 +1019,7 @@ rounder_format (const struct rounder *r, int decimals, char *output)
 
 /* Returns 10**X. */
 static double PURE_FUNCTION
-power10 (int x) 
+power10 (int x)
 {
   static const double p[] =
     {
@@ -929,9 +1034,9 @@ power10 (int x)
 
 /* Returns 256**X. */
 static double PURE_FUNCTION
-power256 (int x) 
+power256 (int x)
 {
-  static const double p[] = 
+  static const double p[] =
     {
       1.0,
       256.0,
@@ -952,7 +1057,7 @@ static void
 output_infinite (double number, const struct fmt_spec *format, char *output)
 {
   assert (!isfinite (number));
-  
+
   if (format->w >= 3)
     {
       const char *s;
@@ -964,10 +1069,12 @@ output_infinite (double number, const struct fmt_spec *format, char *output)
       else
         s = "Unknown";
 
-      buf_copy_str_lpad (output, format->w, s);
+      buf_copy_str_lpad (output, format->w, s, ' ');
     }
-  else 
+  else
     output_overflow (format, output);
+
+  output[format->w] = '\0';
 }
 
 /* Formats OUTPUT as a missing value for the given FORMAT. */
@@ -976,22 +1083,25 @@ output_missing (const struct fmt_spec *format, char *output)
 {
   memset (output, ' ', format->w);
 
-  if (format->type != FMT_N) 
+  if (format->type != FMT_N)
     {
       int dot_ofs = (format->type == FMT_PCT ? 2
                      : format->type == FMT_E ? 5
                      : 1);
-      output[MAX (0, format->w - format->d - dot_ofs)] = '.'; 
+      output[MAX (0, format->w - format->d - dot_ofs)] = '.';
     }
   else
     output[format->w - 1] = '.';
+
+  output[format->w] = '\0';
 }
 
 /* Formats OUTPUT for overflow given FORMAT. */
 static void
-output_overflow (const struct fmt_spec *format, char *output) 
+output_overflow (const struct fmt_spec *format, char *output)
 {
   memset (output, '*', format->w);
+  output[format->w] = '\0';
 }
 
 /* Converts the integer part of NUMBER to a packed BCD number
@@ -1001,34 +1111,36 @@ output_overflow (const struct fmt_spec *format, char *output)
    representable.  On failure, OUTPUT is cleared to all zero
    bytes. */
 static bool
-output_bcd_integer (double number, int digits, char *output) 
+output_bcd_integer (double number, int digits, char *output)
 {
   char decimal[64];
 
   assert (digits < sizeof decimal);
+
+  output[DIV_RND_UP (digits, 2)] = '\0';
   if (number != SYSMIS
       && number >= 0.
       && number < power10 (digits)
-      && sprintf (decimal, "%0*.0f", digits, round (number)) == digits)
+      && c_snprintf (decimal, 64, "%0*.0f", digits, round (number)) == digits)
     {
       const char *src = decimal;
       int i;
 
-      for (i = 0; i < digits / 2; i++) 
+      for (i = 0; i < digits / 2; i++)
         {
           int d0 = *src++ - '0';
           int d1 = *src++ - '0';
-          *output++ = (d0 << 4) + d1; 
+          *output++ = (d0 << 4) + d1;
         }
       if (digits % 2)
         *output = (*src - '0') << 4;
-      
+
       return true;
     }
-  else 
+  else
     {
       memset (output, 0, DIV_RND_UP (digits, 2));
-      return false; 
+      return false;
     }
 }
 
@@ -1036,7 +1148,7 @@ output_bcd_integer (double number, int digits, char *output)
    given INTEGER_FORMAT. */
 static void
 output_binary_integer (uint64_t value, int bytes,
-                       enum integer_format integer_format, char *output) 
+                       enum integer_format integer_format, char *output)
 {
   integer_put (value, integer_format, output, bytes);
 }
@@ -1044,7 +1156,7 @@ output_binary_integer (uint64_t value, int bytes,
 /* Converts the BYTES bytes in DATA to twice as many hexadecimal
    digits in OUTPUT. */
 static void
-output_hex (const void *data_, size_t bytes, char *output) 
+output_hex (const void *data_, size_t bytes, char *output)
 {
   const uint8_t *data = data_;
   size_t i;
@@ -1055,4 +1167,5 @@ output_hex (const void *data_, size_t bytes, char *output)
       *output++ = hex_digits[data[i] >> 4];
       *output++ = hex_digits[data[i] & 15];
     }
+  *output = '\0';
 }