bug-coreutils
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

'seq' improvements to support wider numbers


From: Paul Eggert
Subject: 'seq' improvements to support wider numbers
Date: Fri, 30 Jun 2006 17:19:31 -0700
User-agent: Gnus/5.1008 (Gnus v5.10.8) Emacs/21.4 (gnu/linux)

Fabio Spelta's bug report
<http://lists.gnu.org/archive/html/bug-coreutils/2006-06/msg00202.html>
prompted me to improve 'seq' so that it can handle larger integers.
After all, a limit of 999999 is pretty silly in this day and age.

I redid how the default format is calculated, and used 'long double'
internally, so that integers up to 2**53 are supported without
rounding on almost all modern platforms, and on x86 platforms it's
integers up to 2**64.  This should be enough for file size
calculations and the like.  Plus, you don't have to specify the -f
option for this sort of thing any more; the default format "just
works" for integers.

I also added support for %a, %A, %E, %F, and %G formats (might as
well...), and removed some obsolete cruft about the decimal point.

Here's what I installed into CVS coreutils.

Index: ChangeLog
===================================================================
RCS file: /fetish/cu/ChangeLog,v
retrieving revision 1.1768
diff -p -u -r1.1768 ChangeLog
--- ChangeLog   30 Jun 2006 10:31:42 -0000      1.1768
+++ ChangeLog   1 Jul 2006 00:04:44 -0000
@@ -1,3 +1,32 @@
+2006-06-30  Paul Eggert  <address@hidden>
+
+       * NEWS: seq now uses long double internally rather than double.
+       It now defaults to a minimal fixed point format if possible.
+       It lets you use %a, %A, %E, %F, %G.
+       * src/Makefile.am (seq_LDADD): Remove $(SEQ_LIBM); add $(POW_LIB).
+       * src/seq.c: Don't include <math.h> or <xstrtol.h>; no longer needed.
+       (isfinite) [!defined isfinite]: New macro.
+       (separator, terminator): Now points to const.
+       (first, step, last): Remove.
+       (usage): Update to match new behavior.
+       (struct operand, operand): New type.
+       (scan_arg): Renamed from scan_double_arg, since we no longer use double.
+       All uses changed.
+       Compute and return a value of type operand, not double.
+       (long_double_format): Renamed from valid_format, and now returns a
+       new format with an "L" added if needed, if the original format was
+       valid.  Allow %a, %A, %E, %F, and %G formats.
+       (print_numbers): Take numeric values as args rather than from globals.
+       Print long double, not double.
+       (get_width_format): Remove.
+       (get_default_format): New function.
+       (main): Implement new way of calculating default format.
+       Don't worry about locale's representation of the decimal point, since
+       the arguments are always processed in the C locale.
+       * tests/seq/basic (neg-2): Adjust to new default format.
+       (eq-wid-1, eq-wid-2): Resurrect these tests, since the new
+       implementation should do the right thing.
+
 2006-06-30  Jim Meyering  <address@hidden>
 
        * tests/stty/basic-1: Work around an intermittent test failure
Index: NEWS
===================================================================
RCS file: /fetish/cu/NEWS,v
retrieving revision 1.388
diff -p -u -r1.388 NEWS
--- NEWS        28 Jun 2006 23:49:58 -0000      1.388
+++ NEWS        1 Jul 2006 00:04:45 -0000
@@ -74,6 +74,17 @@ GNU coreutils NEWS                      
 
   rm no longer fails to remove an empty, unreadable directory
 
+  seq changes:
+
+    seq defaults to a minimal fixed point format that does not lose
+    information if seq's operands are all fixed point decimal numbers.
+    You no longer need the `-f%.f' in `seq -f%.f 1048575 1024 1050623',
+    for example, since the default format now has the same effect.
+
+    seq now lets you use %a, %A, %E, %F, and %G formats.
+
+    seq now uses long double internally rather than double.
+
   sort now reports incompatible options (e.g., -i and -n) rather than
   silently ignoring one of them.
 
Index: doc/ChangeLog
===================================================================
RCS file: /fetish/cu/doc/ChangeLog,v
retrieving revision 1.310
diff -p -u -r1.310 ChangeLog
--- doc/ChangeLog       28 Jun 2006 23:50:40 -0000      1.310
+++ doc/ChangeLog       1 Jul 2006 00:04:45 -0000
@@ -1,3 +1,12 @@
+2006-06-30  Paul Eggert  <address@hidden>
+
+       * coreutils.texi (seq invocation): seq now uses long double
+       internally rather than double.  It now defaults to a minimal fixed
+       point format if possible.  It lets you use %a, %A, %E, %F, %G.
+       Don't assume printf doesn't work for numbers that fit in 64 but
+       not 32 bits; typically they work these days.  Improve discussion
+       of large integers and update the rounding-error numbers.
+
 2006-06-28  Paul Eggert  <address@hidden>
 
        * coreutils.texi (sort invocation): 'sort +1 -2' is now supported
Index: doc/coreutils.texi
===================================================================
RCS file: /fetish/cu/doc/coreutils.texi,v
retrieving revision 1.334
diff -p -u -r1.334 coreutils.texi
--- doc/coreutils.texi  28 Jun 2006 23:50:21 -0000      1.334
+++ doc/coreutils.texi  1 Jul 2006 00:04:48 -0000
@@ -13539,9 +13539,16 @@ Options must precede operands.
 @opindex -f @var{format}
 @opindex address@hidden
 @cindex formatting of numbers in @command{seq}
-Print all numbers using @var{format}; default @samp{%g}.
address@hidden must contain exactly one of the floating point
-output formats @samp{%e}, @samp{%f}, or @samp{%g}.
+Print all numbers using @var{format}.
address@hidden must contain exactly one of the @samp{printf}-style
+floating point conversion specifications @samp{%a}, @samp{%e},
address@hidden, @samp{%g}, @samp{%A}, @samp{%E}, @samp{%F}, @samp{%G}.
+
+The default format is derived from @var{first}, @var{step}, and
address@hidden  If these all use a fixed point decimal representation,
+the default format is @address@hidden, where @var{p} is the minimum
+precision that can represent the output numbers exactly.  Otherwise,
+the default format is @samp{%g}.
 
 @item -s @var{string}
 @itemx address@hidden
@@ -13552,34 +13559,17 @@ The output always terminates with a newl
 @item -w
 @itemx --equal-width
 Print all numbers with the same width, by padding with leading zeroes.
address@hidden, @var{step}, and @var{last} should all use a fixed point
+decimal representation.
 (To have other kinds of padding, use @option{--format}).
 
 @end table
 
-If you want to use @command{seq} to print sequences of large integer values,
-don't use the default @samp{%g} format since it can result in
-loss of precision:
-
address@hidden
-$ seq 1000000 1000001
-1e+06
-1e+06
address@hidden example
-
-Instead, you can use the format, @samp{%1.f},
-to print large decimal numbers with no exponent and no decimal point.
-
address@hidden
-$ seq --format=%1.f 1000000 1000001
-1000000
-1000001
address@hidden example
-
-If you want hexadecimal output, you can use @command{printf}
+If you want hexadecimal integer output, you can use @command{printf}
 to perform the conversion:
 
 @example
-$ printf %x'\n' `seq -f %1.f 1048575 1024 1050623`
+$ printf '%x\n' `seq 1048575 1024 1050623`
 fffff
 1003ff
 1007ff
@@ -13589,55 +13579,49 @@ For very long lists of numbers, use xarg
 system limitations on the length of an argument list:
 
 @example
-$ seq -f %1.f 1000000 | xargs printf %x'\n' | tail -n 3
+$ seq 1000000 | xargs printf '%x\n' | tail -n 3
 f423e
 f423f
 f4240
 @end example
 
 To generate octal output, use the printf @code{%o} format instead
-of @code{%x}.  Note however that using printf might not work for numbers
-outside the usual 32-bit range:
-
address@hidden
-$ printf "%x\n" `seq -f %1.f 4294967295 4294967296`
-ffffffff
-bash: printf: 4294967296: Numerical result out of range
address@hidden example
+of @code{%x}.
 
 On most systems, seq can produce whole-number output for values up to
address@hidden, so here's a more general approach to base conversion that
-also happens to be more robust for such large numbers.  It works by
-using @code{bc} and setting its output radix variable, @var{obase},
-to @samp{16} in this case to produce hexadecimal output.
+at least @code{2^53}.  Larger integers are approximated.  The details
+differ depending on your floating-point implementation, but a common
+case is that @command{seq} works with integers through @code{2^64},
+and larger integers may not be numerically correct:
 
 @example
-$ (echo obase=16; seq -f %1.f 4294967295 4294967296)|bc
-FFFFFFFF
-100000000
+$ seq 18446744073709551616 1 18446744073709551618
+18446744073709551616
+18446744073709551616
+18446744073709551618
 @end example
 
-Be careful when using @command{seq} with a fractional @var{increment},
+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.3} printed as the last number in this example:
+see @code{0.000003} printed as the last number in this example:
 
 @example
-$ seq -s ' ' 0 .1 .3
-0 0.1 0.2
+$ seq -s ' ' 0 0.000001 0.000003
+0.000000 0.000001 0.000002
 @end example
 
-But that doesn't happen on most systems because @command{seq} is
+But that doesn't happen on many systems because @command{seq} is
 implemented using binary floating point arithmetic (via the C
address@hidden type)---which means some decimal numbers like @code{.1}
address@hidden double} type)---which means decimal fractions like 
@code{0.000001}
 cannot be represented exactly.  That in turn means some nonintuitive
-conditions like @address@hidden * 3 > .3}} will end up being true.
+conditions like @address@hidden * 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 .1 .31
-0 0.1 0.2 0.3
+$ 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
Index: lib/ChangeLog
===================================================================
RCS file: /fetish/cu/lib/ChangeLog,v
retrieving revision 1.1196
diff -p -u -r1.1196 ChangeLog
--- lib/ChangeLog       29 Jun 2006 21:45:51 -0000      1.1196
+++ lib/ChangeLog       1 Jul 2006 00:04:49 -0000
@@ -1,3 +1,11 @@
+2006-06-30  Paul Eggert  <address@hidden>
+
+       * xstrtod.c (XSTRTOD, DOUBLE): New macros, so that we can support
+       both double and long double versions.
+       (XSTRTOD): Renamed from xstrtod.  Use DOUBLE internally.
+       * xstrtold.c: New file.
+       * xstrtod.h (xstrtold): New decl.
+
 2006-06-29  Derek R. Price  <address@hidden>
 
        * strftime.c: Assume strftime exists.
Index: lib/xstrtod.c
===================================================================
RCS file: /fetish/cu/lib/xstrtod.c,v
retrieving revision 1.12
diff -p -u -r1.12 xstrtod.c
--- lib/xstrtod.c       15 Nov 2005 18:30:28 -0000      1.12
+++ lib/xstrtod.c       1 Jul 2006 00:04:49 -0000
@@ -29,17 +29,26 @@
 #include <limits.h>
 #include <stdio.h>
 
-/* An interface to strtod that encapsulates all the error checking
-   one should usually perform.  Like strtod, but upon successful
+#if LONG
+# define XSTRTOD xstrtold
+# define DOUBLE long double
+#else
+# define XSTRTOD xstrtod
+# define DOUBLE double
+#endif
+
+/* An interface to a string-to-floating-point conversion function that
+   encapsulates all the error checking one should usually perform.
+   Like strtod/strtold, but upon successful
    conversion put the result in *RESULT and return true.  Return
    false and don't modify *RESULT upon any failure.  CONVERT
    specifies the conversion function, e.g., strtod itself.  */
 
 bool
-xstrtod (char const *str, char const **ptr, double *result,
-        double (*convert) (char const *, char **))
+XSTRTOD (char const *str, char const **ptr, DOUBLE *result,
+        DOUBLE (*convert) (char const *, char **))
 {
-  double val;
+  DOUBLE val;
   char *terminator;
   bool ok = true;
 
@@ -51,9 +60,9 @@ xstrtod (char const *str, char const **p
     ok = false;
   else
     {
-      /* Allow underflow (in which case strtod returns zero),
+      /* Allow underflow (in which case CONVERT returns zero),
         but flag overflow as an error. */
-      if (val != 0.0 && errno == ERANGE)
+      if (val != 0 && errno == ERANGE)
        ok = false;
     }
 
Index: lib/xstrtod.h
===================================================================
RCS file: /fetish/cu/lib/xstrtod.h,v
retrieving revision 1.11
diff -p -u -r1.11 xstrtod.h
--- lib/xstrtod.h       14 May 2005 07:58:07 -0000      1.11
+++ lib/xstrtod.h       1 Jul 2006 00:04:49 -0000
@@ -25,5 +25,7 @@
 
 bool xstrtod (const char *str, const char **ptr, double *result,
              double (*convert) (char const *, char **));
+bool xstrtold (const char *str, const char **ptr, long double *result,
+              long double (*convert) (char const *, char **));
 
 #endif /* not XSTRTOD_H */
Index: m4/ChangeLog
===================================================================
RCS file: /fetish/cu/m4/ChangeLog,v
retrieving revision 1.833
diff -p -u -r1.833 ChangeLog
--- m4/ChangeLog        29 Jun 2006 21:45:51 -0000      1.833
+++ m4/ChangeLog        1 Jul 2006 00:04:50 -0000
@@ -1,3 +1,13 @@
+2006-06-30  Paul Eggert  <address@hidden>
+
+       * c-strtod.m4 (gl_C_STRTOLD): Add c-strtod.c to LIBSOURCES.
+       Require gl_USE_SYSTEM_EXTENSIONS, not gl_C_STRTOD, since we don't
+       want to require the building of c-strtod.o.
+       * lib-check.m4 (cu_LIB_CHECK): Remuve SEQ_LIBM, since seq no longer
+       needs -lm directly.
+       * prereq.m4 (gl_PREREQ): Require gl_C_STRTOD and gl_XSTRTOLD.
+       * xstrtod.m4 (gl_XSTRTOLD): New macro.
+
 2006-06-29  Derek R. Price  <address@hidden>
 
        * strftime.m4: Don't call AC_FUNC_STRFTIME.
Index: m4/c-strtod.m4
===================================================================
RCS file: /fetish/cu/m4/c-strtod.m4,v
retrieving revision 1.7
diff -p -u -r1.7 c-strtod.m4
--- m4/c-strtod.m4      28 May 2006 09:16:57 -0000      1.7
+++ m4/c-strtod.m4      1 Jul 2006 00:04:50 -0000
@@ -1,4 +1,4 @@
-# c-strtod.m4 serial 7
+# c-strtod.m4 serial 8
 
 # Copyright (C) 2004, 2005, 2006 Free Software Foundation, Inc.
 # This file is free software; the Free Software Foundation
@@ -45,11 +45,11 @@ AC_DEFUN([gl_C_STRTOD],
 
 AC_DEFUN([gl_C_STRTOLD],
 [
-  AC_LIBSOURCES([c-strtold.c, c-strtod.h])
+  AC_LIBSOURCES([c-strtod.c, c-strtold.c, c-strtod.h])
   AC_LIBOBJ([c-strtold])
 
   dnl Prerequisites of lib/c-strtold.c.
-  AC_REQUIRE([gl_C_STRTOD])
+  AC_REQUIRE([gl_USE_SYSTEM_EXTENSIONS])
   AC_REQUIRE([gl_C99_STRTOLD])
   :
 ])
Index: m4/lib-check.m4
===================================================================
RCS file: /fetish/cu/m4/lib-check.m4,v
retrieving revision 1.13
diff -p -u -r1.13 lib-check.m4
--- m4/lib-check.m4     14 Jul 2005 00:03:08 -0000      1.13
+++ m4/lib-check.m4     1 Jul 2006 00:04:50 -0000
@@ -1,9 +1,9 @@
-#serial 9
+#serial 10
 
 dnl Misc lib-related macros for coreutils.
 
 # Copyright (C) 1993, 1994, 1995, 1996, 1997, 2000, 2001, 2003, 2004,
-# 2005 Free Software Foundation, Inc.
+# 2005, 2006 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
@@ -30,20 +30,6 @@ AC_DEFUN([cu_LIB_CHECK],
   # m88k running dgux 5.4 needs this
   AC_CHECK_LIB(ldgc, main)
 
-  # Some programs need to link with -lm.  printf does if it uses
-  # lib/strtod.c which uses pow.  And seq uses the math functions,
-  # floor, modf, rint.
-
-  # Check for these math functions used by seq.
-  AC_SUBST([SEQ_LIBM])
-  cu_saved_libs=$LIBS
-  AC_SEARCH_LIBS([floor], [m])
-  AC_SEARCH_LIBS([modf], [m])
-  AC_SEARCH_LIBS([rint], [m])
-  AC_CHECK_FUNCS([floor modf rint])
-  test "X$LIBS" = "X$cu_saved_libs" || SEQ_LIBM=-lm
-  LIBS=$cu_saved_libs
-
   # The -lsun library is required for YP support on Irix-4.0.5 systems.
   # m88k/svr3 DolphinOS systems using YP need -lypsec for id.
   AC_SEARCH_LIBS(yp_match, [sun ypsec])
Index: m4/prereq.m4
===================================================================
RCS file: /fetish/cu/m4/prereq.m4,v
retrieving revision 1.124
diff -p -u -r1.124 prereq.m4
--- m4/prereq.m4        12 Mar 2006 08:06:49 -0000      1.124
+++ m4/prereq.m4        1 Jul 2006 00:04:50 -0000
@@ -1,4 +1,4 @@
-#serial 64
+#serial 65
 
 dnl We use gl_ for non Autoconf macros.
 m4_pattern_forbid([^gl_[ABCDEFGHIJKLMNOPQRSTUVXYZ]])dnl
@@ -36,6 +36,7 @@ AC_DEFUN([gl_PREREQ],
   AC_REQUIRE([gl_ALLOCSA])
   AC_REQUIRE([gl_BACKUPFILE])
   AC_REQUIRE([gl_BASENAME])
+  AC_REQUIRE([gl_C_STRTOD])
   AC_REQUIRE([gl_C_STRTOLD])
   AC_REQUIRE([gl_CANON_HOST])
   AC_REQUIRE([gl_CLOEXEC])
@@ -160,6 +161,7 @@ AC_DEFUN([gl_PREREQ],
   AC_REQUIRE([gl_XREADLINK])
   AC_REQUIRE([gl_XSTRTOD])
   AC_REQUIRE([gl_XSTRTOL])
+  AC_REQUIRE([gl_XSTRTOLD])
   AC_REQUIRE([gl_YESNO])
   AC_REQUIRE([AC_FUNC_CALLOC])
   AC_REQUIRE([gl_FUNC_GLIBC_UNLOCKED_IO])
Index: m4/xstrtod.m4
===================================================================
RCS file: /fetish/cu/m4/xstrtod.m4,v
retrieving revision 1.5
diff -p -u -r1.5 xstrtod.m4
--- m4/xstrtod.m4       22 Sep 2005 06:05:40 -0000      1.5
+++ m4/xstrtod.m4       1 Jul 2006 00:04:50 -0000
@@ -1,5 +1,5 @@
-#serial 4
-dnl Copyright (C) 2002, 2003, 2005 Free Software Foundation, Inc.
+#serial 5
+dnl Copyright (C) 2002, 2003, 2005, 2006 Free Software Foundation, Inc.
 dnl This file is free software; the Free Software Foundation
 dnl gives unlimited permission to copy and/or distribute it,
 dnl with or without modifications, as long as this notice is preserved.
@@ -10,3 +10,10 @@ AC_DEFUN([gl_XSTRTOD],
   AC_LIBSOURCES([xstrtod.c, xstrtod.h])
   AC_LIBOBJ([xstrtod])
 ])
+
+# Prerequisites of lib/xstrtold.c.
+AC_DEFUN([gl_XSTRTOLD],
+[
+  AC_LIBSOURCES([xstrtold.c, xstrtod.c, xstrtod.h])
+  AC_LIBOBJ([xstrtold])
+])
Index: src/Makefile.am
===================================================================
RCS file: /fetish/cu/src/Makefile.am,v
retrieving revision 1.69
diff -p -u -r1.69 Makefile.am
--- src/Makefile.am     27 Jun 2006 21:22:15 -0000      1.69
+++ src/Makefile.am     1 Jul 2006 00:04:50 -0000
@@ -88,8 +88,8 @@ touch_LDADD = $(LDADD) $(LIB_CLOCK_GETTI
 # If necessary, add -liconv to resolve use of iconv in lib/unicodeio.c.
 printf_LDADD = $(LDADD) $(POW_LIB) $(LIBICONV)
 
-# If necessary, add -lm to resolve use of floor, rint, modf.
-seq_LDADD = $(LDADD) $(SEQ_LIBM)
+# If necessary, add -lm to resolve use of pow in lib/strtod.c.
+seq_LDADD = $(LDADD) $(POW_LIB)
 
 # If necessary, add libraries to resolve the `pow' reference in lib/strtod.c
 # and the `nanosleep' reference in lib/xnanosleep.c.
Index: src/seq.c
===================================================================
RCS file: /fetish/cu/src/seq.c,v
retrieving revision 1.88
diff -p -u -r1.88 seq.c
--- src/seq.c   16 Jun 2005 21:33:43 -0000      1.88
+++ src/seq.c   1 Jul 2006 00:04:50 -0000
@@ -19,7 +19,6 @@
 
 #include <config.h>
 #include <getopt.h>
-#include <math.h>
 #include <stdio.h>
 #include <sys/types.h>
 
@@ -27,9 +26,14 @@
 #include "c-strtod.h"
 #include "error.h"
 #include "quote.h"
-#include "xstrtol.h"
 #include "xstrtod.h"
 
+/* Roll our own isfinite rather than using <math.h>, so that we don't
+   have to worry about linking -lm just for isfinite.  */
+#ifndef isfinite
+# define isfinite(x) ((x) * 0 == 0)
+#endif
+
 /* The official name of this program (e.g., no `g' prefix).  */
 #define PROGRAM_NAME "seq"
 
@@ -42,24 +46,12 @@ static bool equal_width;
 char *program_name;
 
 /* The string used to separate two numbers.  */
-static char *separator;
+static char const *separator;
 
 /* The string output after all numbers have been output.
    Usually "\n" or "\0".  */
 /* FIXME: make this an option.  */
-static char *terminator = "\n";
-
-/* The representation of the decimal point in the current locale.  */
-static char decimal_point;
-
-/* The starting number.  */
-static double first;
-
-/* The increment.  */
-static double step = 1.0;
-
-/* The last number.  */
-static double last;
+static char const terminator[] = "\n";
 
 static struct option const long_options[] =
 {
@@ -87,7 +79,7 @@ Usage: %s [OPTION]... LAST\n\
       fputs (_("\
 Print numbers from FIRST to LAST, in steps of INCREMENT.\n\
 \n\
-  -f, --format=FORMAT      use printf style floating-point FORMAT (default: 
%g)\n\
+  -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\
 "), stdout);
@@ -100,92 +92,126 @@ omitted INCREMENT defaults to 1 even whe
 FIRST, INCREMENT, and LAST are interpreted as floating point values.\n\
 INCREMENT is usually positive if FIRST is smaller than LAST, and\n\
 INCREMENT is usually negative if FIRST is greater than LAST.\n\
-When given, the FORMAT argument must contain exactly one of\n\
-the printf-style, floating point output formats %e, %f, %g\n\
+FORMAT must be suitable for printing one argument of type `double';\n\
+it defaults to %.PRECf if FIRST, INCREMENT, and LAST are all fixed point\n\
+decimal numbers with maximum precision PREC, and to %g otherwise.\n\
 "), stdout);
       printf (_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT);
     }
   exit (status);
 }
 
-/* Read a double value from the command line.
+/* A command-line operand.  */
+struct operand
+{
+  /* Its value, converted to 'long double'.  */
+  long double value;
+
+  /* Its print width, if it were printed out in a form similar to its
+     input form.  An input like "-.1" is treated like "-0.1", and an
+     input like "1." is treated like "1", but otherwise widths are
+     left alone.  */
+  size_t width;
+
+  /* Number of digits after the decimal point, or INT_MAX if the
+     number can't easily be expressed as a fixed-point number.  */
+  int precision;
+};
+typedef struct operand operand;
+
+/* Read a long double value from the command line.
    Return if the string is correct else signal error.  */
 
-static double
-scan_double_arg (const char *arg)
+static operand
+scan_arg (const char *arg)
 {
-  double ret_val;
+  operand ret;
 
-  if (! xstrtod (arg, NULL, &ret_val, c_strtod))
+  if (! xstrtold (arg, NULL, &ret.value, c_strtold))
     {
       error (0, 0, _("invalid floating point argument: %s"), arg);
       usage (EXIT_FAILURE);
     }
 
-  return ret_val;
-}
-
-/* Return true if the format string is valid for a single `double'
-   argument.  */
-
-static bool
-valid_format (const char *fmt)
-{
-  while (*fmt != '\0')
-    {
-      if (*fmt == '%')
-       {
-         fmt++;
-         if (*fmt != '%')
-           break;
-       }
-
-      fmt++;
-    }
-  if (*fmt == '\0')
-    return false;
+  ret.width = strlen (arg);
+  ret.precision = INT_MAX;
 
-  fmt += strspn (fmt, "-+#0 '");
-  if (ISDIGIT (*fmt) || *fmt == '.')
+  if (! arg[strcspn (arg, "eExX")] && isfinite (ret.value))
     {
-      fmt += strspn (fmt, "0123456789");
-
-      if (*fmt == '.')
+      char const *decimal_point = strchr (arg, '.');
+      if (! decimal_point)
+       ret.precision = 0;
+      else
        {
-         ++fmt;
-         fmt += strspn (fmt, "0123456789");
+         size_t fraction_len = strlen (decimal_point + 1);
+         if (fraction_len <= INT_MAX)
+           ret.precision = fraction_len;
+         ret.width += (fraction_len == 0
+                       ? -1
+                       : (decimal_point == arg
+                          || ! ISDIGIT (decimal_point[-1])));
        }
     }
 
-  if (!(*fmt == 'e' || *fmt == 'f' || *fmt == 'g'))
-    return false;
+  return ret;
+}
 
-  fmt++;
-  while (*fmt != '\0')
-    {
-      if (*fmt == '%')
-       {
-         fmt++;
-         if (*fmt != '%')
-           return false;
-       }
+/* If FORMAT is a valid printf format for a double argument, return
+   its long double equivalent, possibly allocated from dynamic
+   storage; otherwise, return NULL.  */
 
-      fmt++;
-    }
+static char const *
+long_double_format (char const *fmt)
+{
+  size_t i;
+  size_t prefix_len;
+  bool has_L;
+
+  for (i = 0; ! (fmt[i] == '%' && fmt[i + 1] != '%'); i++)
+    if (! fmt[i])
+      return NULL;
+
+  i++;
+  i += strspn (fmt + i, "-+#0 '");
+  i += strspn (fmt + i, "0123456789");
+  if (fmt[i] == '.')
+    {
+      i++;
+      i += strspn (fmt + i, "0123456789");
+    }
+
+  prefix_len = 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])
+      {
+       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);
+       return ldfmt;
+      }
 
-  return true;
+  return NULL;
 }
 
 /* Actually print the sequence of numbers in the specified range, with the
    given or default stepping and format.  */
+
 static void
-print_numbers (const char *fmt)
+print_numbers (char const *fmt,
+              long double first, long double step, long double last)
 {
-  double i;
+  long double i;
 
   for (i = 0; /* empty */; i++)
     {
-      double x = first + i * step;
+      long double x = first + i * step;
       if (step < 0 ? x < last : last < x)
        break;
       if (i)
@@ -197,106 +223,52 @@ print_numbers (const char *fmt)
     fputs (terminator, stdout);
 }
 
-#if HAVE_RINT && HAVE_MODF && HAVE_FLOOR
-
-/* Return a printf-style format string with which all selected numbers
-   will format to strings of the same width.  */
-
-static char *
-get_width_format (void)
+/* Return the default format given FIRST, STEP, and LAST.  */
+static char const *
+get_default_format (operand first, operand step, operand last)
 {
-  static char buffer[256];
-  int full_width;
-  int frac_width;
-  int width1, width2;
-  double max_val;
-  double min_val;
-  double temp;
-
-  if (first > last)
-    {
-      min_val = first - step * floor ((first - last) / step);
-      max_val = first;
-    }
-  else
-    {
-      min_val = first;
-      max_val = first + step * floor ((last - first) / step);
-    }
+  static char format_buf[sizeof "%0.Lf" + 2 * INT_STRLEN_BOUND (int)];
 
-  sprintf (buffer, "%g", rint (max_val));
-  if (buffer[strspn (buffer, "-0123456789")] != '\0')
-    return "%g";
-  width1 = strlen (buffer);
+  int prec = MAX (first.precision, step.precision);
 
-  if (min_val < 0.0)
+  if (prec != INT_MAX && last.precision != INT_MAX)
     {
-      double int_min_val = rint (min_val);
-      sprintf (buffer, "%g", int_min_val);
-      if (buffer[strspn (buffer, "-0123456789")] != '\0')
-       return "%g";
-      /* On some systems, `seq -w -.1 .1 .1' results in buffer being `-0'.
-        On others, it is just `0'.  The former results in better output.  */
-      width2 = (int_min_val == 0 ? 2 : strlen (buffer));
-
-      width1 = width1 > width2 ? width1 : width2;
-    }
-  full_width = width1;
-
-  sprintf (buffer, "%g", 1.0 + modf (fabs (min_val), &temp));
-  width1 = strlen (buffer);
-  if (width1 == 1)
-    width1 = 0;
-  else
-    {
-      if (buffer[0] != '1'
-         || buffer[1] != decimal_point
-         || buffer[2 + strspn (&buffer[2], "0123456789")] != '\0')
-       return "%g";
-      width1 -= 2;
-    }
-
-  sprintf (buffer, "%g", 1.0 + modf (fabs (step), &temp));
-  width2 = strlen (buffer);
-  if (width2 == 1)
-    width2 = 0;
-  else
-    {
-      if (buffer[0] != '1'
-         || buffer[1] != decimal_point
-         || buffer[2 + strspn (&buffer[2], "0123456789")] != '\0')
-       return "%g";
-      width2 -= 2;
+      if (equal_width)
+       {
+         size_t first_width = first.width + (prec - first.precision);
+         size_t last_width = last.width + (prec - last.precision);
+         if (first.width <= first_width
+             && (last.width < last_width) == (prec < last.precision))
+           {
+             size_t width = MAX (first_width, last_width);
+             if (width <= INT_MAX)
+               {
+                 int w = width;
+                 sprintf (format_buf, "%%0%d.%dLf", w, prec);
+                 return format_buf;
+               }
+           }
+       }
+      else
+       {
+         sprintf (format_buf, "%%.%dLf", prec);
+         return format_buf;
+       }
     }
-  frac_width = width1 > width2 ? width1 : width2;
 
-  if (frac_width)
-    sprintf (buffer, "%%0%d.%df", full_width + 1 + frac_width, frac_width);
-  else
-    sprintf (buffer, "%%0%dg", full_width);
-
-  return buffer;
+  return "%Lg";
 }
 
-#else  /* one of the math functions rint, modf, floor is missing.  */
-
-static char *
-get_width_format (void)
-{
-  /* We cannot compute the needed information to determine the correct
-     answer.  So we simply return a value that works for all cases.  */
-  return "%g";
-}
-
-#endif
-
 int
 main (int argc, char **argv)
 {
   int optc;
+  operand first = { 1, 1, 0 };
+  operand step = { 1, 1, 0 };
+  operand last;
 
   /* The printf(3) format used for output.  */
-  char *format_str = NULL;
+  char const *format_str = NULL;
 
   initialize_main (&argc, &argv);
   program_name = argv[0];
@@ -308,19 +280,6 @@ main (int argc, char **argv)
 
   equal_width = false;
   separator = "\n";
-  first = 1.0;
-
-  /* Get locale's representation of the decimal point.  */
-  {
-    struct lconv const *locale = localeconv ();
-
-    /* If the locale doesn't define a decimal point, or if the decimal
-       point is multibyte, use the C locale's decimal point.  FIXME:
-       add support for multibyte decimal points.  */
-    decimal_point = locale->decimal_point[0];
-    if (! decimal_point || locale->decimal_point[1])
-      decimal_point = '.';
-  }
 
   /* We have to handle negative numbers in the command line but this
      conflicts with the command line arguments.  So explicitly check first
@@ -328,8 +287,7 @@ main (int argc, char **argv)
   while (optind < argc)
     {
       if (argv[optind][0] == '-'
-         && ((optc = argv[optind][1]) == decimal_point
-             || ISDIGIT (optc)))
+         && ((optc = argv[optind][1]) == '.' || ISDIGIT (optc)))
        {
          /* means negative number */
          break;
@@ -374,23 +332,28 @@ main (int argc, char **argv)
       usage (EXIT_FAILURE);
     }
 
-  if (format_str && !valid_format (format_str))
+  if (format_str)
     {
-      error (0, 0, _("invalid format string: %s"), quote (format_str));
-      usage (EXIT_FAILURE);
+      char const *f = long_double_format (format_str);
+      if (! f)
+       {
+         error (0, 0, _("invalid format string: %s"), quote (format_str));
+         usage (EXIT_FAILURE);
+       }
+      format_str = f;
     }
 
-  last = scan_double_arg (argv[optind++]);
+  last = scan_arg (argv[optind++]);
 
   if (optind < argc)
     {
       first = last;
-      last = scan_double_arg (argv[optind++]);
+      last = scan_arg (argv[optind++]);
 
       if (optind < argc)
        {
          step = last;
-         last = scan_double_arg (argv[optind++]);
+         last = scan_arg (argv[optind++]);
        }
     }
 
@@ -402,14 +365,9 @@ format string may not be specified when 
     }
 
   if (format_str == NULL)
-    {
-      if (equal_width)
-       format_str = get_width_format ();
-      else
-       format_str = "%g";
-    }
+    format_str = get_default_format (first, step, last);
 
-  print_numbers (format_str);
+  print_numbers (format_str, first.value, step.value, last.value);
 
   exit (EXIT_SUCCESS);
 }
Index: tests/seq/basic
===================================================================
RCS file: /fetish/cu/tests/seq/basic,v
retrieving revision 1.11
diff -p -u -r1.11 basic
--- tests/seq/basic     24 Sep 2005 10:06:29 -0000      1.11
+++ tests/seq/basic     1 Jul 2006 00:04:50 -0000
@@ -27,18 +27,15 @@ my @Tests =
    ['onearg-1',        qw(10),         {OUT => [(1..10)]}],
    ['onearg-2',        qw(-1)],
    ['neg-1',   qw(-10 10 10),  {OUT => [qw(-10 0 10)]}],
-   ['neg-2',   qw(-.1 .1 .1),  {OUT => [qw(-0.1 0 0.1)]}],
+   ['neg-2',   qw(-.1 .1 .1),  {OUT => [qw(-0.1 0.0 0.1)]}],
    ['neg-3',   qw(1 -1 0),     {OUT => [qw(1 0)]}],
    ['neg-4',   qw(1 -1 -1),    {OUT => [qw(1 0 -1)]}],
 
-   # Disable these equal-width tests for now.
-   # They fail with non-gcc compilers and some combinations
-   # of options and libraries on Solaris systems.
-   # ['eq-wid-1',      qw(-w 1 -1 -1), {OUT => [qw(01 00 -1)]}],
+   ['eq-wid-1',        qw(-w 1 -1 -1), {OUT => [qw(01 00 -1)]}],
 
    # Prior to 2.0g, this test would fail on e.g., HPUX systems
    # because it'd end up using %3.1f as the format instead of %4.1f.
-   # ['eq-wid-2',      qw(-w -.1 .1 .1),{OUT => [qw(-0.1 00.0 00.1)]}],
+   ['eq-wid-2',        qw(-w -.1 .1 .1),{OUT => [qw(-0.1 00.0 00.1)]}],
 
    # Prior to coreutils-4.5.11, some of these were not accepted.
    ['fmt-1',   qw(-f %2.1f 1.5 .5 2),{OUT => [qw(1.5 2.0)]}],
--- /dev/null   2005-09-24 22:00:15.000000000 -0700
+++ lib/xstrtold.c      2006-06-30 10:31:57.000000000 -0700
@@ -0,0 +1,2 @@
+#define LONG 1
+#include "xstrtod.c"




reply via email to

[Prev in Thread] Current Thread [Next in Thread]