xref: /freebsd/usr.bin/seq/seq.c (revision eb4d13126d85665197ed7efc17c1ab442daa70ea)
1bf855dd3SXin LI /*	$NetBSD: seq.c,v 1.7 2010/05/27 08:40:19 dholland Exp $	*/
21de7b4b8SPedro F. Giffuni /*-
3b61a5730SWarner Losh  * SPDX-License-Identifier: BSD-2-Clause
41de7b4b8SPedro F. Giffuni  *
5208987a5SXin LI  * Copyright (c) 2005 The NetBSD Foundation, Inc.
6208987a5SXin LI  * All rights reserved.
7208987a5SXin LI  *
8208987a5SXin LI  * This code is derived from software contributed to The NetBSD Foundation
9208987a5SXin LI  * by Brian Ginsbach.
10208987a5SXin LI  *
11208987a5SXin LI  * Redistribution and use in source and binary forms, with or without
12208987a5SXin LI  * modification, are permitted provided that the following conditions
13208987a5SXin LI  * are met:
14208987a5SXin LI  * 1. Redistributions of source code must retain the above copyright
15208987a5SXin LI  *    notice, this list of conditions and the following disclaimer.
16208987a5SXin LI  * 2. Redistributions in binary form must reproduce the above copyright
17208987a5SXin LI  *    notice, this list of conditions and the following disclaimer in the
18208987a5SXin LI  *    documentation and/or other materials provided with the distribution.
19208987a5SXin LI  *
20208987a5SXin LI  * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
21208987a5SXin LI  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
22208987a5SXin LI  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
23208987a5SXin LI  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
24208987a5SXin LI  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
25208987a5SXin LI  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
26208987a5SXin LI  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
27208987a5SXin LI  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
28208987a5SXin LI  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
29208987a5SXin LI  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
30208987a5SXin LI  * POSSIBILITY OF SUCH DAMAGE.
31208987a5SXin LI  */
32208987a5SXin LI 
33208987a5SXin LI #include <sys/cdefs.h>
34208987a5SXin LI #include <ctype.h>
35208987a5SXin LI #include <err.h>
36208987a5SXin LI #include <errno.h>
37a3f2c2feSKyle Evans #include <getopt.h>
38208987a5SXin LI #include <math.h>
39208987a5SXin LI #include <locale.h>
40208987a5SXin LI #include <stdio.h>
41208987a5SXin LI #include <stdlib.h>
42208987a5SXin LI #include <string.h>
43208987a5SXin LI #include <unistd.h>
44208987a5SXin LI 
45208987a5SXin LI #define ZERO	'0'
46208987a5SXin LI #define SPACE	' '
47208987a5SXin LI 
48208987a5SXin LI #define MAX(a, b)	(((a) < (b))? (b) : (a))
49208987a5SXin LI #define ISSIGN(c)	((int)(c) == '-' || (int)(c) == '+')
50208987a5SXin LI #define ISEXP(c)	((int)(c) == 'e' || (int)(c) == 'E')
51208987a5SXin LI #define ISODIGIT(c)	((int)(c) >= '0' && (int)(c) <= '7')
52208987a5SXin LI 
53208987a5SXin LI /* Globals */
54208987a5SXin LI 
55cfbd8d46SEd Schouten static const char *decimal_point = ".";	/* default */
56cfbd8d46SEd Schouten static char default_format[] = { "%g" };	/* default */
57208987a5SXin LI 
588f8da1bcSEd Maste static const struct option long_opts[] = {
59ad4e78b5SKyle Evans 	{"format",	required_argument,	NULL, 'f'},
60ad4e78b5SKyle Evans 	{"separator",	required_argument,	NULL, 's'},
61ad4e78b5SKyle Evans 	{"terminator",	required_argument,	NULL, 't'},
62ad4e78b5SKyle Evans 	{"equal-width",	no_argument,		NULL, 'w'},
63ad4e78b5SKyle Evans 	{NULL,		no_argument,		NULL, 0}
64ad4e78b5SKyle Evans };
65ad4e78b5SKyle Evans 
66208987a5SXin LI /* Prototypes */
67208987a5SXin LI 
68cfbd8d46SEd Schouten static double e_atof(const char *);
69208987a5SXin LI 
70cfbd8d46SEd Schouten static int decimal_places(const char *);
71cfbd8d46SEd Schouten static int numeric(const char *);
72cfbd8d46SEd Schouten static int valid_format(const char *);
73208987a5SXin LI 
74cfbd8d46SEd Schouten static char *generate_format(double, double, double, int, char);
75cfbd8d46SEd Schouten static char *unescape(char *);
76208987a5SXin LI 
77208987a5SXin LI /*
78208987a5SXin LI  * The seq command will print out a numeric sequence from 1, the default,
79208987a5SXin LI  * to a user specified upper limit by 1.  The lower bound and increment
80208987a5SXin LI  * maybe indicated by the user on the command line.  The sequence can
81208987a5SXin LI  * be either whole, the default, or decimal numbers.
82208987a5SXin LI  */
83208987a5SXin LI int
main(int argc,char * argv[])84208987a5SXin LI main(int argc, char *argv[])
85208987a5SXin LI {
863049d4ccSConrad Meyer 	const char *sep, *term;
87208987a5SXin LI 	struct lconv *locale;
88e54db9a9SEd Maste 	char pad, *fmt, *cur_print, *last_print, *prev_print;
89e54db9a9SEd Maste 	double first, last, incr, prev, cur, step;
903049d4ccSConrad Meyer 	int c, errflg, equalize;
913049d4ccSConrad Meyer 
923049d4ccSConrad Meyer 	pad = ZERO;
933049d4ccSConrad Meyer 	fmt = NULL;
943049d4ccSConrad Meyer 	first = 1.0;
95e54db9a9SEd Maste 	last = incr = prev = 0.0;
963049d4ccSConrad Meyer 	c = errflg = equalize = 0;
973049d4ccSConrad Meyer 	sep = "\n";
983049d4ccSConrad Meyer 	term = NULL;
99208987a5SXin LI 
100208987a5SXin LI 	/* Determine the locale's decimal point. */
101208987a5SXin LI 	locale = localeconv();
102208987a5SXin LI 	if (locale && locale->decimal_point && locale->decimal_point[0] != '\0')
103208987a5SXin LI 		decimal_point = locale->decimal_point;
104208987a5SXin LI 
105208987a5SXin LI 	/*
106208987a5SXin LI 	 * Process options, but handle negative numbers separately
107208987a5SXin LI 	 * least they trip up getopt(3).
108208987a5SXin LI 	 */
109208987a5SXin LI 	while ((optind < argc) && !numeric(argv[optind]) &&
110a3f2c2feSKyle Evans 	    (c = getopt_long(argc, argv, "+f:hs:t:w", long_opts, NULL)) != -1) {
111208987a5SXin LI 
112208987a5SXin LI 		switch (c) {
113208987a5SXin LI 		case 'f':	/* format (plan9) */
114208987a5SXin LI 			fmt = optarg;
115208987a5SXin LI 			equalize = 0;
116208987a5SXin LI 			break;
117208987a5SXin LI 		case 's':	/* separator (GNU) */
118208987a5SXin LI 			sep = unescape(optarg);
119208987a5SXin LI 			break;
120208987a5SXin LI 		case 't':	/* terminator (new) */
121208987a5SXin LI 			term = unescape(optarg);
122208987a5SXin LI 			break;
123208987a5SXin LI 		case 'w':	/* equal width (plan9) */
124208987a5SXin LI 			if (!fmt)
125208987a5SXin LI 				if (equalize++)
126208987a5SXin LI 					pad = SPACE;
127208987a5SXin LI 			break;
128208987a5SXin LI 		case 'h':	/* help (GNU) */
129208987a5SXin LI 		default:
130208987a5SXin LI 			errflg++;
131208987a5SXin LI 			break;
132208987a5SXin LI 		}
133208987a5SXin LI 	}
134208987a5SXin LI 
135208987a5SXin LI 	argc -= optind;
136208987a5SXin LI 	argv += optind;
137208987a5SXin LI 	if (argc < 1 || argc > 3)
138208987a5SXin LI 		errflg++;
139208987a5SXin LI 
140208987a5SXin LI 	if (errflg) {
141208987a5SXin LI 		fprintf(stderr,
142208987a5SXin LI 		    "usage: %s [-w] [-f format] [-s string] [-t string] [first [incr]] last\n",
143208987a5SXin LI 		    getprogname());
144208987a5SXin LI 		exit(1);
145208987a5SXin LI 	}
146208987a5SXin LI 
147208987a5SXin LI 	last = e_atof(argv[argc - 1]);
148208987a5SXin LI 
149208987a5SXin LI 	if (argc > 1)
150208987a5SXin LI 		first = e_atof(argv[0]);
151208987a5SXin LI 
152208987a5SXin LI 	if (argc > 2) {
153208987a5SXin LI 		incr = e_atof(argv[1]);
154208987a5SXin LI 		/* Plan 9/GNU don't do zero */
155208987a5SXin LI 		if (incr == 0.0)
156208987a5SXin LI 			errx(1, "zero %screment", (first < last) ? "in" : "de");
157208987a5SXin LI 	}
158208987a5SXin LI 
159208987a5SXin LI 	/* default is one for Plan 9/GNU work alike */
160208987a5SXin LI 	if (incr == 0.0)
161208987a5SXin LI 		incr = (first < last) ? 1.0 : -1.0;
162208987a5SXin LI 
163208987a5SXin LI 	if (incr <= 0.0 && first < last)
164208987a5SXin LI 		errx(1, "needs positive increment");
165208987a5SXin LI 
166208987a5SXin LI 	if (incr >= 0.0 && first > last)
167208987a5SXin LI 		errx(1, "needs negative decrement");
168208987a5SXin LI 
169208987a5SXin LI 	if (fmt != NULL) {
170208987a5SXin LI 		if (!valid_format(fmt))
171208987a5SXin LI 			errx(1, "invalid format string: `%s'", fmt);
172208987a5SXin LI 		fmt = unescape(fmt);
173bf855dd3SXin LI 		if (!valid_format(fmt))
174bf855dd3SXin LI 			errx(1, "invalid format string");
175208987a5SXin LI 		/*
176208987a5SXin LI 		 * XXX to be bug for bug compatible with Plan 9 add a
177208987a5SXin LI 		 * newline if none found at the end of the format string.
178208987a5SXin LI 		 */
179208987a5SXin LI 	} else
180208987a5SXin LI 		fmt = generate_format(first, incr, last, equalize, pad);
181208987a5SXin LI 
1823049d4ccSConrad Meyer 	for (step = 1, cur = first; incr > 0 ? cur <= last : cur >= last;
1833049d4ccSConrad Meyer 	    cur = first + incr * step++) {
184*eb4d1312SPawel Jakub Dawidek 		if (step > 1)
1853049d4ccSConrad Meyer 			fputs(sep, stdout);
186*eb4d1312SPawel Jakub Dawidek 		printf(fmt, cur);
187e54db9a9SEd Maste 		prev = cur;
1883049d4ccSConrad Meyer 	}
1893049d4ccSConrad Meyer 
1903049d4ccSConrad Meyer 	/*
1913049d4ccSConrad Meyer 	 * Did we miss the last value of the range in the loop above?
1923049d4ccSConrad Meyer 	 *
1933049d4ccSConrad Meyer 	 * We might have, so check if the printable version of the last
1943049d4ccSConrad Meyer 	 * computed value ('cur') and desired 'last' value are equal.  If they
195e54db9a9SEd Maste 	 * are equal after formatting truncation, but 'cur' and 'prev' are not
196e54db9a9SEd Maste 	 * equal, it means the exit condition of the loop held true due to a
197e54db9a9SEd Maste 	 * rounding error and we still need to print 'last'.
1983049d4ccSConrad Meyer 	 */
1995dae8905SEd Maste 	if (asprintf(&cur_print, fmt, cur) < 0 ||
2005dae8905SEd Maste 	    asprintf(&last_print, fmt, last) < 0 ||
2015dae8905SEd Maste 	    asprintf(&prev_print, fmt, prev) < 0) {
202e54db9a9SEd Maste 		err(1, "asprintf");
203e54db9a9SEd Maste 	}
204e54db9a9SEd Maste 	if (strcmp(cur_print, last_print) == 0 &&
205e54db9a9SEd Maste 	    strcmp(cur_print, prev_print) != 0) {
206208987a5SXin LI 		fputs(sep, stdout);
207*eb4d1312SPawel Jakub Dawidek 		fputs(last_print, stdout);
208208987a5SXin LI 	}
2093049d4ccSConrad Meyer 	free(cur_print);
2103049d4ccSConrad Meyer 	free(last_print);
211e54db9a9SEd Maste 	free(prev_print);
2123049d4ccSConrad Meyer 
213*eb4d1312SPawel Jakub Dawidek 	if (term != NULL) {
214*eb4d1312SPawel Jakub Dawidek 		fputs(sep, stdout);
215208987a5SXin LI 		fputs(term, stdout);
216*eb4d1312SPawel Jakub Dawidek 	}
217*eb4d1312SPawel Jakub Dawidek 
218*eb4d1312SPawel Jakub Dawidek 	fputs("\n", stdout);
219208987a5SXin LI 
220208987a5SXin LI 	return (0);
221208987a5SXin LI }
222208987a5SXin LI 
223208987a5SXin LI /*
224208987a5SXin LI  * numeric - verify that string is numeric
225208987a5SXin LI  */
226cfbd8d46SEd Schouten static int
numeric(const char * s)227208987a5SXin LI numeric(const char *s)
228208987a5SXin LI {
229208987a5SXin LI 	int seen_decimal_pt, decimal_pt_len;
230208987a5SXin LI 
231208987a5SXin LI 	/* skip any sign */
232208987a5SXin LI 	if (ISSIGN((unsigned char)*s))
233208987a5SXin LI 		s++;
234208987a5SXin LI 
235208987a5SXin LI 	seen_decimal_pt = 0;
236208987a5SXin LI 	decimal_pt_len = strlen(decimal_point);
237208987a5SXin LI 	while (*s) {
238208987a5SXin LI 		if (!isdigit((unsigned char)*s)) {
239208987a5SXin LI 			if (!seen_decimal_pt &&
240208987a5SXin LI 			    strncmp(s, decimal_point, decimal_pt_len) == 0) {
241208987a5SXin LI 				s += decimal_pt_len;
242208987a5SXin LI 				seen_decimal_pt = 1;
243208987a5SXin LI 				continue;
244208987a5SXin LI 			}
245208987a5SXin LI 			if (ISEXP((unsigned char)*s)) {
246208987a5SXin LI 				s++;
2470a167a9bSXin LI 				if (ISSIGN((unsigned char)*s) ||
2480a167a9bSXin LI 				    isdigit((unsigned char)*s)) {
249208987a5SXin LI 					s++;
250208987a5SXin LI 					continue;
251208987a5SXin LI 				}
252208987a5SXin LI 			}
253208987a5SXin LI 			break;
254208987a5SXin LI 		}
255208987a5SXin LI 		s++;
256208987a5SXin LI 	}
257208987a5SXin LI 	return (*s == '\0');
258208987a5SXin LI }
259208987a5SXin LI 
260208987a5SXin LI /*
261208987a5SXin LI  * valid_format - validate user specified format string
262208987a5SXin LI  */
263cfbd8d46SEd Schouten static int
valid_format(const char * fmt)264208987a5SXin LI valid_format(const char *fmt)
265208987a5SXin LI {
266bf855dd3SXin LI 	unsigned conversions = 0;
267208987a5SXin LI 
268208987a5SXin LI 	while (*fmt != '\0') {
269208987a5SXin LI 		/* scan for conversions */
270bf855dd3SXin LI 		if (*fmt != '%') {
271208987a5SXin LI 			fmt++;
272bf855dd3SXin LI 			continue;
273208987a5SXin LI 		}
274208987a5SXin LI 		fmt++;
275208987a5SXin LI 
276bf855dd3SXin LI 		/* allow %% but not things like %10% */
277208987a5SXin LI 		if (*fmt == '%') {
278208987a5SXin LI 			fmt++;
279208987a5SXin LI 			continue;
280bf855dd3SXin LI 		}
281208987a5SXin LI 
282bf855dd3SXin LI 		/* flags */
283bf855dd3SXin LI 		while (*fmt != '\0' && strchr("#0- +'", *fmt)) {
284bf855dd3SXin LI 			fmt++;
285bf855dd3SXin LI 		}
286bf855dd3SXin LI 
287bf855dd3SXin LI 		/* field width */
288bf855dd3SXin LI 		while (*fmt != '\0' && strchr("0123456789", *fmt)) {
289bf855dd3SXin LI 			fmt++;
290bf855dd3SXin LI 		}
291bf855dd3SXin LI 
292bf855dd3SXin LI 		/* precision */
293bf855dd3SXin LI 		if (*fmt == '.') {
294bf855dd3SXin LI 			fmt++;
295bf855dd3SXin LI 			while (*fmt != '\0' && strchr("0123456789", *fmt)) {
296bf855dd3SXin LI 				fmt++;
297bf855dd3SXin LI 			}
298bf855dd3SXin LI 		}
299bf855dd3SXin LI 
300bf855dd3SXin LI 		/* conversion */
301bf855dd3SXin LI 		switch (*fmt) {
302bf855dd3SXin LI 		case 'A':
303bf855dd3SXin LI 		case 'a':
304bf855dd3SXin LI 		case 'E':
305bf855dd3SXin LI 		case 'e':
306bf855dd3SXin LI 		case 'F':
307bf855dd3SXin LI 		case 'f':
308bf855dd3SXin LI 		case 'G':
309bf855dd3SXin LI 		case 'g':
310bf855dd3SXin LI 			/* floating point formats are accepted */
311bf855dd3SXin LI 			conversions++;
312bf855dd3SXin LI 			break;
313bf855dd3SXin LI 		default:
314bf855dd3SXin LI 			/* anything else is not */
315bf855dd3SXin LI 			return 0;
316208987a5SXin LI 		}
317208987a5SXin LI 	}
318208987a5SXin LI 
319905fdc3fSConrad Meyer 	/* PR 236347 -- user format strings must have a conversion */
320905fdc3fSConrad Meyer 	return (conversions == 1);
321208987a5SXin LI }
322208987a5SXin LI 
323208987a5SXin LI /*
324208987a5SXin LI  * unescape - handle C escapes in a string
325208987a5SXin LI  */
326cfbd8d46SEd Schouten static char *
unescape(char * orig)327208987a5SXin LI unescape(char *orig)
328208987a5SXin LI {
329208987a5SXin LI 	char c, *cp, *new = orig;
330208987a5SXin LI 	int i;
331208987a5SXin LI 
332208987a5SXin LI 	for (cp = orig; (*orig = *cp); cp++, orig++) {
333208987a5SXin LI 		if (*cp != '\\')
334208987a5SXin LI 			continue;
335208987a5SXin LI 
336208987a5SXin LI 		switch (*++cp) {
337208987a5SXin LI 		case 'a':	/* alert (bell) */
338208987a5SXin LI 			*orig = '\a';
339208987a5SXin LI 			continue;
340208987a5SXin LI 		case 'b':	/* backspace */
341208987a5SXin LI 			*orig = '\b';
342208987a5SXin LI 			continue;
343208987a5SXin LI 		case 'e':	/* escape */
344208987a5SXin LI 			*orig = '\e';
345208987a5SXin LI 			continue;
346208987a5SXin LI 		case 'f':	/* formfeed */
347208987a5SXin LI 			*orig = '\f';
348208987a5SXin LI 			continue;
349208987a5SXin LI 		case 'n':	/* newline */
350208987a5SXin LI 			*orig = '\n';
351208987a5SXin LI 			continue;
352208987a5SXin LI 		case 'r':	/* carriage return */
353208987a5SXin LI 			*orig = '\r';
354208987a5SXin LI 			continue;
355208987a5SXin LI 		case 't':	/* horizontal tab */
356208987a5SXin LI 			*orig = '\t';
357208987a5SXin LI 			continue;
358208987a5SXin LI 		case 'v':	/* vertical tab */
359208987a5SXin LI 			*orig = '\v';
360208987a5SXin LI 			continue;
361208987a5SXin LI 		case '\\':	/* backslash */
362208987a5SXin LI 			*orig = '\\';
363208987a5SXin LI 			continue;
364208987a5SXin LI 		case '\'':	/* single quote */
365208987a5SXin LI 			*orig = '\'';
366208987a5SXin LI 			continue;
367208987a5SXin LI 		case '\"':	/* double quote */
368208987a5SXin LI 			*orig = '"';
369208987a5SXin LI 			continue;
370208987a5SXin LI 		case '0':
371208987a5SXin LI 		case '1':
372208987a5SXin LI 		case '2':
373208987a5SXin LI 		case '3':	/* octal */
374208987a5SXin LI 		case '4':
375208987a5SXin LI 		case '5':
376208987a5SXin LI 		case '6':
377208987a5SXin LI 		case '7':	/* number */
378208987a5SXin LI 			for (i = 0, c = 0;
379208987a5SXin LI 			     ISODIGIT((unsigned char)*cp) && i < 3;
380208987a5SXin LI 			     i++, cp++) {
381208987a5SXin LI 				c <<= 3;
382208987a5SXin LI 				c |= (*cp - '0');
383208987a5SXin LI 			}
384208987a5SXin LI 			*orig = c;
385208987a5SXin LI 			--cp;
386208987a5SXin LI 			continue;
387b1ce21c6SRebecca Cran 		case 'x':	/* hexadecimal number */
388208987a5SXin LI 			cp++;	/* skip 'x' */
389208987a5SXin LI 			for (i = 0, c = 0;
390208987a5SXin LI 			     isxdigit((unsigned char)*cp) && i < 2;
391208987a5SXin LI 			     i++, cp++) {
392208987a5SXin LI 				c <<= 4;
393208987a5SXin LI 				if (isdigit((unsigned char)*cp))
394208987a5SXin LI 					c |= (*cp - '0');
395208987a5SXin LI 				else
396208987a5SXin LI 					c |= ((toupper((unsigned char)*cp) -
397208987a5SXin LI 					    'A') + 10);
398208987a5SXin LI 			}
399208987a5SXin LI 			*orig = c;
400208987a5SXin LI 			--cp;
401208987a5SXin LI 			continue;
402208987a5SXin LI 		default:
403208987a5SXin LI 			--cp;
404208987a5SXin LI 			break;
405208987a5SXin LI 		}
406208987a5SXin LI 	}
407208987a5SXin LI 
408208987a5SXin LI 	return (new);
409208987a5SXin LI }
410208987a5SXin LI 
411208987a5SXin LI /*
412208987a5SXin LI  * e_atof - convert an ASCII string to a double
413208987a5SXin LI  *	exit if string is not a valid double, or if converted value would
414208987a5SXin LI  *	cause overflow or underflow
415208987a5SXin LI  */
416cfbd8d46SEd Schouten static double
e_atof(const char * num)417208987a5SXin LI e_atof(const char *num)
418208987a5SXin LI {
419208987a5SXin LI 	char *endp;
420208987a5SXin LI 	double dbl;
421208987a5SXin LI 
422208987a5SXin LI 	errno = 0;
423208987a5SXin LI 	dbl = strtod(num, &endp);
424208987a5SXin LI 
425208987a5SXin LI 	if (errno == ERANGE)
426208987a5SXin LI 		/* under or overflow */
427208987a5SXin LI 		err(2, "%s", num);
428208987a5SXin LI 	else if (*endp != '\0')
429208987a5SXin LI 		/* "junk" left in number */
430208987a5SXin LI 		errx(2, "invalid floating point argument: %s", num);
431208987a5SXin LI 
432208987a5SXin LI 	/* zero shall have no sign */
433208987a5SXin LI 	if (dbl == -0.0)
434208987a5SXin LI 		dbl = 0;
435208987a5SXin LI 	return (dbl);
436208987a5SXin LI }
437208987a5SXin LI 
438208987a5SXin LI /*
439208987a5SXin LI  * decimal_places - count decimal places in a number (string)
440208987a5SXin LI  */
441cfbd8d46SEd Schouten static int
decimal_places(const char * number)442208987a5SXin LI decimal_places(const char *number)
443208987a5SXin LI {
444208987a5SXin LI 	int places = 0;
445208987a5SXin LI 	char *dp;
446208987a5SXin LI 
447208987a5SXin LI 	/* look for a decimal point */
448208987a5SXin LI 	if ((dp = strstr(number, decimal_point))) {
449208987a5SXin LI 		dp += strlen(decimal_point);
450208987a5SXin LI 
451208987a5SXin LI 		while (isdigit((unsigned char)*dp++))
452208987a5SXin LI 			places++;
453208987a5SXin LI 	}
454208987a5SXin LI 	return (places);
455208987a5SXin LI }
456208987a5SXin LI 
457208987a5SXin LI /*
458208987a5SXin LI  * generate_format - create a format string
459208987a5SXin LI  *
460b1ce21c6SRebecca Cran  * XXX to be bug for bug compatible with Plan9 and GNU return "%g"
461208987a5SXin LI  * when "%g" prints as "%e" (this way no width adjustments are made)
462208987a5SXin LI  */
463cfbd8d46SEd Schouten static char *
generate_format(double first,double incr,double last,int equalize,char pad)464208987a5SXin LI generate_format(double first, double incr, double last, int equalize, char pad)
465208987a5SXin LI {
466208987a5SXin LI 	static char buf[256];
467208987a5SXin LI 	char cc = '\0';
468208987a5SXin LI 	int precision, width1, width2, places;
469208987a5SXin LI 
470208987a5SXin LI 	if (equalize == 0)
471208987a5SXin LI 		return (default_format);
472208987a5SXin LI 
473208987a5SXin LI 	/* figure out "last" value printed */
474208987a5SXin LI 	if (first > last)
475208987a5SXin LI 		last = first - incr * floor((first - last) / incr);
476208987a5SXin LI 	else
477208987a5SXin LI 		last = first + incr * floor((last - first) / incr);
478208987a5SXin LI 
479208987a5SXin LI 	sprintf(buf, "%g", incr);
480208987a5SXin LI 	if (strchr(buf, 'e'))
481208987a5SXin LI 		cc = 'e';
482208987a5SXin LI 	precision = decimal_places(buf);
483208987a5SXin LI 
484208987a5SXin LI 	width1 = sprintf(buf, "%g", first);
485208987a5SXin LI 	if (strchr(buf, 'e'))
486208987a5SXin LI 		cc = 'e';
487208987a5SXin LI 	if ((places = decimal_places(buf)))
488208987a5SXin LI 		width1 -= (places + strlen(decimal_point));
489208987a5SXin LI 
490208987a5SXin LI 	precision = MAX(places, precision);
491208987a5SXin LI 
492208987a5SXin LI 	width2 = sprintf(buf, "%g", last);
493208987a5SXin LI 	if (strchr(buf, 'e'))
494208987a5SXin LI 		cc = 'e';
495208987a5SXin LI 	if ((places = decimal_places(buf)))
496208987a5SXin LI 		width2 -= (places + strlen(decimal_point));
497208987a5SXin LI 
498208987a5SXin LI 	if (precision) {
499208987a5SXin LI 		sprintf(buf, "%%%c%d.%d%c", pad,
500208987a5SXin LI 		    MAX(width1, width2) + (int) strlen(decimal_point) +
501208987a5SXin LI 		    precision, precision, (cc) ? cc : 'f');
502208987a5SXin LI 	} else {
503208987a5SXin LI 		sprintf(buf, "%%%c%d%c", pad, MAX(width1, width2),
504208987a5SXin LI 		    (cc) ? cc : 'g');
505208987a5SXin LI 	}
506208987a5SXin LI 
507208987a5SXin LI 	return (buf);
508208987a5SXin LI }
509