6f1cbc77afa3d555fa4ccc41258ed4278c2a4622
[platform/upstream/coreutils.git] / src / seq.c
1 /* seq - print sequence of numbers to standard output.
2    Copyright (C) 1994-2003 Free Software Foundation, Inc.
3
4    This program is free software; you can redistribute it and/or modify
5    it under the terms of the GNU General Public License as published by
6    the Free Software Foundation; either version 2, or (at your option)
7    any later version.
8
9    This program is distributed in the hope that it will be useful,
10    but WITHOUT ANY WARRANTY; without even the implied warranty of
11    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12    GNU General Public License for more details.
13
14    You should have received a copy of the GNU General Public License
15    along with this program; if not, write to the Free Software Foundation,
16    Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.  */
17
18 /* Written by Ulrich Drepper.  */
19
20 #include <config.h>
21 #include <getopt.h>
22 #include <math.h>
23 #include <stdio.h>
24 #include <sys/types.h>
25
26 #include "system.h"
27 #include "error.h"
28 #include "xstrtol.h"
29 #include "xstrtod.h"
30
31 /* The official name of this program (e.g., no `g' prefix).  */
32 #define PROGRAM_NAME "seq"
33
34 #define WRITTEN_BY _("Written by Ulrich Drepper.")
35
36 /* If nonzero print all number with equal width.  */
37 static int equal_width;
38
39 /* The name that this program was run with.  */
40 char *program_name;
41
42 /* The string used to separate two numbers.  */
43 static char *separator;
44
45 /* The string output after all numbers have been output.
46    Usually "\n" or "\0".  */
47 /* FIXME: make this an option.  */
48 static char *terminator = "\n";
49
50 /* The representation of the decimal point in the current locale.
51    Always "." if the localeconv function is not supported.  */
52 static char *decimal_point = ".";
53
54 /* The starting number.  */
55 static double first;
56
57 /* The increment.  */
58 static double step = 1.0;
59
60 /* The last number.  */
61 static double last;
62
63 static struct option const long_options[] =
64 {
65   { "equal-width", no_argument, NULL, 'w'},
66   { "format", required_argument, NULL, 'f'},
67   { "separator", required_argument, NULL, 's'},
68   {GETOPT_HELP_OPTION_DECL},
69   {GETOPT_VERSION_OPTION_DECL},
70   { NULL, 0, NULL, 0}
71 };
72
73 void
74 usage (int status)
75 {
76   if (status != 0)
77     fprintf (stderr, _("Try `%s --help' for more information.\n"),
78              program_name);
79   else
80     {
81       printf (_("\
82 Usage: %s [OPTION]... LAST\n\
83   or:  %s [OPTION]... FIRST LAST\n\
84   or:  %s [OPTION]... FIRST INCREMENT LAST\n\
85 "), program_name, program_name, program_name);
86       fputs (_("\
87 Print numbers from FIRST to LAST, in steps of INCREMENT.\n\
88 \n\
89   -f, --format=FORMAT      use printf style floating-point FORMAT (default: %g)\n\
90   -s, --separator=STRING   use STRING to separate numbers (default: \\n)\n\
91   -w, --equal-width        equalize width by padding with leading zeroes\n\
92 "), stdout);
93       fputs (HELP_OPTION_DESCRIPTION, stdout);
94       fputs (VERSION_OPTION_DESCRIPTION, stdout);
95       fputs (_("\
96 \n\
97 If FIRST or INCREMENT is omitted, it defaults to 1.\n\
98 FIRST, INCREMENT, and LAST are interpreted as floating point values.\n\
99 INCREMENT should be positive if FIRST is smaller than LAST, and negative\n\
100 otherwise.  When given, the FORMAT argument must contain exactly one of\n\
101 the printf-style, floating point output formats %e, %f, %g\n\
102 "), stdout);
103       printf (_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT);
104     }
105   exit (status);
106 }
107
108 /* Read a double value from the command line.
109    Return if the string is correct else signal error.  */
110
111 static double
112 scan_double_arg (const char *arg)
113 {
114   double ret_val;
115
116   if (xstrtod (arg, NULL, &ret_val))
117     {
118       error (0, 0, _("invalid floating point argument: %s"), arg);
119       usage (EXIT_FAILURE);
120     }
121
122   return ret_val;
123 }
124
125 /* Check whether the format string is valid for a single `double'
126    argument.  Return 0 if not, 1 if correct. */
127
128 static int
129 valid_format (const char *fmt)
130 {
131   while (*fmt != '\0')
132     {
133       if (*fmt == '%')
134         {
135           fmt++;
136           if (*fmt != '%')
137             break;
138         }
139
140       fmt++;
141     }
142   if (*fmt == '\0')
143     return 0;
144
145   fmt += strspn (fmt, "-+#0 '");
146   if (ISDIGIT (*fmt) || *fmt == '.')
147     {
148       fmt += strspn (fmt, "0123456789");
149
150       if (*fmt == '.')
151         {
152           ++fmt;
153           fmt += strspn (fmt, "0123456789");
154         }
155     }
156
157   if (!(*fmt == 'e' || *fmt == 'f' || *fmt == 'g'))
158     return 0;
159
160   fmt++;
161   while (*fmt != '\0')
162     {
163       if (*fmt == '%')
164         {
165           fmt++;
166           if (*fmt != '%')
167             return 0;
168         }
169
170       fmt++;
171     }
172
173   return 1;
174 }
175
176 /* Actually print the sequence of numbers in the specified range, with the
177    given or default stepping and format.  */
178 static int
179 print_numbers (const char *fmt)
180 {
181   int i;
182
183   for (i = 0; /* empty */; i++)
184     {
185       double x = first + i * step;
186       if (step < 0 ? x < last : last < x)
187         break;
188       if (i)
189         fputs (separator, stdout);
190       printf (fmt, x);
191     }
192
193   if (i)
194     fputs (terminator, stdout);
195
196   return 0;
197 }
198
199 #if HAVE_RINT && HAVE_MODF && HAVE_FLOOR
200
201 /* Return a printf-style format string with which all selected numbers
202    will format to strings of the same width.  */
203
204 static char *
205 get_width_format ()
206 {
207   static char buffer[256];
208   int full_width;
209   int frac_width;
210   int width1, width2;
211   double max_val;
212   double min_val;
213   double temp;
214
215   if (first > last)
216     {
217       min_val = first - step * floor ((first - last) / step);
218       max_val = first;
219     }
220   else
221     {
222       min_val = first;
223       max_val = first + step * floor ((last - first) / step);
224     }
225
226   sprintf (buffer, "%g", rint (max_val));
227   if (buffer[strspn (buffer, "-0123456789")] != '\0')
228     return "%g";
229   width1 = strlen (buffer);
230
231   if (min_val < 0.0)
232     {
233       double int_min_val = rint (min_val);
234       sprintf (buffer, "%g", int_min_val);
235       if (buffer[strspn (buffer, "-0123456789")] != '\0')
236         return "%g";
237       /* On some systems, `seq -w -.1 .1 .1' results in buffer being `-0'.
238          On others, it is just `0'.  The former results in better output.  */
239       width2 = (int_min_val == 0 ? 2 : strlen (buffer));
240
241       width1 = width1 > width2 ? width1 : width2;
242     }
243   full_width = width1;
244
245   sprintf (buffer, "%g", 1.0 + modf (fabs (min_val), &temp));
246   width1 = strlen (buffer);
247   if (width1 == 1)
248     width1 = 0;
249   else
250     {
251       if (buffer[0] != '1'
252           /* FIXME: assumes that decimal_point is a single character
253              string.  */
254           || buffer[1] != decimal_point[0]
255           || buffer[2 + strspn (&buffer[2], "0123456789")] != '\0')
256         return "%g";
257       width1 -= 2;
258     }
259
260   sprintf (buffer, "%g", 1.0 + modf (fabs (step), &temp));
261   width2 = strlen (buffer);
262   if (width2 == 1)
263     width2 = 0;
264   else
265     {
266       if (buffer[0] != '1'
267           /* FIXME: assumes that decimal_point is a single character
268              string.  */
269           || buffer[1] != decimal_point[0]
270           || buffer[2 + strspn (&buffer[2], "0123456789")] != '\0')
271         return "%g";
272       width2 -= 2;
273     }
274   frac_width = width1 > width2 ? width1 : width2;
275
276   if (frac_width)
277     sprintf (buffer, "%%0%d.%df", full_width + 1 + frac_width, frac_width);
278   else
279     sprintf (buffer, "%%0%dg", full_width);
280
281   return buffer;
282 }
283
284 #else   /* one of the math functions rint, modf, floor is missing.  */
285
286 static char *
287 get_width_format (void)
288 {
289   /* We cannot compute the needed information to determine the correct
290      answer.  So we simply return a value that works for all cases.  */
291   return "%g";
292 }
293
294 #endif
295
296 int
297 main (int argc, char **argv)
298 {
299   int errs;
300   int optc;
301
302   /* The printf(3) format used for output.  */
303   char *format_str = NULL;
304
305   initialize_main (&argc, &argv);
306   program_name = argv[0];
307   setlocale (LC_ALL, "");
308   bindtextdomain (PACKAGE, LOCALEDIR);
309   textdomain (PACKAGE);
310
311   atexit (close_stdout);
312
313   equal_width = 0;
314   separator = "\n";
315   first = 1.0;
316
317   /* Figure out the locale's idea of a decimal point.  */
318 #if HAVE_LOCALECONV
319   {
320     struct lconv *locale;
321
322     locale = localeconv ();
323     /* Paranoia.  */
324     if (locale && locale->decimal_point && locale->decimal_point[0] != '\0')
325       decimal_point = locale->decimal_point;
326   }
327 #endif
328
329   /* We have to handle negative numbers in the command line but this
330      conflicts with the command line arguments.  So explicitly check first
331      whether the next argument looks like a negative number.  */
332   while (optind < argc)
333     {
334       if (argv[optind][0] == '-'
335           && ((optc = argv[optind][1]) == decimal_point[0]
336               || ISDIGIT (optc)))
337         {
338           /* means negative number */
339           break;
340         }
341
342       optc = getopt_long (argc, argv, "+f:s:w", long_options, NULL);
343       if (optc == -1)
344         break;
345
346       switch (optc)
347         {
348         case 0:
349           break;
350
351         case 'f':
352           format_str = optarg;
353           break;
354
355         case 's':
356           separator = optarg;
357           break;
358
359         case 'w':
360           equal_width = 1;
361           break;
362
363         case_GETOPT_HELP_CHAR;
364
365         case_GETOPT_VERSION_CHAR (PROGRAM_NAME, WRITTEN_BY);
366
367         default:
368           usage (EXIT_FAILURE);
369         }
370     }
371
372   if (argc - optind < 1)
373     {
374       error (0, 0, _("too few arguments"));
375       usage (EXIT_FAILURE);
376     }
377
378   if (3 < argc - optind)
379     {
380       error (0, 0, _("too many arguments"));
381       usage (EXIT_FAILURE);
382     }
383
384   if (format_str && !valid_format (format_str))
385     {
386       error (0, 0, _("invalid format string: `%s'"), format_str);
387       usage (EXIT_FAILURE);
388     }
389
390   last = scan_double_arg (argv[optind++]);
391
392   if (optind < argc)
393     {
394       first = last;
395       last = scan_double_arg (argv[optind++]);
396
397       if (optind < argc)
398         {
399           step = last;
400           last = scan_double_arg (argv[optind++]);
401         }
402     }
403
404   if (format_str != NULL && equal_width)
405     {
406       error (0, 0, _("\
407 format string may not be specified when printing equal width strings"));
408       usage (EXIT_FAILURE);
409     }
410
411   if (format_str == NULL)
412     {
413       if (equal_width)
414         format_str = get_width_format ();
415       else
416         format_str = "%g";
417     }
418
419   errs = print_numbers (format_str);
420
421   exit (errs);
422 }