xref: /openbsd-src/lib/libc/stdlib/gcvt.c (revision 4c1e55dc91edd6e69ccc60ce855900fbc12cf34f)
1 /*	$OpenBSD: gcvt.c,v 1.12 2010/09/25 13:19:19 millert Exp $	*/
2 
3 /*
4  * Copyright (c) 2002, 2003, 2006, 2010
5  *	Todd C. Miller <Todd.Miller@courtesan.com>
6  *
7  * Permission to use, copy, modify, and distribute this software for any
8  * purpose with or without fee is hereby granted, provided that the above
9  * copyright notice and this permission notice appear in all copies.
10  *
11  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
12  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
13  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
14  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
15  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
16  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
17  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
18  *
19  * Sponsored in part by the Defense Advanced Research Projects
20  * Agency (DARPA) and Air Force Research Laboratory, Air Force
21  * Materiel Command, USAF, under agreement number F39502-99-1-0512.
22  */
23 
24 #include <locale.h>
25 #include <stdio.h>
26 #include <stdlib.h>
27 #include <string.h>
28 
29 extern char *__dtoa(double, int, int, int *, int *, char **);
30 extern void  __freedtoa(char *);
31 
32 #define DEFPREC	6
33 
34 char *
35 gcvt(double value, int ndigit, char *buf)
36 {
37 	char *digits, *dst, *src;
38 	int i, decpt, sign;
39 	struct lconv *lconv;
40 
41 	lconv = localeconv();
42 	if (ndigit <= 0) {
43 		/* Match printf(3) behavior. */
44 		ndigit = ndigit ? DEFPREC : 1;
45 	}
46 
47 	digits = __dtoa(value, 2, ndigit, &decpt, &sign, NULL);
48 	if (digits == NULL)
49 		return (NULL);
50 	if (decpt == 9999) {
51 		/*
52 		 * Infinity or NaN, convert to inf or nan with sign.
53 		 * We can't infer buffer size based on ndigit.
54 		 * We have to assume it is at least 5 chars.
55 		 */
56 		snprintf(buf, 5, "%s%s", sign ? "-" : "",
57 		    *digits == 'I' ? "inf" : "nan");
58 		__freedtoa(digits);
59 		return (buf);
60 	}
61 
62 	dst = buf;
63 	if (sign)
64 		*dst++ = '-';
65 
66 	/* Match printf(3) behavior for exponential vs. regular fomatting. */
67 	if (decpt <= -4 || decpt > ndigit) {
68 		/* exponential format (e.g. 1.2345e+13) */
69 		if (--decpt < 0) {
70 			sign = 1;
71 			decpt = -decpt;
72 		} else
73 			sign = 0;
74 		src = digits;
75 		*dst++ = *src++;
76 		if (*src != '\0') {
77 			*dst++ = *lconv->decimal_point;
78 			do {
79 				*dst++ = *src++;
80 			} while (*src != '\0');
81 		}
82 		*dst++ = 'e';
83 		if (sign)
84 			*dst++ = '-';
85 		else
86 			*dst++ = '+';
87 		if (decpt < 10) {
88 			*dst++ = '0';
89 			*dst++ = '0' + decpt;
90 			*dst = '\0';
91 		} else {
92 			/* XXX - optimize */
93 			for (sign = decpt, i = 0; (sign /= 10) != 0; i++)
94 				continue;
95 			dst[i + 1] = '\0';
96 			while (decpt != 0) {
97 				dst[i--] = '0' + decpt % 10;
98 				decpt /= 10;
99 			}
100 		}
101 	} else {
102 		/* standard format */
103 		for (i = 0, src = digits; i < decpt; i++) {
104 			if (*src != '\0')
105 				*dst++ = *src++;
106 			else
107 				*dst++ = '0';
108 		}
109 		if (*src != '\0') {
110 			if (src == digits)
111 				*dst++ = '0';	/* zero before decimal point */
112 			*dst++ = *lconv->decimal_point;
113 			while (decpt < 0) {
114 				*dst++ = '0';
115 				decpt++;
116 			}
117 			for (i = decpt; digits[i] != '\0'; i++) {
118 				*dst++ = digits[i];
119 			}
120 		}
121 		*dst = '\0';
122 	}
123 	__freedtoa(digits);
124 	return (buf);
125 }
126