X-Git-Url: https://pintos-os.org/cgi-bin/gitweb.cgi?a=blobdiff_plain;f=src%2Fdata%2Fdata-out.c;h=7f15e5b24a8a42caafe8ad48d5dd809b467d1cd6;hb=f15c854d8500105766b2f5666bb62b983ff24f88;hp=53d9850db88a0da0ffb9ac3c1a76ada0d097955b;hpb=ebccf00cbddbcadb5883fa98ddbccbea67642295;p=pspp-builds.git diff --git a/src/data/data-out.c b/src/data/data-out.c index 53d9850d..7f15e5b2 100644 --- a/src/data/data-out.c +++ b/src/data/data-out.c @@ -1,1255 +1,1050 @@ -/* PSPP - computes sample statistics. - Copyright (C) 1997-9, 2000 Free Software Foundation, Inc. - Written by Ben Pfaff . +/* PSPP - a program for statistical analysis. + Copyright (C) 1997-9, 2000, 2006, 2009 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 . */ #include -#include + +#include "data-out.h" + #include -#include #include +#include +#include #include #include -#include "calendar.h" + +#include +#include +#include +#include + +#include +#include +#include #include -#include "format.h" -#include #include -#include -#include "settings.h" #include -#include "variable.h" +#include + +#include "minmax.h" #include "gettext.h" #define _(msgid) gettext (msgid) -/* 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); + +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 *); + - 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; - - case FMT_WKDAY: - ok = convert_WKDAY (s, fp, number); - break; +#define FMT(NAME, METHOD, IMIN, OMIN, IO, CATEGORY) output_##METHOD, +#include "format.def" + }; - case FMT_MONTH: - ok = convert_MONTH (s, fp, number); - break; +/* Similar to data_out. Additionally recodes the output from + native form into the given legacy character ENCODING. + OUTPUT must be provided by the caller and must be at least + FORMAT->w long. No null terminator is appended to OUTPUT. +*/ +void +data_out_legacy (const union value *input, const char *encoding, + const struct fmt_spec *format, char *output) +{ + assert (fmt_check_output (format)); - default: - assert (0); - abort (); - } - } - else - { - /* String formatting. */ - const char *string = v->s; + converters[format->type] (input, format, output); + if (0 != strcmp (encoding, LEGACY_NATIVE) + && fmt_get_category (format->type) != FMT_CAT_BINARY) + legacy_recode (LEGACY_NATIVE, output, encoding, output, format->w); +} - switch (fp->type) - { - case FMT_A: - ok = convert_A (s, fp, string); - break; +/* Converts the INPUT value into printable form, according to format + specification FORMAT. - case FMT_AHEX: - ok = convert_AHEX (s, fp, string); - break; + VALUE must be the correct width for FORMAT, that is, its + width must be fmt_var_width(FORMAT). - default: - assert (0); - abort (); - } - } + 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 struct fmt_spec *format, + struct pool *pool) +{ + char *output = pool_malloc (pool, format->w + 1); + assert (fmt_check_output (format)); - /* Error handling. */ - if (!ok) - strncpy (s, "ERROR", fp->w); - - return ok; + converters[format->type] (input, format, output); + output[format->w] = '\0'; + return output; } -/* 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) +char * +data_out (const union value *input, const struct fmt_spec *format) { - struct fmt_spec f = make_output_format (FMT_F, w, d); - convert_F (s, &f, v); + return data_out_pool (input, format, NULL); } + /* Main conversion functions. */ -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); - -#if FLT_RADIX!=2 -#error Write your own floating-point output routines. -#endif +/* 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; -/* Converts a number between 0 and 15 inclusive to a `hexit' - [0-9A-F]. */ -#define MAKE_HEXIT(X) ("0123456789ABCDEF"[X]) + if (number == SYSMIS) + output_missing (format, output); + else if (!isfinite (number)) + output_infinite (number, format, output); + else + { + if (format->type != FMT_E && fabs (number) < 1.5 * power10 (format->w)) + { + struct rounder r; + rounder_init (&r, number, format->d); -/* 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, - }; + if (output_decimal (&r, format, true, output) + || output_scientific (number, format, true, output) + || output_decimal (&r, format, false, output)) + return; + } -/* 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; + if (!output_scientific (number, format, false, output)) + output_overflow (format, output); + } } -/* Handles N format. */ -static int -convert_N (char *dst, const struct fmt_spec *fp, double number) +/* Outputs N format. */ +static void +output_N (const union value *input, const struct fmt_spec *format, + char *output) { - double d = floor (number); - - if (d < 0 || d == SYSMIS) - { - msg (ME, _("The N output format cannot be used to output a " - "negative number or the system-missing value.")); - return 0; - } - - if (d < power10[fp->w]) + double number = input->f * power10 (format->d); + if (input->f == SYSMIS || number < 0) + output_missing (format, output); + else { char buf[128]; - sprintf (buf, "%0*.0f", fp->w, number); - memcpy (dst, buf, fp->w); + number = fabs (round (number)); + if (number < power10 (format->w) + && sprintf (buf, "%0*.0f", format->w, number) == format->w) + memcpy (output, buf, format->w); + else + output_overflow (format, output); } - else - memset (dst, '*', fp->w); - - return 1; } -/* Handles E format. Also operates as fallback for some other - formats. */ -static int -convert_E (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) { - /* Temporary buffer. */ + double number = input->f * power10 (format->d); 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) - { - memset (dst, '*', fp->w); - return 1; - } - - /* 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); - } - } - - /* 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)) + 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 { - char *cp = strchr (buf, '.'); - if (cp) - *cp = ','; + if (number < 0 && strspn (buf, "0") < format->w) + { + char *p = &buf[format->w - 1]; + *p = "}JKLMNOPQR"[*p - '0']; + } + memcpy (output, buf, format->w); } - - memcpy (dst, buf, fp->w); - return 1; } -/* Handles COMMA, DOT, DOLLAR, and PCT formats. */ -static int -convert_F_plus (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) { - char buf[40]; - - if (try_F (buf, fp, number)) - insert_commas (dst, buf, fp); + 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 - convert_E (dst, fp, number); - - return 1; + output[format->w - 1] |= 0xf; } -static int -convert_Z (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) { - static int warned = 0; - - 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; - } + output_bcd_integer (input->f * power10 (format->d), format->w * 2, output); +} - if (number == SYSMIS) +/* Outputs IB format. */ +static void +output_IB (const union value *input, const struct fmt_spec *format, + char *output) +{ + 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 { - msg (ME, _("The system-missing value cannot be output as a zoned " - "decimal number.")); - return 0; + uint64_t integer = fabs (number); + if (number < 0) + integer = -integer; + output_binary_integer (integer, format->w, + settings_get_output_integer_format (), + output); } - - { - 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; - } - - return 1; } -static int -convert_A (char *dst, const struct fmt_spec *fp, const char *string) +/* Outputs PIB format. */ +static void +output_PIB (const union value *input, const struct fmt_spec *format, + char *output) { - memcpy(dst, string, fp->w); - return 1; + 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); } -static int -convert_AHEX (char *dst, const struct fmt_spec *fp, const char *string) +/* Outputs PIBHEX format. */ +static void +output_PIBHEX (const union value *input, const struct fmt_spec *format, + char *output) { - int i; - - for (i = 0; i < fp->w / 2; i++) + 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 { - *dst++ = MAKE_HEXIT ((string[i]) >> 4); - *dst++ = MAKE_HEXIT ((string[i]) & 0xf); + char tmp[8]; + output_binary_integer (number, format->w / 2, INTEGER_MSB_FIRST, tmp); + output_hex (tmp, format->w / 2, output); } - - return 1; } -static int -convert_IB (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) { - /* 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]; + double d = input->f; + memcpy (output, &d, format->w); +} - /* Fraction (mantissa). */ - double frac; +/* Outputs RBHEX format. */ +static void +output_RBHEX (const union value *input, const struct fmt_spec *format, + char *output) +{ + double d = input->f; + output_hex (&d, format->w / 2, output); +} - /* Exponent. */ - int exp; +/* 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) +{ + double number = input->f; + int year, month, day, yday; - /* Difference between exponent and (-8*fp->w-1). */ - int diff; + const char *template = fmt_date_template (format->type); + size_t template_width = strlen (template); + int excess_width = format->w - template_width; - /* Counter. */ - int i; + char tmp[64]; + char *p = tmp; - /* 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); + assert (format->w >= template_width); + if (number == SYSMIS) + goto missing; - /* Extract each base-256 digit. */ - for (i = 0; i < fp->w; i++) + if (fmt_get_category (format->type) == FMT_CAT_DATE) { - modf (frac, &frac); - frac *= 256.0; - temp[i] = floor (frac); + if (number <= 0) + goto missing; + calendar_offset_to_gregorian (number / 60. / 60. / 24., + &year, &month, &day, &yday); + number = fmod (number, 60. * 60. * 24.); } + else + year = month = day = yday = 0; - /* Perform two's-complement negation if number is negative. */ - if (number < 0) + while (*template != '\0') { - /* 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; - } - } - memcpy (dst, temp, fp->w); -#ifndef WORDS_BIGENDIAN - buf_reverse (dst, fp->w); -#endif - - return 1; -} - -static int -convert_P (char *dst, const struct fmt_spec *fp, double number) -{ - /* Buffer for fp->w*2-1 characters + a decimal point if library is - not quite compliant + a null. */ - char buf[17]; - - /* Counter. */ - int i; + int ch = *template; + int count = 1; + while (template[count] == ch) + count++; + template += count; - /* Main extraction. */ - sprintf (buf, "%0*.0f", fp->w * 2 - 1, floor (fabs (number))); + 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 || excess_width >= 2) + { + 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 += sprintf (p, "%*.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 = 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; + sprintf (p, ":%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); + } + break; + case 'X': + *p++ = ' '; + break; + default: + assert (count == 1); + *p++ = ch; + break; + } + } - for (i = 0; i < fp->w; i++) - ((unsigned char *) dst)[i] - = ((buf[i * 2] - '0') << 4) + buf[i * 2 + 1] - '0'; + buf_copy_lpad (output, format->w, tmp, p - tmp, ' '); + return; - /* Set sign. */ - dst[fp->w - 1] &= 0xf0; - if (number >= 0.0) - dst[fp->w - 1] |= 0xf; - else - dst[fp->w - 1] |= 0xd; + overflow: + output_overflow (format, output); + return; - return 1; + missing: + output_missing (format, output); + return; } -static int -convert_PIB (char *dst, const struct fmt_spec *fp, double number) +/* Outputs WKDAY format. */ +static void +output_WKDAY (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); + static const char *const weekdays[7] = + { + "SUNDAY", "MONDAY", "TUESDAY", "WEDNESDAY", + "THURSDAY", "FRIDAY", "SATURDAY", + }; - /* Extract each base-256 digit. */ - for (i = 0; i < fp->w; i++) + if (input->f >= 1 && input->f < 8) + buf_copy_str_rpad (output, format->w, weekdays[(int) input->f - 1], ' '); + else { - modf (frac, &frac); - frac *= 256.0; - ((unsigned char *) dst)[i] = floor (frac); + if (input->f != SYSMIS) + msg (ME, _("Weekday number %f is not between 1 and 7."), input->f); + output_missing (format, output); } -#ifndef WORDS_BIGENDIAN - buf_reverse (dst, fp->w); -#endif - - return 1; } -static int -convert_PIBHEX (char *dst, const struct fmt_spec *fp, double number) +/* Outputs MONTH format. */ +static void +output_MONTH (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); + static const char *const months[12] = + { + "JANUARY", "FEBRUARY", "MARCH", "APRIL", "MAY", "JUNE", + "JULY", "AUGUST", "SEPTEMBER", "OCTOBER", "NOVEMBER", "DECEMBER", + }; - /* Extract each hexit. */ - for (i = 0; i < fp->w; i++) + if (input->f >= 1 && input->f < 13) + buf_copy_str_rpad (output, format->w, months[(int) input->f - 1], ' '); + else { - modf (frac, &frac); - frac *= 16.0; - *dst++ = MAKE_HEXIT ((int) floor (frac)); + if (input->f != SYSMIS) + msg (ME, _("Month number %f is not between 1 and 12."), input->f); + output_missing (format, output); } - - return 1; } -static int -convert_PK (char *dst, const struct fmt_spec *fp, double number) +/* Outputs A format. */ +static void +output_A (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'; - - return 1; + memcpy (output, value_str (input, format->w), format->w); } -static int -convert_RB (char *dst, const struct fmt_spec *fp, double number) +/* Outputs AHEX format. */ +static void +output_AHEX (const union value *input, const struct fmt_spec *format, + char *output) { - union - { - double d; - char c[8]; - } - u; - - u.d = number; - memcpy (dst, u.c, fp->w); - - return 1; + output_hex (value_str (input, format->w), format->w / 2, output); } + +/* Decimal and scientific formatting. */ -static int -convert_RBHEX (char *dst, const struct fmt_spec *fp, double number) +/* 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) { - union - { - double d; - char c[8]; - } - u; - - int i; - - u.d = number; - for (i = 0; i < fp->w / 2; i++) + assert (*width <= max_width); + if (request + *width <= max_width) { - *dst++ = MAKE_HEXIT (u.c[i] >> 4); - *dst++ = MAKE_HEXIT (u.c[i] & 15); + *width += request; + return true; } - - return 1; -} - -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); - } + return false; } -static int -convert_date (char *dst, const struct fmt_spec *fp, double number) +/* 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) { - 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; + const struct fmt_number_style *style = + settings_get_style (format->type); - if (ofs < 1) - return 0; + int decimals; - calendar_offset_to_gregorian (ofs, &year, &month, &day); - switch (fp->type) + for (decimals = format->d; decimals >= 0; decimals--) { - 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); + /* 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 += ss_length (style->neg_suffix); + if (add_neg_prefix) + width += ss_length (style->neg_prefix); + 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 = mempcpy (p, ss_data (style->neg_prefix), + ss_length (style->neg_prefix)); + if (add_affixes) + p = mempcpy (p, ss_data (style->prefix), ss_length (style->prefix)); + if (!add_grouping) + p = mempcpy (p, magnitude, integer_digits); 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); + { + 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 = mempcpy (p, ss_data (style->suffix), ss_length (style->suffix)); + if (add_neg_prefix) + p = mempcpy (p, ss_data (style->neg_suffix), + ss_length (style->neg_suffix)); 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); - } + p = mempset (p, ' ', ss_length (style->neg_suffix)); + assert (p == output + format->w); - if (buf[0] == 0) - return 0; - buf_copy_str_rpad (dst, fp->w, buf); - return 1; + return true; + } + return false; } -static int -convert_time (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) { - char temp_buf[40]; - char *cp; - - double time; + const struct fmt_number_style *style = + settings_get_style (format->type); int width; + int fraction_width; + bool add_affixes; + char buf[64], *p; - if (fabs (number) > 1e20) - { - msg (ME, _("Time value %g too large in magnitude to convert to " - "alphanumeric time."), number); - return 0; - } - - time = number; - width = fp->w; - cp = temp_buf; - if (time < 0) - *cp++ = '-', time = -time; - if (fp->type == FMT_DTIME) - { - double days = floor (time / 60. / 60. / 24.); - cp = spprintf (temp_buf, "%02.0f ", days); - time = time - days * 60. * 60. * 24.; - width -= 3; - } + /* Allocate minimum required space. */ + width = 6 + ss_length (style->neg_suffix); + if (number < 0) + width += ss_length (style->neg_prefix); + 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 sprintf, 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 = buf; + 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)); + if (add_affixes) + p = mempcpy (p, ss_data (style->prefix), ss_length (style->prefix)); + if (fraction_width > 0) + sprintf (p, "%#.*E", fraction_width - 1, fabs (number)); else - cp = temp_buf; - - cp = spprintf (cp, "%02.0f:%02.0f", - fmod (floor (time / 60. / 60.), 24.), - fmod (floor (time / 60.), 60.)); + sprintf (p, "%.0E", fabs (number)); - if (width >= 8) + /* The C locale always uses a period `.' as a decimal point. + Translate to comma if necessary. */ + if (style->decimal != '.') { - int w, d; - - if (width >= 10 && fp->d >= 0 && fp->d != 0) - d = min (fp->d, width - 9), w = 3 + d; - else - w = 2, d = 0; - - cp = spprintf (cp, ":%0*.*f", w, d, fmod (time, 60.)); + char *cp = strchr (p, '.'); + if (cp != NULL) + *cp = style->decimal; } - buf_copy_str_rpad (dst, fp->w, temp_buf); - return 1; -} + /* 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); + } -static int -convert_WKDAY (char *dst, const struct fmt_spec *fp, double wkday) -{ - static const char *weekdays[7] = - { - "SUNDAY", "MONDAY", "TUESDAY", "WEDNESDAY", - "THURSDAY", "FRIDAY", "SATURDAY", - }; + /* Add suffixes. */ + p = strchr (p, '\0'); + if (add_affixes) + p = mempcpy (p, ss_data (style->suffix), ss_length (style->suffix)); + if (number < 0) + p = mempcpy (p, ss_data (style->neg_suffix), + ss_length (style->neg_suffix)); + else + p = mempset (p, ' ', ss_length (style->neg_suffix)); - if (wkday < 1 || wkday > 7) - { - msg (ME, _("Weekday index %f does not lie between 1 and 7."), - (double) wkday); - return 0; - } - buf_copy_str_rpad (dst, fp->w, weekdays[(int) wkday - 1]); + assert (p == buf + format->w); + memcpy (output, buf, format->w); - return 1; + return true; } - -static int -convert_MONTH (char *dst, const struct fmt_spec *fp, double month) + +/* 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) { - static const char *months[12] = - { - "JANUARY", "FEBRUARY", "MARCH", "APRIL", "MAY", "JUNE", - "JULY", "AUGUST", "SEPTEMBER", "OCTOBER", "NOVEMBER", "DECEMBER", - }; - - if (month < 1 || month > 12) - { - msg (ME, _("Month index %f does not lie between 1 and 12."), - month); - return 0; - } - - buf_copy_str_rpad (dst, fp->w, months[(int) month - 1]); - - return 1; + int digit = r->string[r->integer_digits + decimals + 1]; + assert (digit >= '0' && digit <= '9'); + return digit >= '5'; } - -/* Helper functions. */ -/* Copies SRC to DST, inserting commas and dollar signs as appropriate - for format spec *FP. */ +/* Initializes R for formatting the magnitude of NUMBER to no + more than MAX_DECIMAL decimal places. */ static void -insert_commas (char *dst, const char *src, const struct fmt_spec *fp) +rounder_init (struct rounder *r, double number, int max_decimals) { - /* 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) + assert (fabs (number) < 1e41); + assert (max_decimals >= 0 && max_decimals <= 16); + if (max_decimals == 0) { - n_items -= n_commas; - if (!n_items) - { - memcpy (dst, src, fp->w); - return; - } - } + /* Fast path. No rounding needed. - /* 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; + We append ".00" to the integer representation because + round_up assumes that fractional digits are present. */ + sprintf (r->string, "%.0f.00", fabs (round (number))); } - - /* Insert $ and reserve space for %. */ - n_reserved = 0; - if (fp->type == FMT_DOLLAR) + else { - *dst++ = '$'; - n_items--; + /* 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. */ + sprintf (r->string, "%.*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) + sprintf (r->string, "%.*f", format_decimals, fabs (number)); + } } - else if (fp->type == FMT_PCT) - n_reserved = 1; - /* Copy negative sign and digits, inserting commas. */ - if (sp - src > n_spaces) - *dst++ = '-'; - for (i = n_digits; i; i--) - { - if (i % 3 == 0 && n_digits > i && n_items > n_reserved) - { - n_items--; - *dst++ = fp->type == FMT_COMMA ? get_grouping() : get_decimal(); - } - *dst++ = *sp++; - } + if (r->string[0] == '0') + memmove (r->string, &r->string[1], strlen (r->string)); - /* 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)] = '%'; + r->leading_zeros = strspn (r->string, "0."); + r->leading_nines = strspn (r->string, "9."); + r->integer_digits = strchr (r->string, '.') - r->string; + r->negative = number < 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; -} +/* 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. + + *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_CCx (char *dst, const struct fmt_spec *fp, double number) +rounder_width (const struct rounder *r, int decimals, + int *integer_digits, bool *negative) { - const struct custom_currency *cc = get_cc(fp->type - FMT_CCA); - - struct fmt_spec f; - - char buf[64]; - char buf2[64]; - char *cp; - - /* 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); - if (number < 0) - f.w -= strlen (cc->neg_prefix) + strlen (cc->neg_suffix) - 1; - else - /* Convert -0 to +0. */ - number = fabs (number); - f.d = fp->d; - - if (f.w <= 0) - return 0; - - /* 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); - - /* Postprocess back into buf. */ - cp = buf; - if (number < 0) - cp = stpcpy (cp, cc->neg_prefix); - cp = stpcpy (cp, cc->prefix); - { - char *bp = buf2; - while (*bp == ' ') - bp++; - - assert ((number >= 0) ^ (*bp == '-')); - if (number < 0) - bp++; - - memcpy (cp, bp, f.w - (bp - buf2)); - cp += f.w - (bp - buf2); - } - cp = stpcpy (cp, cc->suffix); - if (number < 0) - cp = stpcpy (cp, cc->neg_suffix); - - /* Copy into dst. */ - assert (cp - buf <= fp->w); - if (cp - buf < fp->w) + /* 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)) { - memcpy (&dst[fp->w - (cp - buf)], buf, cp - buf); - memset (dst, ' ', fp->w - (cp - buf)); + /* Rounding up leading 9s adds a new digit (a 1). */ + if (r->leading_nines >= width) + { + width++; + ++*integer_digits; + } } else - memcpy (dst, buf, fp->w); - - return 1; + { + /* 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; } -static int -format_and_round (char *dst, double number, const struct fmt_spec *fp, - int decimals); - -/* Tries to format NUMBER into DST as the F format specified in - *FP. Return true if successful, false on failure. */ -static int -try_F (char *dst, const struct fmt_spec *fp, double number) +/* 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 + } + else + { + /* Rounding down. */ + if (r->integer_digits != 0 || decimals != 0) + { + /* Common case: just copy the digits. */ + memcpy (output, r->string, base_width); + } + 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); } + +/* 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)); +/* 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); - predot_chars = strcspn (buf, "."); - if (predot_chars > fp->w) - { - /* Can't possibly fit. */ - return 0; - } - else if (predot_chars == fp->w) + if (format->type != FMT_N) { - /* Exact fit for integer part and sign. */ - memcpy (dst, buf, fp->w); - return 1; - } - else if (predot_chars + 1 == fp->w) - { - /* There's room for the decimal point, but not for any - digits of the fraction. - Right-justify the integer part and sign. */ - dst[0] = ' '; - memcpy (dst + 1, buf, fp->w - 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] = '.'; +} - /* 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; +/* Formats OUTPUT for overflow given FORMAT. */ +static void +output_overflow (const struct fmt_spec *format, char *output) +{ + memset (output, '*', format->w); +} - /* Decide rounding direction and truncate string. */ - if (final_frac_dig[1] == '5' - && strspn (final_frac_dig + 2, "0") == strlen (final_frac_dig + 2)) +/* 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]; + + assert (digits < sizeof decimal); + if (number != SYSMIS + && number >= 0. + && number < power10 (digits) + && sprintf (decimal, "%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; }