FreeCalypso > hg > freecalypso-citrine
view sprintf/float.c @ 46:38cf7fa65976
sprintf/float.c: rounding corner case bug
author | Mychaela Falconia <falcon@freecalypso.org> |
---|---|
date | Fri, 29 Sep 2017 03:23:01 +0000 |
parents | a2d5d622e19e |
children |
line wrap: on
line source
/* * Embedded [v]sprintf() implementation by Mychaela Falconia, * loosely based on the 4.3BSD-Tahoe version. * * This module contains the floating point conversion functions. */ #include <sys/types.h> #include <ctype.h> #include "defs.h" extern double modf(); extern u_char * _sprintf_field(u_char *op, int width, int flags, int sign, u_char *body, int size, int dprec, int fpprec); #define MAX_INT_DIGITS 10 /* 2^32-1 in decimal */ #define MAXFRACT 16 /* max sensible for 64-bit double */ #define DEFPREC 6 static char * emit_integer_portion(unsigned number, char *endp) { char *t = endp; do { *--t = tochar(number % 10); number /= 10; } while (number); return (t); } static int f_round(double fract, char *start, char *end, int sign) { double tmp; (void)modf(fract * 10, &tmp); if (tmp > 4) for (;; --end) { if (*end == '.') --end; if (++*end <= '9') break; *end = '0'; if (end == start) { *--end = '1'; return(1); } } /* ``"%.3f", (double)-0.0004'' gives you a negative 0. */ else if (sign == '-') for (;; --end) { if (*end == '.') --end; if (*end != '0') break; if (end == start) return(-1); /* clear the -ve */ } return(0); } u_char * _sprintf_percent_f(u_char *op, int width, int flags, int sign, double number, int prec) { char buf[MAX_INT_DIGITS + 1 + MAXFRACT]; int extra_prec = 0; int origsign = sign; int round_stat; double fract, tmp; char *start, *t; /* first order of business: weed out infinities and NaNs */ if (isinf(number)) { if (number < 0) sign = '-'; return _sprintf_field(op, width, flags, sign, "Inf", 3, 0, 0); } if (isnan(number)) return _sprintf_field(op, width, flags, sign, "NaN", 3, 0, 0); /* OK, we know it's a valid real like in the good old VAX days */ if (number < 0) { sign = '-'; number = -number; } fract = modf(number, &tmp); if (tmp > (double) 0xFFFFFFFF) return _sprintf_field(op, width, flags, sign, "Toobig", 6, 0, 0); start = emit_integer_portion((unsigned) tmp, buf + MAX_INT_DIGITS); if (prec > MAXFRACT) { extra_prec = prec - MAXFRACT; prec = MAXFRACT; } else if (prec == -1) prec = DEFPREC; t = buf + MAX_INT_DIGITS; /* * if precision required or alternate flag set, add in a * decimal point. */ if (prec || flags&ALT) *t++ = '.'; /* if requires more precision and some fraction left */ if (fract) { if (prec) do { fract = modf(fract * 10, &tmp); *t++ = tochar((int)tmp); } while (--prec && fract); if (fract) { round_stat = f_round(fract, start, t - 1, sign); if (round_stat == 1) start--; else if (round_stat == -1) sign = origsign; } } return _sprintf_field(op, width, flags, sign, start, t - start, 0, prec + extra_prec); }