seq no longer mishandles cases like "seq 0 0.000001 0.000003",
authorPaul Eggert <eggert@cs.ucla.edu>
Sat, 23 Jun 2007 07:27:11 +0000 (09:27 +0200)
committerJim Meyering <jim@meyering.net>
Sat, 23 Jun 2007 07:43:59 +0000 (09:43 +0200)
where it would not print the desired last number.
* doc/coreutils.texi (seq invocation): Remove advice about workaround
for seq off-by-one problem, since the bug is fixed now.  Replace
it with more-generic advice about rounding errors.
* src/seq.c (long_double_format, print_numbers):
New arg NUMERIC_FORMAT.  All uses changed.

ChangeLog
NEWS
doc/coreutils.texi
src/seq.c

index d2cfd36..aab0bc2 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,13 @@
+2007-06-22  Paul Eggert  <eggert@cs.ucla.edu>
+
+       * NEWS: seq no longer mishandles obvious cases like
+       "seq 0 0.000001 0.000003" by omitting the last output number.
+       * doc/coreutils.texi (seq invocation): Remove advice about workaround
+       for seq off-by-one problem, since the bug is fixed now.  Replace
+       it with more-generic advice about rounding errors.
+       * src/seq.c (long_double_format, print_numbers):
+       New arg NUMERIC_FORMAT.  All uses changed.
+
 2007-06-22  Pádraig Brady  <P@draigBrady.com>  (trivial change)
 
        * tests/seq/basic: Add test cases for seq off-by-one problem.
diff --git a/NEWS b/NEWS
index c587fe7..a588896 100644 (file)
--- a/NEWS
+++ b/NEWS
@@ -48,6 +48,9 @@ GNU coreutils NEWS                                    -*- outline -*-
   ln=target attribute) would mistakenly output the string "target"
   before the name of each symlink.  [introduced in coreutils-6.0]
 
+  seq no longer mishandles obvious cases like "seq 0 0.000001 0.000003",
+  so workarounds like "seq 0 0.000001 0.0000031" are no longer needed.
+
   split --line-bytes=N (-C N) no longer creates an empty file
   [this bug is present at least as far back as textutils-1.22 (Jan, 1997)]
 
index 7290ab2..42558a3 100644 (file)
@@ -14052,34 +14052,16 @@ $ seq 18446744073709551616 1 18446744073709551618
 18446744073709551618
 @end example
 
-Be careful when using @command{seq} with a fractional @var{increment};
-otherwise you may see surprising results.  Most people would expect to
-see @code{0.000003} printed as the last number in this example:
+Be careful when using @command{seq} with outlandish values: otherwise
+you may see surprising results, as @command{seq} uses floating point
+internally.  For example, on the x86 platform, where the internal
+representation uses a 64-bit fraction, the command:
 
 @example
-$ seq -s ' ' 0 0.000001 0.000003
-0.000000 0.000001 0.000002
+seq 1 0.0000000000000000001 1.0000000000000000009
 @end example
 
-But that doesn't happen on many systems because @command{seq} is
-implemented using binary floating point arithmetic (via the C
-@code{long double} type)---which means decimal fractions like @code{0.000001}
-cannot be represented exactly.  That in turn means some nonintuitive
-conditions like @w{@code{0.000001 * 3 > 0.000003}} will end up being true.
-
-To work around that in the above example, use a slightly larger number as
-the @var{last} value:
-
-@example
-$ seq -s ' ' 0 0.000001 0.0000031
-0.000000 0.000001 0.000002 0.000003
-@end example
-
-In general, when using an @var{increment} with a fractional part, where
-(@var{last} - @var{first}) / @var{increment} is (mathematically) a whole
-number, specify a slightly larger (or smaller, if @var{increment} is negative)
-value for @var{last} to ensure that @var{last} is the final value printed
-by seq.
+outputs 1.0000000000000000007 twice and skips 1.0000000000000000008.
 
 @exitstatus
 
index c59c6b5..d5d5c53 100644 (file)
--- a/src/seq.c
+++ b/src/seq.c
@@ -1,5 +1,5 @@
 /* seq - print sequence of numbers to standard output.
-   Copyright (C) 1994-2006 Free Software Foundation, Inc.
+   Copyright (C) 1994-2007 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
@@ -210,15 +210,53 @@ print_numbers (char const *fmt,
               long double first, long double step, long double last)
 {
   long double i;
+  long double x0 IF_LINT (= 0);
 
   for (i = 0; /* empty */; i++)
     {
       long double x = first + i * step;
+
       if (step < 0 ? x < last : last < x)
-       break;
+       {
+         /* If we go one past the end, but that number prints the
+            same way "last" does, and prints differently from the
+            previous number, then print "last".  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.  */
+
+         if (i)
+           {
+             char *x_str = NULL;
+             char *last_str = NULL;
+             if (asprintf (&x_str, fmt, x) < 0
+                 || asprintf (&last_str, fmt, last) < 0)
+               xalloc_die ();
+
+             if (STREQ (x_str, last_str))
+               {
+                 char *x0_str = NULL;
+                 if (asprintf (&x0_str, fmt, x0) < 0)
+                   xalloc_die ();
+                 if (!STREQ (x0_str, x_str))
+                   {
+                     fputs (separator, stdout);
+                     fputs (x_str, stdout);
+                   }
+                 free (x0_str);
+               }
+
+             free (x_str);
+             free (last_str);
+           }
+
+         break;
+       }
+
       if (i)
        fputs (separator, stdout);
       printf (fmt, x);
+      x0 = x;
     }
 
   if (i)