1e8c23208Sginsbach /*
2e8c23208Sginsbach * Copyright (c) 2005 The NetBSD Foundation, Inc.
3e8c23208Sginsbach * All rights reserved.
4e8c23208Sginsbach *
5e8c23208Sginsbach * This code is derived from software contributed to The NetBSD Foundation
6e8c23208Sginsbach * by Brian Ginsbach.
7e8c23208Sginsbach *
8e8c23208Sginsbach * Redistribution and use in source and binary forms, with or without
9e8c23208Sginsbach * modification, are permitted provided that the following conditions
10e8c23208Sginsbach * are met:
11e8c23208Sginsbach * 1. Redistributions of source code must retain the above copyright
12e8c23208Sginsbach * notice, this list of conditions and the following disclaimer.
13e8c23208Sginsbach * 2. Redistributions in binary form must reproduce the above copyright
14e8c23208Sginsbach * notice, this list of conditions and the following disclaimer in the
15e8c23208Sginsbach * documentation and/or other materials provided with the distribution.
16e8c23208Sginsbach *
17e8c23208Sginsbach * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
18e8c23208Sginsbach * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
19e8c23208Sginsbach * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
20e8c23208Sginsbach * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
21e8c23208Sginsbach * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
22e8c23208Sginsbach * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
23e8c23208Sginsbach * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
24e8c23208Sginsbach * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
25e8c23208Sginsbach * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
26e8c23208Sginsbach * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27e8c23208Sginsbach * POSSIBILITY OF SUCH DAMAGE.
28e8c23208Sginsbach */
29e8c23208Sginsbach
30e8c23208Sginsbach #include <sys/cdefs.h>
31e8c23208Sginsbach #ifndef lint
3298e5374cSlukem __COPYRIGHT("@(#) Copyright (c) 2005\
3398e5374cSlukem The NetBSD Foundation, Inc. All rights reserved.");
34*c2c241e6Smlelstv __RCSID("$NetBSD: seq.c,v 1.14 2024/05/04 13:29:41 mlelstv Exp $");
35e8c23208Sginsbach #endif /* not lint */
36e8c23208Sginsbach
37e8c23208Sginsbach #include <ctype.h>
38e8c23208Sginsbach #include <err.h>
39e8c23208Sginsbach #include <errno.h>
40e8c23208Sginsbach #include <math.h>
41e8c23208Sginsbach #include <locale.h>
42e8c23208Sginsbach #include <stdio.h>
43e8c23208Sginsbach #include <stdlib.h>
44e8c23208Sginsbach #include <string.h>
45e8c23208Sginsbach #include <unistd.h>
46e8c23208Sginsbach
47e8c23208Sginsbach #define ZERO '0'
48e8c23208Sginsbach #define SPACE ' '
49e8c23208Sginsbach
50e8c23208Sginsbach #define MAX(a, b) (((a) < (b))? (b) : (a))
51e8c23208Sginsbach #define ISSIGN(c) ((int)(c) == '-' || (int)(c) == '+')
52e8c23208Sginsbach #define ISEXP(c) ((int)(c) == 'e' || (int)(c) == 'E')
53e8c23208Sginsbach #define ISODIGIT(c) ((int)(c) >= '0' && (int)(c) <= '7')
54e8c23208Sginsbach
55e8c23208Sginsbach /* Globals */
56e8c23208Sginsbach
57e8c23208Sginsbach const char *decimal_point = "."; /* default */
58e8c23208Sginsbach char default_format[] = { "%g" }; /* default */
59c6a10ee6Smlelstv char default_format_fmt[] = { "%%.%uf" };
60c6a10ee6Smlelstv #define MAXPRECISION 40
61e8c23208Sginsbach
62e8c23208Sginsbach /* Prototypes */
63e8c23208Sginsbach
64e8c23208Sginsbach double e_atof(const char *);
65e8c23208Sginsbach
66e8c23208Sginsbach int decimal_places(const char *);
67e8c23208Sginsbach int numeric(const char *);
68e8c23208Sginsbach int valid_format(const char *);
69e8c23208Sginsbach
70c6a10ee6Smlelstv unsigned get_precision(const char *, unsigned);
71c6a10ee6Smlelstv char *generate_format(double, double, double, int, char, char *);
72e8c23208Sginsbach char *unescape(char *);
73e8c23208Sginsbach
74c6a10ee6Smlelstv unsigned
get_precision(const char * number,unsigned minprec)75c6a10ee6Smlelstv get_precision(const char *number, unsigned minprec)
76c6a10ee6Smlelstv {
77c6a10ee6Smlelstv const char *p;
78c6a10ee6Smlelstv unsigned prec;
79c6a10ee6Smlelstv
80c6a10ee6Smlelstv p = strstr(number, decimal_point);
81c6a10ee6Smlelstv if (p) {
82c6a10ee6Smlelstv prec = strlen(number) - (p - number);
83c6a10ee6Smlelstv if (prec > 0)
84c6a10ee6Smlelstv prec -= 1;
85c6a10ee6Smlelstv if (prec > MAXPRECISION)
86c6a10ee6Smlelstv prec = MAXPRECISION;
87c6a10ee6Smlelstv } else
88c6a10ee6Smlelstv prec = 0;
89c6a10ee6Smlelstv
90c6a10ee6Smlelstv return prec < minprec ? minprec : prec;
91c6a10ee6Smlelstv }
92c6a10ee6Smlelstv
93e8c23208Sginsbach /*
94e8c23208Sginsbach * The seq command will print out a numeric sequence from 1, the default,
95e8c23208Sginsbach * to a user specified upper limit by 1. The lower bound and increment
96e8c23208Sginsbach * maybe indicated by the user on the command line. The sequence can
97e8c23208Sginsbach * be either whole, the default, or decimal numbers.
98e8c23208Sginsbach */
99e8c23208Sginsbach int
main(int argc,char * argv[])100e8c23208Sginsbach main(int argc, char *argv[])
101e8c23208Sginsbach {
102e8c23208Sginsbach int c = 0, errflg = 0;
103e8c23208Sginsbach int equalize = 0;
104c6a10ee6Smlelstv unsigned prec;
105e8c23208Sginsbach double first = 1.0;
106e8c23208Sginsbach double last = 0.0;
107e8c23208Sginsbach double incr = 0.0;
108*c2c241e6Smlelstv double prev;
109e8c23208Sginsbach struct lconv *locale;
110e8c23208Sginsbach char *fmt = NULL;
111e8c23208Sginsbach const char *sep = "\n";
11207ed41e7Schristos const char *term = "\n";
113e8c23208Sginsbach char pad = ZERO;
114c6a10ee6Smlelstv char buf[6]; /* %.MAXPRECISIONf */
115c6a10ee6Smlelstv
116e8c23208Sginsbach
117e8c23208Sginsbach /* Determine the locale's decimal point. */
118e8c23208Sginsbach locale = localeconv();
119e8c23208Sginsbach if (locale && locale->decimal_point && locale->decimal_point[0] != '\0')
120e8c23208Sginsbach decimal_point = locale->decimal_point;
121e8c23208Sginsbach
122e8c23208Sginsbach /*
123e8c23208Sginsbach * Process options, but handle negative numbers separately
124e8c23208Sginsbach * least they trip up getopt(3).
125e8c23208Sginsbach */
126e8c23208Sginsbach while ((optind < argc) && !numeric(argv[optind]) &&
127e8c23208Sginsbach (c = getopt(argc, argv, "f:hs:t:w")) != -1) {
128e8c23208Sginsbach
129e8c23208Sginsbach switch (c) {
130e8c23208Sginsbach case 'f': /* format (plan9) */
131e8c23208Sginsbach fmt = optarg;
132e8c23208Sginsbach equalize = 0;
133e8c23208Sginsbach break;
134e8c23208Sginsbach case 's': /* separator (GNU) */
135e8c23208Sginsbach sep = unescape(optarg);
136e8c23208Sginsbach break;
137e8c23208Sginsbach case 't': /* terminator (new) */
138e8c23208Sginsbach term = unescape(optarg);
139e8c23208Sginsbach break;
140e8c23208Sginsbach case 'w': /* equal width (plan9) */
141e8c23208Sginsbach if (!fmt)
142e8c23208Sginsbach if (equalize++)
143e8c23208Sginsbach pad = SPACE;
144e8c23208Sginsbach break;
145e8c23208Sginsbach case 'h': /* help (GNU) */
146e8c23208Sginsbach default:
147e8c23208Sginsbach errflg++;
148e8c23208Sginsbach break;
149e8c23208Sginsbach }
150e8c23208Sginsbach }
151e8c23208Sginsbach
152e8c23208Sginsbach argc -= optind;
153e8c23208Sginsbach argv += optind;
154e8c23208Sginsbach if (argc < 1 || argc > 3)
155e8c23208Sginsbach errflg++;
156e8c23208Sginsbach
157e8c23208Sginsbach if (errflg) {
158e8c23208Sginsbach fprintf(stderr,
159704c845eSwiz "usage: %s [-w] [-f format] [-s string] [-t string] [first [incr]] last\n",
160e8c23208Sginsbach getprogname());
161e8c23208Sginsbach exit(1);
162e8c23208Sginsbach }
163e8c23208Sginsbach
164e8c23208Sginsbach last = e_atof(argv[argc - 1]);
165c6a10ee6Smlelstv prec = get_precision(argv[argc - 1], 0);
166e8c23208Sginsbach
167c6a10ee6Smlelstv if (argc > 1) {
168e8c23208Sginsbach first = e_atof(argv[0]);
169c6a10ee6Smlelstv prec = get_precision(argv[0], prec);
170c6a10ee6Smlelstv }
171e8c23208Sginsbach
172e8c23208Sginsbach if (argc > 2) {
173e8c23208Sginsbach incr = e_atof(argv[1]);
174c6a10ee6Smlelstv prec = get_precision(argv[1], prec);
175e8c23208Sginsbach /* Plan 9/GNU don't do zero */
176e8c23208Sginsbach if (incr == 0.0)
177e8c23208Sginsbach errx(1, "zero %screment", (first < last)? "in" : "de");
178e8c23208Sginsbach }
179e8c23208Sginsbach
180e8c23208Sginsbach /* default is one for Plan 9/GNU work alike */
181e8c23208Sginsbach if (incr == 0.0)
182e8c23208Sginsbach incr = (first < last) ? 1.0 : -1.0;
183e8c23208Sginsbach
184e8c23208Sginsbach if (incr <= 0.0 && first < last)
185e8c23208Sginsbach errx(1, "needs positive increment");
186e8c23208Sginsbach
187e8c23208Sginsbach if (incr >= 0.0 && first > last)
188e8c23208Sginsbach errx(1, "needs negative decrement");
189e8c23208Sginsbach
190e8c23208Sginsbach if (fmt != NULL) {
191e8c23208Sginsbach if (!valid_format(fmt))
192e8c23208Sginsbach errx(1, "invalid format string: `%s'", fmt);
193e8c23208Sginsbach fmt = unescape(fmt);
194d6986f61Sdholland if (!valid_format(fmt))
195d6986f61Sdholland errx(1, "invalid format string");
196e8c23208Sginsbach /*
197e8c23208Sginsbach * XXX to be bug for bug compatible with Plan 9 add a
198e8c23208Sginsbach * newline if none found at the end of the format string.
199e8c23208Sginsbach */
200c6a10ee6Smlelstv } else {
201c6a10ee6Smlelstv if (prec == 0)
202c6a10ee6Smlelstv fmt = default_format;
203c6a10ee6Smlelstv else {
204c6a10ee6Smlelstv sprintf(buf, default_format_fmt, prec);
205c6a10ee6Smlelstv fmt = buf;
206c6a10ee6Smlelstv }
207c6a10ee6Smlelstv fmt = generate_format(first, incr, last, equalize, pad, fmt);
208c6a10ee6Smlelstv }
209e8c23208Sginsbach
210e8c23208Sginsbach if (incr > 0) {
211e8c23208Sginsbach printf(fmt, first);
212*c2c241e6Smlelstv prev = first;
21307ed41e7Schristos for (first += incr; first <= last; first += incr) {
214*c2c241e6Smlelstv if (first <= prev)
215*c2c241e6Smlelstv errx(1, "increment too small\n");
216e8c23208Sginsbach fputs(sep, stdout);
21707ed41e7Schristos printf(fmt, first);
218*c2c241e6Smlelstv prev = first;
219e8c23208Sginsbach }
220e8c23208Sginsbach } else {
221e8c23208Sginsbach printf(fmt, first);
222*c2c241e6Smlelstv prev = first;
22307ed41e7Schristos for (first += incr; first >= last; first += incr) {
224*c2c241e6Smlelstv if (first >= prev)
225*c2c241e6Smlelstv errx(1, "increment too small\n");
226e8c23208Sginsbach fputs(sep, stdout);
22707ed41e7Schristos printf(fmt, first);
228*c2c241e6Smlelstv prev = first;
229e8c23208Sginsbach }
230e8c23208Sginsbach }
231e8c23208Sginsbach if (term != NULL)
232e8c23208Sginsbach fputs(term, stdout);
233e8c23208Sginsbach
234e8c23208Sginsbach return (0);
235e8c23208Sginsbach }
236e8c23208Sginsbach
237e8c23208Sginsbach /*
238e8c23208Sginsbach * numeric - verify that string is numeric
239e8c23208Sginsbach */
240e8c23208Sginsbach int
numeric(const char * s)241e8c23208Sginsbach numeric(const char *s)
242e8c23208Sginsbach {
243e8c23208Sginsbach int seen_decimal_pt, decimal_pt_len;
244e8c23208Sginsbach
245e8c23208Sginsbach /* skip any sign */
246e8c23208Sginsbach if (ISSIGN((unsigned char)*s))
247e8c23208Sginsbach s++;
248e8c23208Sginsbach
249e8c23208Sginsbach seen_decimal_pt = 0;
250e8c23208Sginsbach decimal_pt_len = strlen(decimal_point);
251e8c23208Sginsbach while (*s) {
252e8c23208Sginsbach if (!isdigit((unsigned char)*s)) {
253e8c23208Sginsbach if (!seen_decimal_pt &&
254e8c23208Sginsbach strncmp(s, decimal_point, decimal_pt_len) == 0) {
255e8c23208Sginsbach s += decimal_pt_len;
256e8c23208Sginsbach seen_decimal_pt = 1;
257e8c23208Sginsbach continue;
258e8c23208Sginsbach }
259e8c23208Sginsbach if (ISEXP((unsigned char)*s)) {
260e8c23208Sginsbach s++;
261094918e4Sginsbach /* optional sign */
262094918e4Sginsbach if (ISSIGN((unsigned char)*s))
263e8c23208Sginsbach s++;
264e8c23208Sginsbach continue;
265e8c23208Sginsbach }
266e8c23208Sginsbach break;
267e8c23208Sginsbach }
268e8c23208Sginsbach s++;
269e8c23208Sginsbach }
270e8c23208Sginsbach return (*s == '\0');
271e8c23208Sginsbach }
272e8c23208Sginsbach
273e8c23208Sginsbach /*
274e8c23208Sginsbach * valid_format - validate user specified format string
275e8c23208Sginsbach */
276e8c23208Sginsbach int
valid_format(const char * fmt)277e8c23208Sginsbach valid_format(const char *fmt)
278e8c23208Sginsbach {
27976c7c8ecSdholland unsigned conversions = 0;
280e8c23208Sginsbach
281e8c23208Sginsbach while (*fmt != '\0') {
282e8c23208Sginsbach /* scan for conversions */
28376c7c8ecSdholland if (*fmt != '%') {
284e8c23208Sginsbach fmt++;
28576c7c8ecSdholland continue;
286e8c23208Sginsbach }
287e8c23208Sginsbach fmt++;
288e8c23208Sginsbach
28976c7c8ecSdholland /* allow %% but not things like %10% */
290e8c23208Sginsbach if (*fmt == '%') {
291e8c23208Sginsbach fmt++;
292e8c23208Sginsbach continue;
29376c7c8ecSdholland }
294e8c23208Sginsbach
29576c7c8ecSdholland /* flags */
29676c7c8ecSdholland while (*fmt != '\0' && strchr("#0- +'", *fmt)) {
29776c7c8ecSdholland fmt++;
29876c7c8ecSdholland }
29976c7c8ecSdholland
30076c7c8ecSdholland /* field width */
30176c7c8ecSdholland while (*fmt != '\0' && strchr("0123456789", *fmt)) {
30276c7c8ecSdholland fmt++;
30376c7c8ecSdholland }
30476c7c8ecSdholland
30576c7c8ecSdholland /* precision */
30676c7c8ecSdholland if (*fmt == '.') {
30776c7c8ecSdholland fmt++;
30876c7c8ecSdholland while (*fmt != '\0' && strchr("0123456789", *fmt)) {
30976c7c8ecSdholland fmt++;
31076c7c8ecSdholland }
31176c7c8ecSdholland }
31276c7c8ecSdholland
31376c7c8ecSdholland /* conversion */
31476c7c8ecSdholland switch (*fmt) {
31576c7c8ecSdholland case 'A':
31676c7c8ecSdholland case 'a':
31776c7c8ecSdholland case 'E':
31876c7c8ecSdholland case 'e':
31976c7c8ecSdholland case 'F':
32076c7c8ecSdholland case 'f':
32176c7c8ecSdholland case 'G':
32276c7c8ecSdholland case 'g':
32376c7c8ecSdholland /* floating point formats are accepted */
32476c7c8ecSdholland conversions++;
32576c7c8ecSdholland break;
32676c7c8ecSdholland default:
32776c7c8ecSdholland /* anything else is not */
32876c7c8ecSdholland return 0;
329e8c23208Sginsbach }
330e8c23208Sginsbach }
331e8c23208Sginsbach
332e8c23208Sginsbach return (conversions <= 1);
333e8c23208Sginsbach }
334e8c23208Sginsbach
335e8c23208Sginsbach /*
336e8c23208Sginsbach * unescape - handle C escapes in a string
337e8c23208Sginsbach */
338e8c23208Sginsbach char *
unescape(char * orig)339e8c23208Sginsbach unescape(char *orig)
340e8c23208Sginsbach {
341e8c23208Sginsbach char c, *cp, *new = orig;
342e8c23208Sginsbach int i;
343e8c23208Sginsbach
344e8c23208Sginsbach for (cp = orig; (*orig = *cp); cp++, orig++) {
345e8c23208Sginsbach if (*cp != '\\')
346e8c23208Sginsbach continue;
347e8c23208Sginsbach
348e8c23208Sginsbach switch (*++cp) {
349e8c23208Sginsbach case 'a': /* alert (bell) */
350e8c23208Sginsbach *orig = '\a';
351e8c23208Sginsbach continue;
352e8c23208Sginsbach case 'b': /* backspace */
353e8c23208Sginsbach *orig = '\b';
354e8c23208Sginsbach continue;
355e8c23208Sginsbach case 'e': /* escape */
356d7beb5faScheusov *orig = '\x1B';
357e8c23208Sginsbach continue;
358e8c23208Sginsbach case 'f': /* formfeed */
359e8c23208Sginsbach *orig = '\f';
360e8c23208Sginsbach continue;
361e8c23208Sginsbach case 'n': /* newline */
362e8c23208Sginsbach *orig = '\n';
363e8c23208Sginsbach continue;
364e8c23208Sginsbach case 'r': /* carriage return */
365e8c23208Sginsbach *orig = '\r';
366e8c23208Sginsbach continue;
367e8c23208Sginsbach case 't': /* horizontal tab */
368e8c23208Sginsbach *orig = '\t';
369e8c23208Sginsbach continue;
370e8c23208Sginsbach case 'v': /* vertical tab */
371e8c23208Sginsbach *orig = '\v';
372e8c23208Sginsbach continue;
373e8c23208Sginsbach case '\\': /* backslash */
374e8c23208Sginsbach *orig = '\\';
375e8c23208Sginsbach continue;
376e8c23208Sginsbach case '\'': /* single quote */
377e8c23208Sginsbach *orig = '\'';
378e8c23208Sginsbach continue;
379e8c23208Sginsbach case '\"': /* double quote */
380e8c23208Sginsbach *orig = '"';
381e8c23208Sginsbach continue;
382e8c23208Sginsbach case '0':
383e8c23208Sginsbach case '1':
384e8c23208Sginsbach case '2':
385e8c23208Sginsbach case '3': /* octal */
386e8c23208Sginsbach case '4':
387e8c23208Sginsbach case '5':
388e8c23208Sginsbach case '6':
389e8c23208Sginsbach case '7': /* number */
390e8c23208Sginsbach for (i = 0, c = 0;
391e8c23208Sginsbach ISODIGIT((unsigned char)*cp) && i < 3;
392e8c23208Sginsbach i++, cp++) {
393e8c23208Sginsbach c <<= 3;
394e8c23208Sginsbach c |= (*cp - '0');
395e8c23208Sginsbach }
396e8c23208Sginsbach *orig = c;
3979470f2d1Sginsbach --cp;
398e8c23208Sginsbach continue;
39981821b80Sginsbach case 'x': /* hexadecimal number */
400e8c23208Sginsbach cp++; /* skip 'x' */
401e8c23208Sginsbach for (i = 0, c = 0;
402e8c23208Sginsbach isxdigit((unsigned char)*cp) && i < 2;
403e8c23208Sginsbach i++, cp++) {
404e8c23208Sginsbach c <<= 4;
405e8c23208Sginsbach if (isdigit((unsigned char)*cp))
406e8c23208Sginsbach c |= (*cp - '0');
407e8c23208Sginsbach else
408e8c23208Sginsbach c |= ((toupper((unsigned char)*cp) -
409e8c23208Sginsbach 'A') + 10);
410e8c23208Sginsbach }
411e8c23208Sginsbach *orig = c;
4129470f2d1Sginsbach --cp;
413e8c23208Sginsbach continue;
414e8c23208Sginsbach default:
415e8c23208Sginsbach --cp;
416e8c23208Sginsbach break;
417e8c23208Sginsbach }
418e8c23208Sginsbach }
419e8c23208Sginsbach
420e8c23208Sginsbach return (new);
421e8c23208Sginsbach }
422e8c23208Sginsbach
423e8c23208Sginsbach /*
424e8c23208Sginsbach * e_atof - convert an ASCII string to a double
425e8c23208Sginsbach * exit if string is not a valid double, or if converted value would
426e8c23208Sginsbach * cause overflow or underflow
427e8c23208Sginsbach */
428e8c23208Sginsbach double
e_atof(const char * num)429e8c23208Sginsbach e_atof(const char *num)
430e8c23208Sginsbach {
431e8c23208Sginsbach char *endp;
432e8c23208Sginsbach double dbl;
433e8c23208Sginsbach
434e8c23208Sginsbach errno = 0;
435e8c23208Sginsbach dbl = strtod(num, &endp);
436e8c23208Sginsbach
437e8c23208Sginsbach if (errno == ERANGE)
438e8c23208Sginsbach /* under or overflow */
439e8c23208Sginsbach err(2, "%s", num);
440e8c23208Sginsbach else if (*endp != '\0')
441e8c23208Sginsbach /* "junk" left in number */
442e8c23208Sginsbach errx(2, "invalid floating point argument: %s", num);
443e8c23208Sginsbach
444e8c23208Sginsbach /* zero shall have no sign */
445e8c23208Sginsbach if (dbl == -0.0)
446e8c23208Sginsbach dbl = 0;
447e8c23208Sginsbach return (dbl);
448e8c23208Sginsbach }
449e8c23208Sginsbach
450e8c23208Sginsbach /*
451e8c23208Sginsbach * decimal_places - count decimal places in a number (string)
452e8c23208Sginsbach */
453e8c23208Sginsbach int
decimal_places(const char * number)454e8c23208Sginsbach decimal_places(const char *number)
455e8c23208Sginsbach {
456e8c23208Sginsbach int places = 0;
457e8c23208Sginsbach char *dp;
458e8c23208Sginsbach
459e8c23208Sginsbach /* look for a decimal point */
460e8c23208Sginsbach if ((dp = strstr(number, decimal_point))) {
461e8c23208Sginsbach dp += strlen(decimal_point);
462e8c23208Sginsbach
463e8c23208Sginsbach while (isdigit((unsigned char)*dp++))
464e8c23208Sginsbach places++;
465e8c23208Sginsbach }
466e8c23208Sginsbach return (places);
467e8c23208Sginsbach }
468e8c23208Sginsbach
469e8c23208Sginsbach /*
470e8c23208Sginsbach * generate_format - create a format string
471e8c23208Sginsbach *
47281821b80Sginsbach * XXX to be bug for bug compatible with Plan9 and GNU return "%g"
473e8c23208Sginsbach * when "%g" prints as "%e" (this way no width adjustments are made)
474e8c23208Sginsbach */
475e8c23208Sginsbach char *
generate_format(double first,double incr,double last,int equalize,char pad,char * deffmt)476c6a10ee6Smlelstv generate_format(double first, double incr, double last,
477c6a10ee6Smlelstv int equalize, char pad, char *deffmt)
478e8c23208Sginsbach {
479e8c23208Sginsbach static char buf[256];
480e8c23208Sginsbach char cc = '\0';
481e8c23208Sginsbach int precision, width1, width2, places;
482e8c23208Sginsbach
483e8c23208Sginsbach if (equalize == 0)
484c6a10ee6Smlelstv return deffmt;
485e8c23208Sginsbach
486e8c23208Sginsbach /* figure out "last" value printed */
487e8c23208Sginsbach if (first > last)
488e8c23208Sginsbach last = first - incr * floor((first - last) / incr);
489e8c23208Sginsbach else
490e8c23208Sginsbach last = first + incr * floor((last - first) / incr);
491e8c23208Sginsbach
492c6a10ee6Smlelstv sprintf(buf, deffmt, incr);
493e8c23208Sginsbach if (strchr(buf, 'e'))
494e8c23208Sginsbach cc = 'e';
495e8c23208Sginsbach precision = decimal_places(buf);
496e8c23208Sginsbach
497c6a10ee6Smlelstv width1 = sprintf(buf, deffmt, first);
498e8c23208Sginsbach if (strchr(buf, 'e'))
499e8c23208Sginsbach cc = 'e';
500e8c23208Sginsbach if ((places = decimal_places(buf)))
501e8c23208Sginsbach width1 -= (places + strlen(decimal_point));
502e8c23208Sginsbach
503e8c23208Sginsbach precision = MAX(places, precision);
504e8c23208Sginsbach
505c6a10ee6Smlelstv width2 = sprintf(buf, deffmt, last);
506e8c23208Sginsbach if (strchr(buf, 'e'))
507e8c23208Sginsbach cc = 'e';
508e8c23208Sginsbach if ((places = decimal_places(buf)))
509e8c23208Sginsbach width2 -= (places + strlen(decimal_point));
510e8c23208Sginsbach
511e8c23208Sginsbach if (precision) {
512e8c23208Sginsbach sprintf(buf, "%%%c%d.%d%c", pad,
513e8c23208Sginsbach MAX(width1, width2) + (int) strlen(decimal_point) +
514e8c23208Sginsbach precision, precision, (cc) ? cc : 'f');
515e8c23208Sginsbach } else {
516e8c23208Sginsbach sprintf(buf, "%%%c%d%c", pad, MAX(width1, width2),
517e8c23208Sginsbach (cc) ? cc : 'g');
518e8c23208Sginsbach }
519e8c23208Sginsbach
520e8c23208Sginsbach return (buf);
521e8c23208Sginsbach }
522