]> git.saurik.com Git - apple/libc.git/blobdiff - stdio/FreeBSD/xprintf_float.c
Libc-997.1.1.tar.gz
[apple/libc.git] / stdio / FreeBSD / xprintf_float.c
diff --git a/stdio/FreeBSD/xprintf_float.c b/stdio/FreeBSD/xprintf_float.c
new file mode 100644 (file)
index 0000000..9f4c60e
--- /dev/null
@@ -0,0 +1,445 @@
+/*-
+ * 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: src/lib/libc/stdio/xprintf_float.c,v 1.1 2005/12/16 18:56:38 phk Exp $
+ */
+
+#include <namespace.h>
+#include "xlocale_private.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 "xprintf_private.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 --------------------------------------------------------------*/
+
+extern const char *__fix_nogrouping(const char *str);
+
+#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' ---------------------------------------------------------------*/
+
+__private_extern__ int
+__printf_arginfo_float(const struct printf_info *pi, size_t n, int *argt)
+{
+       assert (n > 0);
+#ifdef VECTORS
+       if (pi->is_vec)
+               argt[0] = PA_VECTOR;
+       else {
+#endif /* VECTORS */
+       argt[0] = PA_DOUBLE;
+       if (pi->is_long_double)
+               argt[0] |= PA_FLAG_LONG_DOUBLE;
+#ifdef VECTORS
+       }
+#endif /* VECTORS */
+       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
+ */
+
+__private_extern__ 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 */
+       const char *decimal_point;      /* locale specific decimal point */
+       int decimal_point_len;  /* length of locale specific decimal point */
+       int n2;                 /* XXX: for PRINTANDPAD */
+       const char *thousands_sep;      /* locale specific thousands separator */
+       int thousands_sep_len;  /* length of locale specific thousands separator */
+       char buf[BUF];          /* buffer with space for digits of uintmax_t */
+       const char *xdigs;
+       int flag;
+
+#ifdef VECTORS
+       if (pi->is_vec) return __xprintf_vector(io, pi, arg);
+#endif /* VECTORS */
+
+       prec = pi->prec;
+       ox[1] = '\0';
+       sign = pi->signchar;
+       flag = 0;
+       ret = 0;
+
+       thousands_sep = localeconv_l(pi->loc)->thousands_sep;
+       thousands_sep_len = strlen(thousands_sep);
+       grouping = NULL;
+       if (pi->group)
+               grouping = __fix_nogrouping(localeconv_l(pi->loc)->grouping);
+       decimal_point = localeconv_l(pi->loc)->decimal_point;
+       decimal_point_len = strlen(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, decimal_point_len);
+                               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, thousands_sep_len);
+                                               PRINTANDPAD(cp,dtoaend,
+                                                   *grouping, 1);
+                                               cp += *grouping;
+                                       }
+                                       if (cp > dtoaend)
+                                               cp = dtoaend;
+                               }
+                               if (prec || pi->alt)
+                                       ret += __printf_puts(io, decimal_point, decimal_point_len);
+                       }
+                       PRINTANDPAD(cp, dtoaend, prec, 1);
+               } else {        /* %[eE] or sufficiently long %[gG] */
+                       if (prec > 1 || pi->alt) {
+                               buf[0] = *cp++;
+                               memcpy(buf + 1, decimal_point, decimal_point_len);
+                               ret += __printf_puts(io, buf, decimal_point_len + 1);
+                               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);
+}