diff options
author | Pádraig Brady <P@draigBrady.com> | 2015-06-19 19:18:21 +0100 |
---|---|---|
committer | Pádraig Brady <P@draigBrady.com> | 2015-06-21 04:16:29 +0100 |
commit | 0a279f619055cc165bb3cfa3bb737cdd28ed4d70 (patch) | |
tree | 6149ce7f474051369efba96680228e3e0b5716db | |
parent | 71063bc858cd927e3622b511297e66b3e13f7453 (diff) | |
download | coreutils-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-- | NEWS | 3 | ||||
-rw-r--r-- | doc/coreutils.texi | 14 | ||||
-rw-r--r-- | src/numfmt.c | 50 | ||||
-rwxr-xr-x | tests/misc/numfmt.pl | 21 |
4 files changed, 67 insertions, 21 deletions
@@ -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"}, |