Introduction
Introduction Statistics Contact Development Disclaimer Help
fltfmt.c - 9base - revived minimalist port of Plan 9 userland to Unix
git clone git://git.suckless.org/9base
Log
Files
Refs
README
LICENSE
---
fltfmt.c (12850B)
---
1 /* Copyright (c) 2002-2006 Lucent Technologies; see LICENSE */
2 #include <stdio.h>
3 #include <math.h>
4 #include <float.h>
5 #include <string.h>
6 #include <stdlib.h>
7 #include <errno.h>
8 #include <stdarg.h>
9 #include <fmt.h>
10 #include <assert.h>
11 #include "plan9.h"
12 #include "fmt.h"
13 #include "fmtdef.h"
14 #include "nan.h"
15
16 enum
17 {
18 FDIGIT = 30,
19 FDEFLT = 6,
20 NSIGNIF = 17
21 };
22
23 /*
24 * first few powers of 10, enough for about 1/2 of the
25 * total space for doubles.
26 */
27 static double pows10[] =
28 {
29 1e0, 1e1, 1e2, 1e3, 1e4, 1e5, 1e6, 1e7, 1e8, …
30 1e10, 1e11, 1e12, 1e13, 1e14, 1e15, 1e16, 1e17, 1e18, …
31 1e20, 1e21, 1e22, 1e23, 1e24, 1e25, 1e26, 1e27, 1e28, …
32 1e30, 1e31, 1e32, 1e33, 1e34, 1e35, 1e36, 1e37, 1e38, …
33 1e40, 1e41, 1e42, 1e43, 1e44, 1e45, 1e46, 1e47, 1e48, …
34 1e50, 1e51, 1e52, 1e53, 1e54, 1e55, 1e56, 1e57, 1e58, …
35 1e60, 1e61, 1e62, 1e63, 1e64, 1e65, 1e66, 1e67, 1e68, …
36 1e70, 1e71, 1e72, 1e73, 1e74, 1e75, 1e76, 1e77, 1e78, …
37 1e80, 1e81, 1e82, 1e83, 1e84, 1e85, 1e86, 1e87, 1e88, …
38 1e90, 1e91, 1e92, 1e93, 1e94, 1e95, 1e96, 1e97, 1e98, …
39 1e100, 1e101, 1e102, 1e103, 1e104, 1e105, 1e106, 1e107, 1e108, 1…
40 1e110, 1e111, 1e112, 1e113, 1e114, 1e115, 1e116, 1e117, 1e118, 1…
41 1e120, 1e121, 1e122, 1e123, 1e124, 1e125, 1e126, 1e127, 1e128, 1…
42 1e130, 1e131, 1e132, 1e133, 1e134, 1e135, 1e136, 1e137, 1e138, 1…
43 1e140, 1e141, 1e142, 1e143, 1e144, 1e145, 1e146, 1e147, 1e148, 1…
44 1e150, 1e151, 1e152, 1e153, 1e154, 1e155, 1e156, 1e157, 1e158, 1…
45 };
46 #define npows10 ((int)(sizeof(pows10)/sizeof(pows10[0])))
47 #define pow10(x) fmtpow10(x)
48
49 static double
50 pow10(int n)
51 {
52 double d;
53 int neg;
54
55 neg = 0;
56 if(n < 0){
57 neg = 1;
58 n = -n;
59 }
60
61 if(n < npows10)
62 d = pows10[n];
63 else{
64 d = pows10[npows10-1];
65 for(;;){
66 n -= npows10 - 1;
67 if(n < npows10){
68 d *= pows10[n];
69 break;
70 }
71 d *= pows10[npows10 - 1];
72 }
73 }
74 if(neg)
75 return 1./d;
76 return d;
77 }
78
79 /*
80 * add 1 to the decimal integer string a of length n.
81 * if 99999 overflows into 10000, return 1 to tell caller
82 * to move the virtual decimal point.
83 */
84 static int
85 xadd1(char *a, int n)
86 {
87 char *b;
88 int c;
89
90 if(n < 0 || n > NSIGNIF)
91 return 0;
92 for(b = a+n-1; b >= a; b--) {
93 c = *b + 1;
94 if(c <= '9') {
95 *b = c;
96 return 0;
97 }
98 *b = '0';
99 }
100 /*
101 * need to overflow adding digit.
102 * shift number down and insert 1 at beginning.
103 * decimal is known to be 0s or we wouldn't
104 * have gotten this far. (e.g., 99999+1 => 00000)
105 */
106 a[0] = '1';
107 return 1;
108 }
109
110 /*
111 * subtract 1 from the decimal integer string a.
112 * if 10000 underflows into 09999, make it 99999
113 * and return 1 to tell caller to move the virtual
114 * decimal point. this way, xsub1 is inverse of xadd1.
115 */
116 static int
117 xsub1(char *a, int n)
118 {
119 char *b;
120 int c;
121
122 if(n < 0 || n > NSIGNIF)
123 return 0;
124 for(b = a+n-1; b >= a; b--) {
125 c = *b - 1;
126 if(c >= '0') {
127 if(c == '0' && b == a) {
128 /*
129 * just zeroed the top digit; shift ever…
130 * decimal is known to be 9s or we would…
131 * have gotten this far. (e.g., 10000-1…
132 */
133 *b = '9';
134 return 1;
135 }
136 *b = c;
137 return 0;
138 }
139 *b = '9';
140 }
141 /*
142 * can't get here. the number a is always normalized
143 * so that it has a nonzero first digit.
144 */
145 abort();
146 }
147
148 /*
149 * format exponent like sprintf(p, "e%+02d", e)
150 */
151 static void
152 xfmtexp(char *p, int e, int ucase)
153 {
154 char se[9];
155 int i;
156
157 *p++ = ucase ? 'E' : 'e';
158 if(e < 0) {
159 *p++ = '-';
160 e = -e;
161 } else
162 *p++ = '+';
163 i = 0;
164 while(e) {
165 se[i++] = e % 10 + '0';
166 e /= 10;
167 }
168 while(i < 2)
169 se[i++] = '0';
170 while(i > 0)
171 *p++ = se[--i];
172 *p++ = '\0';
173 }
174
175 /*
176 * compute decimal integer m, exp such that:
177 * f = m*10^exp
178 * m is as short as possible with losing exactness
179 * assumes special cases (NaN, +Inf, -Inf) have been handled.
180 */
181 static void
182 xdtoa(double f, char *s, int *exp, int *neg, int *ns)
183 {
184 int c, d, e2, e, ee, i, ndigit, oerrno;
185 char tmp[NSIGNIF+10];
186 double g;
187
188 oerrno = errno; /* in case strtod smashes errno */
189
190 /*
191 * make f non-negative.
192 */
193 *neg = 0;
194 if(f < 0) {
195 f = -f;
196 *neg = 1;
197 }
198
199 /*
200 * must handle zero specially.
201 */
202 if(f == 0){
203 *exp = 0;
204 s[0] = '0';
205 s[1] = '\0';
206 *ns = 1;
207 return;
208 }
209
210 /*
211 * find g,e such that f = g*10^e.
212 * guess 10-exponent using 2-exponent, then fine tune.
213 */
214 frexp(f, &e2);
215 e = (int)(e2 * .301029995664);
216 g = f * pow10(-e);
217 while(g < 1) {
218 e--;
219 g = f * pow10(-e);
220 }
221 while(g >= 10) {
222 e++;
223 g = f * pow10(-e);
224 }
225
226 /*
227 * convert NSIGNIF digits as a first approximation.
228 */
229 for(i=0; i<NSIGNIF; i++) {
230 d = (int)g;
231 s[i] = d+'0';
232 g = (g-d) * 10;
233 }
234 s[i] = 0;
235
236 /*
237 * adjust e because s is 314159... not 3.14159...
238 */
239 e -= NSIGNIF-1;
240 xfmtexp(s+NSIGNIF, e, 0);
241
242 /*
243 * adjust conversion until strtod(s) == f exactly.
244 */
245 for(i=0; i<10; i++) {
246 g = fmtstrtod(s, nil);
247 if(f > g) {
248 if(xadd1(s, NSIGNIF)) {
249 /* gained a digit */
250 e--;
251 xfmtexp(s+NSIGNIF, e, 0);
252 }
253 continue;
254 }
255 if(f < g) {
256 if(xsub1(s, NSIGNIF)) {
257 /* lost a digit */
258 e++;
259 xfmtexp(s+NSIGNIF, e, 0);
260 }
261 continue;
262 }
263 break;
264 }
265
266 /*
267 * play with the decimal to try to simplify.
268 */
269
270 /*
271 * bump last few digits up to 9 if we can
272 */
273 for(i=NSIGNIF-1; i>=NSIGNIF-3; i--) {
274 c = s[i];
275 if(c != '9') {
276 s[i] = '9';
277 g = fmtstrtod(s, nil);
278 if(g != f) {
279 s[i] = c;
280 break;
281 }
282 }
283 }
284
285 /*
286 * add 1 in hopes of turning 9s to 0s
287 */
288 if(s[NSIGNIF-1] == '9') {
289 strcpy(tmp, s);
290 ee = e;
291 if(xadd1(tmp, NSIGNIF)) {
292 ee--;
293 xfmtexp(tmp+NSIGNIF, ee, 0);
294 }
295 g = fmtstrtod(tmp, nil);
296 if(g == f) {
297 strcpy(s, tmp);
298 e = ee;
299 }
300 }
301
302 /*
303 * bump last few digits down to 0 as we can.
304 */
305 for(i=NSIGNIF-1; i>=NSIGNIF-3; i--) {
306 c = s[i];
307 if(c != '0') {
308 s[i] = '0';
309 g = fmtstrtod(s, nil);
310 if(g != f) {
311 s[i] = c;
312 break;
313 }
314 }
315 }
316
317 /*
318 * remove trailing zeros.
319 */
320 ndigit = NSIGNIF;
321 while(ndigit > 1 && s[ndigit-1] == '0'){
322 e++;
323 --ndigit;
324 }
325 s[ndigit] = 0;
326 *exp = e;
327 *ns = ndigit;
328 errno = oerrno;
329 }
330
331 #ifdef PLAN9PORT
332 static char *special[] = { "NaN", "NaN", "+Inf", "+Inf", "-Inf", "-Inf" …
333 #else
334 static char *special[] = { "nan", "NAN", "inf", "INF", "-inf", "-INF" };
335 #endif
336
337 int
338 __efgfmt(Fmt *fmt)
339 {
340 char buf[NSIGNIF+10], *dot, *digits, *p, *s, suf[10], *t;
341 double f;
342 int c, chr, dotwid, e, exp, fl, ndigits, neg, newndigits;
343 int pad, point, prec, realchr, sign, sufwid, ucase, wid, z1, z2;
344 Rune r, *rs, *rt;
345
346 if(fmt->flags&FmtLong)
347 f = va_arg(fmt->args, long double);
348 else
349 f = va_arg(fmt->args, double);
350
351 /*
352 * extract formatting flags
353 */
354 fl = fmt->flags;
355 fmt->flags = 0;
356 prec = FDEFLT;
357 if(fl & FmtPrec)
358 prec = fmt->prec;
359 chr = fmt->r;
360 ucase = 0;
361 switch(chr) {
362 case 'A':
363 case 'E':
364 case 'F':
365 case 'G':
366 chr += 'a'-'A';
367 ucase = 1;
368 break;
369 }
370
371 /*
372 * pick off special numbers.
373 */
374 if(__isNaN(f)) {
375 s = special[0+ucase];
376 special:
377 fmt->flags = fl & (FmtWidth|FmtLeft);
378 return __fmtcpy(fmt, s, strlen(s), strlen(s));
379 }
380 if(__isInf(f, 1)) {
381 s = special[2+ucase];
382 goto special;
383 }
384 if(__isInf(f, -1)) {
385 s = special[4+ucase];
386 goto special;
387 }
388
389 /*
390 * get exact representation.
391 */
392 digits = buf;
393 xdtoa(f, digits, &exp, &neg, &ndigits);
394
395 /*
396 * get locale's decimal point.
397 */
398 dot = fmt->decimal;
399 if(dot == nil)
400 dot = ".";
401 dotwid = utflen(dot);
402
403 /*
404 * now the formatting fun begins.
405 * compute parameters for actual fmt:
406 *
407 * pad: number of spaces to insert before/after field.
408 * z1: number of zeros to insert before digits
409 * z2: number of zeros to insert after digits
410 * point: number of digits to print before decimal point
411 * ndigits: number of digits to use from digits[]
412 * suf: trailing suffix, like "e-5"
413 */
414 realchr = chr;
415 switch(chr){
416 case 'g':
417 /*
418 * convert to at most prec significant digits. (prec=0 m…
419 */
420 if(prec == 0)
421 prec = 1;
422 if(ndigits > prec) {
423 if(digits[prec] >= '5' && xadd1(digits, prec))
424 exp++;
425 exp += ndigits-prec;
426 ndigits = prec;
427 }
428
429 /*
430 * extra rules for %g (implemented below):
431 * trailing zeros removed after decimal unless Fm…
432 * decimal point only if digit follows.
433 */
434
435 /* fall through to %e */
436 default:
437 case 'e':
438 /*
439 * one significant digit before decimal, no leading zero…
440 */
441 point = 1;
442 z1 = 0;
443
444 /*
445 * decimal point is after ndigits digits right now.
446 * slide to be after first.
447 */
448 e = exp + (ndigits-1);
449
450 /*
451 * if this is %g, check exponent and convert prec
452 */
453 if(realchr == 'g') {
454 if(-4 <= e && e < prec)
455 goto casef;
456 prec--; /* one digit before decimal; rest…
457 }
458
459 /*
460 * compute trailing zero padding or truncate digits.
461 */
462 if(1+prec >= ndigits)
463 z2 = 1+prec - ndigits;
464 else {
465 /*
466 * truncate digits
467 */
468 assert(realchr != 'g');
469 newndigits = 1+prec;
470 if(digits[newndigits] >= '5' && xadd1(digits, ne…
471 /*
472 * had 999e4, now have 100e5
473 */
474 e++;
475 }
476 ndigits = newndigits;
477 z2 = 0;
478 }
479 xfmtexp(suf, e, ucase);
480 sufwid = strlen(suf);
481 break;
482
483 casef:
484 case 'f':
485 /*
486 * determine where digits go with respect to decimal poi…
487 */
488 if(ndigits+exp > 0) {
489 point = ndigits+exp;
490 z1 = 0;
491 } else {
492 point = 1;
493 z1 = 1 + -(ndigits+exp);
494 }
495
496 /*
497 * %g specifies prec = number of significant digits
498 * convert to number of digits after decimal point
499 */
500 if(realchr == 'g')
501 prec += z1 - point;
502
503 /*
504 * compute trailing zero padding or truncate digits.
505 */
506 if(point+prec >= z1+ndigits)
507 z2 = point+prec - (z1+ndigits);
508 else {
509 /*
510 * truncate digits
511 */
512 assert(realchr != 'g');
513 newndigits = point+prec - z1;
514 if(newndigits < 0) {
515 z1 += newndigits;
516 newndigits = 0;
517 } else if(newndigits == 0) {
518 /* perhaps round up */
519 if(digits[0] >= '5'){
520 digits[0] = '1';
521 newndigits = 1;
522 goto newdigit;
523 }
524 } else if(digits[newndigits] >= '5' && xadd1(dig…
525 /*
526 * digits was 999, is now 100; make it 1…
527 */
528 digits[newndigits++] = '0';
529 newdigit:
530 /*
531 * account for new digit
532 */
533 if(z1) /* 0.099 => 0.100 or 0.99 …
534 z1--;
535 else /* 9.99 => 10.00 */
536 point++;
537 }
538 z2 = 0;
539 ndigits = newndigits;
540 }
541 sufwid = 0;
542 break;
543 }
544
545 /*
546 * if %g is given without FmtSharp, remove trailing zeros.
547 * must do after truncation, so that e.g. print %.3g 1.001
548 * produces 1, not 1.00. sorry, but them's the rules.
549 */
550 if(realchr == 'g' && !(fl & FmtSharp)) {
551 if(z1+ndigits+z2 >= point) {
552 if(z1+ndigits < point)
553 z2 = point - (z1+ndigits);
554 else{
555 z2 = 0;
556 while(z1+ndigits > point && digits[ndigi…
557 ndigits--;
558 }
559 }
560 }
561
562 /*
563 * compute width of all digits and decimal point and suffix if a…
564 */
565 wid = z1+ndigits+z2;
566 if(wid > point)
567 wid += dotwid;
568 else if(wid == point){
569 if(fl & FmtSharp)
570 wid += dotwid;
571 else
572 point++; /* do not print any decimal poin…
573 }
574 wid += sufwid;
575
576 /*
577 * determine sign
578 */
579 sign = 0;
580 if(neg)
581 sign = '-';
582 else if(fl & FmtSign)
583 sign = '+';
584 else if(fl & FmtSpace)
585 sign = ' ';
586 if(sign)
587 wid++;
588
589 /*
590 * compute padding
591 */
592 pad = 0;
593 if((fl & FmtWidth) && fmt->width > wid)
594 pad = fmt->width - wid;
595 if(pad && !(fl & FmtLeft) && (fl & FmtZero)){
596 z1 += pad;
597 point += pad;
598 pad = 0;
599 }
600
601 /*
602 * format the actual field. too bad about doing this twice.
603 */
604 if(fmt->runes){
605 if(pad && !(fl & FmtLeft) && __rfmtpad(fmt, pad) < 0)
606 return -1;
607 rt = (Rune*)fmt->to;
608 rs = (Rune*)fmt->stop;
609 if(sign)
610 FMTRCHAR(fmt, rt, rs, sign);
611 while(z1>0 || ndigits>0 || z2>0) {
612 if(z1 > 0){
613 z1--;
614 c = '0';
615 }else if(ndigits > 0){
616 ndigits--;
617 c = *digits++;
618 }else{
619 z2--;
620 c = '0';
621 }
622 FMTRCHAR(fmt, rt, rs, c);
623 if(--point == 0) {
624 for(p = dot; *p; ){
625 p += chartorune(&r, p);
626 FMTRCHAR(fmt, rt, rs, r);
627 }
628 }
629 }
630 fmt->nfmt += rt - (Rune*)fmt->to;
631 fmt->to = rt;
632 if(sufwid && __fmtcpy(fmt, suf, sufwid, sufwid) < 0)
633 return -1;
634 if(pad && (fl & FmtLeft) && __rfmtpad(fmt, pad) < 0)
635 return -1;
636 }else{
637 if(pad && !(fl & FmtLeft) && __fmtpad(fmt, pad) < 0)
638 return -1;
639 t = (char*)fmt->to;
640 s = (char*)fmt->stop;
641 if(sign)
642 FMTCHAR(fmt, t, s, sign);
643 while(z1>0 || ndigits>0 || z2>0) {
644 if(z1 > 0){
645 z1--;
646 c = '0';
647 }else if(ndigits > 0){
648 ndigits--;
649 c = *digits++;
650 }else{
651 z2--;
652 c = '0';
653 }
654 FMTCHAR(fmt, t, s, c);
655 if(--point == 0)
656 for(p=dot; *p; p++)
657 FMTCHAR(fmt, t, s, *p);
658 }
659 fmt->nfmt += t - (char*)fmt->to;
660 fmt->to = t;
661 if(sufwid && __fmtcpy(fmt, suf, sufwid, sufwid) < 0)
662 return -1;
663 if(pad && (fl & FmtLeft) && __fmtpad(fmt, pad) < 0)
664 return -1;
665 }
666 return 0;
667 }
668
You are viewing proxied material from suckless.org. The copyright of proxied material belongs to its original authors. Any comments or complaints in relation to proxied material should be directed to the original authors of the content concerned. Please see the disclaimer for more details.