You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
- Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
- 02111-1307, USA. */
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ 02110-1301, USA. */
#include <config.h>
-#include <assert.h>
+#include "error.h"
#include <ctype.h>
#include <math.h>
#include <float.h>
#include <stdlib.h>
#include <time.h>
-#include "approx.h"
+#include "calendar.h"
#include "error.h"
#include "format.h"
-#include "julcal/julcal.h"
#include "magic.h"
#include "misc.h"
#include "misc.h"
#include "str.h"
#include "var.h"
-#undef DEBUGGING
-/*#define DEBUGGING 1*/
-#include "debug-print.h"
+#include "gettext.h"
+#define _(msgid) gettext (msgid)
-/* In older versions, numbers got their trailing zeros stripped.
- Newer versions leave them on when there's room. Comment this next
- line out for retro styling. */
-#define NEW_STYLE 1
+#include "debug-print.h"
\f
/* Public functions. */
-typedef int convert_func (char *, const struct fmt_spec *,
- const union value *);
-
-static convert_func convert_F, convert_N, convert_E, convert_F_plus;
-static convert_func convert_Z, convert_A, convert_AHEX, convert_IB;
-static convert_func convert_P, convert_PIB, convert_PIBHEX, convert_PK;
-static convert_func convert_RB, convert_RBHEX, convert_CCx, convert_date;
-static convert_func convert_time, convert_WKDAY, convert_MONTH;
-static convert_func try_F;
-
-/* Converts binary value V into printable form in string S according
- to format specification FP. The string as written has exactly
- FP->W characters. It is not null-terminated. Returns 1 on
- success, 0 on failure. */
-int
+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,
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);
}
\f
/* Main conversion functions. */
#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])
/* 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)
{
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
/* 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];
/* 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)
/* 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')
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;
/* 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)
/* 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."));
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;
}
}
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];
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);
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++)
}
}
memcpy (dst, temp, fp->w);
- if (endian == LITTLE)
- mm_reverse (dst, fp->w);
+#ifndef WORDS_BIGENDIAN
+ 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];
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]
/* 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;
}
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(). */
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);
frac *= 256.0;
((unsigned char *) dst)[i] = floor (frac);
}
- if (endian == LITTLE)
- mm_reverse (dst, fp->w);
+#ifndef WORDS_BIGENDIAN
+ 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
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);
}
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];
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]
}
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
{
}
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
{
int i;
- u.d = v->f;
+ u.d = number;
for (i = 0; i < fp->w / 2; i++)
{
*dst++ = MAKE_HEXIT (u.c[i] >> 4);
}
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
{
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] =
{
};
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:
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:
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);
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;
d = 0;
}
- cp = spprintf (cp, ":%0*.*f", w, d, fmod (v->f, 60.));
+ cp = spprintf (cp, ":%0*.*f", w, d, fmod (number, 60.));
}
}
break;
-#if __CHECKER__
- case 42000:
- assert (0);
-#endif
default:
assert (0);
}
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;
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)
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] =
{
"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] =
{
"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;
}
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++;
}
}
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;
/* 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)
/* 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);
{
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. */
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);
+ s = "Unknown";
- /* If v is positive and its magnitude is less than 1... */
- if (n_int == 0)
- {
- if (*cp == '0')
- {
- /* The value rounds to `.###'. */
- memmove (cp, &cp[1], n - 1);
- n--;
- }
- else
- {
- /* The value rounds to `1.###'. */
- n_int = 1;
- continue;
- }
- }
- /* Else if v is negative and its magnitude is less than 1... */
- else if (v < 0 && n_int == 1)
- {
- if (cp[1] == '0')
- {
- /* The value rounds to `-.###'. */
- memmove (&cp[1], &cp[2], n - 2);
- n--;
- }
- else
- {
- /* The value rounds to `-1.###'. */
- n_int = 2;
- continue;
- }
- }
-
- /* Check for a correct number of digits & decimal places & stuff.
- This is just a desperation check. Hopefully it won't fail too
- often, because then we have to run through the whole loop again:
- sprintf() is not a fast operation with floating-points! */
- if (n == n_int + n_dec)
- {
- /* Convert periods `.' to commas `,' for our foreign friends. */
- if ((set_decimal == ',' && fp->type != FMT_DOT)
- || (set_decimal == '.' && fp->type == FMT_DOT))
- {
- cp = strchr (cp, '.');
- if (cp)
- *cp = ',';
- }
-
- memcpy (dst, buf, fp->w);
- return 1;
- }
-
- n_int = n - n_dec; /* FIXME? Need an idiot check on resulting n_int? */
+ buf_copy_str_lpad (dst, fp->w, s);
}
+ else
+ memset (dst, '*', fp->w);
+
+ return true;
}