Line data Source code
1 : /* seq - print sequence of numbers to standard output.
2 : Copyright (C) 1994-2008 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 3 of the License, or
7 : (at your option) 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, see <http://www.gnu.org/licenses/>. */
16 :
17 : /* Written by Ulrich Drepper. */
18 :
19 : #include <config.h>
20 : #include <getopt.h>
21 : #include <stdio.h>
22 : #include <sys/types.h>
23 : #include <math.h>
24 : #include <float.h>
25 :
26 : #include "system.h"
27 : #include "c-strtod.h"
28 : #include "error.h"
29 : #include "quote.h"
30 : #include "xstrtod.h"
31 :
32 : /* Roll our own isfinite rather than using <math.h>, so that we don't
33 : have to worry about linking -lm just for isfinite. */
34 : #ifndef isfinite
35 : # define isfinite(x) ((x) * 0 == 0)
36 : #endif
37 :
38 : /* The official name of this program (e.g., no `g' prefix). */
39 : #define PROGRAM_NAME "seq"
40 :
41 : #define AUTHORS "Ulrich Drepper"
42 :
43 : /* If true print all number with equal width. */
44 : static bool equal_width;
45 :
46 : /* The name that this program was run with. */
47 : char *program_name;
48 :
49 : /* The string used to separate two numbers. */
50 : static char const *separator;
51 :
52 : /* The string output after all numbers have been output.
53 : Usually "\n" or "\0". */
54 : /* FIXME: make this an option. */
55 : static char const terminator[] = "\n";
56 :
57 : static struct option const long_options[] =
58 : {
59 : { "equal-width", no_argument, NULL, 'w'},
60 : { "format", required_argument, NULL, 'f'},
61 : { "separator", required_argument, NULL, 's'},
62 : {GETOPT_HELP_OPTION_DECL},
63 : {GETOPT_VERSION_OPTION_DECL},
64 : { NULL, 0, NULL, 0}
65 : };
66 :
67 : void
68 55 : usage (int status)
69 : {
70 55 : if (status != EXIT_SUCCESS)
71 53 : fprintf (stderr, _("Try `%s --help' for more information.\n"),
72 : program_name);
73 : else
74 : {
75 2 : printf (_("\
76 : Usage: %s [OPTION]... LAST\n\
77 : or: %s [OPTION]... FIRST LAST\n\
78 : or: %s [OPTION]... FIRST INCREMENT LAST\n\
79 : "), program_name, program_name, program_name);
80 2 : fputs (_("\
81 : Print numbers from FIRST to LAST, in steps of INCREMENT.\n\
82 : \n\
83 : -f, --format=FORMAT use printf style floating-point FORMAT\n\
84 : -s, --separator=STRING use STRING to separate numbers (default: \\n)\n\
85 : -w, --equal-width equalize width by padding with leading zeroes\n\
86 : "), stdout);
87 2 : fputs (HELP_OPTION_DESCRIPTION, stdout);
88 2 : fputs (VERSION_OPTION_DESCRIPTION, stdout);
89 2 : fputs (_("\
90 : \n\
91 : If FIRST or INCREMENT is omitted, it defaults to 1. That is, an\n\
92 : omitted INCREMENT defaults to 1 even when LAST is smaller than FIRST.\n\
93 : FIRST, INCREMENT, and LAST are interpreted as floating point values.\n\
94 : INCREMENT is usually positive if FIRST is smaller than LAST, and\n\
95 : INCREMENT is usually negative if FIRST is greater than LAST.\n\
96 : "), stdout);
97 2 : fputs (_("\
98 : FORMAT must be suitable for printing one argument of type `double';\n\
99 : it defaults to %.PRECf if FIRST, INCREMENT, and LAST are all fixed point\n\
100 : decimal numbers with maximum precision PREC, and to %g otherwise.\n\
101 : "), stdout);
102 2 : emit_bug_reporting_address ();
103 : }
104 55 : exit (status);
105 : }
106 :
107 : /* A command-line operand. */
108 : struct operand
109 : {
110 : /* Its value, converted to 'long double'. */
111 : long double value;
112 :
113 : /* Its print width, if it were printed out in a form similar to its
114 : input form. An input like "-.1" is treated like "-0.1", and an
115 : input like "1." is treated like "1", but otherwise widths are
116 : left alone. */
117 : size_t width;
118 :
119 : /* Number of digits after the decimal point, or INT_MAX if the
120 : number can't easily be expressed as a fixed-point number. */
121 : int precision;
122 : };
123 : typedef struct operand operand;
124 :
125 : /* Description of what a number-generating format will generate. */
126 : struct layout
127 : {
128 : /* Number of bytes before and after the number. */
129 : size_t prefix_len;
130 : size_t suffix_len;
131 : };
132 :
133 : /* Read a long double value from the command line.
134 : Return if the string is correct else signal error. */
135 :
136 : static operand
137 16 : scan_arg (const char *arg)
138 : {
139 : operand ret;
140 :
141 16 : if (! xstrtold (arg, NULL, &ret.value, c_strtold))
142 : {
143 14 : error (0, 0, _("invalid floating point argument: %s"), arg);
144 14 : usage (EXIT_FAILURE);
145 : }
146 :
147 : /* We don't output spaces or '+' so don't include in width */
148 4 : while (isspace (*arg) || *arg == '+')
149 0 : arg++;
150 :
151 2 : ret.width = strlen (arg);
152 2 : ret.precision = INT_MAX;
153 :
154 2 : if (! arg[strcspn (arg, "xX")] && isfinite (ret.value))
155 : {
156 2 : char const *decimal_point = strchr (arg, '.');
157 2 : if (! decimal_point)
158 2 : ret.precision = 0;
159 : else
160 : {
161 0 : size_t fraction_len = strcspn (decimal_point + 1, "eE");
162 0 : if (fraction_len <= INT_MAX)
163 0 : ret.precision = fraction_len;
164 0 : ret.width += (fraction_len == 0 /* #. -> # */
165 : ? -1
166 0 : : (decimal_point == arg /* .# -> 0.# */
167 0 : || ! ISDIGIT (decimal_point[-1]))); /* -.# -> 0.# */
168 : }
169 2 : char const *e = strchr (arg, 'e');
170 2 : if (! e)
171 2 : e = strchr (arg, 'E');
172 2 : if (e)
173 : {
174 0 : long exponent = strtol (e + 1, NULL, 10);
175 0 : ret.precision += exponent < 0 ? -exponent : 0;
176 : }
177 : }
178 :
179 2 : return ret;
180 : }
181 :
182 : /* Validate the format, FMT. Print a diagnostic and exit
183 : if there is not exactly one %-directive. */
184 :
185 : static void
186 26 : validate_format (char const *fmt)
187 : {
188 26 : unsigned int n_directives = 0;
189 : char const *p;
190 :
191 155 : for (p = fmt; *p; p++)
192 : {
193 129 : if (p[0] == '%' && p[1] != '%' && p[1] != '\0')
194 : {
195 19 : ++n_directives;
196 19 : ++p;
197 : }
198 : }
199 26 : if (n_directives == 0)
200 : {
201 8 : error (0, 0, _("no %% directive in format string %s"), quote (fmt));
202 8 : usage (EXIT_FAILURE);
203 : }
204 18 : else if (1 < n_directives)
205 1 : error (EXIT_FAILURE, 0, _("too many %% directives in format string %s"),
206 : quote (fmt));
207 17 : }
208 :
209 : /* If FORMAT is a valid printf format for a double argument, return
210 : its long double equivalent, possibly allocated from dynamic
211 : storage, and store into *LAYOUT a description of the output layout;
212 : otherwise, return NULL. */
213 :
214 : static char const *
215 17 : long_double_format (char const *fmt, struct layout *layout)
216 : {
217 : size_t i;
218 17 : size_t prefix_len = 0;
219 17 : size_t suffix_len = 0;
220 : size_t length_modifier_offset;
221 : bool has_L;
222 :
223 81 : for (i = 0; ! (fmt[i] == '%' && fmt[i + 1] != '%'); i += (fmt[i] == '%') + 1)
224 74 : if (fmt[i])
225 64 : prefix_len++;
226 : else
227 10 : return NULL;
228 :
229 7 : i++;
230 7 : i += strspn (fmt + i, "-+#0 '");
231 7 : i += strspn (fmt + i, "0123456789");
232 7 : if (fmt[i] == '.')
233 : {
234 2 : i++;
235 2 : i += strspn (fmt + i, "0123456789");
236 : }
237 :
238 7 : length_modifier_offset = i;
239 7 : has_L = (fmt[i] == 'L');
240 7 : i += has_L;
241 : /* In a valid format string, fmt[i] must be one of these specifiers. */
242 7 : if (fmt[i] == '\0' || ! strchr ("efgaEFGA", fmt[i]))
243 5 : return NULL;
244 :
245 3 : for (i++; ! (fmt[i] == '%' && fmt[i + 1] != '%'); i += (fmt[i] == '%') + 1)
246 2 : if (fmt[i])
247 1 : suffix_len++;
248 : else
249 : {
250 1 : size_t format_size = i + 1;
251 1 : char *ldfmt = xmalloc (format_size + 1);
252 1 : memcpy (ldfmt, fmt, length_modifier_offset);
253 1 : ldfmt[length_modifier_offset] = 'L';
254 1 : strcpy (ldfmt + length_modifier_offset + 1,
255 1 : fmt + length_modifier_offset + has_L);
256 1 : layout->prefix_len = prefix_len;
257 1 : layout->suffix_len = suffix_len;
258 1 : return ldfmt;
259 : }
260 :
261 1 : return NULL;
262 : }
263 :
264 : /* Return the absolute relative difference from x to y. */
265 : static double
266 0 : abs_rel_diff (double x, double y)
267 : {
268 0 : double s = (y == 0.0 ? 1 : y);
269 0 : return fabs ((y - x) / s);
270 : }
271 :
272 : /* Actually print the sequence of numbers in the specified range, with the
273 : given or default stepping and format. */
274 :
275 : static void
276 0 : print_numbers (char const *fmt, struct layout layout,
277 : long double first, long double step, long double last)
278 : {
279 0 : bool out_of_range = (step < 0 ? first < last : last < first);
280 :
281 0 : if (! out_of_range)
282 : {
283 0 : long double x = first;
284 : long double i;
285 :
286 0 : for (i = 1; ; i++)
287 0 : {
288 0 : long double x0 = x;
289 0 : printf (fmt, x);
290 0 : if (out_of_range)
291 0 : break;
292 0 : x = first + i * step;
293 0 : out_of_range = (step < 0 ? x < last : last < x);
294 :
295 0 : if (out_of_range)
296 : {
297 : /* If the number just past LAST prints as a value equal
298 : to LAST, and prints differently from the previous
299 : number, then print the number. This avoids problems
300 : with rounding. For example, with the x86 it causes
301 : "seq 0 0.000001 0.000003" to print 0.000003 instead
302 : of stopping at 0.000002. */
303 :
304 0 : bool print_extra_number = false;
305 : long double x_val;
306 : char *x_str;
307 0 : int x_strlen = asprintf (&x_str, fmt, x);
308 0 : if (x_strlen < 0)
309 0 : xalloc_die ();
310 0 : x_str[x_strlen - layout.suffix_len] = '\0';
311 :
312 0 : if (xstrtold (x_str + layout.prefix_len, NULL, &x_val, c_strtold)
313 0 : && abs_rel_diff (x_val, last) < DBL_EPSILON)
314 : {
315 0 : char *x0_str = NULL;
316 0 : if (asprintf (&x0_str, fmt, x0) < 0)
317 0 : xalloc_die ();
318 0 : print_extra_number = !STREQ (x0_str, x_str);
319 0 : free (x0_str);
320 : }
321 :
322 0 : free (x_str);
323 0 : if (! print_extra_number)
324 0 : break;
325 : }
326 :
327 0 : fputs (separator, stdout);
328 : }
329 :
330 0 : fputs (terminator, stdout);
331 : }
332 0 : }
333 :
334 : /* Return the default format given FIRST, STEP, and LAST. */
335 : static char const *
336 0 : get_default_format (operand first, operand step, operand last)
337 : {
338 : static char format_buf[sizeof "%0.Lf" + 2 * INT_STRLEN_BOUND (int)];
339 :
340 0 : int prec = MAX (first.precision, step.precision);
341 :
342 0 : if (prec != INT_MAX && last.precision != INT_MAX)
343 : {
344 0 : if (equal_width)
345 : {
346 : /* increase first_width by any increased precision in step */
347 0 : size_t first_width = first.width + (prec - first.precision);
348 : /* adjust last_width to use precision from first/step */
349 0 : size_t last_width = last.width + (prec - last.precision);
350 0 : if (last.precision && prec == 0)
351 0 : last_width--; /* don't include space for '.' */
352 0 : size_t width = MAX (first_width, last_width);
353 0 : if (width <= INT_MAX)
354 : {
355 0 : int w = width;
356 0 : sprintf (format_buf, "%%0%d.%dLf", w, prec);
357 0 : return format_buf;
358 : }
359 : }
360 : else
361 : {
362 0 : sprintf (format_buf, "%%.%dLf", prec);
363 0 : return format_buf;
364 : }
365 : }
366 :
367 0 : return "%Lg";
368 : }
369 :
370 : int
371 57 : main (int argc, char **argv)
372 : {
373 : int optc;
374 57 : operand first = { 1, 1, 0 };
375 57 : operand step = { 1, 1, 0 };
376 : operand last;
377 57 : struct layout layout = { 0, 0 };
378 :
379 : /* The printf(3) format used for output. */
380 57 : char const *format_str = NULL;
381 :
382 : initialize_main (&argc, &argv);
383 57 : program_name = argv[0];
384 57 : setlocale (LC_ALL, "");
385 : bindtextdomain (PACKAGE, LOCALEDIR);
386 : textdomain (PACKAGE);
387 :
388 57 : atexit (close_stdout);
389 :
390 57 : equal_width = false;
391 57 : separator = "\n";
392 :
393 : /* We have to handle negative numbers in the command line but this
394 : conflicts with the command line arguments. So explicitly check first
395 : whether the next argument looks like a negative number. */
396 149 : while (optind < argc)
397 : {
398 86 : if (argv[optind][0] == '-'
399 81 : && ((optc = argv[optind][1]) == '.' || ISDIGIT (optc)))
400 : {
401 : /* means negative number */
402 : break;
403 : }
404 :
405 55 : optc = getopt_long (argc, argv, "+f:s:w", long_options, NULL);
406 55 : if (optc == -1)
407 9 : break;
408 :
409 46 : switch (optc)
410 : {
411 28 : case 'f':
412 28 : format_str = optarg;
413 28 : break;
414 :
415 1 : case 's':
416 1 : separator = optarg;
417 1 : break;
418 :
419 6 : case 'w':
420 6 : equal_width = true;
421 6 : break;
422 :
423 2 : case_GETOPT_HELP_CHAR;
424 :
425 1 : case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS);
426 :
427 8 : default:
428 8 : usage (EXIT_FAILURE);
429 : }
430 : }
431 :
432 46 : if (argc - optind < 1)
433 : {
434 7 : error (0, 0, _("missing operand"));
435 7 : usage (EXIT_FAILURE);
436 : }
437 :
438 39 : if (3 < argc - optind)
439 : {
440 0 : error (0, 0, _("extra operand %s"), quote (argv[optind + 3]));
441 0 : usage (EXIT_FAILURE);
442 : }
443 :
444 39 : if (format_str)
445 : {
446 26 : validate_format (format_str);
447 17 : char const *f = long_double_format (format_str, &layout);
448 17 : if (! f)
449 : {
450 16 : error (0, 0, _("invalid format string: %s"), quote (format_str));
451 16 : usage (EXIT_FAILURE);
452 : }
453 1 : format_str = f;
454 : }
455 :
456 14 : last = scan_arg (argv[optind++]);
457 :
458 2 : if (optind < argc)
459 : {
460 2 : first = last;
461 2 : last = scan_arg (argv[optind++]);
462 :
463 0 : if (optind < argc)
464 : {
465 0 : step = last;
466 0 : last = scan_arg (argv[optind++]);
467 : }
468 : }
469 :
470 0 : if (format_str != NULL && equal_width)
471 : {
472 0 : error (0, 0, _("\
473 : format string may not be specified when printing equal width strings"));
474 0 : usage (EXIT_FAILURE);
475 : }
476 :
477 0 : if (format_str == NULL)
478 0 : format_str = get_default_format (first, step, last);
479 :
480 0 : print_numbers (format_str, layout, first.value, step.value, last.value);
481 :
482 0 : exit (EXIT_SUCCESS);
483 : }
|