(main): Rearrange loops to get it right.
[platform/upstream/coreutils.git] / src / seq.c
1 /* seq - print sequence of numbers to standard output.
2    Copyright (C) 94, 1995 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
25 #include "system.h"
26 #include "error.h"
27 #include "xstrtod.h"
28
29 static double scan_double_arg __P ((const char *arg));
30 static int check_format __P ((const char *format_string));
31 static char *get_width_format __P ((void));
32 static int print_numbers __P ((const char *format_str));
33
34 /* If nonzero print all number with equal width.  */
35 static int equal_width;
36
37 /* The printf(3) format used for output.  */
38 static char *format_str;
39
40 /* The starting number.  */
41 static double first;
42
43 /* The name that this program was run with.  */
44 char *program_name;
45
46 /* The string used to separate two numbers.  */
47 static char *separator;
48
49 /* The string output after all numbers have been output.
50    Usually "\n" or "\0".  */
51 /* FIXME: make this an option.  */
52 static char *terminator = "\n";
53
54 /* If nonzero, display usage information and exit.  */
55 static int show_help;
56
57 /* If nonzero, print the version on standard output and exit.  */
58 static int show_version;
59
60 /* The increment.  */
61 static double step;
62
63 /* The last number.  */
64 static double last;
65
66 static struct option const long_options[] =
67 {
68   { "equal-width", no_argument, NULL, 'w'},
69   { "format", required_argument, NULL, 'f'},
70   { "help", no_argument, &show_help, 1},
71   { "separator", required_argument, NULL, 's'},
72   { "version", no_argument, &show_version, 1},
73   { NULL, 0, NULL, 0}
74 };
75
76 static void
77 usage (int status)
78 {
79   if (status != 0)
80     (void) fprintf (stderr, _("Try `%s --help' for more information.\n"),
81                     program_name);
82   else
83     {
84       (void) printf (_("\
85 Usage: %s [OPTION]... [FIRST [step]] LAST\n\
86 "), program_name);
87       (void) printf (_("\
88 \n\
89   -f, --format FORMAT      use printf(3) style FORMAT (default: %%g)\n\
90       --help               display this help and exit\n\
91   -s, --separator STRING   use STRING for separating numbers (default: \\n)\n\
92       --version            output version information and exit\n\
93   -w, --equal-width        equalize width by padding with leading zeroes\n\
94 \n\
95   FROM, STEP, TO are interpreted as floating point.  STEP should be > 0 if\n\
96   FROM is smaller than TO and vice versa.  When given, the FORMAT argument\n\
97   must contain exactly one of the float output formats %%e, %%f, or %%g.\n\
98 "));
99     }
100   exit (status);
101 }
102
103 int
104 main (int argc, char **argv)
105 {
106   int errs;
107   int optc;
108   int step_is_set;
109
110   program_name = argv[0];
111   setlocale (LC_ALL, "");
112   bindtextdomain (PACKAGE, LOCALEDIR);
113   textdomain (PACKAGE);
114
115   equal_width = 0;
116   format_str = NULL;
117   separator = "\n";
118   first = 1.0;
119   step_is_set = 0;
120
121   /* We have to handle negative numbers in the command line but this
122      conflicts with the command line arguments.  So the getopt mode is
123      REQUIRE_ORDER (the '+' in the format string) and it abort on the
124      first non-option or negative number.  */
125   while ((optc = getopt_long (argc, argv, "+0123456789f:s:w", long_options,
126                               (int *) 0)) != EOF)
127     {
128       if ('0' <= optc && optc <= '9')
129         {
130           /* means negative number */
131           break;
132         }
133
134       switch (optc)
135         {
136         case 0:
137           break;
138
139         case 'f':
140           format_str = optarg;
141           break;
142
143         case 's':
144           separator = optarg;
145           break;
146
147         case 'w':
148           equal_width = 1;
149           break;
150
151         default:
152           usage (1);
153           /* NOTREACHED */
154         }
155     }
156
157   if (show_version)
158     {
159       (void) printf ("seq - %s\n", PACKAGE_VERSION);
160       exit (0);
161     }
162
163   if (show_help)
164     {
165       usage (0);
166       /* NOTREACHED */
167     }
168
169   if (optind >= argc)
170     {
171       error (0, 0, _("too few arguments"));
172       usage (1);
173       /* NOTREACHED */
174     }
175   last = scan_double_arg (argv[optind++]);
176
177   if (optind < argc)
178     {
179       first = last;
180       last = scan_double_arg (argv[optind++]);
181
182       if (optind < argc)
183         {
184           step = last;
185           step_is_set = 1;
186           last = scan_double_arg (argv[optind++]);
187
188           if (optind < argc)
189             {
190               usage (1);
191               /* NOTREACHED */
192             }
193         }
194     }
195
196   if (format_str != NULL && equal_width)
197     {
198       error (0, 0, _("\
199 format string may not be specified when printing equal width strings"));
200       usage (1);
201     }
202
203   if (!step_is_set)
204     {
205       step = first <= last ? 1.0 : -1.0;
206     }
207
208   if (format_str != NULL)
209     {
210       if (!check_format (format_str))
211         {
212           error (0, 0, _("invalid format string: `%s'"), format_str);
213           usage (1);
214         }
215     }
216   else
217     {
218       if (equal_width)
219         format_str = get_width_format ();
220       else
221         format_str = "%g";
222     }
223
224   errs = print_numbers (format_str);
225
226   exit (errs);
227   /* NOTREACHED */
228 }
229
230 /* Read a double value from the command line.
231    Return if the string is correct else signal error.  */
232
233 static double
234 scan_double_arg (const char *arg)
235 {
236   double ret_val;
237
238   if (xstrtod (arg, NULL, &ret_val))
239     {
240       error (0, 0, _("invalid floating point argument: %s"), arg);
241       usage (1);
242       /* NOTREACHED */
243     }
244
245   return ret_val;
246 }
247
248 /* Check whether the format string is valid for a single double
249    argument.
250    Return 0 if not, 1 if correct.  */
251
252 static int
253 check_format (const char *format_string)
254 {
255   while (*format_string != '\0')
256     {
257       if (*format_string == '%')
258         {
259           format_string++;
260           if (*format_string != '%')
261             break;
262         }
263
264       format_string++;
265     }
266   if (*format_string == '\0')
267     return 0;
268
269   format_string += strspn (format_string, "-+#0");
270   if (isdigit (*format_string))
271     {
272       format_string += strspn (format_string, "012345789");
273
274       if (*format_string == '.')
275         format_string += strspn (++format_string, "0123456789");
276     }
277
278   if (*format_string != 'e' && *format_string != 'f' &&
279       *format_string != 'g')
280     return 0;
281
282   format_string++;
283   while (*format_string != '\0')
284     {
285       if (*format_string == '%')
286         {
287           format_string++;
288           if (*format_string != '%')
289             return 0;
290         }
291
292       format_string++;
293     }
294
295   return 1;
296 }
297
298 #if defined (HAVE_RINT) && defined (HAVE_MODF) && defined (HAVE_FLOOR)
299
300 /* Return a printf-style format string with which all selected numbers
301    will format to strings of the same width.  */
302
303 static char *
304 get_width_format ()
305 {
306   static char buffer[256];
307   int full_width;
308   int frac_width;
309   int width1, width2;
310   double max_val;
311   double min_val;
312   double temp;
313
314   if (first > last)
315     {
316       min_val = first - step * floor ((first - last) / step);
317       max_val = first;
318     }
319   else
320     {
321       min_val = first;
322       max_val = first + step * floor ((last - first) / step);
323     }
324
325   (void) sprintf (buffer, "%g", rint (max_val));
326   if (buffer[strspn (buffer, "0123456789")] != '\0')
327     return "%g";
328   width1 = strlen (buffer);
329
330   if (min_val < 0.0)
331     {
332       (void) sprintf (buffer, "%g", rint (min_val));
333       if (buffer[strspn (buffer, "-0123456789")] != '\0')
334         return "%g";
335       width2 = strlen (buffer);
336
337       width1 = width1 > width2 ? width1 : width2;
338     }
339   full_width = width1;
340
341   (void) sprintf (buffer, "%g", 1.0 + modf (min_val, &temp));
342   width1 = strlen (buffer);
343   if (width1 == 1)
344     width1 = 0;
345   else
346     {
347       if (buffer[0] != '1' || buffer[1] != '.' ||
348           buffer[2 + strspn (&buffer[2], "0123456789")] != '\0')
349         return "%g";
350       width1 -= 2;
351     }
352
353   (void) sprintf (buffer, "%g", 1.0 + modf (step, &temp));
354   width2 = strlen (buffer);
355   if (width2 == 1)
356     width2 = 0;
357   else
358     {
359       if (buffer[0] != '1' || buffer[1] != '.' ||
360           buffer[2 + strspn (&buffer[2], "0123456789")] != '\0')
361         return "%g";
362       width2 -= 2;
363     }
364   frac_width = width1 > width2 ? width1 : width2;
365
366   if (frac_width)
367     (void) sprintf (buffer, "%%0%d.%df", full_width + 1 + frac_width, frac_width);
368   else
369     (void) sprintf (buffer, "%%0%dg", full_width);
370
371   return buffer;
372 }
373
374 #else   /* one of the math functions rint, modf, floor is missing.  */
375
376 static char *
377 get_width_format (void)
378 {
379   /* We cannot compute the needed information to determine the correct
380      answer.  So we simply return a value that works for all cases.  */
381   return "%g";
382 }
383
384 #endif
385
386 /* Actually print the sequence of numbers in the specified range, with the
387    given or default stepping and format.  */
388 static int
389 print_numbers (const char *format_str)
390 {
391   if (first > last)
392     {
393       int i;
394
395       if (step >= 0)
396         {
397           error (0, 0, _("invalid increment: %g"), step);
398           usage (1);
399           /* NOTREACHED */
400         }
401
402       printf (format_str, first);
403       for (i = 0; /* empty */; i++)
404         {
405           double x = first + i * step;
406
407           if (x < last)
408             break;
409
410           fputs (separator, stdout);
411           printf (format_str, x);
412         }
413     }
414   else
415     {
416       int i;
417
418       if (step <= 0)
419         {
420           error (0, 0, _("invalid increment: %g"), step);
421           usage (1);
422           /* NOTREACHED */
423         }
424
425       printf (format_str, first);
426       for (i = 1; /* empty */; i++)
427         {
428           double x = first + i * step;
429
430           if (x > last)
431             break;
432
433           fputs (separator, stdout);
434           printf (format_str, x);
435         }
436     }
437   fputs (terminator, stdout);
438
439   return 0;
440 }