summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPádraig Brady <P@draigBrady.com>2015-06-19 19:18:21 +0100
committerPádraig Brady <P@draigBrady.com>2015-06-21 04:16:29 +0100
commit0a279f619055cc165bb3cfa3bb737cdd28ed4d70 (patch)
tree6149ce7f474051369efba96680228e3e0b5716db
parent71063bc858cd927e3622b511297e66b3e13f7453 (diff)
downloadcoreutils-0a279f619055cc165bb3cfa3bb737cdd28ed4d70.tar.xz
numfmt: support user specified output precision
* src/numfmt.c (usage): Update the --format description to indicate precision is allowed. (parse_format_string): Parse a precision specification like the standard printf does. (double_to_human): Honor the precision in --to mode. * tests/misc/numfmt.pl: New tests. * doc/coreutils.texi (numfmt invocation): Mention the new feature. * NEWS: Likewise.
-rw-r--r--NEWS3
-rw-r--r--doc/coreutils.texi14
-rw-r--r--src/numfmt.c50
-rwxr-xr-xtests/misc/numfmt.pl21
4 files changed, 67 insertions, 21 deletions
diff --git a/NEWS b/NEWS
index 9c551d514..9b86d45f6 100644
--- a/NEWS
+++ b/NEWS
@@ -70,7 +70,8 @@ GNU coreutils NEWS -*- outline -*-
dd accepts a new status=progress level to print data transfer statistics
on stderr approximately every second.
- numfmt can now process multiple fields using field ranges similar to cut.
+ numfmt can now process multiple fields with field range specifications similar
+ to cut, and supports setting the output precision with the --format option.
split accepts a new --separator option to select a record separator character
other than the default newline character.
diff --git a/doc/coreutils.texi b/doc/coreutils.texi
index 9197cb426..c4f3a07c1 100644
--- a/doc/coreutils.texi
+++ b/doc/coreutils.texi
@@ -16909,12 +16909,14 @@ N-M from N'th to M'th field (inclusive)
@item --format=@var{format}
@opindex --format
Use printf-style floating FORMAT string. The @var{format} string must contain
-one @samp{%f} directive, optionally with @samp{'}, @samp{-}, @samp{0}, or width
-modifiers. The @samp{'} modifier will enable @option{--grouping}, the @samp{-}
-modifier will enable left-aligned @option{--padding} and the width modifier will
-enable right-aligned @option{--padding}. The @samp{0} width modifier
-(without the @samp{-} modifier) will generate leading zeros on the number,
-up to the specified width.
+one @samp{%f} directive, optionally with @samp{'}, @samp{-}, @samp{0}, width
+or precision modifiers. The @samp{'} modifier will enable @option{--grouping},
+the @samp{-} modifier will enable left-aligned @option{--padding} and the width
+modifier will enable right-aligned @option{--padding}. The @samp{0} width
+modifier (without the @samp{-} modifier) will generate leading zeros on the
+number, up to the specified width. A precision specification like @samp{%.1f}
+will override the precision determined from the input data or set due to
+@option{--to} option auto scaling.
@item --from=@var{unit}
@opindex --from
diff --git a/src/numfmt.c b/src/numfmt.c
index 18243dd9f..81c624aeb 100644
--- a/src/numfmt.c
+++ b/src/numfmt.c
@@ -173,6 +173,7 @@ static char *padding_buffer = NULL;
static size_t padding_buffer_size = 0;
static long int padding_width = 0;
static long int zero_padding_width = 0;
+static long int user_precision = -1;
static const char *format_str = NULL;
static char *format_str_prefix = NULL;
static char *format_str_suffix = NULL;
@@ -737,15 +738,20 @@ double_to_human (long double val, int precision,
devmsg (" scaled value to %Lf * %0.f ^ %u\n", val, scale_base, power);
/* Perform rounding. */
- int ten_or_less = 0;
- if (absld (val) < 10)
+ unsigned int power_adjust = 0;
+ if (user_precision != -1)
+ power_adjust = MIN (power * 3, user_precision);
+ else if (absld (val) < 10)
{
/* for values less than 10, we allow one decimal-point digit,
so adjust before rounding. */
- ten_or_less = 1;
- val *= 10;
+ power_adjust = 1;
}
+
+ val *= powerld (10, power_adjust);
val = simple_round (val, round);
+ val /= powerld (10, power_adjust);
+
/* two special cases after rounding:
1. a "999.99" can turn into 1000 - so scale down
2. a "9.99" can turn into 10 - so don't display decimal-point. */
@@ -754,8 +760,6 @@ double_to_human (long double val, int precision,
val /= scale_base;
power++;
}
- if (ten_or_less)
- val /= 10;
/* should "7.0" be printed as "7" ?
if removing the ".0" is preferred, enable the fourth condition. */
@@ -764,10 +768,13 @@ double_to_human (long double val, int precision,
devmsg (" after rounding, value=%Lf * %0.f ^ %u\n", val, scale_base, power);
- stpcpy (pfmt, show_decimal_point ? ".1Lf%s" : ".0Lf%s");
+ stpcpy (pfmt, ".*Lf%s");
+
+ int prec = user_precision == -1 ? show_decimal_point : user_precision;
/* buf_size - 1 used here to ensure place for possible scale_IEC_I suffix. */
- num_size = snprintf (buf, buf_size - 1, fmt, val, suffix_power_char (power));
+ num_size = snprintf (buf, buf_size - 1, fmt, val, prec,
+ suffix_power_char (power));
if (num_size < 0 || num_size >= (int) buf_size - 1)
error (EXIT_FAILURE, 0,
_("failed to prepare value '%Lf' for printing"), val);
@@ -953,6 +960,7 @@ FORMAT must be suitable for printing one floating-point argument '%f'.\n\
Optional quote (%'f) will enable --grouping (if supported by current locale).\n\
Optional width value (%10f) will pad output. Optional zero (%010f) width\n\
will zero pad the number. Optional negative values (%-10f) will left align.\n\
+Optional precision (%.1f) will override the input determined precision.\n\
"), stdout);
printf (_("\n\
@@ -996,7 +1004,6 @@ Examples:\n\
Only a limited subset of printf(3) syntax is supported.
TODO:
- support .precision
support %e %g etc. rather than just %f
NOTES:
@@ -1071,9 +1078,28 @@ parse_format_string (char const *fmt)
if (fmt[i] == '\0')
error (EXIT_FAILURE, 0, _("format %s ends in %%"), quote (fmt));
+ if (fmt[i] == '.')
+ {
+ i++;
+ errno = 0;
+ user_precision = strtol (fmt + i, &endptr, 10);
+ if (errno == ERANGE || user_precision < 0 || SIZE_MAX < user_precision
+ || isblank (fmt[i]) || fmt[i] == '+')
+ {
+ /* Note we disallow negative user_precision to be
+ consistent with printf(1). POSIX states that
+ negative precision is only supported (and ignored)
+ when used with '.*f'. glibc at least will malform
+ output when passed a direct negative precision. */
+ error (EXIT_FAILURE, 0,
+ _("invalid precision in format %s"), quote (fmt));
+ }
+ i = endptr - fmt;
+ }
+
if (fmt[i] != 'f')
error (EXIT_FAILURE, 0, _("invalid format %s,"
- " directive must be %%[0]['][-][N]f"),
+ " directive must be %%[0]['][-][N][.][N]f"),
quote (fmt));
i++;
suffix_pos = i;
@@ -1158,8 +1184,8 @@ prepare_padded_number (const long double val, size_t precision)
return 0;
}
- double_to_human (val, precision, buf, sizeof (buf), scale_to, grouping,
- round_style);
+ double_to_human (val, user_precision == -1 ? precision : user_precision, buf,
+ sizeof (buf), scale_to, grouping, round_style);
if (suffix)
strncat (buf, suffix, sizeof (buf) - strlen (buf) -1);
diff --git a/tests/misc/numfmt.pl b/tests/misc/numfmt.pl
index 630d18707..4ed1d666d 100755
--- a/tests/misc/numfmt.pl
+++ b/tests/misc/numfmt.pl
@@ -648,6 +648,23 @@ my @Tests =
"(cannot handle values > 999Y)\n"},
{EXIT => 2}],
+ # precision override
+ ['precision-1','--format=%.4f 9991239123 --to=si', {OUT=>"9.9913G"}],
+ ['precision-2','--format=%.1f 9991239123 --to=si', {OUT=>"10.0G"}],
+ ['precision-3','--format=%.1f 1', {OUT=>"1.0"}],
+ ['precision-4','--format=%.1f 1.12', {OUT=>"1.2"}],
+ ['precision-5','--format=%.1f 9991239123 --to-unit=G', {OUT=>"10.0"}],
+ ['precision-6','--format="% .1f" 9991239123 --to-unit=G', {OUT=>"10.0"}],
+ ['precision-7','--format=%.-1f 1.1',
+ {ERR => "$prog: invalid precision in format '%.-1f'\n"},
+ {EXIT => 1}],
+ ['precision-8','--format=%.+1f 1.1',
+ {ERR => "$prog: invalid precision in format '%.+1f'\n"},
+ {EXIT => 1}],
+ ['precision-9','--format="%. 1f" 1.1',
+ {ERR => "$prog: invalid precision in format '%. 1f'\n"},
+ {EXIT => 1}],
+
# debug warnings
['debug-1', '--debug 4096', {OUT=>"4096"},
{ERR=>"$prog: no conversion option specified\n"}],
@@ -715,11 +732,11 @@ my @Tests =
{EXIT=>1}],
['fmt-err-4', '--format "%d"',
{ERR=>"$prog: invalid format '%d', " .
- "directive must be %[0]['][-][N]f\n"},
+ "directive must be %[0]['][-][N][.][N]f\n"},
{EXIT=>1}],
['fmt-err-5', '--format "% -43 f"',
{ERR=>"$prog: invalid format '% -43 f', " .
- "directive must be %[0]['][-][N]f\n"},
+ "directive must be %[0]['][-][N][.][N]f\n"},
{EXIT=>1}],
['fmt-err-6', '--format "%f %f"',
{ERR=>"$prog: format '%f %f' has too many % directives\n"},