summaryrefslogtreecommitdiff
path: root/src/seq.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/seq.c')
-rw-r--r--src/seq.c197
1 files changed, 189 insertions, 8 deletions
diff --git a/src/seq.c b/src/seq.c
index 90e9fc15..acbe2350 100644
--- a/src/seq.c
+++ b/src/seq.c
@@ -1,5 +1,5 @@
/* seq - print sequence of numbers to standard output.
- Copyright (C) 1994-2012 Free Software Foundation, Inc.
+ Copyright (C) 1994-2013 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
@@ -72,7 +72,11 @@ Usage: %s [OPTION]... LAST\n\
"), program_name, program_name, program_name);
fputs (_("\
Print numbers from FIRST to LAST, in steps of INCREMENT.\n\
-\n\
+"), stdout);
+
+ emit_mandatory_arg_note ();
+
+ fputs (_("\
-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\
@@ -166,6 +170,21 @@ scan_arg (const char *arg)
{
long exponent = strtol (e + 1, NULL, 10);
ret.precision += exponent < 0 ? -exponent : 0;
+ /* Don't account for e.... in the width since this is not output. */
+ ret.width -= strlen (arg) - (e - arg);
+ /* Adjust the width as per the exponent. */
+ if (exponent < 0)
+ {
+ if (decimal_point)
+ {
+ if (e == decimal_point + 1) /* undo #. -> # above */
+ ret.width++;
+ }
+ else
+ ret.width++;
+ exponent = -exponent;
+ }
+ ret.width += exponent;
}
}
@@ -317,6 +336,8 @@ get_default_format (operand first, operand step, operand last)
last_width--; /* don't include space for '.' */
if (last.precision == 0 && prec)
last_width++; /* include space for '.' */
+ if (first.precision == 0 && prec)
+ first_width++; /* include space for '.' */
size_t width = MAX (first_width, last_width);
if (width <= INT_MAX)
{
@@ -335,6 +356,122 @@ get_default_format (operand first, operand step, operand last)
return "%Lg";
}
+/* The NUL-terminated string S0 of length S_LEN represents a valid
+ non-negative decimal integer. Adjust the string and length so
+ that the pair describe the next-larger value. */
+static void
+incr (char **s0, size_t *s_len)
+{
+ char *s = *s0;
+ char *endp = s + *s_len - 1;
+
+ do
+ {
+ if ((*endp)++ < '9')
+ return;
+ *endp-- = '0';
+ }
+ while (endp >= s);
+ *--(*s0) = '1';
+ ++*s_len;
+}
+
+/* Compare A and B (each a NUL-terminated digit string), with lengths
+ given by A_LEN and B_LEN. Return +1 if A < B, -1 if B < A, else 0. */
+static int
+cmp (char const *a, size_t a_len, char const *b, size_t b_len)
+{
+ if (a_len < b_len)
+ return -1;
+ if (b_len < a_len)
+ return 1;
+ return (strcmp (a, b));
+}
+
+/* Trim leading 0's from S, but if S is all 0's, leave one.
+ Return a pointer to the trimmed string. */
+static char const * _GL_ATTRIBUTE_PURE
+trim_leading_zeros (char const *s)
+{
+ char const *p = s;
+ while (*s == '0')
+ ++s;
+
+ /* If there were only 0's, back up, to leave one. */
+ if (!*s && s != p)
+ --s;
+ return s;
+}
+
+/* Print all whole numbers from A to B, inclusive -- to stdout, each
+ followed by a newline. If B < A, return false and print nothing.
+ Otherwise, return true. */
+static bool
+seq_fast (char const *a, char const *b)
+{
+ /* Skip past any leading 0's. Without this, our naive cmp
+ function would declare 000 to be larger than 99. */
+ a = trim_leading_zeros (a);
+ b = trim_leading_zeros (b);
+
+ size_t p_len = strlen (a);
+ size_t q_len = strlen (b);
+ size_t n = MAX (p_len, q_len);
+ char *p0 = xmalloc (n + 1);
+ char *p = memcpy (p0 + n - p_len, a, p_len + 1);
+ char *q0 = xmalloc (n + 1);
+ char *q = memcpy (q0 + n - q_len, b, q_len + 1);
+
+ bool ok = cmp (p, p_len, q, q_len) <= 0;
+ if (ok)
+ {
+ /* Buffer at least this many numbers per fwrite call.
+ This gives a speed-up of more than 2x over the unbuffered code
+ when printing the first 10^9 integers. */
+ enum {N = 40};
+ char *buf = xmalloc (N * (n + 1));
+ char const *buf_end = buf + N * (n + 1);
+
+ char *z = buf;
+
+ /* Write first number to buffer. */
+ z = mempcpy (z, p, p_len);
+
+ /* Append separator then number. */
+ while (cmp (p, p_len, q, q_len) < 0)
+ {
+ *z++ = *separator;
+ incr (&p, &p_len);
+ z = mempcpy (z, p, p_len);
+ /* If no place for another separator + number then
+ output buffer so far, and reset to start of buffer. */
+ if (buf_end - (n + 1) < z)
+ {
+ fwrite (buf, z - buf, 1, stdout);
+ z = buf;
+ }
+ }
+
+ /* Write any remaining buffered output, and the terminator. */
+ *z++ = *terminator;
+ fwrite (buf, z - buf, 1, stdout);
+
+ IF_LINT (free (buf));
+ }
+
+ free (p0);
+ free (q0);
+ return ok;
+}
+
+/* Return true if S consists of at least one digit and no non-digits. */
+static bool _GL_ATTRIBUTE_PURE
+all_digits_p (char const *s)
+{
+ size_t n = strlen (s);
+ return ISDIGIT (s[0]) && n == strspn (s, "0123456789");
+}
+
int
main (int argc, char **argv)
{
@@ -397,13 +534,14 @@ main (int argc, char **argv)
}
}
- if (argc - optind < 1)
+ unsigned int n_args = argc - optind;
+ if (n_args < 1)
{
error (0, 0, _("missing operand"));
usage (EXIT_FAILURE);
}
- if (3 < argc - optind)
+ if (3 < n_args)
{
error (0, 0, _("extra operand %s"), quote (argv[optind + 3]));
usage (EXIT_FAILURE);
@@ -412,6 +550,33 @@ main (int argc, char **argv)
if (format_str)
format_str = long_double_format (format_str, &layout);
+ if (format_str != NULL && equal_width)
+ {
+ error (0, 0, _("format string may not be specified"
+ " when printing equal width strings"));
+ usage (EXIT_FAILURE);
+ }
+
+ /* If the following hold:
+ - no format string, [FIXME: relax this, eventually]
+ - integer start (or no start)
+ - integer end
+ - increment == 1 or not specified [FIXME: relax this, eventually]
+ then use the much more efficient integer-only code. */
+ if (all_digits_p (argv[optind])
+ && (n_args == 1 || all_digits_p (argv[optind + 1]))
+ && (n_args < 3 || (STREQ ("1", argv[optind + 1])
+ && all_digits_p (argv[optind + 2])))
+ && !equal_width && !format_str && strlen (separator) == 1)
+ {
+ char const *s1 = n_args == 1 ? "1" : argv[optind];
+ char const *s2 = argv[optind + (n_args - 1)];
+ if (seq_fast (s1, s2))
+ exit (EXIT_SUCCESS);
+
+ /* Upon any failure, let the more general code deal with it. */
+ }
+
last = scan_arg (argv[optind++]);
if (optind < argc)
@@ -426,11 +591,27 @@ main (int argc, char **argv)
}
}
- if (format_str != NULL && equal_width)
+ if (first.precision == 0 && step.precision == 0 && last.precision == 0
+ && 0 <= first.value && step.value == 1 && 0 <= last.value
+ && !equal_width && !format_str && strlen (separator) == 1)
{
- error (0, 0, _("format string may not be specified"
- " when printing equal width strings"));
- usage (EXIT_FAILURE);
+ char *s1;
+ char *s2;
+ if (asprintf (&s1, "%0.Lf", first.value) < 0)
+ xalloc_die ();
+ if (asprintf (&s2, "%0.Lf", last.value) < 0)
+ xalloc_die ();
+
+ if (seq_fast (s1, s2))
+ {
+ IF_LINT (free (s1));
+ IF_LINT (free (s2));
+ exit (EXIT_SUCCESS);
+ }
+
+ free (s1);
+ free (s2);
+ /* Upon any failure, let the more general code deal with it. */
}
if (format_str == NULL)