From a860ca32b0cadfff722f7639b2158a59be7bd346 Mon Sep 17 00:00:00 2001 From: Paul Eggert Date: Sat, 3 Nov 2007 01:10:59 -0700 Subject: Fix bug with "seq 10.8 0.1 10.95", plus another bug with %% in format. * NEWS: Mention the %%-in-format bug fix. * src/seq.c (struct layout): New type. (long_double_format): New arg LAYOUT. Fill it in. Fix mishandling of %% in formats. (print_numbers): New arg LAYOUT. Don't convert LAST to output format when deciding whether to go slightly past LAST. Instead, convert X to output format and back. This fixes a bug reported by Andreas Schwab in where "seq 10.8 0.1 10.95" would output 11.0 on platforms where 10.95 rounds to a value that prints as 11.0 when only one digit past the decimal point is asked for. (main): Compute layout, for benefit of print_numbers. * tests/misc/seq (float-3): Undo previous change, since the bug should be fixed now. (fmt-b): New test, for the %% bug. --- ChangeLog | 21 +++++++++++++++++++ NEWS | 3 +++ src/seq.c | 64 ++++++++++++++++++++++++++++++++++++++-------------------- tests/misc/seq | 3 ++- 4 files changed, 68 insertions(+), 23 deletions(-) diff --git a/ChangeLog b/ChangeLog index 052e13c16..ed47c2446 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,24 @@ +2007-11-03 Paul Eggert + + Fix bug with "seq 10.8 0.1 10.95", plus another bug with %% in format. + + * NEWS: Mention the %%-in-format bug fix. + * src/seq.c (struct layout): New type. + (long_double_format): New arg LAYOUT. Fill it in. Fix mishandling + of %% in formats. + (print_numbers): New arg LAYOUT. Don't convert LAST to output format + when deciding whether to go slightly past LAST. Instead, convert + X to output format and back. This fixes a bug reported by + Andreas Schwab in + + where "seq 10.8 0.1 10.95" would output 11.0 on platforms where + 10.95 rounds to a value that prints as 11.0 when only one digit + past the decimal point is asked for. + (main): Compute layout, for benefit of print_numbers. + * tests/misc/seq (float-3): Undo previous change, since the bug + should be fixed now. + (fmt-b): New test, for the %% bug. + 2007-11-01 Jim Meyering * tests/misc/printf-surprise: Correct sed transform. diff --git a/NEWS b/NEWS index 0ccfc49fa..a8434c54c 100644 --- a/NEWS +++ b/NEWS @@ -139,6 +139,9 @@ GNU coreutils NEWS -*- outline -*- 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. + seq would mistakenly reject some valid format strings containing %%, + and would mistakenly accept some invalid ones. e.g., %g%% and %%g, resp. + Obsolete sort usage with an invalid ordering-option character, e.g., "env _POSIX2_VERSION=199209 sort +1x" no longer makes sort free an invalid pointer [introduced in coreutils-6.5] diff --git a/src/seq.c b/src/seq.c index d9420abd1..d7d2521b1 100644 --- a/src/seq.c +++ b/src/seq.c @@ -120,6 +120,14 @@ struct operand }; 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. */ @@ -171,17 +179,22 @@ scan_arg (const char *arg) /* If FORMAT is a valid printf format for a double argument, return its long double equivalent, possibly allocated from dynamic - storage; otherwise, return NULL. */ + storage, and store into *LAYOUT a description of the output layout; + otherwise, return NULL. */ static char const * -long_double_format (char const *fmt) +long_double_format (char const *fmt, struct layout *layout) { size_t i; - size_t prefix_len; + 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++) - if (! fmt[i]) + for (i = 0; ! (fmt[i] == '%' && fmt[i + 1] != '%'); i += (fmt[i] == '%') + 1) + if (fmt[i]) + prefix_len++; + else return NULL; i++; @@ -193,20 +206,25 @@ long_double_format (char const *fmt) i += strspn (fmt + i, "0123456789"); } - prefix_len = i; + length_modifier_offset = i; has_L = (fmt[i] == 'L'); i += has_L; if (! strchr ("efgaEFGA", fmt[i])) return NULL; - for (i++; ! (fmt[i] == '%' && fmt[i + 1] != '%'); i++) - if (! fmt[i]) + for (i++; ! (fmt[i] == '%' && fmt[i + 1] != '%'); i += (fmt[i] == '%') + 1) + if (fmt[i]) + suffix_len++; + else { size_t format_size = i + 1; char *ldfmt = xmalloc (format_size + 1); - memcpy (ldfmt, fmt, prefix_len); - ldfmt[prefix_len] = 'L'; - strcpy (ldfmt + prefix_len + 1, fmt + prefix_len + has_L); + 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; } @@ -217,7 +235,7 @@ long_double_format (char const *fmt) given or default stepping and format. */ static void -print_numbers (char const *fmt, +print_numbers (char const *fmt, struct layout layout, long double first, long double step, long double last) { long double i; @@ -229,8 +247,8 @@ print_numbers (char const *fmt, if (step < 0 ? x < last : last < x) { - /* If we go one past the end, but that number prints the - same way "last" does, and prints differently from the + /* 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 @@ -238,13 +256,15 @@ print_numbers (char const *fmt, if (i) { - char *x_str = NULL; - char *last_str = NULL; - if (asprintf (&x_str, fmt, x) < 0 - || asprintf (&last_str, fmt, last) < 0) + long double x_val; + char *x_str; + int x_strlen = asprintf (&x_str, fmt, x); + if (x_strlen < 0) xalloc_die (); + x_str[x_strlen - layout.suffix_len] = '\0'; - if (STREQ (x_str, last_str)) + 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) @@ -258,7 +278,6 @@ print_numbers (char const *fmt, } free (x_str); - free (last_str); } break; @@ -317,6 +336,7 @@ main (int argc, char **argv) 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; @@ -385,7 +405,7 @@ main (int argc, char **argv) if (format_str) { - char const *f = long_double_format (format_str); + char const *f = long_double_format (format_str, &layout); if (! f) { error (0, 0, _("invalid format string: %s"), quote (format_str)); @@ -418,7 +438,7 @@ format string may not be specified when printing equal width strings")); if (format_str == NULL) format_str = get_default_format (first, step, last); - print_numbers (format_str, first.value, step.value, last.value); + print_numbers (format_str, layout, first.value, step.value, last.value); exit (EXIT_SUCCESS); } diff --git a/tests/misc/seq b/tests/misc/seq index 1f3ec06c1..73f41336d 100755 --- a/tests/misc/seq +++ b/tests/misc/seq @@ -47,7 +47,7 @@ my @Tests = ['float-1', qw(0.8 0.1 0.9), {OUT => [qw(0.8 0.9)]}], ['float-2', qw(0.1 0.99 1.99), {OUT => [qw(0.10 1.09)]}], - ['float-3', qw(10.8 0.1 10.94), {OUT => [qw(10.8 10.9)]}], + ['float-3', qw(10.8 0.1 10.95), {OUT => [qw(10.8 10.9)]}], ['float-4', qw(0.1 -0.1 -0.2), {OUT => [qw(0.1 0.0 -0.1 -0.2)]}, {OUT_SUBST => 's,^-0\.0$,0.0,'}, ], @@ -79,6 +79,7 @@ my @Tests = ['fmt-8', qw(-f %0+.0f 1 2), {OUT => [qw(+1 +2)]}], ['fmt-9', '-f "% -3.0f"', qw(-1 0), {OUT => ['-1 ', ' 0 ']}], ['fmt-a', '-f "% -.0f"',qw(-1 0), {OUT => ['-1', ' 0']}], + ['fmt-b', qw(-f %%%g%% 1), {OUT => ['%1%']}], ); # Append a newline to each entry in the OUT array. -- cgit v1.2.3-54-g00ecf