X-Git-Url: https://pintos-os.org/cgi-bin/gitweb.cgi?a=blobdiff_plain;f=src%2Fdata-out.c;h=c9c17a5e642fd6f14fcefe1472df9586dd44bdae;hb=40271dcbfdecb01dfe808684741215eb2ddeb508;hp=14c0f94b39bd514dfe2e6cf24e66a5bdfcd1c882;hpb=fcb9e49b2a2d57af7c001ae5d2eda9ac443ba36b;p=pspp diff --git a/src/data-out.c b/src/data-out.c index 14c0f94b39..c9c17a5e64 100644 --- a/src/data-out.c +++ b/src/data-out.c @@ -14,20 +14,19 @@ You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA - 02111-1307, USA. */ + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301, USA. */ #include -#include +#include "error.h" #include #include #include #include #include -#include "approx.h" +#include "calendar.h" #include "error.h" #include "format.h" -#include "julcal/julcal.h" #include "magic.h" #include "misc.h" #include "misc.h" @@ -35,65 +34,163 @@ #include "str.h" #include "var.h" -#include "debug-print.h" +#include "gettext.h" +#define _(msgid) gettext (msgid) -/* In older versions, numbers got their trailing zeros stripped. - Newer versions leave them on when there's room. Comment this next - line out for retro styling. */ -#define NEW_STYLE 1 +#include "debug-print.h" /* Public functions. */ -typedef int convert_func (char *, const struct fmt_spec *, - const union value *); - -static convert_func convert_F, convert_N, convert_E, convert_F_plus; -static convert_func convert_Z, convert_A, convert_AHEX, convert_IB; -static convert_func convert_P, convert_PIB, convert_PIBHEX, convert_PK; -static convert_func convert_RB, convert_RBHEX, convert_CCx, convert_date; -static convert_func convert_time, convert_WKDAY, convert_MONTH; -static convert_func try_F; - -/* Converts binary value V into printable form in string S according - to format specification FP. The string as written has exactly - FP->W characters. It is not null-terminated. Returns 1 on - success, 0 on failure. */ -int +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; + +/* 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) { - union value tmp_val; - - { - int cat = formats[fp->type].cat; - if ((cat & FCAT_BLANKS_SYSMIS) && v->f == SYSMIS) - { - memset (s, ' ', fp->w); - s[fp->w - fp->d - 1] = '.'; - return 1; - } - if ((cat & FCAT_SHIFT_DECIMAL) && v->f != SYSMIS && fp->d) - { - tmp_val.f = v->f * pow (10.0, fp->d); - v = &tmp_val; - } - } + int cat = formats[fp->type].cat; + int ok; + + assert (check_output_specifier (fp, 0)); + if (!(cat & FCAT_STRING)) + { + /* 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; + + case FMT_MONTH: + ok = convert_MONTH (s, fp, number); + break; + + default: + assert (0); + abort (); + } + } + 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 (); + } + } + + /* Error handling. */ + if (!ok) + strncpy (s, "ERROR", fp->w); - { - static convert_func *const handlers[FMT_NUMBER_OF_FORMATS] = - { - convert_F, convert_N, convert_E, convert_F_plus, - convert_F_plus, convert_F_plus, convert_F_plus, - convert_Z, convert_A, convert_AHEX, convert_IB, convert_P, convert_PIB, - convert_PIBHEX, convert_PK, convert_RB, convert_RBHEX, - convert_CCx, convert_CCx, convert_CCx, convert_CCx, convert_CCx, - convert_date, convert_date, convert_date, convert_date, convert_date, - convert_date, convert_date, convert_date, convert_date, - convert_time, convert_time, - convert_WKDAY, convert_MONTH, - }; - - return handlers[fp->type] (s, fp, v); - } + return ok; } /* Converts V into S in F format with width W and D decimal places, @@ -101,94 +198,8 @@ data_out (char *s, const struct fmt_spec *fp, const union value *v) void num_to_string (double v, char *s, int w, int d) { - /* Dummies to pass to convert_F. */ - union value val; - struct fmt_spec f; - -#if !NEW_STYLE - /* Pointer to `.' in S. */ - char *decp; - - /* Pointer to `E' in S. */ - char *expp; - - /* Number of characters to delete. */ - int n = 0; -#endif - - f.w = w; - f.d = d; - val.f = v; - - /* Cut out the jokers. */ - if (!finite (v)) - { - char temp[9]; - int len; - - if (isnan (v)) - { - memcpy (temp, "NaN", 3); - len = 3; - } - else if (isinf (v)) - { - memcpy (temp, "+Infinity", 9); - if (v < 0) - temp[0] = '-'; - len = 9; - } - else - { - memcpy (temp, _("Unknown"), 7); - len = 7; - } - if (w > len) - { - int pad = w - len; - memset (s, ' ', pad); - s += pad; - w -= pad; - } - memcpy (s, temp, w); - return; - } - - try_F (s, &f, &val); - -#if !NEW_STYLE - decp = memchr (s, set_decimal, w); - if (!decp) - return; - - /* If there's an `E' we can only delete 0s before the E. */ - expp = memchr (s, 'E', w); - if (expp) - { - while (expp[-n - 1] == '0') - n++; - if (expp[-n - 1] == set_decimal) - n++; - memmove (&s[n], s, expp - s - n); - memset (s, ' ', n); - return; - } - - /* Otherwise delete all trailing 0s. */ - n++; - while (s[w - n] == '0') - n++; - if (s[w - n] != set_decimal) - { - /* Avoid stripping `.0' to `'. */ - if (w == n || !isdigit ((unsigned char) s[w - n - 1])) - n -= 2; - } - else - n--; - memmove (&s[n], s, w - n); - memset (s, ' ', n); -#endif + struct fmt_spec f = make_output_format (FMT_F, w, d); + convert_F (s, &f, v); } /* Main conversion functions. */ @@ -202,21 +213,6 @@ static int try_CCx (char *s, const struct fmt_spec *fp, double v); #error Write your own floating-point output routines. #endif -/* PORTME: - - Some of the routines in this file are likely very specific to - base-2 representation of floating-point numbers, most notably the - routines that use frexp() or ldexp(). These attempt to extract - individual digits by setting the base-2 exponent and - multiplying/dividing by powers of 2. In base-2 numeration systems, - this just nudges the exponent up or down, but in base-10 floating - point, such multiplications/division can cause catastrophic loss of - precision. - - The author has never personally used a machine that didn't use - binary floating point formats, so he is unwilling, and perhaps - unable, to code around this "problem". */ - /* Converts a number between 0 and 15 inclusive to a `hexit' [0-9A-F]. */ #define MAKE_HEXIT(X) ("0123456789ABCDEF"[X]) @@ -233,18 +229,18 @@ static const double power10[] = /* Handles F format. */ static int -convert_F (char *dst, const struct fmt_spec *fp, const union value *v) +convert_F (char *dst, const struct fmt_spec *fp, double number) { - if (!try_F (dst, fp, v)) - convert_E (dst, fp, v); + if (!try_F (dst, fp, number)) + convert_E (dst, fp, number); return 1; } /* Handles N format. */ static int -convert_N (char *dst, const struct fmt_spec *fp, const union value *v) +convert_N (char *dst, const struct fmt_spec *fp, double number) { - double d = floor (v->f); + double d = floor (number); if (d < 0 || d == SYSMIS) { @@ -256,7 +252,7 @@ convert_N (char *dst, const struct fmt_spec *fp, const union value *v) if (d < power10[fp->w]) { char buf[128]; - sprintf (buf, "%0*.0f", fp->w, v->f); + sprintf (buf, "%0*.0f", fp->w, number); memcpy (dst, buf, fp->w); } else @@ -268,7 +264,7 @@ convert_N (char *dst, const struct fmt_spec *fp, const union value *v) /* Handles E format. Also operates as fallback for some other formats. */ static int -convert_E (char *dst, const struct fmt_spec *fp, const union value *v) +convert_E (char *dst, const struct fmt_spec *fp, double number) { /* Temporary buffer. */ char buf[128]; @@ -276,7 +272,10 @@ convert_E (char *dst, const struct fmt_spec *fp, const union value *v) /* Ranged number of decimal places. */ int d; - /* Check that the format is width enough. + if (!finite (number)) + return convert_infinite (dst, fp, number); + + /* Check that the format is wide enough. Although PSPP generally checks this, convert_E() can be called as a fallback from other formats which do not check. */ if (fp->w < 6) @@ -287,11 +286,11 @@ convert_E (char *dst, const struct fmt_spec *fp, const union value *v) /* Put decimal places in usable range. */ d = min (fp->d, fp->w - 6); - if (v->f < 0) + if (number < 0) d--; if (d < 0) d = 0; - sprintf (buf, "%*.*E", fp->w, d, v->f); + sprintf (buf, "%*.*E", fp->w, d, number); /* What we do here is force the exponent part to have four characters whenever possible. That is, 1.00E+99 is okay (`E+99') @@ -299,7 +298,7 @@ convert_E (char *dst, const struct fmt_spec *fp, const union value *v) the other hand, 1.00E1000 (`E+100') cannot be canonicalized. Note that ANSI C guarantees at least two digits in the exponent. */ - if (fabs (v->f) > 1e99) + if (fabs (number) > 1e99) { /* Pointer to the `E' in buf. */ char *cp; @@ -325,8 +324,8 @@ convert_E (char *dst, const struct fmt_spec *fp, const union value *v) /* The C locale always uses a period `.' as a decimal point. Translate to comma if necessary. */ - if ((set_decimal == ',' && fp->type != FMT_DOT) - || (set_decimal == '.' && fp->type == FMT_DOT)) + if ((get_decimal() == ',' && fp->type != FMT_DOT) + || (get_decimal() == '.' && fp->type == FMT_DOT)) { char *cp = strchr (buf, '.'); if (cp) @@ -339,31 +338,33 @@ convert_E (char *dst, const struct fmt_spec *fp, const union value *v) /* Handles COMMA, DOT, DOLLAR, and PCT formats. */ static int -convert_F_plus (char *dst, const struct fmt_spec *fp, const union value *v) +convert_F_plus (char *dst, const struct fmt_spec *fp, double number) { char buf[40]; - if (try_F (buf, fp, v)) + if (try_F (buf, fp, number)) insert_commas (dst, buf, fp); else - convert_E (dst, fp, v); + convert_E (dst, fp, number); return 1; } static int -convert_Z (char *dst, const struct fmt_spec *fp, const union value *v) +convert_Z (char *dst, const struct fmt_spec *fp, double number) { static int warned = 0; if (!warned) { - msg (MW, _("Quality of zoned decimal (Z) output format code is " - "suspect. Check your results, report bugs to author.")); + msg (MW, + _("Quality of zoned decimal (Z) output format code is " + "suspect. Check your results. Report bugs to %s."), + PACKAGE_BUGREPORT); warned = 1; } - if (v->f == SYSMIS) + if (number == SYSMIS) { msg (ME, _("The system-missing value cannot be output as a zoned " "decimal number.")); @@ -375,18 +376,18 @@ convert_Z (char *dst, const struct fmt_spec *fp, const union value *v) double d; int i; - d = fabs (floor (v->f)); + d = fabs (floor (number)); if (d >= power10[fp->w]) { msg (ME, _("Number %g too big to fit in field with format Z%d.%d."), - v->f, fp->w, fp->d); + number, fp->w, fp->d); return 0; } - sprintf (buf, "%*.0f", fp->w, v->f); + sprintf (buf, "%*.0f", fp->w, number); for (i = 0; i < fp->w; i++) dst[i] = (buf[i] - '0') | 0xf0; - if (v->f < 0) + if (number < 0) dst[fp->w - 1] &= 0xdf; } @@ -394,32 +395,32 @@ convert_Z (char *dst, const struct fmt_spec *fp, const union value *v) } static int -convert_A (char *dst, const struct fmt_spec *fp, const union value *v) +convert_A (char *dst, const struct fmt_spec *fp, const char *string) { - memcpy (dst, v->c, fp->w); + memcpy (dst, string, fp->w); return 1; } static int -convert_AHEX (char *dst, const struct fmt_spec *fp, const union value *v) +convert_AHEX (char *dst, const struct fmt_spec *fp, const char *string) { int i; for (i = 0; i < fp->w / 2; i++) { - ((unsigned char *) dst)[i * 2] = MAKE_HEXIT ((v->c[i]) >> 4); - ((unsigned char *) dst)[i * 2 + 1] = MAKE_HEXIT ((v->c[i]) & 0xf); + *dst++ = MAKE_HEXIT ((string[i]) >> 4); + *dst++ = MAKE_HEXIT ((string[i]) & 0xf); } return 1; } static int -convert_IB (char *dst, const struct fmt_spec *fp, const union value *v) +convert_IB (char *dst, const struct fmt_spec *fp, double number) { - /* Strategy: Basically the same as convert_PIBHEX() but with base - 256. Then it's necessary to negate the two's-complement result if - v->f is negative. */ + /* Strategy: Basically the same as convert_PIBHEX() but with + base 256. Then negate the two's-complement result if number + is negative. */ /* Used for constructing the two's-complement result. */ unsigned temp[8]; @@ -437,7 +438,7 @@ convert_IB (char *dst, const struct fmt_spec *fp, const union value *v) int i; /* Make the exponent (-8*fp->w-1). */ - frac = frexp (fabs (v->f), &exp); + frac = frexp (fabs (number), &exp); diff = exp - (-8 * fp->w - 1); exp -= diff; frac *= ldexp (1.0, diff); @@ -450,8 +451,8 @@ convert_IB (char *dst, const struct fmt_spec *fp, const union value *v) temp[i] = floor (frac); } - /* Perform two's-complement negation if v->f is negative. */ - if (v->f < 0) + /* Perform two's-complement negation if number is negative. */ + if (number < 0) { /* Perform NOT operation. */ for (i = 0; i < fp->w; i++) @@ -466,16 +467,16 @@ convert_IB (char *dst, const struct fmt_spec *fp, const union value *v) } memcpy (dst, temp, fp->w); #ifndef WORDS_BIGENDIAN - mm_reverse (dst, fp->w); + buf_reverse (dst, fp->w); #endif return 1; } static int -convert_P (char *dst, const struct fmt_spec *fp, const union value *v) +convert_P (char *dst, const struct fmt_spec *fp, double number) { - /* Buffer for v->f*2-1 characters + a decimal point if library is + /* Buffer for fp->w*2-1 characters + a decimal point if library is not quite compliant + a null. */ char buf[17]; @@ -483,7 +484,7 @@ convert_P (char *dst, const struct fmt_spec *fp, const union value *v) int i; /* Main extraction. */ - sprintf (buf, "%0*.0f", fp->w * 2 - 1, floor (fabs (v->f))); + sprintf (buf, "%0*.0f", fp->w * 2 - 1, floor (fabs (number))); for (i = 0; i < fp->w; i++) ((unsigned char *) dst)[i] @@ -491,7 +492,7 @@ convert_P (char *dst, const struct fmt_spec *fp, const union value *v) /* Set sign. */ dst[fp->w - 1] &= 0xf0; - if (v->f >= 0.0) + if (number >= 0.0) dst[fp->w - 1] |= 0xf; else dst[fp->w - 1] |= 0xd; @@ -500,7 +501,7 @@ convert_P (char *dst, const struct fmt_spec *fp, const union value *v) } static int -convert_PIB (char *dst, const struct fmt_spec *fp, const union value *v) +convert_PIB (char *dst, const struct fmt_spec *fp, double number) { /* Strategy: Basically the same as convert_IB(). */ @@ -517,7 +518,7 @@ convert_PIB (char *dst, const struct fmt_spec *fp, const union value *v) int i; /* Make the exponent (-8*fp->w). */ - frac = frexp (fabs (v->f), &exp); + frac = frexp (fabs (number), &exp); diff = exp - (-8 * fp->w); exp -= diff; frac *= ldexp (1.0, diff); @@ -530,14 +531,14 @@ convert_PIB (char *dst, const struct fmt_spec *fp, const union value *v) ((unsigned char *) dst)[i] = floor (frac); } #ifndef WORDS_BIGENDIAN - mm_reverse (dst, fp->w); + buf_reverse (dst, fp->w); #endif return 1; } static int -convert_PIBHEX (char *dst, const struct fmt_spec *fp, const union value *v) +convert_PIBHEX (char *dst, const struct fmt_spec *fp, double number) { /* Strategy: Use frexp() to create a normalized result (but mostly to find the base-2 exponent), then change the base-2 exponent to @@ -557,7 +558,7 @@ convert_PIBHEX (char *dst, const struct fmt_spec *fp, const union value *v) int i; /* Make the exponent (-4*fp->w). */ - frac = frexp (fabs (v->f), &exp); + frac = frexp (fabs (number), &exp); diff = exp - (-4 * fp->w); exp -= diff; frac *= ldexp (1.0, diff); @@ -574,9 +575,9 @@ convert_PIBHEX (char *dst, const struct fmt_spec *fp, const union value *v) } static int -convert_PK (char *dst, const struct fmt_spec *fp, const union value *v) +convert_PK (char *dst, const struct fmt_spec *fp, double number) { - /* Buffer for v->f*2 characters + a decimal point if library is not + /* Buffer for fp->w*2 characters + a decimal point if library is not quite compliant + a null. */ char buf[18]; @@ -584,7 +585,7 @@ convert_PK (char *dst, const struct fmt_spec *fp, const union value *v) int i; /* Main extraction. */ - sprintf (buf, "%0*.0f", fp->w * 2, floor (fabs (v->f))); + sprintf (buf, "%0*.0f", fp->w * 2, floor (fabs (number))); for (i = 0; i < fp->w; i++) ((unsigned char *) dst)[i] @@ -594,7 +595,7 @@ convert_PK (char *dst, const struct fmt_spec *fp, const union value *v) } static int -convert_RB (char *dst, const struct fmt_spec *fp, const union value *v) +convert_RB (char *dst, const struct fmt_spec *fp, double number) { union { @@ -603,14 +604,14 @@ convert_RB (char *dst, const struct fmt_spec *fp, const union value *v) } u; - u.d = v->f; + u.d = number; memcpy (dst, u.c, fp->w); return 1; } static int -convert_RBHEX (char *dst, const struct fmt_spec *fp, const union value *v) +convert_RBHEX (char *dst, const struct fmt_spec *fp, double number) { union { @@ -621,7 +622,7 @@ convert_RBHEX (char *dst, const struct fmt_spec *fp, const union value *v) int i; - u.d = v->f; + u.d = number; for (i = 0; i < fp->w / 2; i++) { *dst++ = MAKE_HEXIT (u.c[i] >> 4); @@ -632,9 +633,9 @@ convert_RBHEX (char *dst, const struct fmt_spec *fp, const union value *v) } static int -convert_CCx (char *dst, const struct fmt_spec *fp, const union value *v) +convert_CCx (char *dst, const struct fmt_spec *fp, double number) { - if (try_CCx (dst, fp, v->f)) + if (try_CCx (dst, fp, number)) return 1; else { @@ -644,12 +645,12 @@ convert_CCx (char *dst, const struct fmt_spec *fp, const union value *v) f.w = fp->w; f.d = fp->d; - return convert_F (dst, &f, v); + return convert_F_plus (dst, &f, number); } } static int -convert_date (char *dst, const struct fmt_spec *fp, const union value *v) +convert_date (char *dst, const struct fmt_spec *fp, double number) { static const char *months[12] = { @@ -658,9 +659,13 @@ convert_date (char *dst, const struct fmt_spec *fp, const union value *v) }; char buf[64] = {0}; + int ofs = number / 86400.; int month, day, year; - julian_to_calendar (v->f / 86400., &year, &month, &day); + if (ofs < 1) + return 0; + + calendar_offset_to_gregorian (ofs, &year, &month, &day); switch (fp->type) { case FMT_DATE: @@ -689,15 +694,13 @@ convert_date (char *dst, const struct fmt_spec *fp, const union value *v) break; case FMT_JDATE: { - int yday = (v->f / 86400.) - calendar_to_julian (year, 1, 1) + 1; + int yday = calendar_offset_to_yday (ofs); - if (fp->w >= 7) - { - if (year4 (year)) - sprintf (buf, "%04d%03d", year, yday); - } - else - sprintf (buf, "%02d%03d", year % 100, yday); + if (fp->w < 7) + sprintf (buf, "%02d%03d", year % 100, yday); + else if (year4 (year)) + sprintf (buf, "%04d%03d", year, yday); + else break; } case FMT_QYR: @@ -714,7 +717,7 @@ convert_date (char *dst, const struct fmt_spec *fp, const union value *v) break; case FMT_WKYR: { - int yday = (v->f / 86400.) - calendar_to_julian (year, 1, 1) + 1; + int yday = calendar_offset_to_yday (ofs); if (fp->w >= 10) sprintf (buf, "%02d WK% 04d", (yday - 1) / 7 + 1, year); @@ -728,8 +731,8 @@ convert_date (char *dst, const struct fmt_spec *fp, const union value *v) cp = spprintf (buf, "%02d-%s-%04d %02d:%02d", day, months[month - 1], year, - (int) fmod (floor (v->f / 60. / 60.), 24.), - (int) fmod (floor (v->f / 60.), 60.)); + (int) fmod (floor (number / 60. / 60.), 24.), + (int) fmod (floor (number / 60.), 60.)); if (fp->w >= 20) { int w, d; @@ -745,7 +748,7 @@ convert_date (char *dst, const struct fmt_spec *fp, const union value *v) d = 0; } - cp = spprintf (cp, ":%0*.*f", w, d, fmod (v->f, 60.)); + cp = spprintf (cp, ":%0*.*f", w, d, fmod (number, 60.)); } } break; @@ -755,12 +758,12 @@ convert_date (char *dst, const struct fmt_spec *fp, const union value *v) if (buf[0] == 0) return 0; - st_bare_pad_copy (dst, buf, fp->w); + buf_copy_str_rpad (dst, fp->w, buf); return 1; } static int -convert_time (char *dst, const struct fmt_spec *fp, const union value *v) +convert_time (char *dst, const struct fmt_spec *fp, double number) { char temp_buf[40]; char *cp; @@ -768,14 +771,14 @@ convert_time (char *dst, const struct fmt_spec *fp, const union value *v) double time; int width; - if (fabs (v->f) > 1e20) + if (fabs (number) > 1e20) { msg (ME, _("Time value %g too large in magnitude to convert to " - "alphanumeric time."), v->f); + "alphanumeric time."), number); return 0; } - time = v->f; + time = number; width = fp->w; cp = temp_buf; if (time < 0) @@ -805,13 +808,13 @@ convert_time (char *dst, const struct fmt_spec *fp, const union value *v) cp = spprintf (cp, ":%0*.*f", w, d, fmod (time, 60.)); } - st_bare_pad_copy (dst, temp_buf, fp->w); + buf_copy_str_rpad (dst, fp->w, temp_buf); return 1; } static int -convert_WKDAY (char *dst, const struct fmt_spec *fp, const union value *v) +convert_WKDAY (char *dst, const struct fmt_spec *fp, double wkday) { static const char *weekdays[7] = { @@ -819,20 +822,19 @@ convert_WKDAY (char *dst, const struct fmt_spec *fp, const union value *v) "THURSDAY", "FRIDAY", "SATURDAY", }; - int x = v->f; - - if (x < 1 || x > 7) + if (wkday < 1 || wkday > 7) { - msg (ME, _("Weekday index %d does not lie between 1 and 7."), x); + msg (ME, _("Weekday index %f does not lie between 1 and 7."), + (double) wkday); return 0; } - st_bare_pad_copy (dst, weekdays[x - 1], fp->w); + buf_copy_str_rpad (dst, fp->w, weekdays[(int) wkday - 1]); return 1; } static int -convert_MONTH (char *dst, const struct fmt_spec *fp, const union value *v) +convert_MONTH (char *dst, const struct fmt_spec *fp, double month) { static const char *months[12] = { @@ -840,15 +842,14 @@ convert_MONTH (char *dst, const struct fmt_spec *fp, const union value *v) "JULY", "AUGUST", "SEPTEMBER", "OCTOBER", "NOVEMBER", "DECEMBER", }; - int x = v->f; - - if (x < 1 || x > 12) + if (month < 1 || month > 12) { - msg (ME, _("Month index %d does not lie between 1 and 12."), x); + msg (ME, _("Month index %f does not lie between 1 and 12."), + month); return 0; } - st_bare_pad_copy (dst, months[x - 1], fp->w); + buf_copy_str_rpad (dst, fp->w, months[(int) month - 1]); return 1; } @@ -938,7 +939,7 @@ insert_commas (char *dst, const char *src, const struct fmt_spec *fp) if (i % 3 == 0 && n_digits > i && n_items > n_reserved) { n_items--; - *dst++ = fp->type == FMT_COMMA ? set_grouping : set_decimal; + *dst++ = fp->type == FMT_COMMA ? get_grouping() : get_decimal(); } *dst++ = *sp++; } @@ -962,9 +963,9 @@ year4 (int year) } static int -try_CCx (char *dst, const struct fmt_spec *fp, double v) +try_CCx (char *dst, const struct fmt_spec *fp, double number) { - struct set_cust_currency *cc = &set_cc[fp->type - FMT_CCA]; + const struct custom_currency *cc = get_cc(fp->type - FMT_CCA); struct fmt_spec f; @@ -974,13 +975,13 @@ try_CCx (char *dst, const struct fmt_spec *fp, double v) /* Determine length available, decimal character for number proper. */ - f.type = cc->decimal == set_decimal ? FMT_COMMA : FMT_DOT; + f.type = cc->decimal == get_decimal () ? FMT_COMMA : FMT_DOT; f.w = fp->w - strlen (cc->prefix) - strlen (cc->suffix); - if (v < 0) + if (number < 0) f.w -= strlen (cc->neg_prefix) + strlen (cc->neg_suffix) - 1; else /* Convert -0 to +0. */ - v = fabs (v); + number = fabs (number); f.d = fp->d; if (f.w <= 0) @@ -988,13 +989,13 @@ try_CCx (char *dst, const struct fmt_spec *fp, double v) /* There's room for all that currency crap. Let's do the F conversion first. */ - if (!convert_F (buf, &f, (union value *) &v) || *buf == '*') + if (!convert_F (buf, &f, number) || *buf == '*') return 0; insert_commas (buf2, buf, &f); /* Postprocess back into buf. */ cp = buf; - if (v < 0) + if (number < 0) cp = stpcpy (cp, cc->neg_prefix); cp = stpcpy (cp, cc->prefix); { @@ -1002,15 +1003,15 @@ try_CCx (char *dst, const struct fmt_spec *fp, double v) while (*bp == ' ') bp++; - assert ((v >= 0) ^ (*bp == '-')); - if (v < 0) + assert ((number >= 0) ^ (*bp == '-')); + if (number < 0) bp++; memcpy (cp, bp, f.w - (bp - buf2)); cp += f.w - (bp - buf2); } cp = stpcpy (cp, cc->suffix); - if (v < 0) + if (number < 0) cp = stpcpy (cp, cc->neg_suffix); /* Copy into dst. */ @@ -1026,202 +1027,230 @@ try_CCx (char *dst, const struct fmt_spec *fp, double v) return 1; } -/* This routine relies on the underlying implementation of sprintf: - - If the number has a magnitude 1e40 or greater, then we needn't - bother with it, since it's guaranteed to need processing in - scientific notation. - - Otherwise, do a binary search for the base-10 magnitude of the - thing. log10() is not accurate enough, and the alternatives are - frightful. Besides, we never need as many as 6 (pairs of) - comparisons. The algorithm used for searching is Knuth's Algorithm - 6.2.1C (Uniform binary search). +static int +format_and_round (char *dst, double number, const struct fmt_spec *fp, + int decimals); - DON'T CHANGE ANYTHING HERE UNLESS YOU'VE THOUGHT ABOUT IT FOR A - LONG TIME! The rest of the program is heavily dependent on - specific properties of this routine's output. LOG ALL CHANGES! */ +/* Tries to format NUMBER into DST as the F format specified in + *FP. Return true if successful, false on failure. */ static int -try_F (char *dst, const struct fmt_spec *fp, const union value *value) +try_F (char *dst, const struct fmt_spec *fp, double number) { - /* This is the DELTA array from Knuth. - DELTA[j] = floor((40+2**(j-1))/(2**j)). */ - static const int delta[8] = - { - 0, (40 + 1) / 2, (40 + 2) / 4, (40 + 4) / 8, (40 + 8) / 16, - (40 + 16) / 32, (40 + 32) / 64, (40 + 64) / 128, - }; - - /* The number of digits in floor(v), including sign. This is `i' - from Knuth. */ - int n_int = (40 + 1) / 2; + assert (fp->w <= 40); + if (finite (number)) + { + if (fabs (number) < power10[fp->w]) + { + /* The value may fit in the field. */ + if (fp->d == 0) + { + /* There are no decimal places, so there's no way + that the value can be shortened. Either it fits + or it doesn't. */ + char buf[41]; + sprintf (buf, "%*.0f", fp->w, number); + if (strlen (buf) <= fp->w) + { + buf_copy_str_lpad (dst, fp->w, buf); + return true; + } + else + return false; + } + else + { + /* First try to format it with 2 extra decimal + places. This gives us a good chance of not + needing even more decimal places, but it also + avoids wasting too much time formatting more + decimal places on the first try. */ + int result = format_and_round (dst, number, fp, fp->d + 2); + if (result >= 0) + return result; + + /* 2 extra decimal places weren't enough to + correctly round. Try again with the maximum + number of places. */ + return format_and_round (dst, number, fp, LDBL_DIG + 1); + } + } + else + { + /* The value is too big to fit in the field. */ + return false; + } + } + else + return convert_infinite (dst, fp, number); +} - /* Used to step through delta[]. This is `j' from Knuth. */ - int j = 2; +/* Tries to compose NUMBER into DST in format FP by first + formatting it with DECIMALS decimal places, then rounding off + to as many decimal places will fit or the number specified in + FP, whichever is fewer. - /* Value. */ - double v = value->f; + Returns 1 if conversion succeeds, 0 if this try at conversion + failed and so will any other tries (because the integer part + of the number is too long), or -1 if this try failed but + another with higher DECIMALS might succeed (because we'd be + able to properly round). */ +static int +format_and_round (char *dst, double number, const struct fmt_spec *fp, + int decimals) +{ + /* Number of characters before the decimal point, + which includes digits and possibly a minus sign. */ + int predot_chars; - /* Magnitude of v. This is `K' from Knuth. */ - double mag; + /* Number of digits in the output fraction, + which may be smaller than fp->d if there's not enough room. */ + int fraction_digits; - /* Number of characters for the fractional part, including the - decimal point. */ - int n_dec; + /* Points to last digit that will remain in the fraction after + rounding. */ + char *final_frac_dig; - /* Pointer into buf used for formatting. */ - char *cp; + /* Round up? */ + bool round_up; + + char buf[128]; + + assert (decimals > fp->d); + if (decimals > LDBL_DIG) + decimals = LDBL_DIG + 1; - /* Used to count characters formatted by nsprintf(). */ - int n; + sprintf (buf, "%.*f", decimals, number); - /* Temporary buffer. */ - char buf[128]; + /* 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)); - /* First check for infinities and NaNs. 12/13/96. */ - if (!finite (v)) + predot_chars = strcspn (buf, "."); + if (predot_chars > fp->w) { - n = nsprintf (buf, "%f", v); - if (n > fp->w) - memset (buf, '*', fp->w); - else if (n < fp->w) - { - memmove (&buf[fp->w - n], buf, n); - memset (buf, ' ', fp->w - n); - } + /* Can't possibly fit. */ + return 0; + } + else if (predot_chars == fp->w) + { + /* Exact fit for integer part and sign. */ memcpy (dst, buf, fp->w); return 1; } + else if (predot_chars + 1 == fp->w) + { + /* There's room for the decimal point, but not for any + digits of the fraction. + Right-justify the integer part and sign. */ + dst[0] = ' '; + memcpy (dst + 1, buf, fp->w); + return 1; + } - /* Then check for radically out-of-range values. */ - mag = fabs (v); - if (mag >= power10[fp->w]) - return 0; + /* It looks like we have room for at least one digit of the + fraction. Figure out how many. */ + fraction_digits = fp->w - predot_chars - 1; + if (fraction_digits > fp->d) + fraction_digits = fp->d; + final_frac_dig = buf + predot_chars + fraction_digits; - if (mag < 1.0) + /* Decide rounding direction and truncate string. */ + if (final_frac_dig[1] == '5' + && strspn (final_frac_dig + 2, "0") == strlen (final_frac_dig + 2)) { - n_int = 0; - - /* Avoid printing `-.000'. 7/6/96. */ - if (approx_eq (v, 0.0)) - v = 0.0; + /* Exactly 1/2. */ + if (decimals <= LDBL_DIG) + { + /* Don't have enough fractional digits to know which way to + round. We can format with more decimal places, so go + around again. */ + return -1; + } + else + { + /* We used up all our fractional digits and still don't + know. Round to even. */ + round_up = (final_frac_dig[0] - '0') % 2 != 0; + } } else - /* Now perform a `uniform binary search' based on the tables - power10[] and delta[]. After this step, nint is the number of - digits in floor(v), including any sign. */ - for (;;) - { - if (mag >= power10[n_int]) /* Should this be approx_ge()? */ - { - assert (delta[j]); - n_int += delta[j++]; - } - else if (mag < power10[n_int - 1]) - { - assert (delta[j]); - n_int -= delta[j++]; - } - else - break; - } + round_up = final_frac_dig[1] >= '5'; + final_frac_dig[1] = '\0'; - /* If we have any decimal places, then there is a decimal point, - too. */ - n_dec = fp->d; - if (n_dec) - n_dec++; + /* Do rounding. */ + if (round_up) + { + char *cp = final_frac_dig; + for (;;) + { + if (*cp >= '0' && *cp <= '8') + { + (*cp)++; + break; + } + else if (*cp == '9') + *cp = '0'; + else + assert (*cp == '.'); + + if (cp == buf || *--cp == '-') + { + size_t length; + + /* Tried to go past the leftmost digit. Insert a 1. */ + memmove (cp + 1, cp, strlen (cp) + 1); + *cp = '1'; + + length = strlen (buf); + if (length > fp->w) + { + /* Inserting the `1' overflowed our space. + Drop a decimal place. */ + buf[--length] = '\0'; + + /* If that was the last decimal place, drop the + decimal point too. */ + if (buf[length - 1] == '.') + buf[length - 1] = '\0'; + } + + break; + } + } + } - /* 1/10/96: If there aren't any digits at all, add one. This occurs - only when fabs(v) < 1.0. */ - if (n_int + n_dec == 0) - n_int++; + /* Omit `-' if value output is zero. */ + if (buf[0] == '-' && buf[strspn (buf, "-.0")] == '\0') + memmove (buf, buf + 1, strlen (buf)); - /* Give space for a minus sign. Moved 1/10/96. */ - if (v < 0) - n_int++; + buf_copy_str_lpad (dst, fp->w, buf); + return 1; +} - /* Normally we only go through the loop once; occasionally twice. - Three times or more indicates a very serious bug somewhere. */ - for (;;) +/* Formats non-finite NUMBER into DST according to the width + given in FP. */ +static int +convert_infinite (char *dst, const struct fmt_spec *fp, double number) +{ + assert (!finite (number)); + + if (fp->w >= 3) { - /* Check out the total length of the string. */ - cp = buf; - if (n_int + n_dec > fp->w) - { - /* The string is too long. Let's see what can be done. */ - if (n_int <= fp->w) - /* If we can, just reduce the number of decimal places. */ - n_dec = fp->w - n_int; - else - return 0; - } - else if (n_int + n_dec < fp->w) - { - /* The string is too short. Left-pad with spaces. */ - int n_spaces = fp->w - n_int - n_dec; - memset (cp, ' ', n_spaces); - cp += n_spaces; - } + const char *s; - /* Finally, format the number. */ - if (n_dec) - n = nsprintf (cp, "%.*f", n_dec - 1, v); + if (isnan (number)) + s = "NaN"; + else if (isinf (number)) + s = number > 0 ? "+Infinity" : "-Infinity"; else - n = nsprintf (cp, "%.0f", v); - - /* If v is positive and its magnitude is less than 1... */ - if (n_int == 0) - { - if (*cp == '0') - { - /* The value rounds to `.###'. */ - memmove (cp, &cp[1], n - 1); - n--; - } - else - { - /* The value rounds to `1.###'. */ - n_int = 1; - continue; - } - } - /* Else if v is negative and its magnitude is less than 1... */ - else if (v < 0 && n_int == 1) - { - if (cp[1] == '0') - { - /* The value rounds to `-.###'. */ - memmove (&cp[1], &cp[2], n - 2); - n--; - } - else - { - /* The value rounds to `-1.###'. */ - n_int = 2; - continue; - } - } + s = "Unknown"; - /* Check for a correct number of digits & decimal places & stuff. - This is just a desperation check. Hopefully it won't fail too - often, because then we have to run through the whole loop again: - sprintf() is not a fast operation with floating-points! */ - if (n == n_int + n_dec) - { - /* Convert periods `.' to commas `,' for our foreign friends. */ - if ((set_decimal == ',' && fp->type != FMT_DOT) - || (set_decimal == '.' && fp->type == FMT_DOT)) - { - cp = strchr (cp, '.'); - if (cp) - *cp = ','; - } - - memcpy (dst, buf, fp->w); - return 1; - } - - n_int = n - n_dec; /* FIXME? Need an idiot check on resulting n_int? */ + buf_copy_str_lpad (dst, fp->w, s); } + else + memset (dst, '*', fp->w); + + return true; }