Shorten `#ifdef HAVE_...' to `#if HAVE_...' and
[platform/upstream/coreutils.git] / src / seq.c
1 /* seq - print sequence of numbers to standard output.
2    Copyright (C) 1994-2000 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 AUTHORS "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;
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       printf (_("\
87 Print numbers from FIRST to LAST, in steps of INCREMENT.\n\
88 \n\
89   -f, --format FORMAT      use printf(3) style 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       --help               display this help and exit\n\
93       --version            output version information and exit\n\
94 \n\
95 If FIRST or INCREMENT is omitted, it defaults to 1.\n\
96 FIRST, INCREMENT, and LAST are interpreted as floating point values.\n\
97 INCREMENT should be positive if FIRST is smaller than LAST, and negative\n\
98 otherwise.  When given, the FORMAT argument must contain exactly one of\n\
99 the printf-style, floating point output formats %%e, %%f, %%g\n\
100 "));
101       puts (_("\nReport bugs to <bug-sh-utils@gnu.org>."));
102     }
103   exit (status);
104 }
105
106 /* Read a double value from the command line.
107    Return if the string is correct else signal error.  */
108
109 static double
110 scan_double_arg (const char *arg)
111 {
112   double ret_val;
113
114   if (xstrtod (arg, NULL, &ret_val))
115     {
116       error (0, 0, _("invalid floating point argument: %s"), arg);
117       usage (1);
118     }
119
120   return ret_val;
121 }
122
123 /* Check whether the format string is valid for a single `double'
124    argument.  Return 0 if not, 1 if correct. */
125
126 static int
127 valid_format (const char *fmt)
128 {
129   while (*fmt != '\0')
130     {
131       if (*fmt == '%')
132         {
133           fmt++;
134           if (*fmt != '%')
135             break;
136         }
137
138       fmt++;
139     }
140   if (*fmt == '\0')
141     return 0;
142
143   fmt += strspn (fmt, "-+#0");
144   if (ISDIGIT (*fmt))
145     {
146       fmt += strspn (fmt, "0123456789");
147
148       if (*fmt == '.')
149         fmt += strspn (++fmt, "0123456789");
150     }
151
152   if (!(*fmt == 'e' || *fmt == 'f' || *fmt == 'g'))
153     return 0;
154
155   fmt++;
156   while (*fmt != '\0')
157     {
158       if (*fmt == '%')
159         {
160           fmt++;
161           if (*fmt != '%')
162             return 0;
163         }
164
165       fmt++;
166     }
167
168   return 1;
169 }
170
171 /* Actually print the sequence of numbers in the specified range, with the
172    given or default stepping and format.  */
173 static int
174 print_numbers (const char *fmt)
175 {
176   if (first > last)
177     {
178       int i;
179
180       if (step >= 0)
181         {
182           error (0, 0,
183                  _("when the starting value is larger than the limit,\n\
184 the increment must be negative"));
185           usage (1);
186         }
187
188       printf (fmt, first);
189       for (i = 1; /* empty */; i++)
190         {
191           double x = first + i * step;
192
193           if (x < last)
194             break;
195
196           fputs (separator, stdout);
197           printf (fmt, x);
198         }
199     }
200   else
201     {
202       int i;
203
204       if (step <= 0)
205         {
206           error (0, 0,
207                  _("when the starting value is smaller than the limit,\n\
208 the increment must be positive"));
209           usage (1);
210         }
211
212       printf (fmt, first);
213       for (i = 1; /* empty */; i++)
214         {
215           double x = first + i * step;
216
217           if (x > last)
218             break;
219
220           fputs (separator, stdout);
221           printf (fmt, x);
222         }
223     }
224   fputs (terminator, stdout);
225
226   return 0;
227 }
228
229 #if HAVE_RINT && HAVE_MODF && HAVE_FLOOR
230
231 /* Return a printf-style format string with which all selected numbers
232    will format to strings of the same width.  */
233
234 static char *
235 get_width_format ()
236 {
237   static char buffer[256];
238   int full_width;
239   int frac_width;
240   int width1, width2;
241   double max_val;
242   double min_val;
243   double temp;
244
245   if (first > last)
246     {
247       min_val = first - step * floor ((first - last) / step);
248       max_val = first;
249     }
250   else
251     {
252       min_val = first;
253       max_val = first + step * floor ((last - first) / step);
254     }
255
256   sprintf (buffer, "%g", rint (max_val));
257   if (buffer[strspn (buffer, "0123456789")] != '\0')
258     return "%g";
259   width1 = strlen (buffer);
260
261   if (min_val < 0.0)
262     {
263       double int_min_val = rint (min_val);
264       sprintf (buffer, "%g", int_min_val);
265       if (buffer[strspn (buffer, "-0123456789")] != '\0')
266         return "%g";
267       /* On some systems, `seq -w -.1 .1 .1' results in buffer being `-0'.
268          On others, it is just `0'.  The former results in better output.  */
269       width2 = (int_min_val == 0 ? 2 : strlen (buffer));
270
271       width1 = width1 > width2 ? width1 : width2;
272     }
273   full_width = width1;
274
275   sprintf (buffer, "%g", 1.0 + modf (fabs (min_val), &temp));
276   width1 = strlen (buffer);
277   if (width1 == 1)
278     width1 = 0;
279   else
280     {
281       if (buffer[0] != '1'
282           /* FIXME: assumes that decimal_point is a single character
283              string.  */
284           || buffer[1] != decimal_point[0]
285           || buffer[2 + strspn (&buffer[2], "0123456789")] != '\0')
286         return "%g";
287       width1 -= 2;
288     }
289
290   sprintf (buffer, "%g", 1.0 + modf (fabs (step), &temp));
291   width2 = strlen (buffer);
292   if (width2 == 1)
293     width2 = 0;
294   else
295     {
296       if (buffer[0] != '1'
297           /* FIXME: assumes that decimal_point is a single character
298              string.  */
299           || buffer[1] != decimal_point[0]
300           || buffer[2 + strspn (&buffer[2], "0123456789")] != '\0')
301         return "%g";
302       width2 -= 2;
303     }
304   frac_width = width1 > width2 ? width1 : width2;
305
306   if (frac_width)
307     sprintf (buffer, "%%0%d.%df", full_width + 1 + frac_width, frac_width);
308   else
309     sprintf (buffer, "%%0%dg", full_width);
310
311   return buffer;
312 }
313
314 #else   /* one of the math functions rint, modf, floor is missing.  */
315
316 static char *
317 get_width_format (void)
318 {
319   /* We cannot compute the needed information to determine the correct
320      answer.  So we simply return a value that works for all cases.  */
321   return "%g";
322 }
323
324 #endif
325
326 int
327 main (int argc, char **argv)
328 {
329   int errs;
330   int optc;
331   int step_is_set;
332
333   /* The printf(3) format used for output.  */
334   char *format_str = NULL;
335
336   program_name = argv[0];
337   setlocale (LC_ALL, "");
338   bindtextdomain (PACKAGE, LOCALEDIR);
339   textdomain (PACKAGE);
340
341   equal_width = 0;
342   separator = "\n";
343   first = 1.0;
344   step_is_set = 0;
345
346   /* Figure out the locale's idea of a decimal point.  */
347 #if HAVE_LOCALECONV
348   {
349     struct lconv *locale;
350
351     locale = localeconv ();
352     /* Paranoia.  */
353     if (locale && locale->decimal_point && locale->decimal_point[0] != '\0')
354       decimal_point = locale->decimal_point;
355   }
356 #endif
357
358   /* We have to handle negative numbers in the command line but this
359      conflicts with the command line arguments.  So explicitly check first
360      whether the next argument looks like a negative number.  */
361   while (optind < argc)
362     {
363       if (argv[optind][0] == '-'
364           && ((optc = argv[optind][1]) == decimal_point[0]
365               || ISDIGIT (optc)))
366         {
367           /* means negative number */
368           break;
369         }
370
371       optc = getopt_long (argc, argv, "+f:s:w", long_options, NULL);
372       if (optc == -1)
373         break;
374
375       switch (optc)
376         {
377         case 0:
378           break;
379
380         case 'f':
381           format_str = optarg;
382           break;
383
384         case 's':
385           separator = optarg;
386           break;
387
388         case 'w':
389           equal_width = 1;
390           break;
391
392         case_GETOPT_HELP_CHAR;
393
394         case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS);
395
396         default:
397           usage (1);
398         }
399     }
400
401   if (argc - optind < 1)
402     {
403       error (0, 0, _("too few arguments"));
404       usage (1);
405     }
406
407   if (3 < argc - optind)
408     {
409       error (0, 0, _("too many arguments"));
410       usage (1);
411     }
412
413   if (format_str && !valid_format (format_str))
414     {
415       error (0, 0, _("invalid format string: `%s'"), format_str);
416       usage (1);
417     }
418
419   last = scan_double_arg (argv[optind++]);
420
421   if (optind < argc)
422     {
423       first = last;
424       last = scan_double_arg (argv[optind++]);
425
426       if (optind < argc)
427         {
428           step = last;
429           step_is_set = 1;
430           last = scan_double_arg (argv[optind++]);
431
432         }
433     }
434
435   if (format_str != NULL && equal_width)
436     {
437       error (0, 0, _("\
438 format string may not be specified when printing equal width strings"));
439       usage (1);
440     }
441
442   if (!step_is_set)
443     {
444       step = first <= last ? 1.0 : -1.0;
445     }
446
447   if (format_str == NULL)
448     {
449       if (equal_width)
450         format_str = get_width_format ();
451       else
452         format_str = "%g";
453     }
454
455   errs = print_numbers (format_str);
456
457   exit (errs);
458 }