]> git.ipfire.org Git - thirdparty/coreutils.git/commitdiff
numfmt: support user specified output precision
authorPádraig Brady <P@draigBrady.com>
Fri, 19 Jun 2015 18:18:21 +0000 (19:18 +0100)
committerPádraig Brady <P@draigBrady.com>
Sun, 21 Jun 2015 03:16:29 +0000 (04:16 +0100)
* 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.

NEWS
doc/coreutils.texi
src/numfmt.c
tests/misc/numfmt.pl

diff --git a/NEWS b/NEWS
index 9c551d5149a9f8117da5e8285ad0ef9aff68840d..9b86d45f60a6f8fa9e5a87a4d9a23bb6f0ea0d12 100644 (file)
--- 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.
index 9197cb426d9fba0563f92de739240a7dbf929d15..c4f3a07c164f819aaf589717b3bc0d04ad23f303 100644 (file)
@@ -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
index 18243dd9f9257ee0cfbee68eb2742fe0804af612..81c624aeb67aa4198279121218990382628c2857 100644 (file)
@@ -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);
 
index 630d18707c4c377def4645fc6289321a7483cef9..4ed1d666dedcd8ff11b6eca18e5518f0a03d1753 100755 (executable)
@@ -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"},