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