diff options
Diffstat (limited to 'lib/libc/stdio/xprintf_float.c')
-rw-r--r-- | lib/libc/stdio/xprintf_float.c | 425 |
1 files changed, 425 insertions, 0 deletions
diff --git a/lib/libc/stdio/xprintf_float.c b/lib/libc/stdio/xprintf_float.c new file mode 100644 index 0000000..b719aac --- /dev/null +++ b/lib/libc/stdio/xprintf_float.c @@ -0,0 +1,425 @@ +/*- + * Copyright (c) 2005 Poul-Henning Kamp + * Copyright (c) 1990, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Chris Torek. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ + +#include <namespace.h> +#include <stdio.h> +#include <wchar.h> +#include <assert.h> +#include <locale.h> +#include <limits.h> + +#define dtoa __dtoa +#define freedtoa __freedtoa + +#include <float.h> +#include <math.h> +#include "gdtoa.h" +#include "floatio.h" +#include "printf.h" +#include <un-namespace.h> + +/* + * The size of the buffer we use as scratch space for integer + * conversions, among other things. Technically, we would need the + * most space for base 10 conversions with thousands' grouping + * characters between each pair of digits. 100 bytes is a + * conservative overestimate even for a 128-bit uintmax_t. + */ +#define BUF 100 + +#define DEFPREC 6 /* Default FP precision */ + + +/* various globals ---------------------------------------------------*/ + + +/* padding function---------------------------------------------------*/ + +#define PRINTANDPAD(p, ep, len, with) do { \ + n2 = (ep) - (p); \ + if (n2 > (len)) \ + n2 = (len); \ + if (n2 > 0) \ + ret += __printf_puts(io, (p), n2); \ + ret += __printf_pad(io, (len) - (n2 > 0 ? n2 : 0), (with)); \ +} while(0) + +/* misc --------------------------------------------------------------*/ + +#define to_char(n) ((n) + '0') + +static int +exponent(char *p0, int expo, int fmtch) +{ + char *p, *t; + char expbuf[MAXEXPDIG]; + + p = p0; + *p++ = fmtch; + if (expo < 0) { + expo = -expo; + *p++ = '-'; + } + else + *p++ = '+'; + t = expbuf + MAXEXPDIG; + if (expo > 9) { + do { + *--t = to_char(expo % 10); + } while ((expo /= 10) > 9); + *--t = to_char(expo); + for (; t < expbuf + MAXEXPDIG; *p++ = *t++) + ; + } + else { + /* + * Exponents for decimal floating point conversions + * (%[eEgG]) must be at least two characters long, + * whereas exponents for hexadecimal conversions can + * be only one character long. + */ + if (fmtch == 'e' || fmtch == 'E') + *p++ = '0'; + *p++ = to_char(expo); + } + return (p - p0); +} + +/* 'f' ---------------------------------------------------------------*/ + +int +__printf_arginfo_float(const struct printf_info *pi, size_t n, int *argt) +{ + assert (n > 0); + argt[0] = PA_DOUBLE; + if (pi->is_long_double) + argt[0] |= PA_FLAG_LONG_DOUBLE; + return (1); +} + +/* + * We can decompose the printed representation of floating + * point numbers into several parts, some of which may be empty: + * + * [+|-| ] [0x|0X] MMM . NNN [e|E|p|P] [+|-] ZZ + * A B ---C--- D E F + * + * A: 'sign' holds this value if present; '\0' otherwise + * B: ox[1] holds the 'x' or 'X'; '\0' if not hexadecimal + * C: cp points to the string MMMNNN. Leading and trailing + * zeros are not in the string and must be added. + * D: expchar holds this character; '\0' if no exponent, e.g. %f + * F: at least two digits for decimal, at least one digit for hex + */ + +int +__printf_render_float(struct __printf_io *io, const struct printf_info *pi, const void *const *arg) +{ + int prec; /* precision from format; <0 for N/A */ + char *dtoaresult; /* buffer allocated by dtoa */ + char expchar; /* exponent character: [eEpP\0] */ + char *cp; + int expt; /* integer value of exponent */ + int signflag; /* true if float is negative */ + char *dtoaend; /* pointer to end of converted digits */ + char sign; /* sign prefix (' ', '+', '-', or \0) */ + int size; /* size of converted field or string */ + int ndig; /* actual number of digits returned by dtoa */ + int expsize; /* character count for expstr */ + char expstr[MAXEXPDIG+2]; /* buffer for exponent string: e+ZZZ */ + int nseps; /* number of group separators with ' */ + int nrepeats; /* number of repeats of the last group */ + const char *grouping; /* locale specific numeric grouping rules */ + int lead; /* sig figs before decimal or group sep */ + long double ld; + double d; + int realsz; /* field size expanded by dprec, sign, etc */ + int dprec; /* a copy of prec if [diouxX], 0 otherwise */ + char ox[2]; /* space for 0x; ox[1] is either x, X, or \0 */ + int prsize; /* max size of printed field */ + int ret; /* return value accumulator */ + char *decimal_point; /* locale specific decimal point */ + int n2; /* XXX: for PRINTANDPAD */ + char thousands_sep; /* locale specific thousands separator */ + char buf[BUF]; /* buffer with space for digits of uintmax_t */ + const char *xdigs; + int flag; + + prec = pi->prec; + ox[1] = '\0'; + sign = pi->showsign; + flag = 0; + ret = 0; + + thousands_sep = *(localeconv()->thousands_sep); + grouping = NULL; + if (pi->alt) + grouping = localeconv()->grouping; + decimal_point = localeconv()->decimal_point; + dprec = -1; + + switch(pi->spec) { + case 'a': + case 'A': + if (pi->spec == 'a') { + ox[1] = 'x'; + xdigs = __lowercase_hex; + expchar = 'p'; + } else { + ox[1] = 'X'; + xdigs = __uppercase_hex; + expchar = 'P'; + } + if (prec >= 0) + prec++; + if (pi->is_long_double) { + ld = *((long double *)arg[0]); + dtoaresult = cp = + __hldtoa(ld, xdigs, prec, + &expt, &signflag, &dtoaend); + } else { + d = *((double *)arg[0]); + dtoaresult = cp = + __hdtoa(d, xdigs, prec, + &expt, &signflag, &dtoaend); + } + if (prec < 0) + prec = dtoaend - cp; + if (expt == INT_MAX) + ox[1] = '\0'; + goto fp_common; + case 'e': + case 'E': + expchar = pi->spec; + if (prec < 0) /* account for digit before decpt */ + prec = DEFPREC + 1; + else + prec++; + break; + case 'f': + case 'F': + expchar = '\0'; + break; + case 'g': + case 'G': + expchar = pi->spec - ('g' - 'e'); + if (prec == 0) + prec = 1; + break; + default: + assert(pi->spec == 'f'); + } + + if (prec < 0) + prec = DEFPREC; + if (pi->is_long_double) { + ld = *((long double *)arg[0]); + dtoaresult = cp = + __ldtoa(&ld, expchar ? 2 : 3, prec, + &expt, &signflag, &dtoaend); + } else { + d = *((double *)arg[0]); + dtoaresult = cp = + dtoa(d, expchar ? 2 : 3, prec, + &expt, &signflag, &dtoaend); + if (expt == 9999) + expt = INT_MAX; + } +fp_common: + if (signflag) + sign = '-'; + if (expt == INT_MAX) { /* inf or nan */ + if (*cp == 'N') { + cp = (pi->spec >= 'a') ? "nan" : "NAN"; + sign = '\0'; + } else + cp = (pi->spec >= 'a') ? "inf" : "INF"; + size = 3; + flag = 1; + goto here; + } + ndig = dtoaend - cp; + if (pi->spec == 'g' || pi->spec == 'G') { + if (expt > -4 && expt <= prec) { + /* Make %[gG] smell like %[fF] */ + expchar = '\0'; + if (pi->alt) + prec -= expt; + else + prec = ndig - expt; + if (prec < 0) + prec = 0; + } else { + /* + * Make %[gG] smell like %[eE], but + * trim trailing zeroes if no # flag. + */ + if (!pi->alt) + prec = ndig; + } + } + if (expchar) { + expsize = exponent(expstr, expt - 1, expchar); + size = expsize + prec; + if (prec > 1 || pi->alt) + ++size; + } else { + /* space for digits before decimal point */ + if (expt > 0) + size = expt; + else /* "0" */ + size = 1; + /* space for decimal pt and following digits */ + if (prec || pi->alt) + size += prec + 1; + if (grouping && expt > 0) { + /* space for thousands' grouping */ + nseps = nrepeats = 0; + lead = expt; + while (*grouping != CHAR_MAX) { + if (lead <= *grouping) + break; + lead -= *grouping; + if (*(grouping+1)) { + nseps++; + grouping++; + } else + nrepeats++; + } + size += nseps + nrepeats; + } else + lead = expt; + } + +here: + /* + * All reasonable formats wind up here. At this point, `cp' + * points to a string which (if not flags&LADJUST) should be + * padded out to `width' places. If flags&ZEROPAD, it should + * first be prefixed by any sign or other prefix; otherwise, + * it should be blank padded before the prefix is emitted. + * After any left-hand padding and prefixing, emit zeroes + * required by a decimal [diouxX] precision, then print the + * string proper, then emit zeroes required by any leftover + * floating precision; finally, if LADJUST, pad with blanks. + * + * Compute actual size, so we know how much to pad. + * size excludes decimal prec; realsz includes it. + */ + realsz = dprec > size ? dprec : size; + if (sign) + realsz++; + if (ox[1]) + realsz += 2; + + prsize = pi->width > realsz ? pi->width : realsz; + + /* right-adjusting blank padding */ + if (pi->pad != '0' && pi->left == 0) + ret += __printf_pad(io, pi->width - realsz, 0); + + /* prefix */ + if (sign) + ret += __printf_puts(io, &sign, 1); + + if (ox[1]) { /* ox[1] is either x, X, or \0 */ + ox[0] = '0'; + ret += __printf_puts(io, ox, 2); + } + + /* right-adjusting zero padding */ + if (pi->pad == '0' && pi->left == 0) + ret += __printf_pad(io, pi->width - realsz, 1); + + /* leading zeroes from decimal precision */ + ret += __printf_pad(io, dprec - size, 1); + + if (flag) + ret += __printf_puts(io, cp, size); + else { + /* glue together f_p fragments */ + if (!expchar) { /* %[fF] or sufficiently short %[gG] */ + if (expt <= 0) { + ret += __printf_puts(io, "0", 1); + if (prec || pi->alt) + ret += __printf_puts(io, decimal_point, 1); + ret += __printf_pad(io, -expt, 1); + /* already handled initial 0's */ + prec += expt; + } else { + PRINTANDPAD(cp, dtoaend, lead, 1); + cp += lead; + if (grouping) { + while (nseps>0 || nrepeats>0) { + if (nrepeats > 0) + nrepeats--; + else { + grouping--; + nseps--; + } + ret += __printf_puts(io, &thousands_sep, 1); + PRINTANDPAD(cp,dtoaend, + *grouping, 1); + cp += *grouping; + } + if (cp > dtoaend) + cp = dtoaend; + } + if (prec || pi->alt) + ret += __printf_puts(io, decimal_point,1); + } + PRINTANDPAD(cp, dtoaend, prec, 1); + } else { /* %[eE] or sufficiently long %[gG] */ + if (prec > 1 || pi->alt) { + buf[0] = *cp++; + buf[1] = *decimal_point; + ret += __printf_puts(io, buf, 2); + ret += __printf_puts(io, cp, ndig-1); + ret += __printf_pad(io, prec - ndig, 1); + } else /* XeYYY */ + ret += __printf_puts(io, cp, 1); + ret += __printf_puts(io, expstr, expsize); + } + } + /* left-adjusting padding (always blank) */ + if (pi->left) + ret += __printf_pad(io, pi->width - realsz, 0); + + __printf_flush(io); + if (dtoaresult != NULL) + freedtoa(dtoaresult); + + return (ret); +} |