summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAssaf Gordon <assafgordon@gmail.com>2018-01-10 23:48:56 (GMT)
committerAssaf Gordon <assafgordon@gmail.com>2018-01-17 22:29:08 (GMT)
commitef42f4551fe9939f90a1d963a1ddcea6f5a8c2d1 (patch)
treeb33f04ceb35cff3ba2af09c09151c7a2f713f8aa
parent3d4e163019a4a334e71698131b83b5292cd37008 (diff)
downloaddatamash-ef42f4551fe9939f90a1d963a1ddcea6f5a8c2d1.zip
datamash-ef42f4551fe9939f90a1d963a1ddcea6f5a8c2d1.tar.gz
datamash-ef42f4551fe9939f90a1d963a1ddcea6f5a8c2d1.tar.bz2
datamash: add -R/--round=N option
Print numeric values with N decimal places. Example: $ echo 1.1 | datamash --round=5 sum 1 1.10000 Requested in https://github.com/agordon/datamash/issues/5 . * NEWS: Mention this. * src/datamash.c (usage): Mention new option. * configure.ac: Remove nonliteral-printf warning. * doc/datamash.texi: Mention new option. * src/text-options.c,src/text-options.h (numeric_output_format, numeric_output_bufsize): New variables. (set_numeric_output_precision,finalize_numeric_output_buffer): New functions to set the printf style based on requested precision. * src/fields-ops.c: Print numeric output using new format variables. * tests/datamash-error-msgs.pl: Test invalid --round usage. * tests/datamash-output-format.pl: Test --round usage. * Makefile.am: Add new test file.
-rw-r--r--Makefile.am1
-rw-r--r--NEWS2
-rw-r--r--configure.ac1
-rw-r--r--doc/datamash.texi5
-rw-r--r--src/datamash.c11
-rw-r--r--src/field-ops.c8
-rw-r--r--src/text-options.c42
-rw-r--r--src/text-options.h10
-rw-r--r--tests/datamash-error-msgs.pl10
-rwxr-xr-xtests/datamash-output-format.pl73
10 files changed, 152 insertions, 11 deletions
diff --git a/Makefile.am b/Makefile.am
index c9ae89e..1baa5ab 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -125,6 +125,7 @@ TESTS = \
tests/datamash-check.pl \
tests/datamash-pair-tests.pl \
tests/datamash-check-tabular.pl \
+ tests/datamash-output-format.pl \
tests/datamash-sort-header.sh \
tests/datamash-sort-errors.sh \
tests/datamash-io-errors.sh \
diff --git a/NEWS b/NEWS
index e80e853..7489a6b 100644
--- a/NEWS
+++ b/NEWS
@@ -2,6 +2,8 @@
** New Features
+ New option: -R/--round=N rounds numeric values to N decimal places.
+
New option: --output-delimiter=X overrides -t/-W.
New operation: trimmean (trimmed mean value).
diff --git a/configure.ac b/configure.ac
index dd49ec4..708ccda 100644
--- a/configure.ac
+++ b/configure.ac
@@ -48,7 +48,6 @@ gl_WARN_ADD([-Wextra])
gl_WARN_ADD([-Wformat-security])
gl_WARN_ADD([-Wswitch-enum])
gl_WARN_ADD([-Wswitch-default])
-gl_WARN_ADD([-Wformat-nonliteral])
gl_WARN_ADD([-Wunused-parameter])
gl_WARN_ADD([-Wfloat-equal])
gl_WARN_ADD([-fdiagnostics-show-option])
diff --git a/doc/datamash.texi b/doc/datamash.texi
index c64a83a..4519cad 100644
--- a/doc/datamash.texi
+++ b/doc/datamash.texi
@@ -295,6 +295,11 @@ Use character @var{X} instead as output field delimiter.
This option overrides @option{--field-separator}/@option{-t}/
@option{--whitespace}/@option{-W}.
+@item --round=@var{N}
+@itemx -R @var{N}
+@opindex --round
+@opindex -R
+Round numeric output to @var{N} decimal places.
@item --whitespace
@itemx -W
diff --git a/src/datamash.c b/src/datamash.c
index 09468ef..b8d4c1d 100644
--- a/src/datamash.c
+++ b/src/datamash.c
@@ -113,7 +113,7 @@ enum
UNDOC_RMDUP_TEST
};
-static char const short_options[] = "sfF:izg:t:HW";
+static char const short_options[] = "sfF:izg:t:HWR:";
static struct option const long_options[] =
{
@@ -131,6 +131,7 @@ static struct option const long_options[] =
{"sort", no_argument, NULL, 's'},
{"no-strict", no_argument, NULL, NO_STRICT_OPTION},
{"narm", no_argument, NULL, REMOVE_NA_VALUES_OPTION},
+ {"round", required_argument, NULL, 'R'},
{GETOPT_HELP_OPTION_DECL},
{GETOPT_VERSION_OPTION_DECL},
/* Undocumented options */
@@ -258,6 +259,9 @@ which require a pair of fields (e.g. 'pcov 2:6').\n"), stdout);
--narm skip NA/NaN values\n\
"), stdout);
fputs (_("\
+ -R, --round=N round numeric output to N decimal places\n\
+"), stdout);
+ fputs (_("\
-W, --whitespace use whitespace (one or more spaces and/or tabs)\n\
for field delimiters\n\
"), stdout);
@@ -1165,6 +1169,10 @@ int main (int argc, char* argv[])
input_header = output_header = true;
break;
+ case 'R':
+ set_numeric_output_precision (optarg);
+ break;
+
case 's':
pipe_through_sort = true;
break;
@@ -1221,7 +1229,6 @@ int main (int argc, char* argv[])
}
}
-
if (argc <= optind)
{
error (0, 0, _("missing operation specifiers"));
diff --git a/src/field-ops.c b/src/field-ops.c
index 750b519..1a6a4ab 100644
--- a/src/field-ops.c
+++ b/src/field-ops.c
@@ -742,9 +742,9 @@ field_op_summarize_empty (struct fieldop *op)
if (op->res_type==NUMERIC_RESULT)
{
- field_op_reserve_out_buf (op, numeric_output_precision*2);
+ field_op_reserve_out_buf (op, numeric_output_bufsize);
snprintf (op->out_buf, op->out_buf_alloc,
- "%.*Lg", numeric_output_precision, numeric_result);
+ numeric_output_format, numeric_result);
}
}
@@ -970,9 +970,9 @@ field_op_summarize (struct fieldop *op)
if (op->res_type==NUMERIC_RESULT)
{
- field_op_reserve_out_buf (op, numeric_output_precision*2);
+ field_op_reserve_out_buf (op, numeric_output_bufsize);
snprintf (op->out_buf, op->out_buf_alloc,
- "%.*Lg", numeric_output_precision, numeric_result);
+ numeric_output_format, numeric_result);
}
}
diff --git a/src/text-options.c b/src/text-options.c
index 4446b03..ba274d3 100644
--- a/src/text-options.c
+++ b/src/text-options.c
@@ -20,12 +20,13 @@
/* Written by Assaf Gordon */
#include <config.h>
-
+#include <float.h>
#include <ctype.h>
#include <stdbool.h>
#include "system.h"
+#include "die.h"
#include "text-options.h"
/* The character marking end of line. Default to \n. */
@@ -41,7 +42,10 @@ int out_tab= '\t';
bool case_sensitive = true;
/* In the future: allow users to change this */
-int numeric_output_precision = 14;
+char* numeric_output_format = "%.14Lg";
+
+/* number of bytes to allocate for output buffer */
+int numeric_output_bufsize = 200;
/* The character used to separate collapsed/uniqued strings */
/* In the future: allow users to change this */
@@ -78,3 +82,37 @@ init_blank_table (void)
See: http://stackoverflow.com/a/16245669 */
void print_field_separator ();
void print_line_separator ();
+
+
+
+/* Calculate the required size of the output buffer */
+static void
+finalize_numeric_output_buffer()
+{
+ char c;
+ long double d = LDBL_MAX;
+ int n = snprintf (&c, 1, numeric_output_format, d);
+ numeric_output_bufsize = n + 100 ;
+}
+
+void
+set_numeric_output_precision(const char* digits)
+{
+ long int l;
+ char *p;
+ char tmp[100];
+
+ if (digits == NULL || digits[0] == '\0')
+ die (EXIT_FAILURE, 0, _("missing rounding digits value"));
+
+ errno = 0;
+ l = strtol (digits, &p, 10);
+ if (errno != 0 || *p != '\0' || l <=0 || l> 50)
+ die (EXIT_FAILURE, 0, _("invalid rounding digits value %s"),
+ quote (digits));
+
+ snprintf (tmp, sizeof (tmp), "%%.%dLf", (int)l);
+ numeric_output_format = xstrdup (tmp);
+
+ finalize_numeric_output_buffer ();
+}
diff --git a/src/text-options.h b/src/text-options.h
index b5a0679..f074e38 100644
--- a/src/text-options.h
+++ b/src/text-options.h
@@ -42,8 +42,10 @@ extern int out_tab ;
/* Global case-sensitivity option. Defaults to 'true' . */
extern bool case_sensitive ;
-/* precision used with printf "%.*Lg" */
-extern int numeric_output_precision;
+/* Numeric output format (default: "%.14Lg" */
+extern char* numeric_output_format;
+/* number of bytes to allocate for output buffer */
+extern int numeric_output_bufsize;
/* The character used to separate collapsed/uniqued strings */
extern char collapse_separator;
@@ -79,4 +81,8 @@ print_line_separator ()
putchar (eolchar);
}
+
+void
+set_numeric_output_precision(const char* digits);
+
#endif
diff --git a/tests/datamash-error-msgs.pl b/tests/datamash-error-msgs.pl
index 3808edc..0420505 100644
--- a/tests/datamash-error-msgs.pl
+++ b/tests/datamash-error-msgs.pl
@@ -211,6 +211,16 @@ my @Tests =
['e106','trimmean:1:2 1', {IN_PIPE=>""}, {EXIT=>1},
{ERR=>"$prog: too many parameters for operation 'trimmean'\n"}],
+ # Rounding
+ ['e110','--round ""', {IN_PIPE=>""}, {EXIT=>1},
+ {ERR=>"$prog: missing rounding digits value\n"}],
+ ['e111','--round "3a"', {IN_PIPE=>""}, {EXIT=>1},
+ {ERR=>"$prog: invalid rounding digits value '3a'\n"}],
+ # Rounding Currently hard-coded to 1 to 50 decimal-point digits.
+ ['e112','--round "0"', {IN_PIPE=>""}, {EXIT=>1},
+ {ERR=>"$prog: invalid rounding digits value '0'\n"}],
+ ['e113','--round "51"', {IN_PIPE=>""}, {EXIT=>1},
+ {ERR=>"$prog: invalid rounding digits value '51'\n"}],
);
my $save_temps = $ENV{SAVE_TEMPS};
diff --git a/tests/datamash-output-format.pl b/tests/datamash-output-format.pl
new file mode 100755
index 0000000..c987ac5
--- /dev/null
+++ b/tests/datamash-output-format.pl
@@ -0,0 +1,73 @@
+#!/usr/bin/env perl
+=pod
+ Unit Tests for GNU Datamash - perform simple calculation on input data
+
+ Copyright (C) 2018 Assaf Gordon <assafgordon@gmail.com
+
+ This file is part of GNU Datamash.
+
+ GNU Datamash is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ GNU Datamash is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with GNU Datamash. If not, see <https://www.gnu.org/licenses/>.
+
+ Written by Assaf Gordon.
+=cut
+use strict;
+use warnings;
+
+##
+## This script tests output format options
+##
+
+
+# Until a better way comes along to auto-use Coreutils Perl modules
+# as in the coreutils' autotools system.
+use Coreutils;
+use CuSkip;
+use CuTmpdir qw(datamash);
+
+(my $program_name = $0) =~ s|.*/||;
+my $prog = 'datamash';
+
+# TODO: add localization tests with "grouping"
+# Turn off localization of executable's output.
+@ENV{qw(LANGUAGE LANG LC_ALL)} = ('C') x 3;
+
+my $in1=<<'EOF';
+1.000004
+0.000005
+EOF
+
+my @Tests =
+(
+ # Test Rouding
+ ['r1', 'sum 1' , {IN_PIPE=>$in1}, {OUT => "1.000009\n"}],
+ ['r2', '--round 1 sum 1' , {IN_PIPE=>$in1}, {OUT => "1.0\n"}],
+ ['r3', '--round 3 sum 1' , {IN_PIPE=>$in1}, {OUT => "1.000\n"}],
+ ['r4', '--round 5 sum 1' , {IN_PIPE=>$in1}, {OUT => "1.00001\n"}],
+ ['r5', '--round 6 sum 1' , {IN_PIPE=>$in1}, {OUT => "1.000009\n"}],
+ ['r6', '--round 7 sum 1' , {IN_PIPE=>$in1}, {OUT => "1.0000090\n"}],
+
+ # Test short rounding option
+ ['r7', '-R 7 sum 1', {IN_PIPE=>$in1}, {OUT => "1.0000090\n"}],
+
+ # Test multiple rounding options
+ ['r8', '--round 3 -R 7 sum 1', {IN_PIPE=>$in1}, {OUT => "1.0000090\n"}],
+ ['r9', '--round 7 -R 3 sum 1', {IN_PIPE=>$in1}, {OUT => "1.000\n"}],
+);
+
+
+my $save_temps = $ENV{SAVE_TEMPS};
+my $verbose = $ENV{VERBOSE};
+
+my $fail = run_tests ($program_name, $prog, \@Tests, $save_temps, $verbose);
+exit $fail;