[Top][All Lists]
[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
Re: "seq .1 .1" would mistakenly generate no output on FreeBSD 6.1
From: |
Paul Eggert |
Subject: |
Re: "seq .1 .1" would mistakenly generate no output on FreeBSD 6.1 |
Date: |
Sun, 18 Nov 2007 23:56:03 -0800 |
User-agent: |
Gnus/5.11 (Gnus v5.11) Emacs/22.1 (gnu/linux) |
Jim Meyering wrote:
> (gdb) p last
> $4 = 0.10000000000000000000135525271560688
> (gdb) p x
> $5 = 0.1000000000000000055511151231257827
That's odd, since X was set via this assignment:
x = first + i * step;
and I is zero, which means X should equal FIRST, and (with "seq .1 .1")
FIRST should equal LAST. All the values in question have the type
"long double" so there shouldn't be any rounding error here.
I suspect the problem came up because FreeBSD has what I like to call
"varying width" floating point. That is, "long double" sometimes has
a 53-bit fraction, and sometimes a 64-bit fraction, depending on which
temporary happens to hold the "long double" value. This would explain
the problem, since the closest approximation to 0.1 with a 53-bit
fraction (IEEE double) is exactly
0.1000000000000000055511151231257827021181583404541015625, and with a
64-bit fraction (Intel extended) it's exactly
0.1000000000000000000013552527156068805425093160010874271392822265625,
and these numbers match the values shown above (modulo the finite
print width of GDB).
"Varying width" floating point conforms to C89, but it's a real hassle
to code portably to. For example, if you have code like this:
long double x = (something);
long double y = x;
you cannot assume that y == x. Here's another, more-drastic example:
long double x = (something);
long double y = (something_else);
assert (x == y || x != y);
The assertion might fail, since the first use of X might have a 64-bit
fraction, but the second might have a 53-bit fraction.
I looked at the revised print_numbers function and found what I think
is one or two other instances of similar problems. I hope the
following code will have a better chance of surviving similar problems
in the future. (The proposed code is a tad shorter and avoids some
code duplication and IF_LINT stuff; that's a good sign...)
2007-11-18 Paul Eggert <address@hidden>
* src/seq.c (print_numbers): Rewrite in an attempt to avoid the
more-general rounding issues exposed by the previous patch.
diff --git a/src/seq.c b/src/seq.c
index eec5ed5..261a44b 100644
--- a/src/seq.c
+++ b/src/seq.c
@@ -238,24 +238,32 @@ static void
print_numbers (char const *fmt, struct layout layout,
long double first, long double step, long double last)
{
- long double i;
- long double x0 IF_LINT (= 0);
+ bool out_of_range = (step < 0 ? first < last : last < first);
- for (i = 0; /* empty */; i++)
+ if (! out_of_range)
{
- long double x = first + i * step;
+ long double x = first;
+ long double i;
- if (step < 0 ? x < last : last < x)
+ for (i = 1; ; i++)
{
- /* If we go one past the end, but that number prints as a
- value equal to "last", 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)
+ 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 = asprintf (&x_str, fmt, x);
@@ -269,34 +277,20 @@ print_numbers (char const *fmt, struct layout layout,
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);
- }
+ print_extra_number = !STREQ (x0_str, x_str);
free (x0_str);
}
free (x_str);
+ if (! print_extra_number)
+ break;
}
- /* With floating point arithmetic, we may well reach this point
- with i == 0 and first == last. E.g., ./seq .1 .1 on FreeBSD 6.1.
- Hence the first conjunct: don't break out of this loop when
- i == 0. *unless* first and last themselves are out of order,
- in which case we must print nothing, e.g. for ./seq -1 */
- if (i || (0 < step && last < first) || (step < 0 && first < last))
- break;
+ fputs (separator, stdout);
}
- if (i)
- fputs (separator, stdout);
- printf (fmt, x);
- x0 = x;
+ fputs (terminator, stdout);
}
-
- if (i)
- fputs (terminator, stdout);
}
/* Return the default format given FIRST, STEP, and LAST. */