Please send PSPP bug reports to bug-gnu-pspp@gnu.org.
+Changes from 1.6.0 to 1.6.1:
+
+ * The SET command now supports LEADZERO for controlling output of a
+ leading zero in F, COMMA, and DOT format.
+
Changes from 1.4.1 to 1.6.0:
* In the Kruskal-Wallis test, a misleading result could occur
Y1 =>
string[command] string[command-local]
string[language] string[charset] string[locale]
- bool bool bool bool
+ bool[x10] bool[include-leading-zero] bool[x12] bool[x13]
Y0
Y2 => CustomCurrency byte[missing] bool[x17]
@end example
into the output language; it is often empty and, when it is not,
sometimes the same as @code{command}.
+@code{include-leading-zero} is the @code{LEADZERO} setting for the
+table, where false is @code{OFF} (the default) and true is @code{ON}.
+@xref{SET LEADZERO,,, pspp, PSPP}.
+
@code{missing} is the character used to indicate that a cell contains
a missing value. It is always observed as @samp{.}.
-A writer may safely use false for @code{x17}.
+A writer may safely use false for @code{x10} and @code{x17} and true
+for @code{x12} and @code{x13}.
@subsubheading X1
@item
Except in scientific notation, a decimal point is included only when
it is followed by a digit. If the integer part of the number being
-output is 0, and a decimal point is included, then the zero before the
-decimal point is dropped.
+output is 0, and a decimal point is included, then @pspp{} ordinarily
+drops the zero before the decimal point. However, in @code{F},
+@code{COMMA}, or @code{DOT} formats, @pspp{} keeps the zero if
+@code{SET LEADZERO} is set to @code{ON} (@pxref{SET LEADZERO}).
In scientific notation, the number always includes a decimal point,
even if it is not followed by a digit.
/CC@{A,B,C,D,E@}=@{'@var{npre},@var{pre},@var{suf},@var{nsuf}','@var{npre}.@var{pre}.@var{suf}.@var{nsuf}'@}
/DECIMAL=@{DOT,COMMA@}
/FORMAT=@var{fmt_spec}
+ /LEADZERO=@{ON,OFF@}
/MDISPLAY=@{TEXT,TABLES@}
/SMALL=@var{number}
/WIB=@{NATIVE,MSBFIRST,LSBFIRST,VAX@}
Allows the default numeric input/output format to be specified. The
default is F8.2. @xref{Input and Output Formats}.
+@item LEADZERO
+@anchor{SET LEADZERO}
+
+Controls whether numbers with magnitude less than one are displayed
+with a zero before the decimal point. For example, with @code{SET
+LEADZERO=OFF}, which is the default, one-half is shown as 0.5, and
+with @code{SET LEADZERO=ON}, it is shown as .5. This setting affects
+only the @code{F}, @code{COMMA}, and @code{DOT} formats.
+
@item MDISPLAY
@anchor{SET MDISPLAY}
bool negative; /* Is the number negative? */
};
-static void rounder_init (struct rounder *, double number, int max_decimals);
+static void rounder_init (struct rounder *, const struct fmt_number_style *,
+ 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,
#include "format.def"
static bool output_decimal (const struct rounder *, const struct fmt_spec *,
- const struct fmt_settings *, bool require_affixes,
- char *);
+ const struct fmt_number_style *,
+ bool require_affixes, char *);
static bool output_scientific (double, const struct fmt_spec *,
- const struct fmt_settings *,
+ const struct fmt_number_style *,
bool require_affixes, char *);
static double power10 (int) PURE_FUNCTION;
output_infinite (number, format, output);
else
{
+ const struct fmt_number_style *style =
+ fmt_settings_get_style (settings, format->type);
+
if (format->type != FMT_E && fabs (number) < 1.5 * power10 (format->w))
{
struct rounder r;
- rounder_init (&r, number, format->d);
+ rounder_init (&r, style, number, format->d);
- if (output_decimal (&r, format, settings, true, output)
- || output_scientific (number, format, settings, true, output)
- || output_decimal (&r, format, settings, false, output))
+ if (output_decimal (&r, format, style, true, output)
+ || output_scientific (number, format, style, true, output)
+ || output_decimal (&r, format, style, false, output))
return;
}
- if (!output_scientific (number, format, settings, false, output))
+ if (!output_scientific (number, format, style, false, output))
output_overflow (format, output);
}
}
}
/* 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
+ FORMAT and STYLE, into OUTPUT. Returns true if successful, false on
+ failure, which cocurs 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,
- const struct fmt_settings *settings, bool require_affixes,
+ const struct fmt_number_style *style, bool require_affixes,
char *output)
{
- const struct fmt_number_style *style =
- fmt_settings_get_style (settings, format->type);
-
int decimals;
for (decimals = format->d; decimals >= 0; decimals--)
return false;
}
-/* Formats NUMBER into OUTPUT in scientific notation according to
- the style of the format specified in FORMAT. */
+/* Formats NUMBER into OUTPUT in scientific notation according to FORMAT and
+ STYLE. */
static bool
output_scientific (double number, const struct fmt_spec *format,
- const struct fmt_settings *settings,
+ const struct fmt_number_style *style,
bool require_affixes, char *output)
{
- const struct fmt_number_style *style =
- fmt_settings_get_style (settings, format->type);
int width;
int fraction_width;
bool add_affixes;
return digit >= '5';
}
-/* Initializes R for formatting the magnitude of NUMBER to no
+/* Initializes R for formatting the magnitude of NUMBER with STYLE to no
more than MAX_DECIMAL decimal places. */
static void
-rounder_init (struct rounder *r, double number, int max_decimals)
+rounder_init (struct rounder *r, const struct fmt_number_style *style,
+ double number, int max_decimals)
{
assert (fabs (number) < 1e41);
assert (max_decimals >= 0 && max_decimals <= 16);
}
}
- if (r->string[0] == '0')
+ if (r->string[0] == '0' && !style->include_leading_zero)
memmove (r->string, &r->string[1], strlen (r->string));
r->leading_zeros = strspn (r->string, "0.");
#define OPPOSITE(C) ((C) == ',' ? '.' : ',')
#define AFFIX(S) { .s = (char *) (S), .width = sizeof (S) - 1 }
-#define NS(PREFIX, SUFFIX, DECIMAL, GROUPING) { \
+#define NS(PREFIX, SUFFIX, DECIMAL, GROUPING, INCLUDE_LEADING_ZERO) { \
.neg_prefix = AFFIX ("-"), \
.prefix = AFFIX (PREFIX), \
.suffix = AFFIX (SUFFIX), \
.neg_suffix = AFFIX (""), \
.decimal = DECIMAL, \
.grouping = GROUPING, \
+ .include_leading_zero = INCLUDE_LEADING_ZERO \
}
-#define ANS(DECIMAL, GROUPING) { \
- [FMT_F] = NS( "", "", DECIMAL, 0), \
- [FMT_E] = NS( "", "", DECIMAL, 0), \
- [FMT_COMMA] = NS( "", "", DECIMAL, GROUPING), \
- [FMT_DOT] = NS( "", "", GROUPING, DECIMAL), \
- [FMT_DOLLAR] = NS("$", "", DECIMAL, GROUPING), \
- [FMT_PCT] = NS( "", "%", DECIMAL, 0), \
+#define ANS(DECIMAL, GROUPING, INCLUDE_LEADING_ZERO) { \
+ [FMT_F] = NS( "", "", DECIMAL, 0, INCLUDE_LEADING_ZERO), \
+ [FMT_E] = NS( "", "", DECIMAL, 0, INCLUDE_LEADING_ZERO), \
+ [FMT_COMMA] = NS( "", "", DECIMAL, GROUPING, INCLUDE_LEADING_ZERO), \
+ [FMT_DOT] = NS( "", "", GROUPING, DECIMAL, INCLUDE_LEADING_ZERO), \
+ [FMT_DOLLAR] = NS("$", "", DECIMAL, GROUPING, false), \
+ [FMT_PCT] = NS( "", "%", DECIMAL, 0, false), \
}
+#define ANS2(DECIMAL, GROUPING) { \
+ ANS(DECIMAL, GROUPING, false), \
+ ANS(DECIMAL, GROUPING, true), \
+ }
+
+ /* First index: 0 for ',' decimal point, 1 for '.' decimal point.
+ Second index: 0 for no leading zero, 1 for leading zero.
+ Third index: TYPE.
+ */
+ static const struct fmt_number_style styles[2][2][6] = {
+ ANS2 (',', '.'),
+ ANS2 ('.', ','),
+ };
- static const struct fmt_number_style period_styles[6] = ANS ('.', ',');
- static const struct fmt_number_style comma_styles[6] = ANS (',', '.');
- static const struct fmt_number_style default_style = NS ("", "", '.', 0);
+ static const struct fmt_number_style default_style = NS ("", "", '.', 0, false);
switch (type)
{
case FMT_DOLLAR:
case FMT_PCT:
case FMT_E:
- return (settings->decimal == '.'
- ? &period_styles[type]
- : &comma_styles[type]);
+ {
+ int decimal_idx = settings->decimal == '.';
+ int leadzero_idx = settings->include_leading_zero;
+ return &styles[decimal_idx][leadzero_idx][type];
+ }
case FMT_CCA:
case FMT_CCB:
.neg_suffix = neg_suffix,
.decimal = grouping == '.' ? ',' : '.',
.grouping = grouping,
+ .include_leading_zero = false,
.extra_bytes = extra_bytes,
};
return style;
struct fmt_affix neg_suffix; /* Negative suffix. */
char decimal; /* Decimal point: '.' or ','. */
char grouping; /* Grouping character: ',', '.', or 0. */
+ bool include_leading_zero; /* Format as ".5" or "0.5"? */
/* A fmt_affix may require more bytes than its display width; for example,
U+00A5 (¥) is 2 bytes in UTF-8 but occupies only one display column.
{
int epoch; /* 0 for default epoch. */
char decimal; /* '.' or ','. */
+
+ /* Format F, E, COMMA, and DOT with leading zero (e.g. "0.5" instead of
+ ".5")? */
+ bool include_leading_zero;
+
struct fmt_number_style *ccs[FMT_N_CCS]; /* CCA through CCE. */
};
#define FMT_SETTINGS_INIT { .decimal = '.' }
the_settings.styles.decimal = decimal;
}
+void
+settings_set_include_leading_zero (bool include_leading_zero)
+{
+ the_settings.styles.include_leading_zero = include_leading_zero;
+}
+
const struct fmt_settings *
settings_get_fmt_settings (void)
{
bool settings_set_cc (const char *cc_string, enum fmt_type type);
void settings_set_decimal_char (char decimal);
+void settings_set_include_leading_zero (bool include_leading_zero);
const struct fmt_settings *settings_get_fmt_settings (void);
: xstrdup (enabled));
}
+static bool
+parse_LEADZERO (struct lexer *lexer)
+{
+ int leadzero = force_parse_bool (lexer);
+ if (leadzero != -1)
+ settings_set_include_leading_zero (leadzero);
+ return leadzero != -1;
+}
+
+static char *
+show_LEADZERO (const struct dataset *ds UNUSED)
+{
+ bool leadzero = settings_get_fmt_settings ()->include_leading_zero;
+ return xstrdup (leadzero ? "ON" : "OFF");
+}
+
static bool
parse_LENGTH (struct lexer *lexer)
{
{ "HEADER", parse_HEADER, NULL },
{ "INCLUDE", parse_INCLUDE, show_INCLUDE },
{ "JOURNAL", parse_JOURNAL, show_JOURNAL },
+ { "LEADZERO", parse_LEADZERO, show_LEADZERO },
{ "LENGTH", parse_LENGTH, show_LENGTH },
{ "LOCALE", parse_LOCALE, show_LOCALE },
{ "MDISPLAY", parse_MDISPLAY, show_MDISPLAY },
Y1 =>
string[command] string[command-local]
string[language] string[charset] string[locale]
- bool[x10] bool[x11] bool[x12] bool[x13]
+ bool[x10] bool[include-leading-zero] bool[x12] bool[x13]
Y0
Y2 => CustomCurrency byte[missing] bool[x17]
/* XXX warn if parsing fails */
}
}
+ if (y1)
+ out->settings.include_leading_zero = y1->include_leading_zero;
out->small = in->formats->x3 ? in->formats->x3->small : 0;
/* Command information. */
put_string (buf, table->language);
put_string (buf, "UTF-8"); /* XXX */
put_string (buf, table->locale);
- put_bytes (buf, "\0\0\1\1", 4);
+ put_bool (buf, false); /* x10 */
+ put_bool (buf, table->settings.include_leading_zero);
+ put_bool (buf, true); /* x12 */
+ put_bool (buf, true); /* x13 */
put_y0 (buf, table);
}
AT_CHECK([$PYTHON3 num-out-compare.py $PSPP_NUM_OUT_COMPARE_FLAGS expout.inexact output.inexact])
AT_CLEANUP
+AT_SETUP([leading zeros in numeric output])
+AT_KEYWORDS([data-out LEADZERO])
+AT_DATA([data-out.sps], [dnl
+DATA LIST LIST NOTABLE/x.
+BEGIN DATA.
+0.5
+0.99
+0.01
+0
+-0
+-0.5
+-0.99
+-0.01
+END DATA.
+
+PRINT/x (F5.2) x (F5.1).
+EXECUTE.
+
+SET LEADZERO=ON.
+PRINT/x (F5.2) x (F5.1).
+EXECUTE.
+])
+AT_CHECK([pspp -O format=csv data-out.sps], [0], [dnl
+.50 .5
+.99 1.0
+.01 .0
+.00 .0
+.00 .0
+-.50 -.5
+-.99 -1.0
+-.01 .0
+
+0.50 0.5
+0.99 1.0
+0.01 0.0
+0.00 0.0
+0.00 0.0
+-0.50 -0.5
+-0.99 -1.0
+-0.01 0.0
+])
+AT_CLEANUP
+
AT_SETUP([non-ASCII custom currency formats])
AT_KEYWORDS([data-out])
AT_DATA([data-out.sps], [dnl