fix incorrect rounding in printf floating point corner cases
[oweals/musl.git] / src / stdio / vfprintf.c
index a3bf18dd8c6d4f4b1ef0702239664b74ca33e874..31c3d5ddca8f78fc5f8dedf3e6801ab2688433d9 100644 (file)
@@ -1,4 +1,13 @@
 #include "stdio_impl.h"
+#include <errno.h>
+#include <ctype.h>
+#include <limits.h>
+#include <string.h>
+#include <stdarg.h>
+#include <wchar.h>
+#include <inttypes.h>
+#include <math.h>
+#include <float.h>
 
 /* Some useful macros */
 
@@ -189,9 +198,17 @@ static char *fmt_u(uintmax_t x, char *s)
        return s;
 }
 
+/* Do not override this check. The floating point printing code below
+ * depends on the float.h constants being right. If they are wrong, it
+ * may overflow the stack. */
+#if LDBL_MANT_DIG == 53
+typedef char compiler_defines_long_double_incorrectly[9-(int)sizeof(long double)];
+#endif
+
 static int fmt_fp(FILE *f, long double y, int w, int p, int fl, int t)
 {
-       uint32_t big[(LDBL_MAX_EXP+LDBL_MANT_DIG)/9+1];
+       uint32_t big[(LDBL_MANT_DIG+28)/29 + 1          // mantissa expansion
+               + (LDBL_MAX_EXP+LDBL_MANT_DIG+28+8)/9]; // exponent expansion
        uint32_t *a, *d, *r, *z;
        int e2=0, e, i, j, l;
        char buf[9+LDBL_MANT_DIG/4], *s;
@@ -200,7 +217,7 @@ static int fmt_fp(FILE *f, long double y, int w, int p, int fl, int t)
        char ebuf0[3*sizeof(int)], *ebuf=&ebuf0[3*sizeof(int)], *estr;
 
        pl=1;
-       if (y<0 || 1/y<0) {
+       if (signbit(y)) {
                y=-y;
        } else if (fl & MARK_POS) {
                prefix+=3;
@@ -297,7 +314,7 @@ static int fmt_fp(FILE *f, long double y, int w, int p, int fl, int t)
        }
        while (e2<0) {
                uint32_t carry=0, *b;
-               int sh=MIN(9,-e2);
+               int sh=MIN(9,-e2), need=1+(p+LDBL_MANT_DIG/3+8)/9;
                for (d=a; d<z; d++) {
                        uint32_t rm = *d & (1<<sh)-1;
                        *d = (*d>>sh) + carry;
@@ -307,7 +324,7 @@ static int fmt_fp(FILE *f, long double y, int w, int p, int fl, int t)
                if (carry) *z++ = carry;
                /* Avoid (slow!) computation past requested precision */
                b = (t|32)=='f' ? r : a;
-               if (z-b > 2+p/9) z = b+2+p/9;
+               if (z-b > need) z = b+need;
                e2+=sh;
        }
 
@@ -319,7 +336,7 @@ static int fmt_fp(FILE *f, long double y, int w, int p, int fl, int t)
        if (j < 9*(z-r-1)) {
                uint32_t x;
                /* We avoid C's broken division of negative numbers */
-               d = r + 1 + (j+9*LDBL_MAX_EXP)/9 - LDBL_MAX_EXP;
+               d = r + 1 + ((j+9*LDBL_MAX_EXP)/9 - LDBL_MAX_EXP);
                j += 9*LDBL_MAX_EXP;
                j %= 9;
                for (i=10, j++; j<9; i*=10, j++);
@@ -430,7 +447,7 @@ static int getint(char **s) {
 static int printf_core(FILE *f, const char *fmt, va_list *ap, union arg *nl_arg, int *nl_type)
 {
        char *a, *z, *s=(char *)fmt;
-       unsigned l10n=0, litpct, fl;
+       unsigned l10n=0, fl;
        int w, p;
        union arg arg;
        int argpos;
@@ -455,9 +472,7 @@ static int printf_core(FILE *f, const char *fmt, va_list *ap, union arg *nl_arg,
 
                /* Handle literal text and %% format specifiers */
                for (a=s; *s && *s!='%'; s++);
-               litpct = strspn(s, "%")/2; /* Optimize %%%% runs */
-               z = s+litpct;
-               s += 2*litpct;
+               for (z=s; s[0]=='%' && s[1]=='%'; z++, s+=2);
                l = z-a;
                if (f) out(f, a, l);
                if (l) continue;
@@ -516,7 +531,6 @@ static int printf_core(FILE *f, const char *fmt, va_list *ap, union arg *nl_arg,
                /* Check validity of argument type (nl/normal) */
                if (st==NOARG) {
                        if (argpos>=0) return -1;
-                       else if (!f) continue;
                } else {
                        if (argpos>=0) nl_type[argpos]=st, arg=nl_arg[argpos];
                        else if (f) pop_arg(&arg, st, ap);
@@ -638,7 +652,7 @@ static int printf_core(FILE *f, const char *fmt, va_list *ap, union arg *nl_arg,
        return 1;
 }
 
-int vfprintf(FILE *f, const char *fmt, va_list ap)
+int vfprintf(FILE *restrict f, const char *restrict fmt, va_list ap)
 {
        va_list ap2;
        int nl_type[NL_ARGMAX+1] = {0};
@@ -646,8 +660,12 @@ int vfprintf(FILE *f, const char *fmt, va_list ap)
        unsigned char internal_buf[80], *saved_buf = 0;
        int ret;
 
+       /* the copy allows passing va_list* even if va_list is an array */
        va_copy(ap2, ap);
-       if (printf_core(0, fmt, &ap2, nl_arg, nl_type) < 0) return -1;
+       if (printf_core(0, fmt, &ap2, nl_arg, nl_type) < 0) {
+               va_end(ap2);
+               return -1;
+       }
 
        FLOCK(f);
        if (!f->buf_size) {