xref: /inferno-os/lib9/fltfmt.c (revision d0e1d143ef6f03c75c008c7ec648859dd260cbab)
1 /*
2  * The authors of this software are Rob Pike and Ken Thompson.
3  *              Copyright (c) 2002 by Lucent Technologies.
4  * Permission to use, copy, modify, and distribute this software for any
5  * purpose without fee is hereby granted, provided that this entire notice
6  * is included in all copies of any software which is or includes a copy
7  * or modification of this software and in all copies of the supporting
8  * documentation for such software.
9  * THIS SOFTWARE IS BEING PROVIDED "AS IS", WITHOUT ANY EXPRESS OR IMPLIED
10  * WARRANTY.  IN PARTICULAR, NEITHER THE AUTHORS NOR LUCENT TECHNOLOGIES MAKE ANY
11  * REPRESENTATION OR WARRANTY OF ANY KIND CONCERNING THE MERCHANTABILITY
12  * OF THIS SOFTWARE OR ITS FITNESS FOR ANY PARTICULAR PURPOSE.
13  */
14 #include "lib9.h"
15 #include <ctype.h>
16 #include "fmtdef.h"
17 
18 enum
19 {
20 	FDIGIT	= 30,
21 	FDEFLT	= 6,
22 	NSIGNIF	= 17,
23 };
24 
25 static int
26 xadd(char *a, int n, int v)
27 {
28 	char *b;
29 	int c;
30 
31 	if(n < 0 || n >= NSIGNIF)
32 		return 0;
33 	for(b = a+n; b >= a; b--) {
34 		c = *b + v;
35 		if(c <= '9') {
36 			*b = c;
37 			return 0;
38 		}
39 		*b = '0';
40 		v = 1;
41 	}
42 	*a = '1';	// overflow adding
43 	return 1;
44 }
45 
46 static int
47 xsub(char *a, int n, int v)
48 {
49 	char *b;
50 	int c;
51 
52 	for(b = a+n; b >= a; b--) {
53 		c = *b - v;
54 		if(c >= '0') {
55 			*b = c;
56 			return 0;
57 		}
58 		*b = '9';
59 		v = 1;
60 	}
61 	*a = '9';	// underflow subtracting
62 	return 1;
63 }
64 
65 static void
66 xdtoa(Fmt *fmt, char *s2, double f)
67 {
68 	char s1[NSIGNIF+10];
69 	double g, h;
70 	int e, d, i, n;
71 	int c1, c2, c3, c4, ucase, sign, chr, prec;
72 
73 	prec = FDEFLT;
74 	if(fmt->flags & FmtPrec)
75 		prec = fmt->prec;
76 	if(prec > FDIGIT)
77 		prec = FDIGIT;
78 	if(isNaN(f)) {
79 		strcpy(s2, "NaN");
80 		return;
81 	}
82 	if(isInf(f, 1)) {
83 		strcpy(s2, "+Inf");
84 		return;
85 	}
86 	if(isInf(f, -1)) {
87 		strcpy(s2, "-Inf");
88 		return;
89 	}
90 	sign = 0;
91 	if(f < 0) {
92 		f = -f;
93 		sign++;
94 	}
95 	ucase = 0;
96 	chr = fmt->r;
97 	if(isupper(chr)) {
98 		ucase = 1;
99 		chr = tolower(chr);
100 	}
101 
102 	e = 0;
103 	g = f;
104 	if(g != 0) {
105 		frexp(f, &e);
106 		e = e * .301029995664;
107 		if(e >= -150 && e <= +150) {
108 			d = 0;
109 			h = f;
110 		} else {
111 			d = e/2;
112 			h = f * pow10(-d);
113 		}
114 		g = h * pow10(d-e);
115 		while(g < 1) {
116 			e--;
117 			g = h * pow10(d-e);
118 		}
119 		while(g >= 10) {
120 			e++;
121 			g = h * pow10(d-e);
122 		}
123 	}
124 
125 	/*
126 	 * convert NSIGNIF digits and convert
127 	 * back to get accuracy.
128 	 */
129 	for(i=0; i<NSIGNIF; i++) {
130 		d = g;
131 		s1[i] = d + '0';
132 		g = (g - d) * 10;
133 	}
134 	s1[i] = 0;
135 
136 	/*
137 	 * try decimal rounding to eliminate 9s
138 	 */
139 	c2 = prec + 1;
140 	if(chr == 'f')
141 		c2 += e;
142 	if(c2 >= NSIGNIF-2) {
143 		strcpy(s2, s1);
144 		d = e;
145 		s1[NSIGNIF-2] = '0';
146 		s1[NSIGNIF-1] = '0';
147 		sprint(s1+NSIGNIF, "e%d", e-NSIGNIF+1);
148 		g = strtod(s1, nil);
149 		if(g == f)
150 			goto found;
151 		if(xadd(s1, NSIGNIF-3, 1)) {
152 			e++;
153 			sprint(s1+NSIGNIF, "e%d", e-NSIGNIF+1);
154 		}
155 		g = strtod(s1, nil);
156 		if(g == f)
157 			goto found;
158 		strcpy(s1, s2);
159 		e = d;
160 	}
161 
162 	/*
163 	 * convert back so s1 gets exact answer
164 	 */
165 	for(;;) {
166 		sprint(s1+NSIGNIF, "e%d", e-NSIGNIF+1);
167 		g = strtod(s1, nil);
168 		if(f > g) {
169 			if(xadd(s1, NSIGNIF-1, 1))
170 				e--;
171 			continue;
172 		}
173 		if(f < g) {
174 			if(xsub(s1, NSIGNIF-1, 1))
175 				e++;
176 			continue;
177 		}
178 		break;
179 	}
180 
181 found:
182 	/*
183 	 * sign
184 	 */
185 	d = 0;
186 	i = 0;
187 	if(sign)
188 		s2[d++] = '-';
189 	else if(fmt->flags & FmtSign)
190 		s2[d++] = '+';
191 	else if(fmt->flags & FmtSpace)
192 		s2[d++] = ' ';
193 
194 	/*
195 	 * copy into final place
196 	 * c1 digits of leading '0'
197 	 * c2 digits from conversion
198 	 * c3 digits of trailing '0'
199 	 * c4 digits after '.'
200 	 */
201 	c1 = 0;
202 	c2 = prec + 1;
203 	c3 = 0;
204 	c4 = prec;
205 	switch(chr) {
206 	default:
207 		if(xadd(s1, c2, 5))
208 			e++;
209 		break;
210 	case 'g':
211 		/*
212 		 * decide on 'e' of 'f' style convers
213 		 */
214 		if(xadd(s1, c2, 5))
215 			e++;
216 		if(e >= -5 && e <= prec) {
217 			c1 = -e - 1;
218 			c4 = prec - e;
219 			chr = 'h';	// flag for 'f' style
220 		}
221 		break;
222 	case 'f':
223 		if(xadd(s1, c2+e, 5))
224 			e++;
225 		c1 = -e;
226 		if(c1 > prec)
227 			c1 = c2;
228 		c2 += e;
229 		break;
230 	}
231 
232 	/*
233 	 * clean up c1 c2 and c3
234 	 */
235 	if(c1 < 0)
236 		c1 = 0;
237 	if(c2 < 0)
238 		c2 = 0;
239 	if(c2 > NSIGNIF) {
240 		c3 = c2-NSIGNIF;
241 		c2 = NSIGNIF;
242 	}
243 
244 	/*
245 	 * copy digits
246 	 */
247 	while(c1 > 0) {
248 		if(c1+c2+c3 == c4)
249 			s2[d++] = '.';
250 		s2[d++] = '0';
251 		c1--;
252 	}
253 	while(c2 > 0) {
254 		if(c2+c3 == c4)
255 			s2[d++] = '.';
256 		s2[d++] = s1[i++];
257 		c2--;
258 	}
259 	while(c3 > 0) {
260 		if(c3 == c4)
261 			s2[d++] = '.';
262 		s2[d++] = '0';
263 		c3--;
264 	}
265 
266 	/*
267 	 * strip trailing '0' on g conv
268 	 */
269 	if(fmt->flags & FmtSharp) {
270 		if(0 == c4)
271 			s2[d++] = '.';
272 	} else
273 	if(chr == 'g' || chr == 'h') {
274 		for(n=d-1; n>=0; n--)
275 			if(s2[n] != '0')
276 				break;
277 		for(i=n; i>=0; i--)
278 			if(s2[i] == '.') {
279 				d = n;
280 				if(i != n)
281 					d++;
282 				break;
283 			}
284 	}
285 	if(chr == 'e' || chr == 'g') {
286 		if(ucase)
287 			s2[d++] = 'E';
288 		else
289 			s2[d++] = 'e';
290 		c1 = e;
291 		if(c1 < 0) {
292 			s2[d++] = '-';
293 			c1 = -c1;
294 		} else
295 			s2[d++] = '+';
296 		if(c1 >= 100) {
297 			s2[d++] = c1/100 + '0';
298 			c1 = c1%100;
299 		}
300 		s2[d++] = c1/10 + '0';
301 		s2[d++] = c1%10 + '0';
302 	}
303 	s2[d] = 0;
304 }
305 
306 int
307 _floatfmt(Fmt *fmt, double f)
308 {
309 	char s[FDIGIT+10];
310 
311 	xdtoa(fmt, s, f);
312 	fmt->flags &= FmtWidth|FmtLeft;
313 	_fmtcpy(fmt, s, strlen(s), strlen(s));
314 	return 0;
315 }
316 
317 int
318 _efgfmt(Fmt *f)
319 {
320 	double d;
321 
322 	d = va_arg(f->args, double);
323 	return _floatfmt(f, d);
324 }
325