xref: /plan9/sys/src/libc/fmt/fltfmt.c (revision c4a39de769883244c79891f835f929a2fcf87c3a)
19a747e4fSDavid du Colombier #include <u.h>
29a747e4fSDavid du Colombier #include <libc.h>
39a747e4fSDavid du Colombier #include <ctype.h>
49a747e4fSDavid du Colombier #include "fmtdef.h"
59a747e4fSDavid du Colombier 
69a747e4fSDavid du Colombier enum
79a747e4fSDavid du Colombier {
89a747e4fSDavid du Colombier 	FDIGIT	= 30,
99a747e4fSDavid du Colombier 	FDEFLT	= 6,
109a747e4fSDavid du Colombier 	NSIGNIF	= 17,
11*c4a39de7SDavid du Colombier 	NEXP10	= 308,
129a747e4fSDavid du Colombier };
139a747e4fSDavid du Colombier 
149a747e4fSDavid du Colombier static int
xadd(char * a,int n,int v)159a747e4fSDavid du Colombier xadd(char *a, int n, int v)
169a747e4fSDavid du Colombier {
179a747e4fSDavid du Colombier 	char *b;
189a747e4fSDavid du Colombier 	int c;
199a747e4fSDavid du Colombier 
209a747e4fSDavid du Colombier 	if(n < 0 || n >= NSIGNIF)
219a747e4fSDavid du Colombier 		return 0;
229a747e4fSDavid du Colombier 	for(b = a+n; b >= a; b--) {
239a747e4fSDavid du Colombier 		c = *b + v;
249a747e4fSDavid du Colombier 		if(c <= '9') {
259a747e4fSDavid du Colombier 			*b = c;
269a747e4fSDavid du Colombier 			return 0;
279a747e4fSDavid du Colombier 		}
289a747e4fSDavid du Colombier 		*b = '0';
299a747e4fSDavid du Colombier 		v = 1;
309a747e4fSDavid du Colombier 	}
319a747e4fSDavid du Colombier 	*a = '1';	// overflow adding
329a747e4fSDavid du Colombier 	return 1;
339a747e4fSDavid du Colombier }
349a747e4fSDavid du Colombier 
359a747e4fSDavid du Colombier static int
xsub(char * a,int n,int v)369a747e4fSDavid du Colombier xsub(char *a, int n, int v)
379a747e4fSDavid du Colombier {
389a747e4fSDavid du Colombier 	char *b;
399a747e4fSDavid du Colombier 	int c;
409a747e4fSDavid du Colombier 
419a747e4fSDavid du Colombier 	for(b = a+n; b >= a; b--) {
429a747e4fSDavid du Colombier 		c = *b - v;
439a747e4fSDavid du Colombier 		if(c >= '0') {
449a747e4fSDavid du Colombier 			*b = c;
459a747e4fSDavid du Colombier 			return 0;
469a747e4fSDavid du Colombier 		}
479a747e4fSDavid du Colombier 		*b = '9';
489a747e4fSDavid du Colombier 		v = 1;
499a747e4fSDavid du Colombier 	}
509a747e4fSDavid du Colombier 	*a = '9';	// underflow subtracting
519a747e4fSDavid du Colombier 	return 1;
529a747e4fSDavid du Colombier }
539a747e4fSDavid du Colombier 
549a747e4fSDavid du Colombier static void
xdtoa(Fmt * fmt,char * s2,double f)559a747e4fSDavid du Colombier xdtoa(Fmt *fmt, char *s2, double f)
569a747e4fSDavid du Colombier {
579a747e4fSDavid du Colombier 	char s1[NSIGNIF+10];
589a747e4fSDavid du Colombier 	double g, h;
599a747e4fSDavid du Colombier 	int e, d, i, n;
609a747e4fSDavid du Colombier 	int c1, c2, c3, c4, ucase, sign, chr, prec;
619a747e4fSDavid du Colombier 
629a747e4fSDavid du Colombier 	prec = FDEFLT;
639a747e4fSDavid du Colombier 	if(fmt->flags & FmtPrec)
649a747e4fSDavid du Colombier 		prec = fmt->prec;
659a747e4fSDavid du Colombier 	if(prec > FDIGIT)
669a747e4fSDavid du Colombier 		prec = FDIGIT;
679a747e4fSDavid du Colombier 	if(isNaN(f)) {
689a747e4fSDavid du Colombier 		strcpy(s2, "NaN");
699a747e4fSDavid du Colombier 		return;
709a747e4fSDavid du Colombier 	}
719a747e4fSDavid du Colombier 	if(isInf(f, 1)) {
729a747e4fSDavid du Colombier 		strcpy(s2, "+Inf");
739a747e4fSDavid du Colombier 		return;
749a747e4fSDavid du Colombier 	}
759a747e4fSDavid du Colombier 	if(isInf(f, -1)) {
769a747e4fSDavid du Colombier 		strcpy(s2, "-Inf");
779a747e4fSDavid du Colombier 		return;
789a747e4fSDavid du Colombier 	}
799a747e4fSDavid du Colombier 	sign = 0;
809a747e4fSDavid du Colombier 	if(f < 0) {
819a747e4fSDavid du Colombier 		f = -f;
829a747e4fSDavid du Colombier 		sign++;
839a747e4fSDavid du Colombier 	}
849a747e4fSDavid du Colombier 	ucase = 0;
859a747e4fSDavid du Colombier 	chr = fmt->r;
869a747e4fSDavid du Colombier 	if(isupper(chr)) {
879a747e4fSDavid du Colombier 		ucase = 1;
889a747e4fSDavid du Colombier 		chr = tolower(chr);
899a747e4fSDavid du Colombier 	}
909a747e4fSDavid du Colombier 
919a747e4fSDavid du Colombier 	e = 0;
929a747e4fSDavid du Colombier 	g = f;
939a747e4fSDavid du Colombier 	if(g != 0) {
949a747e4fSDavid du Colombier 		frexp(f, &e);
959a747e4fSDavid du Colombier 		e = e * .301029995664;
969a747e4fSDavid du Colombier 		if(e >= -150 && e <= +150) {
979a747e4fSDavid du Colombier 			d = 0;
989a747e4fSDavid du Colombier 			h = f;
999a747e4fSDavid du Colombier 		} else {
1009a747e4fSDavid du Colombier 			d = e/2;
1019a747e4fSDavid du Colombier 			h = f * pow10(-d);
1029a747e4fSDavid du Colombier 		}
1039a747e4fSDavid du Colombier 		g = h * pow10(d-e);
1049a747e4fSDavid du Colombier 		while(g < 1) {
1059a747e4fSDavid du Colombier 			e--;
1069a747e4fSDavid du Colombier 			g = h * pow10(d-e);
1079a747e4fSDavid du Colombier 		}
1089a747e4fSDavid du Colombier 		while(g >= 10) {
1099a747e4fSDavid du Colombier 			e++;
1109a747e4fSDavid du Colombier 			g = h * pow10(d-e);
1119a747e4fSDavid du Colombier 		}
1129a747e4fSDavid du Colombier 	}
1139a747e4fSDavid du Colombier 
1149a747e4fSDavid du Colombier 	/*
1159a747e4fSDavid du Colombier 	 * convert NSIGNIF digits and convert
1169a747e4fSDavid du Colombier 	 * back to get accuracy.
1179a747e4fSDavid du Colombier 	 */
1189a747e4fSDavid du Colombier 	for(i=0; i<NSIGNIF; i++) {
1199a747e4fSDavid du Colombier 		d = g;
1209a747e4fSDavid du Colombier 		s1[i] = d + '0';
1219a747e4fSDavid du Colombier 		g = (g - d) * 10;
1229a747e4fSDavid du Colombier 	}
1239a747e4fSDavid du Colombier 	s1[i] = 0;
1249a747e4fSDavid du Colombier 
1259a747e4fSDavid du Colombier 	/*
1269a747e4fSDavid du Colombier 	 * try decimal rounding to eliminate 9s
1279a747e4fSDavid du Colombier 	 */
1289a747e4fSDavid du Colombier 	c2 = prec + 1;
1299a747e4fSDavid du Colombier 	if(chr == 'f')
1309a747e4fSDavid du Colombier 		c2 += e;
1319a747e4fSDavid du Colombier 	if(c2 >= NSIGNIF-2) {
1329a747e4fSDavid du Colombier 		strcpy(s2, s1);
1339a747e4fSDavid du Colombier 		d = e;
1349a747e4fSDavid du Colombier 		s1[NSIGNIF-2] = '0';
1359a747e4fSDavid du Colombier 		s1[NSIGNIF-1] = '0';
1369a747e4fSDavid du Colombier 		sprint(s1+NSIGNIF, "e%d", e-NSIGNIF+1);
1379a747e4fSDavid du Colombier 		g = strtod(s1, nil);
1389a747e4fSDavid du Colombier 		if(g == f)
1399a747e4fSDavid du Colombier 			goto found;
1409a747e4fSDavid du Colombier 		if(xadd(s1, NSIGNIF-3, 1)) {
1419a747e4fSDavid du Colombier 			e++;
1429a747e4fSDavid du Colombier 			sprint(s1+NSIGNIF, "e%d", e-NSIGNIF+1);
1439a747e4fSDavid du Colombier 		}
1449a747e4fSDavid du Colombier 		g = strtod(s1, nil);
1459a747e4fSDavid du Colombier 		if(g == f)
1469a747e4fSDavid du Colombier 			goto found;
1479a747e4fSDavid du Colombier 		strcpy(s1, s2);
1489a747e4fSDavid du Colombier 		e = d;
1499a747e4fSDavid du Colombier 	}
1509a747e4fSDavid du Colombier 
1519a747e4fSDavid du Colombier 	/*
1529a747e4fSDavid du Colombier 	 * convert back so s1 gets exact answer
1539a747e4fSDavid du Colombier 	 */
1549a747e4fSDavid du Colombier 	for(;;) {
1559a747e4fSDavid du Colombier 		sprint(s1+NSIGNIF, "e%d", e-NSIGNIF+1);
1569a747e4fSDavid du Colombier 		g = strtod(s1, nil);
1579a747e4fSDavid du Colombier 		if(f > g) {
1589a747e4fSDavid du Colombier 			if(xadd(s1, NSIGNIF-1, 1))
1599a747e4fSDavid du Colombier 				e--;
1609a747e4fSDavid du Colombier 			continue;
1619a747e4fSDavid du Colombier 		}
1629a747e4fSDavid du Colombier 		if(f < g) {
1639a747e4fSDavid du Colombier 			if(xsub(s1, NSIGNIF-1, 1))
1649a747e4fSDavid du Colombier 				e++;
1659a747e4fSDavid du Colombier 			continue;
1669a747e4fSDavid du Colombier 		}
1679a747e4fSDavid du Colombier 		break;
1689a747e4fSDavid du Colombier 	}
1699a747e4fSDavid du Colombier 
1709a747e4fSDavid du Colombier found:
1719a747e4fSDavid du Colombier 	/*
1729a747e4fSDavid du Colombier 	 * sign
1739a747e4fSDavid du Colombier 	 */
1749a747e4fSDavid du Colombier 	d = 0;
1759a747e4fSDavid du Colombier 	i = 0;
1769a747e4fSDavid du Colombier 	if(sign)
1779a747e4fSDavid du Colombier 		s2[d++] = '-';
1789a747e4fSDavid du Colombier 	else if(fmt->flags & FmtSign)
1799a747e4fSDavid du Colombier 		s2[d++] = '+';
1809a747e4fSDavid du Colombier 	else if(fmt->flags & FmtSpace)
181d9306527SDavid du Colombier 		s2[d++] = ' ';
1829a747e4fSDavid du Colombier 
1839a747e4fSDavid du Colombier 	/*
1849a747e4fSDavid du Colombier 	 * copy into final place
1859a747e4fSDavid du Colombier 	 * c1 digits of leading '0'
1869a747e4fSDavid du Colombier 	 * c2 digits from conversion
1879a747e4fSDavid du Colombier 	 * c3 digits of trailing '0'
1889a747e4fSDavid du Colombier 	 * c4 digits after '.'
1899a747e4fSDavid du Colombier 	 */
1909a747e4fSDavid du Colombier 	c1 = 0;
1919a747e4fSDavid du Colombier 	c2 = prec + 1;
1929a747e4fSDavid du Colombier 	c3 = 0;
1939a747e4fSDavid du Colombier 	c4 = prec;
1949a747e4fSDavid du Colombier 	switch(chr) {
1959a747e4fSDavid du Colombier 	default:
1969a747e4fSDavid du Colombier 		if(xadd(s1, c2, 5))
1979a747e4fSDavid du Colombier 			e++;
1989a747e4fSDavid du Colombier 		break;
1999a747e4fSDavid du Colombier 	case 'g':
2009a747e4fSDavid du Colombier 		/*
2019a747e4fSDavid du Colombier 		 * decide on 'e' of 'f' style convers
2029a747e4fSDavid du Colombier 		 */
2039a747e4fSDavid du Colombier 		if(xadd(s1, c2, 5))
2049a747e4fSDavid du Colombier 			e++;
2059a747e4fSDavid du Colombier 		if(e >= -5 && e <= prec) {
2069a747e4fSDavid du Colombier 			c1 = -e - 1;
2079a747e4fSDavid du Colombier 			c4 = prec - e;
2089a747e4fSDavid du Colombier 			chr = 'h';	// flag for 'f' style
2099a747e4fSDavid du Colombier 		}
2109a747e4fSDavid du Colombier 		break;
2119a747e4fSDavid du Colombier 	case 'f':
2129a747e4fSDavid du Colombier 		if(xadd(s1, c2+e, 5))
2139a747e4fSDavid du Colombier 			e++;
2149a747e4fSDavid du Colombier 		c1 = -e;
2159a747e4fSDavid du Colombier 		if(c1 > prec)
2169a747e4fSDavid du Colombier 			c1 = c2;
2179a747e4fSDavid du Colombier 		c2 += e;
2189a747e4fSDavid du Colombier 		break;
2199a747e4fSDavid du Colombier 	}
2209a747e4fSDavid du Colombier 
2219a747e4fSDavid du Colombier 	/*
2229a747e4fSDavid du Colombier 	 * clean up c1 c2 and c3
2239a747e4fSDavid du Colombier 	 */
2249a747e4fSDavid du Colombier 	if(c1 < 0)
2259a747e4fSDavid du Colombier 		c1 = 0;
2269a747e4fSDavid du Colombier 	if(c2 < 0)
2279a747e4fSDavid du Colombier 		c2 = 0;
2289a747e4fSDavid du Colombier 	if(c2 > NSIGNIF) {
2299a747e4fSDavid du Colombier 		c3 = c2-NSIGNIF;
2309a747e4fSDavid du Colombier 		c2 = NSIGNIF;
2319a747e4fSDavid du Colombier 	}
2329a747e4fSDavid du Colombier 
2339a747e4fSDavid du Colombier 	/*
2349a747e4fSDavid du Colombier 	 * copy digits
2359a747e4fSDavid du Colombier 	 */
2369a747e4fSDavid du Colombier 	while(c1 > 0) {
2379a747e4fSDavid du Colombier 		if(c1+c2+c3 == c4)
2389a747e4fSDavid du Colombier 			s2[d++] = '.';
2399a747e4fSDavid du Colombier 		s2[d++] = '0';
2409a747e4fSDavid du Colombier 		c1--;
2419a747e4fSDavid du Colombier 	}
2429a747e4fSDavid du Colombier 	while(c2 > 0) {
2439a747e4fSDavid du Colombier 		if(c2+c3 == c4)
2449a747e4fSDavid du Colombier 			s2[d++] = '.';
2459a747e4fSDavid du Colombier 		s2[d++] = s1[i++];
2469a747e4fSDavid du Colombier 		c2--;
2479a747e4fSDavid du Colombier 	}
2489a747e4fSDavid du Colombier 	while(c3 > 0) {
2499a747e4fSDavid du Colombier 		if(c3 == c4)
2509a747e4fSDavid du Colombier 			s2[d++] = '.';
2519a747e4fSDavid du Colombier 		s2[d++] = '0';
2529a747e4fSDavid du Colombier 		c3--;
2539a747e4fSDavid du Colombier 	}
2549a747e4fSDavid du Colombier 
2559a747e4fSDavid du Colombier 	/*
2569a747e4fSDavid du Colombier 	 * strip trailing '0' on g conv
2579a747e4fSDavid du Colombier 	 */
2589a747e4fSDavid du Colombier 	if(fmt->flags & FmtSharp) {
2599a747e4fSDavid du Colombier 		if(0 == c4)
2609a747e4fSDavid du Colombier 			s2[d++] = '.';
2619a747e4fSDavid du Colombier 	} else
2629a747e4fSDavid du Colombier 	if(chr == 'g' || chr == 'h') {
2639a747e4fSDavid du Colombier 		for(n=d-1; n>=0; n--)
2649a747e4fSDavid du Colombier 			if(s2[n] != '0')
2659a747e4fSDavid du Colombier 				break;
2669a747e4fSDavid du Colombier 		for(i=n; i>=0; i--)
2679a747e4fSDavid du Colombier 			if(s2[i] == '.') {
2689a747e4fSDavid du Colombier 				d = n;
2699a747e4fSDavid du Colombier 				if(i != n)
2709a747e4fSDavid du Colombier 					d++;
2719a747e4fSDavid du Colombier 				break;
2729a747e4fSDavid du Colombier 			}
2739a747e4fSDavid du Colombier 	}
2749a747e4fSDavid du Colombier 	if(chr == 'e' || chr == 'g') {
2759a747e4fSDavid du Colombier 		if(ucase)
2769a747e4fSDavid du Colombier 			s2[d++] = 'E';
2779a747e4fSDavid du Colombier 		else
2789a747e4fSDavid du Colombier 			s2[d++] = 'e';
2799a747e4fSDavid du Colombier 		c1 = e;
2809a747e4fSDavid du Colombier 		if(c1 < 0) {
2819a747e4fSDavid du Colombier 			s2[d++] = '-';
2829a747e4fSDavid du Colombier 			c1 = -c1;
2839a747e4fSDavid du Colombier 		} else
2849a747e4fSDavid du Colombier 			s2[d++] = '+';
2859a747e4fSDavid du Colombier 		if(c1 >= 100) {
2869a747e4fSDavid du Colombier 			s2[d++] = c1/100 + '0';
2879a747e4fSDavid du Colombier 			c1 = c1%100;
2889a747e4fSDavid du Colombier 		}
2899a747e4fSDavid du Colombier 		s2[d++] = c1/10 + '0';
2909a747e4fSDavid du Colombier 		s2[d++] = c1%10 + '0';
2919a747e4fSDavid du Colombier 	}
2929a747e4fSDavid du Colombier 	s2[d] = 0;
2939a747e4fSDavid du Colombier }
2949a747e4fSDavid du Colombier 
2959a747e4fSDavid du Colombier int
_floatfmt(Fmt * fmt,double f)2969a747e4fSDavid du Colombier _floatfmt(Fmt *fmt, double f)
2979a747e4fSDavid du Colombier {
298*c4a39de7SDavid du Colombier 	char s[1+NEXP10+1+FDIGIT+1];
2999a747e4fSDavid du Colombier 
300*c4a39de7SDavid du Colombier 	/*
301*c4a39de7SDavid du Colombier 	 * The max length of a %f string is
302*c4a39de7SDavid du Colombier 	 *	'[+-]'+"max exponent"+'.'+"max precision"+'\0'
303*c4a39de7SDavid du Colombier 	 * which is 341 currently.
304*c4a39de7SDavid du Colombier 	 */
3059a747e4fSDavid du Colombier 	xdtoa(fmt, s, f);
3069a747e4fSDavid du Colombier 	fmt->flags &= FmtWidth|FmtLeft;
3079a747e4fSDavid du Colombier 	_fmtcpy(fmt, s, strlen(s), strlen(s));
3089a747e4fSDavid du Colombier 	return 0;
3099a747e4fSDavid du Colombier }
3109a747e4fSDavid du Colombier 
3119a747e4fSDavid du Colombier int
_efgfmt(Fmt * f)3129a747e4fSDavid du Colombier _efgfmt(Fmt *f)
3139a747e4fSDavid du Colombier {
3149a747e4fSDavid du Colombier 	double d;
3159a747e4fSDavid du Colombier 
3169a747e4fSDavid du Colombier 	d = va_arg(f->args, double);
3179a747e4fSDavid du Colombier 	return _floatfmt(f, d);
3189a747e4fSDavid du Colombier }
319