summaryrefslogtreecommitdiff
path: root/src/seq.c
diff options
context:
space:
mode:
authorIgor Pashev <pashev.igor@gmail.com>2012-09-09 02:33:07 +0000
committerIgor Pashev <pashev.igor@gmail.com>2012-09-09 02:33:07 +0000
commit8e7ba70eba02f88d4f3ba12e07ab9c7bdf32240a (patch)
tree35373d0bc6f80a2f2ce1c49c91b3817f26b6c465 /src/seq.c
downloadcoreutils-8e7ba70eba02f88d4f3ba12e07ab9c7bdf32240a.tar.gz
Imported Upstream version 8.17upstream/8.17
Diffstat (limited to 'src/seq.c')
-rw-r--r--src/seq.c442
1 files changed, 442 insertions, 0 deletions
diff --git a/src/seq.c b/src/seq.c
new file mode 100644
index 00000000..90e9fc15
--- /dev/null
+++ b/src/seq.c
@@ -0,0 +1,442 @@
+/* seq - print sequence of numbers to standard output.
+ Copyright (C) 1994-2012 Free Software Foundation, Inc.
+
+ This program 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.
+
+ This program 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 this program. If not, see <http://www.gnu.org/licenses/>. */
+
+/* Written by Ulrich Drepper. */
+
+#include <config.h>
+#include <getopt.h>
+#include <stdio.h>
+#include <sys/types.h>
+
+#include "system.h"
+#include "c-strtod.h"
+#include "error.h"
+#include "quote.h"
+#include "xstrtod.h"
+
+/* Roll our own isfinite rather than using <math.h>, so that we don't
+ have to worry about linking -lm just for isfinite. */
+#ifndef isfinite
+# define isfinite(x) ((x) * 0 == 0)
+#endif
+
+/* The official name of this program (e.g., no 'g' prefix). */
+#define PROGRAM_NAME "seq"
+
+#define AUTHORS proper_name ("Ulrich Drepper")
+
+/* If true print all number with equal width. */
+static bool equal_width;
+
+/* The string used to separate two numbers. */
+static char const *separator;
+
+/* The string output after all numbers have been output.
+ Usually "\n" or "\0". */
+static char const terminator[] = "\n";
+
+static struct option const long_options[] =
+{
+ { "equal-width", no_argument, NULL, 'w'},
+ { "format", required_argument, NULL, 'f'},
+ { "separator", required_argument, NULL, 's'},
+ {GETOPT_HELP_OPTION_DECL},
+ {GETOPT_VERSION_OPTION_DECL},
+ { NULL, 0, NULL, 0}
+};
+
+void
+usage (int status)
+{
+ if (status != EXIT_SUCCESS)
+ emit_try_help ();
+ else
+ {
+ printf (_("\
+Usage: %s [OPTION]... LAST\n\
+ or: %s [OPTION]... FIRST LAST\n\
+ or: %s [OPTION]... FIRST INCREMENT LAST\n\
+"), program_name, program_name, program_name);
+ fputs (_("\
+Print numbers from FIRST to LAST, in steps of INCREMENT.\n\
+\n\
+ -f, --format=FORMAT use printf style floating-point FORMAT\n\
+ -s, --separator=STRING use STRING to separate numbers (default: \\n)\n\
+ -w, --equal-width equalize width by padding with leading zeroes\n\
+"), stdout);
+ fputs (HELP_OPTION_DESCRIPTION, stdout);
+ fputs (VERSION_OPTION_DESCRIPTION, stdout);
+ fputs (_("\
+\n\
+If FIRST or INCREMENT is omitted, it defaults to 1. That is, an\n\
+omitted INCREMENT defaults to 1 even when LAST is smaller than FIRST.\n\
+FIRST, INCREMENT, and LAST are interpreted as floating point values.\n\
+INCREMENT is usually positive if FIRST is smaller than LAST, and\n\
+INCREMENT is usually negative if FIRST is greater than LAST.\n\
+"), stdout);
+ fputs (_("\
+FORMAT must be suitable for printing one argument of type 'double';\n\
+it defaults to %.PRECf if FIRST, INCREMENT, and LAST are all fixed point\n\
+decimal numbers with maximum precision PREC, and to %g otherwise.\n\
+"), stdout);
+ emit_ancillary_info ();
+ }
+ exit (status);
+}
+
+/* A command-line operand. */
+struct operand
+{
+ /* Its value, converted to 'long double'. */
+ long double value;
+
+ /* Its print width, if it were printed out in a form similar to its
+ input form. An input like "-.1" is treated like "-0.1", and an
+ input like "1." is treated like "1", but otherwise widths are
+ left alone. */
+ size_t width;
+
+ /* Number of digits after the decimal point, or INT_MAX if the
+ number can't easily be expressed as a fixed-point number. */
+ int precision;
+};
+typedef struct operand operand;
+
+/* Description of what a number-generating format will generate. */
+struct layout
+{
+ /* Number of bytes before and after the number. */
+ size_t prefix_len;
+ size_t suffix_len;
+};
+
+/* Read a long double value from the command line.
+ Return if the string is correct else signal error. */
+
+static operand
+scan_arg (const char *arg)
+{
+ operand ret;
+
+ if (! xstrtold (arg, NULL, &ret.value, c_strtold))
+ {
+ error (0, 0, _("invalid floating point argument: %s"), arg);
+ usage (EXIT_FAILURE);
+ }
+
+ /* We don't output spaces or '+' so don't include in width */
+ while (isspace (to_uchar (*arg)) || *arg == '+')
+ arg++;
+
+ ret.width = strlen (arg);
+ ret.precision = INT_MAX;
+
+ if (! arg[strcspn (arg, "xX")] && isfinite (ret.value))
+ {
+ char const *decimal_point = strchr (arg, '.');
+ if (! decimal_point)
+ ret.precision = 0;
+ else
+ {
+ size_t fraction_len = strcspn (decimal_point + 1, "eE");
+ if (fraction_len <= INT_MAX)
+ ret.precision = fraction_len;
+ ret.width += (fraction_len == 0 /* #. -> # */
+ ? -1
+ : (decimal_point == arg /* .# -> 0.# */
+ || ! ISDIGIT (decimal_point[-1]))); /* -.# -> 0.# */
+ }
+ char const *e = strchr (arg, 'e');
+ if (! e)
+ e = strchr (arg, 'E');
+ if (e)
+ {
+ long exponent = strtol (e + 1, NULL, 10);
+ ret.precision += exponent < 0 ? -exponent : 0;
+ }
+ }
+
+ return ret;
+}
+
+/* If FORMAT is a valid printf format for a double argument, return
+ its long double equivalent, allocated from dynamic storage, and
+ store into *LAYOUT a description of the output layout; otherwise,
+ report an error and exit. */
+
+static char const *
+long_double_format (char const *fmt, struct layout *layout)
+{
+ size_t i;
+ size_t prefix_len = 0;
+ size_t suffix_len = 0;
+ size_t length_modifier_offset;
+ bool has_L;
+
+ for (i = 0; ! (fmt[i] == '%' && fmt[i + 1] != '%'); i += (fmt[i] == '%') + 1)
+ {
+ if (!fmt[i])
+ error (EXIT_FAILURE, 0,
+ _("format %s has no %% directive"), quote (fmt));
+ prefix_len++;
+ }
+
+ i++;
+ i += strspn (fmt + i, "-+#0 '");
+ i += strspn (fmt + i, "0123456789");
+ if (fmt[i] == '.')
+ {
+ i++;
+ i += strspn (fmt + i, "0123456789");
+ }
+
+ length_modifier_offset = i;
+ has_L = (fmt[i] == 'L');
+ i += has_L;
+ if (fmt[i] == '\0')
+ error (EXIT_FAILURE, 0, _("format %s ends in %%"), quote (fmt));
+ if (! strchr ("efgaEFGA", fmt[i]))
+ error (EXIT_FAILURE, 0,
+ _("format %s has unknown %%%c directive"), quote (fmt), fmt[i]);
+
+ for (i++; ; i += (fmt[i] == '%') + 1)
+ if (fmt[i] == '%' && fmt[i + 1] != '%')
+ error (EXIT_FAILURE, 0, _("format %s has too many %% directives"),
+ quote (fmt));
+ else if (fmt[i])
+ suffix_len++;
+ else
+ {
+ size_t format_size = i + 1;
+ char *ldfmt = xmalloc (format_size + 1);
+ memcpy (ldfmt, fmt, length_modifier_offset);
+ ldfmt[length_modifier_offset] = 'L';
+ strcpy (ldfmt + length_modifier_offset + 1,
+ fmt + length_modifier_offset + has_L);
+ layout->prefix_len = prefix_len;
+ layout->suffix_len = suffix_len;
+ return ldfmt;
+ }
+}
+
+/* Actually print the sequence of numbers in the specified range, with the
+ given or default stepping and format. */
+
+static void
+print_numbers (char const *fmt, struct layout layout,
+ long double first, long double step, long double last)
+{
+ bool out_of_range = (step < 0 ? first < last : last < first);
+
+ if (! out_of_range)
+ {
+ long double x = first;
+ long double i;
+
+ for (i = 1; ; i++)
+ {
+ long double x0 = x;
+ printf (fmt, x);
+ if (out_of_range)
+ break;
+ x = first + i * step;
+ out_of_range = (step < 0 ? x < last : last < x);
+
+ if (out_of_range)
+ {
+ /* If the number just past LAST prints as a value equal
+ to LAST, and prints differently from the previous
+ number, then print the number. This avoids problems
+ with rounding. For example, with the x86 it causes
+ "seq 0 0.000001 0.000003" to print 0.000003 instead
+ of stopping at 0.000002. */
+
+ bool print_extra_number = false;
+ long double x_val;
+ char *x_str;
+ int x_strlen;
+ setlocale (LC_NUMERIC, "C");
+ x_strlen = asprintf (&x_str, fmt, x);
+ setlocale (LC_NUMERIC, "");
+ if (x_strlen < 0)
+ xalloc_die ();
+ x_str[x_strlen - layout.suffix_len] = '\0';
+
+ if (xstrtold (x_str + layout.prefix_len, NULL, &x_val, c_strtold)
+ && x_val == last)
+ {
+ char *x0_str = NULL;
+ if (asprintf (&x0_str, fmt, x0) < 0)
+ xalloc_die ();
+ print_extra_number = !STREQ (x0_str, x_str);
+ free (x0_str);
+ }
+
+ free (x_str);
+ if (! print_extra_number)
+ break;
+ }
+
+ fputs (separator, stdout);
+ }
+
+ fputs (terminator, stdout);
+ }
+}
+
+/* Return the default format given FIRST, STEP, and LAST. */
+static char const *
+get_default_format (operand first, operand step, operand last)
+{
+ static char format_buf[sizeof "%0.Lf" + 2 * INT_STRLEN_BOUND (int)];
+
+ int prec = MAX (first.precision, step.precision);
+
+ if (prec != INT_MAX && last.precision != INT_MAX)
+ {
+ if (equal_width)
+ {
+ /* increase first_width by any increased precision in step */
+ size_t first_width = first.width + (prec - first.precision);
+ /* adjust last_width to use precision from first/step */
+ size_t last_width = last.width + (prec - last.precision);
+ if (last.precision && prec == 0)
+ last_width--; /* don't include space for '.' */
+ if (last.precision == 0 && prec)
+ last_width++; /* include space for '.' */
+ size_t width = MAX (first_width, last_width);
+ if (width <= INT_MAX)
+ {
+ int w = width;
+ sprintf (format_buf, "%%0%d.%dLf", w, prec);
+ return format_buf;
+ }
+ }
+ else
+ {
+ sprintf (format_buf, "%%.%dLf", prec);
+ return format_buf;
+ }
+ }
+
+ return "%Lg";
+}
+
+int
+main (int argc, char **argv)
+{
+ int optc;
+ operand first = { 1, 1, 0 };
+ operand step = { 1, 1, 0 };
+ operand last;
+ struct layout layout = { 0, 0 };
+
+ /* The printf(3) format used for output. */
+ char const *format_str = NULL;
+
+ initialize_main (&argc, &argv);
+ set_program_name (argv[0]);
+ setlocale (LC_ALL, "");
+ bindtextdomain (PACKAGE, LOCALEDIR);
+ textdomain (PACKAGE);
+
+ atexit (close_stdout);
+
+ equal_width = false;
+ separator = "\n";
+
+ /* We have to handle negative numbers in the command line but this
+ conflicts with the command line arguments. So explicitly check first
+ whether the next argument looks like a negative number. */
+ while (optind < argc)
+ {
+ if (argv[optind][0] == '-'
+ && ((optc = argv[optind][1]) == '.' || ISDIGIT (optc)))
+ {
+ /* means negative number */
+ break;
+ }
+
+ optc = getopt_long (argc, argv, "+f:s:w", long_options, NULL);
+ if (optc == -1)
+ break;
+
+ switch (optc)
+ {
+ case 'f':
+ format_str = optarg;
+ break;
+
+ case 's':
+ separator = optarg;
+ break;
+
+ case 'w':
+ equal_width = true;
+ break;
+
+ case_GETOPT_HELP_CHAR;
+
+ case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS);
+
+ default:
+ usage (EXIT_FAILURE);
+ }
+ }
+
+ if (argc - optind < 1)
+ {
+ error (0, 0, _("missing operand"));
+ usage (EXIT_FAILURE);
+ }
+
+ if (3 < argc - optind)
+ {
+ error (0, 0, _("extra operand %s"), quote (argv[optind + 3]));
+ usage (EXIT_FAILURE);
+ }
+
+ if (format_str)
+ format_str = long_double_format (format_str, &layout);
+
+ last = scan_arg (argv[optind++]);
+
+ if (optind < argc)
+ {
+ first = last;
+ last = scan_arg (argv[optind++]);
+
+ if (optind < argc)
+ {
+ step = last;
+ last = scan_arg (argv[optind++]);
+ }
+ }
+
+ if (format_str != NULL && equal_width)
+ {
+ error (0, 0, _("format string may not be specified"
+ " when printing equal width strings"));
+ usage (EXIT_FAILURE);
+ }
+
+ if (format_str == NULL)
+ format_str = get_default_format (first, step, last);
+
+ print_numbers (format_str, layout, first.value, step.value, last.value);
+
+ exit (EXIT_SUCCESS);
+}