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