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