-/* PSPP - computes sample statistics.
- Copyright (C) 1997-9, 2000 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 <libpspp/message.h>
+
+#include "data/data-out.h"
+
#include <ctype.h>
-#include <math.h>
#include <float.h>
+#include <math.h>
+#include <stdint.h>
#include <stdlib.h>
#include <time.h>
-#include "calendar.h"
-#include <libpspp/message.h>
-#include "format.h"
-#include <libpspp/magic.h>
-#include <libpspp/misc.h>
-#include <libpspp/misc.h>
-#include "settings.h"
-#include <libpspp/str.h>
-#include "variable.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)
\f
-/* Public functions. */
-
-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;
-
-static numeric_converter try_F, convert_infinite;
-
-typedef int string_converter (char *, const struct fmt_spec *, const char *);
-static string_converter convert_A, convert_AHEX;
+/* A representation of a number that can be quickly rounded to
+ any desired number of decimal places (up to a specified
+ maximum). */
+struct rounder
+ {
+ char string[64]; /* Magnitude of number with excess precision. */
+ int integer_digits; /* Number of digits before decimal point. */
+ int leading_nines; /* Number of `9's or `.'s at start of string. */
+ int leading_zeros; /* Number of `0's or `.'s at start of string. */
+ bool negative; /* Is the number negative? */
+ };
-/* 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. */
-bool
-data_out (char *s, const struct fmt_spec *fp, const union value *v)
-{
- int cat = formats[fp->type].cat;
- int ok;
+static void rounder_init (struct rounder *, double number, int max_decimals);
+static int rounder_width (const struct rounder *, int decimals,
+ int *integer_digits, bool *negative);
+static void rounder_format (const struct rounder *, int decimals,
+ char *output);
+\f
+typedef void data_out_converter_func (const union value *,
+ const struct fmt_spec *,
+ char *);
+#define FMT(NAME, METHOD, IMIN, OMIN, IO, CATEGORY) \
+ static data_out_converter_func output_##METHOD;
+#include "format.def"
+
+static bool output_decimal (const struct rounder *, const struct fmt_spec *,
+ bool require_affixes, char *);
+static bool output_scientific (double, const struct fmt_spec *,
+ bool require_affixes, char *);
+
+static double power10 (int) PURE_FUNCTION;
+static double power256 (int) PURE_FUNCTION;
+
+static void output_infinite (double, const struct fmt_spec *, char *);
+static void output_missing (const struct fmt_spec *, char *);
+static void output_overflow (const struct fmt_spec *, char *);
+static bool output_bcd_integer (double, int digits, char *);
+static void output_binary_integer (uint64_t, int bytes, enum integer_format,
+ char *);
+static void output_hex (const void *, size_t bytes, char *);
+\f
- assert (check_output_specifier (fp, 0));
- if (!(cat & FCAT_STRING))
+static data_out_converter_func *const converters[FMT_NUMBER_OF_FORMATS] =
{
- /* 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 true;
- }
-
- /* 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;
+#define FMT(NAME, METHOD, IMIN, OMIN, IO, CATEGORY) output_##METHOD,
+#include "format.def"
+ };
- case FMT_WKDAY:
- ok = convert_WKDAY (s, fp, number);
- break;
+/* 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.
- case FMT_MONTH:
- ok = convert_MONTH (s, fp, number);
- break;
+ VALUE must be the correct width for FORMAT, that is, its width must be
+ fmt_var_width(FORMAT).
- default:
- assert (0);
- abort ();
- }
+ 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
+ else if (fmt_get_category (format->type) == FMT_CAT_BINARY)
+ converters[format->type] (input, format,
+ ds_put_uninit (output, format->w));
+ else
{
- /* 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 ();
- }
+ 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);
}
-
- /* Error handling. */
- if (!ok)
- strncpy (s, "ERROR", fp->w);
-
- return ok;
}
-/* 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)
+static char *
+binary_to_utf8 (const char *in, struct pool *pool)
{
- struct fmt_spec f = make_output_format (FMT_F, w, d);
- convert_F (s, &f, v);
-}
-\f
-/* Main conversion functions. */
+ uint8_t *out = pool_alloc_unaligned (pool, strlen (in) * 2 + 1);
+ uint8_t *p = out;
-static void insert_commas (char *dst, const char *src,
- const struct fmt_spec *fp);
-static int year4 (int year);
-static int try_CCx (char *s, const struct fmt_spec *fp, double v);
+ while (*in != '\0')
+ {
+ uint8_t byte = *in++;
+ int mblen = u8_uctomb (p, byte, 2);
+ assert (mblen > 0);
+ p += mblen;
+ }
+ *p = '\0';
-#if FLT_RADIX!=2
-#error Write your own floating-point output routines.
-#endif
+ return CHAR_CAST (char *, out);
+}
-/* Converts a number between 0 and 15 inclusive to a `hexit'
- [0-9A-F]. */
-#define MAKE_HEXIT(X) ("0123456789ABCDEF"[X])
+/* Converts the INPUT value into a UTF-8 encoded string, according to format
+ specification FORMAT.
-/* Table of powers of 10. */
-static const double power10[] =
- {
- 0, /* Not used. */
- 1e01, 1e02, 1e03, 1e04, 1e05, 1e06, 1e07, 1e08, 1e09, 1e10,
- 1e11, 1e12, 1e13, 1e14, 1e15, 1e16, 1e17, 1e18, 1e19, 1e20,
- 1e21, 1e22, 1e23, 1e24, 1e25, 1e26, 1e27, 1e28, 1e29, 1e30,
- 1e31, 1e32, 1e33, 1e34, 1e35, 1e36, 1e37, 1e38, 1e39, 1e40,
- };
+ VALUE must be the correct width for FORMAT, that is, its width must be
+ fmt_var_width(FORMAT).
-/* Handles F format. */
-static int
-convert_F (char *dst, const struct fmt_spec *fp, double number)
-{
- if (!try_F (dst, fp, number))
- convert_E (dst, fp, number);
- return 1;
-}
+ 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.
-/* Handles N format. */
-static int
-convert_N (char *dst, const struct fmt_spec *fp, double number)
+ 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)
{
- double d = floor (number);
-
- if (d < 0 || d == SYSMIS)
+ assert (fmt_check_output (format));
+ if (format->type == FMT_A)
{
- msg (ME, _("The N output format cannot be used to output a "
- "negative number or the system-missing value."));
- return 0;
+ char *in = CHAR_CAST (char *, value_str (input, format->w));
+ return recode_string_pool (UTF8, encoding, in, format->w, pool);
}
-
- if (d < power10[fp->w])
+ else if (fmt_get_category (format->type) == FMT_CAT_BINARY)
{
- char buf[128];
- sprintf (buf, "%0*.0f", fp->w, number);
- memcpy (dst, buf, fp->w);
+ char tmp[16];
+
+ assert (format->w + 1 <= sizeof tmp);
+ converters[format->type] (input, format, tmp);
+ return binary_to_utf8 (tmp, pool);
}
else
- memset (dst, '*', fp->w);
+ {
+ const struct fmt_number_style *style = settings_get_style (format->type);
+ size_t size = format->w + style->extra_bytes + 1;
+ char *output;
- return 1;
+ output = pool_alloc_unaligned (pool, size);
+ converters[format->type] (input, format, output);
+ return output;
+ }
}
-/* Handles E format. Also operates as fallback for some other
- formats. */
-static int
-convert_E (char *dst, const struct fmt_spec *fp, double number)
+/* 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)
{
- /* Temporary buffer. */
- char buf[128];
-
- /* Ranged number of decimal places. */
- int d;
-
- 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)
+ if (fmt_get_category (format->type) & (FMT_CAT_BASIC | FMT_CAT_CUSTOM))
{
- memset (dst, '*', fp->w);
- return 1;
- }
+ const struct fmt_number_style *style = settings_get_style (format->type);
+ struct fmt_spec wide_format;
+ char tmp[128];
+ size_t size;
- /* Put decimal places in usable range. */
- d = min (fp->d, fp->w - 6);
- if (number < 0)
- d--;
- if (d < 0)
- d = 0;
- 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')
- but 1.00E+100 (`E+100') must be coerced to 1.00+100 (`+100'). On
- the other hand, 1.00E1000 (`E+100') cannot be canonicalized.
- Note that ANSI C guarantees at least two digits in the
- exponent. */
- if (fabs (number) > 1e99)
- {
- /* Pointer to the `E' in buf. */
- char *cp;
-
- cp = strchr (buf, 'E');
- if (cp)
- {
- /* Exponent better not be bigger than an int. */
- int exp = atoi (cp + 1);
-
- if (abs (exp) > 99 && abs (exp) < 1000)
- {
- /* Shift everything left one place: 1.00e+100 -> 1.00+100. */
- cp[0] = cp[1];
- cp[1] = cp[2];
- cp[2] = cp[3];
- cp[3] = cp[4];
- }
- else if (abs (exp) >= 1000)
- memset (buf, '*', fp->w);
- }
- }
+ wide_format.type = format->type;
+ wide_format.w = 40;
+ wide_format.d = format->d;
- /* The C locale always uses a period `.' as a decimal point.
- Translate to comma if necessary. */
- if ((get_decimal() == ',' && fp->type != FMT_DOT)
- || (get_decimal() == '.' && fp->type == FMT_DOT))
- {
- char *cp = strchr (buf, '.');
- if (cp)
- *cp = ',';
+ size = format->w + style->extra_bytes + 1;
+ if (size <= sizeof tmp)
+ {
+ output_number (input, &wide_format, tmp);
+ return pool_strdup (pool, tmp + strspn (tmp, " "));
+ }
}
- memcpy (dst, buf, fp->w);
- return 1;
+ return data_out_pool (input, encoding, format, pool);
}
-/* Handles COMMA, DOT, DOLLAR, and PCT formats. */
-static int
-convert_F_plus (char *dst, const struct fmt_spec *fp, double number)
+char *
+data_out (const union value *input, const char *encoding, const struct fmt_spec *format)
{
- char buf[40];
-
- if (try_F (buf, fp, number))
- insert_commas (dst, buf, fp);
- else
- convert_E (dst, fp, number);
-
- return 1;
+ return data_out_pool (input, encoding, format, NULL);
}
-static int
-convert_Z (char *dst, const struct fmt_spec *fp, double number)
-{
- static int warned = 0;
+\f
+/* Main conversion functions. */
- if (!warned)
- {
- msg (MW,
- _("Quality of zoned decimal (Z) output format code is "
- "suspect. Check your results. Report bugs to %s."),
- PACKAGE_BUGREPORT);
- warned = 1;
- }
+/* Outputs F, COMMA, DOT, DOLLAR, PCT, E, CCA, CCB, CCC, CCD, and
+ CCE formats. */
+static void
+output_number (const union value *input, const struct fmt_spec *format,
+ char *output)
+{
+ double number = input->f;
if (number == SYSMIS)
+ output_missing (format, output);
+ else if (!isfinite (number))
+ output_infinite (number, format, output);
+ else
{
- msg (ME, _("The system-missing value cannot be output as a zoned "
- "decimal number."));
- return 0;
- }
-
- {
- char buf[41];
- double d;
- int i;
-
- d = fabs (floor (number));
- if (d >= power10[fp->w])
- {
- msg (ME, _("Number %g too big to fit in field with format Z%d.%d."),
- number, fp->w, fp->d);
- return 0;
- }
-
- sprintf (buf, "%*.0f", fp->w, number);
- for (i = 0; i < fp->w; i++)
- dst[i] = (buf[i] - '0') | 0xf0;
- if (number < 0)
- dst[fp->w - 1] &= 0xdf;
- }
+ if (format->type != FMT_E && fabs (number) < 1.5 * power10 (format->w))
+ {
+ struct rounder r;
+ rounder_init (&r, number, format->d);
- return 1;
-}
+ if (output_decimal (&r, format, true, output)
+ || output_scientific (number, format, true, output)
+ || output_decimal (&r, format, false, output))
+ return;
+ }
-static int
-convert_A (char *dst, const struct fmt_spec *fp, const char *string)
-{
- memcpy(dst, string, fp->w);
- return 1;
+ if (!output_scientific (number, format, false, output))
+ output_overflow (format, output);
+ }
}
-static int
-convert_AHEX (char *dst, const struct fmt_spec *fp, const char *string)
+/* Outputs N format. */
+static void
+output_N (const union value *input, const struct fmt_spec *format,
+ char *output)
{
- int i;
-
- for (i = 0; i < fp->w / 2; i++)
+ double number = input->f * power10 (format->d);
+ if (input->f == SYSMIS || number < 0)
+ output_missing (format, output);
+ else
{
- *dst++ = MAKE_HEXIT ((string[i]) >> 4);
- *dst++ = MAKE_HEXIT ((string[i]) & 0xf);
+ char buf[128];
+ number = fabs (round (number));
+ if (number < power10 (format->w)
+ && c_snprintf (buf, 128, "%0*.0f", format->w, number) == format->w)
+ memcpy (output, buf, format->w);
+ else
+ output_overflow (format, output);
}
- return 1;
+ output[format->w] = '\0';
}
-static int
-convert_IB (char *dst, const struct fmt_spec *fp, double number)
+/* Outputs Z format. */
+static void
+output_Z (const union value *input, const struct fmt_spec *format,
+ char *output)
{
- /* 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];
-
- /* Fraction (mantissa). */
- double frac;
-
- /* Exponent. */
- int exp;
-
- /* Difference between exponent and (-8*fp->w-1). */
- int diff;
-
- /* Counter. */
- int i;
-
- /* Make the exponent (-8*fp->w-1). */
- frac = frexp (fabs (number), &exp);
- diff = exp - (-8 * fp->w - 1);
- exp -= diff;
- frac *= ldexp (1.0, diff);
-
- /* Extract each base-256 digit. */
- for (i = 0; i < fp->w; i++)
- {
- modf (frac, &frac);
- frac *= 256.0;
- temp[i] = floor (frac);
- }
-
- /* Perform two's-complement negation if number is negative. */
- if (number < 0)
+ double number = input->f * power10 (format->d);
+ char buf[128];
+ if (input->f == SYSMIS)
+ output_missing (format, output);
+ else if (fabs (number) < power10 (format->w)
+ && c_snprintf (buf, 128, "%0*.0f", format->w,
+ fabs (round (number))) == format->w)
{
- /* Perform NOT operation. */
- for (i = 0; i < fp->w; i++)
- temp[i] = ~temp[i];
- /* Add 1 to the whole number. */
- for (i = fp->w - 1; i >= 0; i--)
- {
- temp[i]++;
- if (temp[i])
- break;
- }
+ if (number < 0 && strspn (buf, "0") < format->w)
+ {
+ char *p = &buf[format->w - 1];
+ *p = "}JKLMNOPQR"[*p - '0'];
+ }
+ memcpy (output, buf, format->w);
+ output[format->w] = '\0';
}
- memcpy (dst, temp, fp->w);
-#ifndef WORDS_BIGENDIAN
- buf_reverse (dst, fp->w);
-#endif
-
- return 1;
+ else
+ output_overflow (format, output);
}
-static int
-convert_P (char *dst, const struct fmt_spec *fp, double number)
+/* Outputs P format. */
+static void
+output_P (const union value *input, const struct fmt_spec *format,
+ char *output)
{
- /* Buffer for fp->w*2-1 characters + a decimal point if library is
- not quite compliant + a null. */
- char buf[17];
-
- /* Counter. */
- int i;
-
- /* Main extraction. */
- sprintf (buf, "%0*.0f", fp->w * 2 - 1, floor (fabs (number)));
-
- for (i = 0; i < fp->w; i++)
- ((unsigned char *) dst)[i]
- = ((buf[i * 2] - '0') << 4) + buf[i * 2 + 1] - '0';
-
- /* Set sign. */
- dst[fp->w - 1] &= 0xf0;
- if (number >= 0.0)
- dst[fp->w - 1] |= 0xf;
+ if (output_bcd_integer (fabs (input->f * power10 (format->d)),
+ format->w * 2 - 1, output)
+ && input->f < 0.0)
+ output[format->w - 1] |= 0xd;
else
- dst[fp->w - 1] |= 0xd;
-
- return 1;
+ output[format->w - 1] |= 0xf;
}
-static int
-convert_PIB (char *dst, const struct fmt_spec *fp, double number)
+/* Outputs PK format. */
+static void
+output_PK (const union value *input, const struct fmt_spec *format,
+ char *output)
{
- /* Strategy: Basically the same as convert_IB(). */
-
- /* Fraction (mantissa). */
- double frac;
-
- /* Exponent. */
- int exp;
-
- /* Difference between exponent and (-8*fp->w). */
- int diff;
-
- /* Counter. */
- int i;
-
- /* Make the exponent (-8*fp->w). */
- frac = frexp (fabs (number), &exp);
- diff = exp - (-8 * fp->w);
- exp -= diff;
- frac *= ldexp (1.0, diff);
-
- /* Extract each base-256 digit. */
- for (i = 0; i < fp->w; i++)
- {
- modf (frac, &frac);
- frac *= 256.0;
- ((unsigned char *) dst)[i] = floor (frac);
- }
-#ifndef WORDS_BIGENDIAN
- buf_reverse (dst, fp->w);
-#endif
-
- return 1;
+ output_bcd_integer (input->f * power10 (format->d), format->w * 2, output);
}
-static int
-convert_PIBHEX (char *dst, const struct fmt_spec *fp, double number)
+/* Outputs IB format. */
+static void
+output_IB (const union value *input, const struct fmt_spec *format,
+ char *output)
{
- /* Strategy: Use frexp() to create a normalized result (but mostly
- to find the base-2 exponent), then change the base-2 exponent to
- (-4*fp->w) using multiplication and division by powers of two.
- Extract each hexit by multiplying by 16. */
-
- /* Fraction (mantissa). */
- double frac;
-
- /* Exponent. */
- int exp;
-
- /* Difference between exponent and (-4*fp->w). */
- int diff;
-
- /* Counter. */
- int i;
-
- /* Make the exponent (-4*fp->w). */
- frac = frexp (fabs (number), &exp);
- diff = exp - (-4 * fp->w);
- exp -= diff;
- frac *= ldexp (1.0, diff);
-
- /* Extract each hexit. */
- for (i = 0; i < fp->w; i++)
+ double number = round (input->f * power10 (format->d));
+ if (input->f == SYSMIS
+ || number >= power256 (format->w) / 2 - 1
+ || number < -power256 (format->w) / 2)
+ memset (output, 0, format->w);
+ else
{
- modf (frac, &frac);
- frac *= 16.0;
- *dst++ = MAKE_HEXIT ((int) floor (frac));
+ uint64_t integer = fabs (number);
+ if (number < 0)
+ integer = -integer;
+ output_binary_integer (integer, format->w,
+ settings_get_output_integer_format (),
+ output);
}
- return 1;
+ output[format->w] = '\0';
}
-static int
-convert_PK (char *dst, const struct fmt_spec *fp, double number)
+/* Outputs PIB format. */
+static void
+output_PIB (const union value *input, const struct fmt_spec *format,
+ char *output)
{
- /* Buffer for fp->w*2 characters + a decimal point if library is not
- quite compliant + a null. */
- char buf[18];
-
- /* Counter. */
- int i;
-
- /* Main extraction. */
- sprintf (buf, "%0*.0f", fp->w * 2, floor (fabs (number)));
-
- for (i = 0; i < fp->w; i++)
- ((unsigned char *) dst)[i]
- = ((buf[i * 2] - '0') << 4) + buf[i * 2 + 1] - '0';
+ double number = round (input->f * power10 (format->d));
+ if (input->f == SYSMIS
+ || number < 0 || number >= power256 (format->w))
+ memset (output, 0, format->w);
+ else
+ output_binary_integer (number, format->w,
+ settings_get_output_integer_format (), output);
- return 1;
+ output[format->w] = '\0';
}
-static int
-convert_RB (char *dst, const struct fmt_spec *fp, double number)
+/* Outputs PIBHEX format. */
+static void
+output_PIBHEX (const union value *input, const struct fmt_spec *format,
+ char *output)
{
- union
+ double number = round (input->f);
+ if (input->f == SYSMIS)
+ output_missing (format, output);
+ else if (input->f < 0 || number >= power256 (format->w / 2))
+ output_overflow (format, output);
+ else
{
- double d;
- char c[8];
+ char tmp[8];
+ output_binary_integer (number, format->w / 2, INTEGER_MSB_FIRST, tmp);
+ output_hex (tmp, format->w / 2, output);
}
- u;
-
- u.d = number;
- memcpy (dst, u.c, fp->w);
- return 1;
}
-static int
-convert_RBHEX (char *dst, const struct fmt_spec *fp, double number)
+/* Outputs RB format. */
+static void
+output_RB (const union value *input, const struct fmt_spec *format,
+ char *output)
{
- union
- {
- double d;
- char c[8];
- }
- u;
-
- int i;
+ double d = input->f;
+ memcpy (output, &d, format->w);
- u.d = number;
- for (i = 0; i < fp->w / 2; i++)
- {
- *dst++ = MAKE_HEXIT (u.c[i] >> 4);
- *dst++ = MAKE_HEXIT (u.c[i] & 15);
- }
-
- return 1;
+ output[format->w] = '\0';
}
-static int
-convert_CCx (char *dst, const struct fmt_spec *fp, double number)
-{
- if (try_CCx (dst, fp, number))
- return 1;
- else
- {
- struct fmt_spec f;
-
- f.type = FMT_COMMA;
- f.w = fp->w;
- f.d = fp->d;
-
- return convert_F_plus (dst, &f, number);
- }
-}
-
-static int
-convert_date (char *dst, const struct fmt_spec *fp, double number)
+/* Outputs RBHEX format. */
+static void
+output_RBHEX (const union value *input, const struct fmt_spec *format,
+ char *output)
{
- static const char *months[12] =
- {
- "JAN", "FEB", "MAR", "APR", "MAY", "JUN",
- "JUL", "AUG", "SEP", "OCT", "NOV", "DEC",
- };
-
- char buf[64] = {0};
- int ofs = number / 86400.;
- int month, day, year;
+ double d = input->f;
- if (ofs < 1)
- return 0;
-
- calendar_offset_to_gregorian (ofs, &year, &month, &day);
- switch (fp->type)
- {
- case FMT_DATE:
- if (fp->w >= 11)
- sprintf (buf, "%02d-%s-%04d", day, months[month - 1], year);
- else
- sprintf (buf, "%02d-%s-%02d", day, months[month - 1], year % 100);
- break;
- case FMT_EDATE:
- if (fp->w >= 10)
- sprintf (buf, "%02d.%02d.%04d", day, month, year);
- else
- sprintf (buf, "%02d.%02d.%02d", day, month, year % 100);
- break;
- case FMT_SDATE:
- if (fp->w >= 10)
- sprintf (buf, "%04d/%02d/%02d", year, month, day);
- else
- sprintf (buf, "%02d/%02d/%02d", year % 100, month, day);
- break;
- case FMT_ADATE:
- if (fp->w >= 10)
- sprintf (buf, "%02d/%02d/%04d", month, day, year);
- else
- sprintf (buf, "%02d/%02d/%02d", month, day, year % 100);
- break;
- case FMT_JDATE:
- {
- int yday = calendar_offset_to_yday (ofs);
-
- 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:
- if (fp->w >= 8)
- sprintf (buf, "%d Q% 04d", (month - 1) / 3 + 1, year);
- else
- sprintf (buf, "%d Q% 02d", (month - 1) / 3 + 1, year % 100);
- break;
- case FMT_MOYR:
- if (fp->w >= 8)
- sprintf (buf, "%s% 04d", months[month - 1], year);
- else
- sprintf (buf, "%s% 02d", months[month - 1], year % 100);
- break;
- case FMT_WKYR:
- {
- int yday = calendar_offset_to_yday (ofs);
-
- if (fp->w >= 10)
- sprintf (buf, "%02d WK% 04d", (yday - 1) / 7 + 1, year);
- else
- sprintf (buf, "%02d WK% 02d", (yday - 1) / 7 + 1, year % 100);
- }
- break;
- case FMT_DATETIME:
- {
- char *cp;
-
- cp = spprintf (buf, "%02d-%s-%04d %02d:%02d",
- day, months[month - 1], year,
- (int) fmod (floor (number / 60. / 60.), 24.),
- (int) fmod (floor (number / 60.), 60.));
- if (fp->w >= 20)
- {
- int w, d;
-
- if (fp->w >= 22 && fp->d > 0)
- {
- d = min (fp->d, fp->w - 21);
- w = 3 + d;
- }
- else
- {
- w = 2;
- d = 0;
- }
-
- cp = spprintf (cp, ":%0*.*f", w, d, fmod (number, 60.));
- }
- }
- break;
- default:
- assert (0);
- }
-
- if (buf[0] == 0)
- return 0;
- buf_copy_str_rpad (dst, fp->w, buf);
- return 1;
+ output_hex (&d, format->w / 2, output);
}
-static int
-convert_time (char *dst, const struct fmt_spec *fp, double number)
+/* Outputs DATE, ADATE, EDATE, JDATE, SDATE, QYR, MOYR, WKYR,
+ DATETIME, TIME, and DTIME formats. */
+static void
+output_date (const union value *input, const struct fmt_spec *format,
+ char *output)
{
- char temp_buf[40];
- char *cp;
+ double number = input->f;
+ int year, month, day, yday;
- double time;
- int width;
+ const char *template = fmt_date_template (format->type, format->w);
- if (fabs (number) > 1e20)
- {
- msg (ME, _("Time value %g too large in magnitude to convert to "
- "alphanumeric time."), number);
- return 0;
- }
+ char tmp[64];
+ char *p = tmp;
- time = number;
- width = fp->w;
- cp = temp_buf;
- if (time < 0)
- *cp++ = '-', time = -time;
- if (fp->type == FMT_DTIME)
+ if (number == SYSMIS)
+ goto missing;
+
+ if (fmt_get_category (format->type) == FMT_CAT_DATE)
{
- double days = floor (time / 60. / 60. / 24.);
- cp = spprintf (temp_buf, "%02.0f ", days);
- time = time - days * 60. * 60. * 24.;
- width -= 3;
+ if (number <= 0)
+ goto missing;
+ calendar_offset_to_gregorian (number / 60. / 60. / 24.,
+ &year, &month, &day, &yday);
+ number = fmod (number, 60. * 60. * 24.);
}
else
- cp = temp_buf;
-
- cp = spprintf (cp, "%02.0f:%02.0f",
- fmod (floor (time / 60. / 60.), 24.),
- fmod (floor (time / 60.), 60.));
+ year = month = day = yday = 0;
- if (width >= 8)
+ while (*template != '\0')
{
- int w, d;
+ int excess_width;
- if (width >= 10 && fp->d >= 0 && fp->d != 0)
- d = min (fp->d, width - 9), w = 3 + d;
- else
- w = 2, d = 0;
+ int ch = *template;
+ int count = 1;
+ while (template[count] == ch)
+ count++;
+ template += count;
- cp = spprintf (cp, ":%0*.*f", w, d, fmod (time, 60.));
+ switch (ch)
+ {
+ case 'd':
+ if (count < 3)
+ p += sprintf (p, "%02d", day);
+ else
+ p += sprintf (p, "%03d", yday);
+ break;
+ case 'm':
+ if (count < 3)
+ p += sprintf (p, "%02d", month);
+ else
+ {
+ static const char *const months[12] =
+ {
+ "JAN", "FEB", "MAR", "APR", "MAY", "JUN",
+ "JUL", "AUG", "SEP", "OCT", "NOV", "DEC",
+ };
+ p = stpcpy (p, months[month - 1]);
+ }
+ break;
+ case 'y':
+ if (count >= 4)
+ {
+ if (year <= 9999)
+ p += sprintf (p, "%04d", year);
+ else if (format->type == FMT_DATETIME)
+ p = stpcpy (p, "****");
+ else
+ goto overflow;
+ }
+ else
+ {
+ int epoch = settings_get_epoch ();
+ int offset = year - epoch;
+ if (offset < 0 || offset > 99)
+ goto overflow;
+ p += sprintf (p, "%02d", abs (year) % 100);
+ }
+ break;
+ case 'q':
+ p += sprintf (p, "%d", (month - 1) / 3 + 1);
+ break;
+ case 'w':
+ p += sprintf (p, "%2d", (yday - 1) / 7 + 1);
+ break;
+ case 'D':
+ if (number < 0)
+ *p++ = '-';
+ 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 += 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)
+ goto overflow;
+ if (excess_width == 3 || excess_width == 4
+ || (excess_width >= 5 && format->d == 0))
+ p += sprintf (p, ":%02d", (int) number);
+ else if (excess_width >= 5)
+ {
+ int d = MIN (format->d, excess_width - 4);
+ int w = d + 3;
+ c_snprintf (p, 64, ":%0*.*f", w, d, number);
+ if (settings_get_decimal_char (FMT_F) != '.')
+ {
+ char *cp = strchr (p, '.');
+ if (cp != NULL)
+ *cp = settings_get_decimal_char (FMT_F);
+ }
+ p += strlen (p);
+ }
+ goto done;
+ default:
+ assert (count == 1);
+ *p++ = ch;
+ break;
+ }
}
- buf_copy_str_rpad (dst, fp->w, temp_buf);
- return 1;
+ done:
+ buf_copy_lpad (output, format->w, tmp, p - tmp, ' ');
+ output[format->w] = '\0';
+ return;
+
+ overflow:
+ output_overflow (format, output);
+ return;
+
+ missing:
+ output_missing (format, output);
+ return;
}
-static int
-convert_WKDAY (char *dst, const struct fmt_spec *fp, double wkday)
+/* Outputs WKDAY format. */
+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 (wkday < 1 || wkday > 7)
+ if (input->f >= 1 && input->f < 8)
{
- msg (ME, _("Weekday index %f does not lie between 1 and 7."),
- (double) wkday);
- return 0;
+ 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);
}
- buf_copy_str_rpad (dst, fp->w, weekdays[(int) wkday - 1]);
- return 1;
}
-static int
-convert_MONTH (char *dst, const struct fmt_spec *fp, double month)
+/* Outputs MONTH format. */
+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 (month < 1 || month > 12)
+ if (input->f >= 1 && input->f < 13)
{
- msg (ME, _("Month index %f does not lie between 1 and 12."),
- month);
- return 0;
+ 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);
}
-
- buf_copy_str_rpad (dst, fp->w, months[(int) month - 1]);
- return 1;
}
-\f
-/* Helper functions. */
-/* Copies SRC to DST, inserting commas and dollar signs as appropriate
- for format spec *FP. */
+/* Outputs A format. */
static void
-insert_commas (char *dst, const char *src, const struct fmt_spec *fp)
+output_A (const union value *input UNUSED,
+ const struct fmt_spec *format UNUSED, char *output UNUSED)
{
- /* Number of leading spaces in the number. This is the amount of
- room we have for inserting commas and dollar signs. */
- int n_spaces;
-
- /* Number of digits before the decimal point. This is used to
- determine the Number of commas to insert. */
- int n_digits;
-
- /* Number of commas to insert. */
- int n_commas;
-
- /* Number of items ,%$ to insert. */
- int n_items;
-
- /* Number of n_items items not to use for commas. */
- int n_reserved;
-
- /* Digit iterator. */
- int i;
-
- /* Source pointer. */
- const char *sp;
-
- /* Count spaces and digits. */
- sp = src;
- while (sp < src + fp->w && *sp == ' ')
- sp++;
- n_spaces = sp - src;
- sp = src + n_spaces;
- if (*sp == '-')
- sp++;
- n_digits = 0;
- while (sp + n_digits < src + fp->w && isdigit ((unsigned char) sp[n_digits]))
- n_digits++;
- n_commas = (n_digits - 1) / 3;
- n_items = n_commas + (fp->type == FMT_DOLLAR || fp->type == FMT_PCT);
-
- /* Check whether we have enough space to do insertions. */
- if (!n_spaces || !n_items)
- {
- memcpy (dst, src, fp->w);
- return;
- }
- if (n_items > n_spaces)
- {
- n_items -= n_commas;
- if (!n_items)
- {
- memcpy (dst, src, fp->w);
- return;
- }
- }
+ NOT_REACHED ();
+}
- /* Put spaces at the beginning if there's extra room. */
- if (n_spaces > n_items)
- {
- memset (dst, ' ', n_spaces - n_items);
- dst += n_spaces - n_items;
- }
+/* Outputs AHEX format. */
+static void
+output_AHEX (const union value *input, const struct fmt_spec *format,
+ char *output)
+{
+ output_hex (value_str (input, format->w), format->w / 2, output);
+}
+\f
+/* Decimal and scientific formatting. */
- /* Insert $ and reserve space for %. */
- n_reserved = 0;
- if (fp->type == FMT_DOLLAR)
+/* If REQUEST plus the current *WIDTH fits within MAX_WIDTH,
+ increments *WIDTH by REQUEST and return true.
+ Otherwise returns false without changing *WIDTH. */
+static bool
+allocate_space (int request, int max_width, int *width)
+{
+ assert (*width <= max_width);
+ if (request + *width <= max_width)
{
- *dst++ = '$';
- n_items--;
+ *width += request;
+ return true;
}
- else if (fp->type == FMT_PCT)
- n_reserved = 1;
+ else
+ return false;
+}
+
+/* Tries to compose the number represented by R, in the style of
+ FORMAT, into OUTPUT. Returns true if successful, false on
+ failure, which occurs if FORMAT's width is too narrow. If
+ REQUIRE_AFFIXES is true, then the prefix and suffix specified
+ by FORMAT's style must be included; otherwise, they may be
+ omitted to make the number fit. */
+static bool
+output_decimal (const struct rounder *r, const struct fmt_spec *format,
+ bool require_affixes, char *output)
+{
+ const struct fmt_number_style *style =
+ settings_get_style (format->type);
- /* Copy negative sign and digits, inserting commas. */
- if (sp - src > n_spaces)
- *dst++ = '-';
- for (i = n_digits; i; i--)
+ int decimals;
+
+ for (decimals = format->d; decimals >= 0; decimals--)
{
- if (i % 3 == 0 && n_digits > i && n_items > n_reserved)
- {
- n_items--;
- *dst++ = fp->type == FMT_COMMA ? get_grouping() : get_decimal();
- }
- *dst++ = *sp++;
- }
+ /* Formatted version of magnitude of NUMBER. */
+ char magnitude[64];
+
+ /* Number of digits in MAGNITUDE's integer and fractional parts. */
+ int integer_digits;
+
+ /* Amount of space within the field width already claimed.
+ Initially this is the width of MAGNITUDE, then it is reduced
+ in stages as space is allocated to prefixes and suffixes and
+ grouping characters. */
+ int width;
+
+ /* Include various decorations? */
+ bool add_neg_prefix;
+ bool add_affixes;
+ bool add_grouping;
+
+ /* Position in output. */
+ char *p;
+
+ /* Make sure there's room for the number's magnitude, plus
+ the negative suffix, plus (if negative) the negative
+ prefix. */
+ width = rounder_width (r, decimals, &integer_digits, &add_neg_prefix);
+ width += style->neg_suffix.width;
+ if (add_neg_prefix)
+ width += style->neg_prefix.width;
+ if (width > format->w)
+ continue;
+
+ /* If there's room for the prefix and suffix, allocate
+ space. If the affixes are required, but there's no
+ space, give up. */
+ add_affixes = allocate_space (fmt_affix_width (style),
+ 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
+ requested but they were all dropped. */
+ add_grouping = (style->grouping != 0
+ && integer_digits > 3
+ && (format->d == 0 || decimals > 0)
+ && allocate_space ((integer_digits - 1) / 3,
+ format->w, &width));
+
+ /* 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 = stpcpy (p, style->neg_prefix.s);
+ if (add_affixes)
+ p = stpcpy (p, style->prefix.s);
+ if (!add_grouping)
+ p = mempcpy (p, magnitude, integer_digits);
+ else
+ {
+ int i;
+ for (i = 0; i < integer_digits; i++)
+ {
+ if (i > 0 && (integer_digits - i) % 3 == 0)
+ *p++ = style->grouping;
+ *p++ = magnitude[i];
+ }
+ }
+ if (decimals > 0)
+ {
+ *p++ = style->decimal;
+ p = mempcpy (p, &magnitude[integer_digits + 1], decimals);
+ }
+ if (add_affixes)
+ p = stpcpy (p, style->suffix.s);
+ if (add_neg_prefix)
+ p = stpcpy (p, style->neg_suffix.s);
+ else
+ p = mempset (p, ' ', style->neg_suffix.width);
- /* Copy decimal places and insert % if necessary. */
- memcpy (dst, sp, fp->w - (sp - src));
- if (fp->type == FMT_PCT && n_items > 0)
- dst[fp->w - (sp - src)] = '%';
-}
+ assert (p >= output + format->w);
+ assert (p <= output + format->w + style->extra_bytes);
+ *p = '\0';
-/* Returns 1 if YEAR (i.e., 1987) can be represented in four digits, 0
- otherwise. */
-static int
-year4 (int year)
-{
- if (year >= 1 && year <= 9999)
- return 1;
- msg (ME, _("Year %d cannot be represented in four digits for "
- "output formatting purposes."), year);
- return 0;
+ return true;
+ }
+ return false;
}
-static int
-try_CCx (char *dst, const struct fmt_spec *fp, double number)
+/* Formats NUMBER into OUTPUT in scientific notation according to
+ the style of the format specified in FORMAT. */
+static bool
+output_scientific (double number, const struct fmt_spec *format,
+ bool require_affixes, char *output)
{
- const struct custom_currency *cc = get_cc(fp->type - FMT_CCA);
-
- struct fmt_spec f;
-
- char buf[64];
- char buf2[64];
- char *cp;
+ const struct fmt_number_style *style =
+ settings_get_style (format->type);
+ int width;
+ int fraction_width;
+ bool add_affixes;
+ char *p;
- /* Determine length available, decimal character for number
- proper. */
- f.type = cc->decimal == get_decimal () ? FMT_COMMA : FMT_DOT;
- f.w = fp->w - strlen (cc->prefix) - strlen (cc->suffix);
+ /* Allocate minimum required space. */
+ width = 6 + style->neg_suffix.width;
+ if (number < 0)
+ width += style->neg_prefix.width;
+ if (width > format->w)
+ return false;
+
+ /* Check for room for prefix and suffix. */
+ add_affixes = allocate_space (fmt_affix_width (style), format->w, &width);
+ if (require_affixes && !add_affixes)
+ return false;
+
+ /* 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 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;
+ width += fraction_width;
+
+ /* Format (except suffix). */
+ p = output;
+ if (width < format->w)
+ p = mempset (p, ' ', format->w - width);
if (number < 0)
- f.w -= strlen (cc->neg_prefix) + strlen (cc->neg_suffix) - 1;
+ p = stpcpy (p, style->neg_prefix.s);
+ if (add_affixes)
+ p = stpcpy (p, style->prefix.s);
+ if (fraction_width > 0)
+ c_snprintf (p, 64, "%#.*E", fraction_width - 1, fabs (number));
else
- /* Convert -0 to +0. */
- number = fabs (number);
- f.d = fp->d;
+ c_snprintf (p, 64, "%.0E", fabs (number));
- if (f.w <= 0)
- return 0;
+ /* The C locale always uses a period `.' as a decimal point.
+ Translate to comma if necessary. */
+ if (style->decimal != '.')
+ {
+ char *cp = strchr (p, '.');
+ if (cp != NULL)
+ *cp = style->decimal;
+ }
- /* There's room for all that currency crap. Let's do the F
- conversion first. */
- if (!convert_F (buf, &f, number) || *buf == '*')
- return 0;
- insert_commas (buf2, buf, &f);
+ /* Make exponent have exactly three digits, plus sign. */
+ {
+ char *cp = strchr (p, 'E') + 1;
+ long int exponent = strtol (cp, NULL, 10);
+ if (abs (exponent) > 999)
+ return false;
+ sprintf (cp, "%+04ld", exponent);
+ }
- /* Postprocess back into buf. */
- cp = buf;
+ /* Add suffixes. */
+ p = strchr (p, '\0');
+ if (add_affixes)
+ p = stpcpy (p, style->suffix.s);
if (number < 0)
- cp = stpcpy (cp, cc->neg_prefix);
- cp = stpcpy (cp, cc->prefix);
- {
- char *bp = buf2;
- while (*bp == ' ')
- bp++;
+ p = stpcpy (p, style->neg_suffix.s);
+ else
+ p = mempset (p, ' ', style->neg_suffix.width);
- assert ((number >= 0) ^ (*bp == '-'));
- if (number < 0)
- bp++;
+ assert (p >= output + format->w);
+ assert (p <= output + format->w + style->extra_bytes);
+ *p = '\0';
- memcpy (cp, bp, f.w - (bp - buf2));
- cp += f.w - (bp - buf2);
- }
- cp = stpcpy (cp, cc->suffix);
- if (number < 0)
- cp = stpcpy (cp, cc->neg_suffix);
+ return true;
+}
+\f
+/* 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)
+{
+ int digit = r->string[r->integer_digits + decimals + 1];
+ assert (digit >= '0' && digit <= '9');
+ return digit >= '5';
+}
- /* Copy into dst. */
- assert (cp - buf <= fp->w);
- if (cp - buf < fp->w)
+/* Initializes R for formatting the magnitude of NUMBER to no
+ more than MAX_DECIMAL decimal places. */
+static void
+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)
{
- memcpy (&dst[fp->w - (cp - buf)], buf, cp - buf);
- memset (dst, ' ', fp->w - (cp - buf));
+ /* Fast path. No rounding needed.
+
+ We append ".00" to the integer representation because
+ round_up assumes that fractional digits are present. */
+ c_snprintf (r->string, 64, "%.0f.00", fabs (round (number)));
}
else
- memcpy (dst, buf, fp->w);
+ {
+ /* 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
+ away from zero. This is not what sprintf normally does
+ (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
+ Steele and White paper on printing floating-point
+ 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. */
+ 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;
+ frexp (number, &binary_exponent);
+ decimal_exponent = binary_exponent * 3 / 10;
+ format_decimals = (DBL_DIG + 1) - decimal_exponent;
+ if (format_decimals > max_decimals + 2)
+ c_snprintf (r->string, 64, "%.*f", format_decimals, fabs (number));
+ }
+ }
- return 1;
+ 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;
}
-static int
-format_and_round (char *dst, double number, const struct fmt_spec *fp,
- int decimals);
+/* Returns the number of characters required to format the
+ magnitude represented by R to DECIMALS decimal places.
+ The return value includes integer digits and a decimal point
+ and fractional digits, if any, but it does not include any
+ negative prefix or suffix or other affixes.
-/* Tries to format NUMBER into DST as the F format specified in
- *FP. Return true if successful, false on failure. */
+ *INTEGER_DIGITS is set to the number of digits before the
+ decimal point in the output, between 0 and 40.
+
+ If R represents a negative number and its rounded
+ representation would include at least one nonzero digit,
+ *NEGATIVE is set to true; otherwise, it is set to false. */
static int
-try_F (char *dst, const struct fmt_spec *fp, double number)
+rounder_width (const struct rounder *r, int decimals,
+ int *integer_digits, bool *negative)
+{
+ /* Calculate base measures. */
+ int width = r->integer_digits;
+ if (decimals > 0)
+ width += decimals + 1;
+ *integer_digits = r->integer_digits;
+ *negative = r->negative;
+
+ /* Rounding can cause adjustments. */
+ if (should_round_up (r, decimals))
+ {
+ /* Rounding up leading 9s adds a new digit (a 1). */
+ if (r->leading_nines >= width)
+ {
+ width++;
+ ++*integer_digits;
+ }
+ }
+ else
+ {
+ /* Rounding down. */
+ 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)
+ {
+ /* 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;
+}
+
+/* Formats the magnitude represented by R into OUTPUT, rounding
+ to DECIMALS decimal places. Exactly as many characters as
+ indicated by rounder_width are written. No terminating null
+ is appended. */
+static void
+rounder_format (const struct rounder *r, int decimals, char *output)
{
- assert (fp->w <= 40);
- if (finite (number))
+ int base_width = r->integer_digits + (decimals > 0 ? decimals + 1 : 0);
+ if (should_round_up (r, decimals))
{
- if (fabs (number) < power10[fp->w])
+ if (r->leading_nines < base_width)
{
- /* The value may fit in the field. */
- if (fp->d == 0)
+ /* 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--)
{
- /* 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)
+ assert (p >= output);
+ if (*p == '9')
+ *p = '0';
+ else if (*p >= '0' && *p <= '8')
{
- buf_copy_str_lpad (dst, fp->w, buf);
- return true;
+ (*p)++;
+ break;
}
- else
- return false;
+ else
+ 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)
{
- /* 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);
+ *p++ = '.';
+ p = mempset (p, '0', decimals);
}
+ assert (p == output + base_width + 1);
+ }
+ }
+ else
+ {
+ /* Rounding down. */
+ if (r->integer_digits != 0 || decimals != 0)
+ {
+ /* Common case: just copy the digits. */
+ memcpy (output, r->string, base_width);
}
- else
+ else
{
- /* The value is too big to fit in the field. */
- return false;
+ /* No digits remain. The output is just a zero. */
+ output[0] = '0';
}
}
- else
- return convert_infinite (dst, fp, number);
}
+\f
+/* Helper functions. */
-/* 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.
+/* Returns 10**X. */
+static double PURE_FUNCTION
+power10 (int x)
+{
+ static const double p[] =
+ {
+ 1e0, 1e1, 1e2, 1e3, 1e4, 1e5, 1e6, 1e7, 1e8, 1e9,
+ 1e10, 1e11, 1e12, 1e13, 1e14, 1e15, 1e16, 1e17, 1e18, 1e19,
+ 1e20, 1e21, 1e22, 1e23, 1e24, 1e25, 1e26, 1e27, 1e28, 1e29,
+ 1e30, 1e31, 1e32, 1e33, 1e34, 1e35, 1e36, 1e37, 1e38, 1e39,
+ 1e40,
+ };
+ return x >= 0 && x < sizeof p / sizeof *p ? p[x] : pow (10.0, x);
+}
- 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)
+/* Returns 256**X. */
+static double PURE_FUNCTION
+power256 (int x)
{
- /* Number of characters before the decimal point,
- which includes digits and possibly a minus sign. */
- int predot_chars;
+ static const double p[] =
+ {
+ 1.0,
+ 256.0,
+ 65536.0,
+ 16777216.0,
+ 4294967296.0,
+ 1099511627776.0,
+ 281474976710656.0,
+ 72057594037927936.0,
+ 18446744073709551616.0
+ };
+ return x >= 0 && x < sizeof p / sizeof *p ? p[x] : pow (256.0, x);
+}
- /* Number of digits in the output fraction,
- which may be smaller than fp->d if there's not enough room. */
- int fraction_digits;
+/* Formats non-finite NUMBER into OUTPUT according to the width
+ given in FORMAT. */
+static void
+output_infinite (double number, const struct fmt_spec *format, char *output)
+{
+ assert (!isfinite (number));
- /* Points to last digit that will remain in the fraction after
- rounding. */
- char *final_frac_dig;
+ if (format->w >= 3)
+ {
+ const char *s;
- /* Round up? */
- bool round_up;
-
- char buf[128];
-
- assert (decimals > fp->d);
- if (decimals > LDBL_DIG)
- decimals = LDBL_DIG + 1;
+ if (isnan (number))
+ s = "NaN";
+ else if (isinf (number))
+ s = number > 0 ? "+Infinity" : "-Infinity";
+ else
+ s = "Unknown";
- sprintf (buf, "%.*f", decimals, number);
+ buf_copy_str_lpad (output, format->w, s, ' ');
+ }
+ else
+ output_overflow (format, output);
- /* 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));
+ output[format->w] = '\0';
+}
- predot_chars = strcspn (buf, ".");
- if (predot_chars > fp->w)
- {
- /* 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)
+/* Formats OUTPUT as a missing value for the given FORMAT. */
+static void
+output_missing (const struct fmt_spec *format, char *output)
+{
+ memset (output, ' ', format->w);
+
+ if (format->type != FMT_N)
{
- /* 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 - 1);
- return 1;
+ int dot_ofs = (format->type == FMT_PCT ? 2
+ : format->type == FMT_E ? 5
+ : 1);
+ 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)
+{
+ memset (output, '*', format->w);
+ output[format->w] = '\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;
+/* Converts the integer part of NUMBER to a packed BCD number
+ with the given number of DIGITS in OUTPUT. If DIGITS is odd,
+ the least significant nibble of the final byte in OUTPUT is
+ set to 0. Returns true if successful, false if NUMBER is not
+ representable. On failure, OUTPUT is cleared to all zero
+ bytes. */
+static bool
+output_bcd_integer (double number, int digits, char *output)
+{
+ char decimal[64];
- /* Decide rounding direction and truncate string. */
- if (final_frac_dig[1] == '5'
- && strspn (final_frac_dig + 2, "0") == strlen (final_frac_dig + 2))
+ assert (digits < sizeof decimal);
+
+ output[DIV_RND_UP (digits, 2)] = '\0';
+ if (number != SYSMIS
+ && number >= 0.
+ && number < power10 (digits)
+ && c_snprintf (decimal, 64, "%0*.0f", digits, round (number)) == digits)
{
- /* 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
+ const char *src = decimal;
+ int i;
+
+ for (i = 0; i < digits / 2; i++)
{
- /* We used up all our fractional digits and still don't
- know. Round to even. */
- round_up = (final_frac_dig[0] - '0') % 2 != 0;
+ int d0 = *src++ - '0';
+ int d1 = *src++ - '0';
+ *output++ = (d0 << 4) + d1;
}
+ if (digits % 2)
+ *output = (*src - '0') << 4;
+
+ return true;
}
else
- round_up = final_frac_dig[1] >= '5';
- final_frac_dig[1] = '\0';
-
- /* 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;
- }
- }
+ memset (output, 0, DIV_RND_UP (digits, 2));
+ return false;
}
-
- /* Omit `-' if value output is zero. */
- if (buf[0] == '-' && buf[strspn (buf, "-.0")] == '\0')
- memmove (buf, buf + 1, strlen (buf));
-
- buf_copy_str_lpad (dst, fp->w, buf);
- return 1;
}
-/* 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)
+/* Writes VALUE to OUTPUT as a BYTES-byte binary integer of the
+ given INTEGER_FORMAT. */
+static void
+output_binary_integer (uint64_t value, int bytes,
+ enum integer_format integer_format, char *output)
{
- assert (!finite (number));
-
- if (fp->w >= 3)
- {
- const char *s;
+ integer_put (value, integer_format, output, bytes);
+}
- if (isnan (number))
- s = "NaN";
- else if (isinf (number))
- s = number > 0 ? "+Infinity" : "-Infinity";
- else
- s = "Unknown";
+/* 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)
+{
+ const uint8_t *data = data_;
+ size_t i;
- buf_copy_str_lpad (dst, fp->w, s);
+ for (i = 0; i < bytes; i++)
+ {
+ static const char hex_digits[] = "0123456789ABCDEF";
+ *output++ = hex_digits[data[i] >> 4];
+ *output++ = hex_digits[data[i] & 15];
}
- else
- memset (dst, '*', fp->w);
-
- return true;
+ *output = '\0';
}