xref: /netbsd-src/usr.bin/seq/seq.c (revision c2c241e6a627d1c0c295f0b0b2a08ace8399c557)
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