summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPádraig Brady <P@draigBrady.com>2014-10-11 10:06:48 +0100
committerPádraig Brady <P@draigBrady.com>2015-06-24 17:03:35 +0100
commitfc6825b231b43c12738fb65a9309cdecb67f6833 (patch)
tree50bb0b64a18e3a6b636ecc11f8d41b5778488d45
parent53883af063d13a628520615e12705bc6f3a549ec (diff)
downloadcoreutils-fc6825b231b43c12738fb65a9309cdecb67f6833.tar.xz
seq: support inf last item more generally/efficiently
* src/seq.c (main): Call seq_fast for infinite last value. This implicitly avoids format conversion on the 999999 -> 1000000 transition. * src/seq.c (seq_fast): Generalize the buffer handling, and adjust to handle the "inf" last value specifics. * tests/misc/seq-precision.sh: A new test. * tests/local.mk: Reference the new test.
-rw-r--r--src/seq.c85
-rw-r--r--tests/local.mk1
-rwxr-xr-xtests/misc/seq-precision.sh37
3 files changed, 98 insertions, 25 deletions
diff --git a/src/seq.c b/src/seq.c
index c8c0492b1..e8d4f1892 100644
--- a/src/seq.c
+++ b/src/seq.c
@@ -411,52 +411,84 @@ trim_leading_zeros (char const *s)
static bool
seq_fast (char const *a, char const *b)
{
+ bool inf = STREQ (b, "inf");
+
/* 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;
+ size_t q_len = inf ? 0 : strlen (b);
+
+ /* Allow for at least 31 digits without realloc.
+ 1 more than p_len is needed for the inf case. */
+ size_t inc_size = MAX (MAX (p_len + 1, q_len), 31);
+
+ /* Copy input strings (incl NUL) to end of new buffers. */
+ char *p0 = xmalloc (inc_size + 1);
+ char *p = memcpy (p0 + inc_size - p_len, a, p_len + 1);
+ char *q;
+ char *q0;
+ if (! inf)
+ {
+ q0 = xmalloc (inc_size + 1);
+ q = memcpy (q0 + inc_size - q_len, b, q_len + 1);
+ }
+ else
+ q = q0 = NULL;
+
+ bool ok = inf || 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
+ /* Reduce number of fwrite calls which is seen to
+ give 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);
+ size_t buf_size = MAX (BUFSIZ, (inc_size + 1) * 2);
+ char *buf = xmalloc (buf_size);
+ char const *buf_end = buf + buf_size;
- char *z = buf;
+ char *bufp = buf;
/* Write first number to buffer. */
- z = mempcpy (z, p, p_len);
+ bufp = mempcpy (bufp, p, p_len);
/* Append separator then number. */
- while (cmp (p, p_len, q, q_len) < 0)
+ while (inf || cmp (p, p_len, q, q_len) < 0)
{
- *z++ = *separator;
+ *bufp++ = *separator;
incr (&p, &p_len);
- z = mempcpy (z, p, p_len);
+
+ /* Double up the buffers when needed for the inf case. */
+ if (p_len == inc_size)
+ {
+ inc_size *= 2;
+ p0 = xrealloc (p0, inc_size + 1);
+ p = memmove (p0 + p_len, p0, p_len + 1);
+
+ if (buf_size < (inc_size + 1) * 2)
+ {
+ size_t buf_offset = bufp - buf;
+ buf_size = (inc_size + 1) * 2;
+ buf = xrealloc (buf, buf_size);
+ buf_end = buf + buf_size;
+ bufp = buf + buf_offset;
+ }
+ }
+
+ bufp = mempcpy (bufp, 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)
+ if (buf_end - (p_len + 1) < bufp)
{
- fwrite (buf, z - buf, 1, stdout);
- z = buf;
+ fwrite (buf, bufp - buf, 1, stdout);
+ bufp = buf;
}
}
/* Write any remaining buffered output, and the terminator. */
- *z++ = *terminator;
- fwrite (buf, z - buf, 1, stdout);
+ *bufp++ = *terminator;
+ fwrite (buf, bufp - buf, 1, stdout);
IF_LINT (free (buf));
}
@@ -593,7 +625,8 @@ main (int argc, char **argv)
}
}
- if (first.precision == 0 && step.precision == 0 && last.precision == 0
+ if (first.precision == 0 && step.precision == 0
+ && (! isfinite (last.value) || last.precision == 0)
&& 0 <= first.value && step.value == 1 && 0 <= last.value
&& !equal_width && !format_str && strlen (separator) == 1)
{
@@ -601,7 +634,9 @@ main (int argc, char **argv)
char *s2;
if (asprintf (&s1, "%0.Lf", first.value) < 0)
xalloc_die ();
- if (asprintf (&s2, "%0.Lf", last.value) < 0)
+ if (! isfinite (last.value))
+ s2 = xstrdup ("inf"); /* Ensure "inf" is used. */
+ else if (asprintf (&s2, "%0.Lf", last.value) < 0)
xalloc_die ();
if (*s1 != '-' && *s2 != '-' && seq_fast (s1, s2))
diff --git a/tests/local.mk b/tests/local.mk
index 7b8c91edf..d60f6cfec 100644
--- a/tests/local.mk
+++ b/tests/local.mk
@@ -236,6 +236,7 @@ all_tests = \
tests/misc/test.pl \
tests/misc/seq.pl \
tests/misc/seq-long-double.sh \
+ tests/misc/seq-precision.sh \
tests/misc/head.pl \
tests/misc/head-elide-tail.pl \
tests/tail-2/tail-n0f.sh \
diff --git a/tests/misc/seq-precision.sh b/tests/misc/seq-precision.sh
new file mode 100755
index 000000000..9ba601446
--- /dev/null
+++ b/tests/misc/seq-precision.sh
@@ -0,0 +1,37 @@
+#!/bin/sh
+# Test for output with appropriate precision
+
+# Copyright (C) 2015 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/>.
+
+. "${srcdir=.}/tests/init.sh"; path_prepend_ ./src
+print_ver_ seq
+
+# Integer only. Before v8.24 this would switch output format
+seq 999999 inf | head -n2 > out || fail=1
+printf "%s\n" 999999 1000000 > exp || framework_failure_
+compare exp out || fail=1
+
+# Excercise buffer handling in non floating point output
+for i in $(seq 100); do
+ n1="$(printf '%*s' $i '' | tr ' ' 9)"
+ n2="1$(echo $n1 | tr 9 0)"
+
+ seq $n1 $n2 > out || fail=1
+ printf "%s\n" "$n1" "$n2" > exp || framework_failure_
+ compare exp out || fail=1
+done
+
+Exit $fail