Skip to content

Instantly share code, notes, and snippets.

@ridiculousfish
Created April 14, 2024 02:42
Show Gist options
  • Save ridiculousfish/4061c3d04cfe6e2ed982b4ede29b55d6 to your computer and use it in GitHub Desktop.
Save ridiculousfish/4061c3d04cfe6e2ed982b4ede29b55d6 to your computer and use it in GitHub Desktop.
#include <ctype.h>
#include <errno.h>
#include <float.h>
#include <inttypes.h>
#include <limits.h>
#include <math.h>
#include <stdarg.h>
#include <stddef.h>
#include <stdlib.h>
#include <string.h>
#include <wchar.h>
/* Some useful macros */
#define MAX(a, b) ((a) > (b) ? (a) : (b))
#define MIN(a, b) ((a) < (b) ? (a) : (b))
/* Convenient bit representation for modifier flags, which all fall
* within 31 codepoints of the space character. */
#define ALT_FORM (1U << ('#' - ' '))
#define ZERO_PAD (1U << ('0' - ' '))
#define LEFT_ADJ (1U << ('-' - ' '))
#define PAD_POS (1U << (' ' - ' '))
#define MARK_POS (1U << ('+' - ' '))
#define GROUPED (1U << ('\'' - ' '))
#define FLAGMASK (ALT_FORM | ZERO_PAD | LEFT_ADJ | PAD_POS | MARK_POS | GROUPED)
/* State machine to accept length modifiers + conversion specifiers.
* Result is 0 on failure, or an argument type to pop on success. */
enum {
BARE,
LPRE,
LLPRE,
HPRE,
HHPRE,
BIGLPRE,
ZTPRE,
JPRE,
STOP,
PTR,
INT,
UINT,
ULLONG,
LONG,
ULONG,
SHORT,
USHORT,
CHAR,
UCHAR,
LLONG,
SIZET,
IMAX,
UMAX,
PDIFF,
UIPTR,
DBL,
LDBL,
NOARG,
MAXSTATE
};
#define S(x) [(x) - 'A']
static const unsigned char states[]['z' - 'A' + 1] = {
{
/* 0: bare types */
S('d') = INT, S('i') = INT, S('o') = UINT, S('u') = UINT,
S('x') = UINT, S('X') = UINT, S('e') = DBL, S('f') = DBL,
S('g') = DBL, S('a') = DBL, S('E') = DBL, S('F') = DBL,
S('G') = DBL, S('A') = DBL, S('c') = INT, S('C') = UINT,
S('s') = PTR, S('S') = PTR, S('p') = UIPTR, S('n') = PTR,
S('m') = NOARG, S('l') = LPRE, S('h') = HPRE, S('L') = BIGLPRE,
S('z') = ZTPRE, S('j') = JPRE, S('t') = ZTPRE,
},
{
/* 1: l-prefixed */
S('d') = LONG,
S('i') = LONG,
S('o') = ULONG,
S('u') = ULONG,
S('x') = ULONG,
S('X') = ULONG,
S('e') = DBL,
S('f') = DBL,
S('g') = DBL,
S('a') = DBL,
S('E') = DBL,
S('F') = DBL,
S('G') = DBL,
S('A') = DBL,
S('c') = UINT,
S('s') = PTR,
S('n') = PTR,
S('l') = LLPRE,
},
{
/* 2: ll-prefixed */
S('d') = LLONG,
S('i') = LLONG,
S('o') = ULLONG,
S('u') = ULLONG,
S('x') = ULLONG,
S('X') = ULLONG,
S('n') = PTR,
},
{
/* 3: h-prefixed */
S('d') = SHORT,
S('i') = SHORT,
S('o') = USHORT,
S('u') = USHORT,
S('x') = USHORT,
S('X') = USHORT,
S('n') = PTR,
S('h') = HHPRE,
},
{
/* 4: hh-prefixed */
S('d') = CHAR,
S('i') = CHAR,
S('o') = UCHAR,
S('u') = UCHAR,
S('x') = UCHAR,
S('X') = UCHAR,
S('n') = PTR,
},
{
/* 5: L-prefixed */
S('e') = LDBL,
S('f') = LDBL,
S('g') = LDBL,
S('a') = LDBL,
S('E') = LDBL,
S('F') = LDBL,
S('G') = LDBL,
S('A') = LDBL,
S('n') = PTR,
},
{
/* 6: z- or t-prefixed (assumed to be same size) */
S('d') = PDIFF,
S('i') = PDIFF,
S('o') = SIZET,
S('u') = SIZET,
S('x') = SIZET,
S('X') = SIZET,
S('n') = PTR,
},
{
/* 7: j-prefixed */
S('d') = IMAX,
S('i') = IMAX,
S('o') = UMAX,
S('u') = UMAX,
S('x') = UMAX,
S('X') = UMAX,
S('n') = PTR,
}};
#define OOB(x) ((unsigned)(x) - 'A' > 'z' - 'A')
union arg {
uintmax_t i;
long double f;
void *p;
};
static void pop_arg(union arg *arg, int type, va_list *ap) {
switch (type) {
case PTR:
arg->p = va_arg(*ap, void *);
break;
case INT:
arg->i = va_arg(*ap, int);
break;
case UINT:
arg->i = va_arg(*ap, unsigned int);
break;
case LONG:
arg->i = va_arg(*ap, long);
break;
case ULONG:
arg->i = va_arg(*ap, unsigned long);
break;
case ULLONG:
arg->i = va_arg(*ap, unsigned long long);
break;
case SHORT:
arg->i = (short)va_arg(*ap, int);
break;
case USHORT:
arg->i = (unsigned short)va_arg(*ap, int);
break;
case CHAR:
arg->i = (signed char)va_arg(*ap, int);
break;
case UCHAR:
arg->i = (unsigned char)va_arg(*ap, int);
break;
case LLONG:
arg->i = va_arg(*ap, long long);
break;
case SIZET:
arg->i = va_arg(*ap, size_t);
break;
case IMAX:
arg->i = va_arg(*ap, intmax_t);
break;
case UMAX:
arg->i = va_arg(*ap, uintmax_t);
break;
case PDIFF:
arg->i = va_arg(*ap, ptrdiff_t);
break;
case UIPTR:
arg->i = (uintptr_t)va_arg(*ap, void *);
break;
case DBL:
arg->f = va_arg(*ap, double);
break;
case LDBL:
arg->f = va_arg(*ap, long double);
}
}
static void out(FILE *f, const char *s, size_t l) {
if (!ferror(f)) {
fwrite(s, 1, l, f);
}
}
static void pad(FILE *f, char c, int w, int l, int fl) {
char pad[256];
if (fl & (LEFT_ADJ | ZERO_PAD) || l >= w)
return;
l = w - l;
memset(pad, c, l > sizeof pad ? sizeof pad : l);
for (; l >= sizeof pad; l -= sizeof pad)
out(f, pad, sizeof pad);
out(f, pad, l);
}
static const char xdigits[16] = {"0123456789ABCDEF"};
static char *format_hex(uintmax_t x, char *s, int lower) {
for (; x; x >>= 4)
*--s = xdigits[(x & 15)] | lower;
return s;
}
static char *format_octal(uintmax_t x, char *s) {
for (; x; x >>= 3)
*--s = '0' + (x & 7);
return s;
}
static char *format_decimal(uintmax_t x, char *s) {
unsigned long y;
for (; x > ULONG_MAX; x /= 10)
*--s = '0' + x % 10;
for (y = x; y; y /= 10)
*--s = '0' + y % 10;
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 format_float(FILE *f, long double y, int w, int p, int fl, int t) {
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;
int j;
char buf[9 + LDBL_MANT_DIG / 4] = {}, *s;
const char *prefix = "-0X+0X 0X-0x+0x 0x";
int pl;
char ebuf0[3 * sizeof(int)], *ebuf = &ebuf0[3 * sizeof(int)], *estr;
// t is in "e f g a E F G A"
pl = 1;
if (signbit(y)) {
y = -y;
} else if (fl & MARK_POS) {
prefix += 3;
} else if (fl & PAD_POS) {
prefix += 6;
} else
prefix++, pl = 0;
if (!isfinite(y)) {
char *s = (t & 32) ? "inf" : "INF";
if (y != y)
s = (t & 32) ? "nan" : "NAN";
pad(f, ' ', w, 3 + pl, fl & ~ZERO_PAD);
out(f, prefix, pl);
out(f, s, 3);
pad(f, ' ', w, 3 + pl, fl ^ LEFT_ADJ);
return MAX(w, 3 + pl);
}
// y is 0 or in [1, 2)
y = frexpl(y, &e2) * 2;
if (y)
e2--;
printf("y: %Lf, e2: %d\n", y, e2);
if ((t | 32) == 'a') {
// Set to half a hex digit to get rounding agreeing with the fp rounding
// mode.
long double round = 8.0;
int re;
if (t & 32)
prefix += 9;
pl += 2;
if (p < 0 || p >= LDBL_MANT_DIG / 4 - 1)
re = 0;
else
re = LDBL_MANT_DIG / 4 - 1 - p;
printf("re: %d, p: %d, weird: %d %d\n", re, p, LDBL_MANT_DIG,
LDBL_MANT_DIG / 4 - 1);
if (re) {
round *= 1 << (LDBL_MANT_DIG % 4);
while (re--)
round *= 16;
printf("round: %La (%Lf)\n", round, round);
if (*prefix == '-') {
y = -y;
y -= round;
y += round;
y = -y;
} else {
y += round;
y -= round;
}
}
estr = format_decimal(e2 < 0 ? -e2 : e2, ebuf);
if (estr == ebuf)
*--estr = '0';
*--estr = (e2 < 0 ? '-' : '+');
*--estr = t + ('p' - 'a');
s = buf;
do {
int x = y;
*s++ = xdigits[x] | (t & 32);
y = 16 * (y - x);
if (s - buf == 1 && (y || p > 0 || (fl & ALT_FORM)))
*s++ = '.';
} while (y);
if (p > INT_MAX - 2 - (ebuf - estr) - pl)
return -1;
// If precision is nonzero, and length of mantissa string minus 2 (that is,
// to remove leading digit and decimal) is less than the precision,
if (p && s - buf - 2 < p)
l = (p + 2) + (ebuf - estr);
else
l = (s - buf) + (ebuf - estr);
printf("before exponent: %ld\n", l - (ebuf - estr));
pad(f, ' ', w, pl + l, fl);
out(f, prefix, pl);
pad(f, '0', w, pl + l, fl ^ ZERO_PAD);
out(f, buf, s - buf);
pad(f, '0', l - (ebuf - estr) - (s - buf), 0, 0);
out(f, estr, ebuf - estr);
pad(f, ' ', w, pl + l, fl ^ LEFT_ADJ);
return MAX(w, pl + l);
}
// t is in "e f g E F G"
if (p < 0)
p = 6;
if (y)
y *= 0x1p28, e2 -= 28;
if (e2 < 0)
a = r = z = big;
else
a = r = z = big + sizeof(big) / sizeof(*big) - LDBL_MANT_DIG - 1;
do {
*z = y;
y = 1000000000 * (y - *z++);
} while (y);
while (e2 > 0) {
uint32_t carry = 0;
int sh = MIN(29, e2);
for (d = z - 1; d >= a; d--) {
uint64_t x = ((uint64_t)*d << sh) + carry;
*d = x % 1000000000;
carry = x / 1000000000;
}
if (carry)
*--a = carry;
while (z > a && !z[-1])
z--;
e2 -= sh;
}
while (e2 < 0) {
uint32_t carry = 0, *b;
int sh = MIN(9, -e2), need = 1 + (p + LDBL_MANT_DIG / 3U + 8) / 9;
for (d = a; d < z; d++) {
uint32_t rm = *d & (1 << sh) - 1;
*d = (*d >> sh) + carry;
carry = (1000000000 >> sh) * rm;
}
if (!*a)
a++;
if (carry)
*z++ = carry;
/* Avoid (slow!) computation past requested precision */
b = (t | 32) == 'f' ? r : a;
if (z - b > need)
z = b + need;
e2 += sh;
}
if (a < z)
for (i = 10, e = 9 * (r - a); *a >= i; i *= 10, e++)
;
else
e = 0;
// precision is "number of digits to appear after the decimal-point for a, A,
// e, E, f, and F" or "maximum number of significant digits for g and G
// conversions"
// t is in efgEFG
// t != f => t = e, E, g, G
// If we are g, then we have one
/* Perform rounding: j is precision after the radix (possibly neg) */
j = p - ((t | 32) != 'f') * e - ((t | 32) == 'g' && p);
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);
j += 9 * LDBL_MAX_EXP;
j %= 9;
for (i = 10, j++; j < 9; i *= 10, j++)
;
// printf("i: %d p: %d j: %lld\n", i, p, j);
x = *d % i;
/* Are there any significant digits past j? */
if (x || d + 1 != z) {
long double round = 2 / LDBL_EPSILON;
long double small;
if ((*d / i & 1) || (i == 1000000000 && d > a && (d[-1] & 1)))
round += 2;
if (x < i / 2)
small = 0x0.8p0;
else if (x == i / 2 && d + 1 == z)
small = 0x1.0p0;
else
small = 0x1.8p0;
if (pl && *prefix == '-')
round *= -1, small *= -1;
*d -= x;
/* Decide whether to round by probing round+small */
if (round + small != round) {
*d = *d + i;
while (*d > 999999999) {
*d-- = 0;
if (d < a)
*--a = 0;
(*d)++;
}
for (i = 10, e = 9 * (r - a); *a >= i; i *= 10, e++)
;
}
}
if (z > d + 1)
z = d + 1;
}
for (; z > a && !z[-1]; z--)
;
if ((t | 32) == 'g') {
if (!p)
p++;
if (p > e && e >= -4) {
t--;
p -= e + 1;
} else {
t -= 2;
p--;
}
if (!(fl & ALT_FORM)) {
/* Count trailing zeros in last place */
if (z > a && z[-1])
for (i = 10, j = 0; z[-1] % i == 0; i *= 10, j++)
;
else
j = 9;
if ((t | 32) == 'f')
p = MIN(p, MAX(0, 9 * (z - r - 1) - j));
else
p = MIN(p, MAX(0, 9 * (z - r - 1) + e - j));
}
}
if (p > INT_MAX - 1 - (p || (fl & ALT_FORM)))
return -1;
l = 1 + p + (p || (fl & ALT_FORM));
if ((t | 32) == 'f') {
if (e > INT_MAX - l)
return -1;
if (e > 0)
l += e;
} else {
// estr is set if t is in "e g E G"
estr = format_decimal(e < 0 ? -e : e, ebuf);
while (ebuf - estr < 2)
*--estr = '0';
*--estr = (e < 0 ? '-' : '+');
*--estr = t;
if (ebuf - estr > INT_MAX - l)
return -1;
l += ebuf - estr;
}
if (l > INT_MAX - pl)
return -1;
pad(f, ' ', w, pl + l, fl);
out(f, prefix, pl);
pad(f, '0', w, pl + l, fl ^ ZERO_PAD);
if ((t | 32) == 'f') {
if (a > r)
a = r;
for (d = a; d <= r; d++) {
char *s = format_decimal(*d, buf + 9);
if (d != a)
while (s > buf)
*--s = '0';
else if (s == buf + 9)
*--s = '0';
out(f, s, buf + 9 - s);
}
if (p || (fl & ALT_FORM))
out(f, ".", 1);
for (; d < z && p > 0; d++, p -= 9) {
char *s = format_decimal(*d, buf + 9);
while (s > buf)
*--s = '0';
out(f, s, MIN(9, p));
}
pad(f, '0', p + 9, 9, 0);
} else {
// t is in "e g E G"
if (z <= a)
z = a + 1;
for (d = a; d < z && p >= 0; d++) {
char *s = format_decimal(*d, buf + 9);
if (s == buf + 9)
*--s = '0';
if (d != a)
while (s > buf)
*--s = '0';
else {
out(f, s++, 1);
if (p > 0 || (fl & ALT_FORM))
out(f, ".", 1);
}
out(f, s, MIN(buf + 9 - s, p));
p -= buf + 9 - s;
}
pad(f, '0', p + 18, 18, 0);
out(f, estr, ebuf - estr);
}
pad(f, ' ', w, pl + l, fl ^ LEFT_ADJ);
return MAX(w, pl + l);
}
static int getint(char **s) {
int i;
for (i = 0; isdigit(**s); (*s)++) {
if (i > INT_MAX / 10U || **s - '0' > INT_MAX - 10 * i)
i = -1;
else
i = 10 * i + (**s - '0');
}
return i;
}
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, fl;
int w, p, xp;
union arg arg;
int argpos;
unsigned st, ps;
int cnt = 0, l = 0;
size_t i;
char buf[sizeof(uintmax_t) * 3];
const char *prefix;
int t, pl;
wchar_t wc[2], *ws;
char mb[4];
for (;;) {
/* This error is only specified for snprintf, but since it's
* unspecified for other forms, do the same. Stop immediately
* on overflow; otherwise %n could produce wrong results. */
if (l > INT_MAX - cnt)
goto overflow;
/* Update output count, end loop when fmt is exhausted */
cnt += l;
if (!*s)
break;
/* Handle literal text and %% format specifiers */
for (a = s; *s && *s != '%'; s++)
;
for (z = s; s[0] == '%' && s[1] == '%'; z++, s += 2)
;
if (z - a > INT_MAX - cnt)
goto overflow;
l = z - a;
if (f)
out(f, a, l);
if (l)
continue;
if (isdigit(s[1]) && s[2] == '$') {
l10n = 1;
argpos = s[1] - '0';
s += 3;
} else {
argpos = -1;
s++;
}
/* Read modifier flags */
for (fl = 0; (unsigned)*s - ' ' < 32 && (FLAGMASK & (1U << (*s - ' ')));
s++)
fl |= 1U << (*s - ' ');
/* Read field width */
if (*s == '*') {
if (isdigit(s[1]) && s[2] == '$') {
l10n = 1;
if (!f)
nl_type[s[1] - '0'] = INT, w = 0;
else
w = nl_arg[s[1] - '0'].i;
s += 3;
} else if (!l10n) {
w = f ? va_arg(*ap, int) : 0;
s++;
} else
goto inval;
if (w < 0)
fl |= LEFT_ADJ, w = -w;
} else if ((w = getint(&s)) < 0)
goto overflow;
/* Read precision */
if (*s == '.' && s[1] == '*') {
if (isdigit(s[2]) && s[3] == '$') {
if (!f)
nl_type[s[2] - '0'] = INT, p = 0;
else
p = nl_arg[s[2] - '0'].i;
s += 4;
} else if (!l10n) {
p = f ? va_arg(*ap, int) : 0;
s += 2;
} else
goto inval;
xp = (p >= 0);
} else if (*s == '.') {
s++;
p = getint(&s);
xp = 1;
} else {
p = -1;
xp = 0;
}
/* Format specifier state machine */
st = 0;
do {
if (OOB(*s))
goto inval;
ps = st;
st = states[st] S(*s++);
} while (st - 1 < STOP);
if (!st)
goto inval;
/* Check validity of argument type (nl/normal) */
if (st == NOARG) {
if (argpos >= 0)
goto inval;
} else {
if (argpos >= 0) {
if (!f)
nl_type[argpos] = st;
else
arg = nl_arg[argpos];
} else if (f)
pop_arg(&arg, st, ap);
else
return 0;
}
if (!f)
continue;
/* Do not process any new directives once in error state. */
if (ferror(f))
return -1;
z = buf + sizeof(buf);
prefix = "-+ 0X0x";
pl = 0;
t = s[-1];
/* Transform ls,lc -> S,C */
if (ps && (t & 15) == 3)
t &= ~32;
/* - and 0 flags are mutually exclusive */
if (fl & LEFT_ADJ)
fl &= ~ZERO_PAD;
switch (t) {
case 'n':
switch (ps) {
case BARE:
*(int *)arg.p = cnt;
break;
case LPRE:
*(long *)arg.p = cnt;
break;
case LLPRE:
*(long long *)arg.p = cnt;
break;
case HPRE:
*(unsigned short *)arg.p = cnt;
break;
case HHPRE:
*(unsigned char *)arg.p = cnt;
break;
case ZTPRE:
*(size_t *)arg.p = cnt;
break;
case JPRE:
*(uintmax_t *)arg.p = cnt;
break;
}
continue;
case 'p':
p = MAX(p, 2 * sizeof(void *));
t = 'x';
fl |= ALT_FORM;
case 'x':
case 'X':
a = format_hex(arg.i, z, t & 32);
if (arg.i && (fl & ALT_FORM))
prefix += (t >> 4), pl = 2;
if (0) {
case 'o':
a = format_octal(arg.i, z);
if ((fl & ALT_FORM) && p < z - a + 1)
p = z - a + 1;
}
if (0) {
case 'd':
case 'i':
pl = 1;
if (arg.i > INTMAX_MAX) {
arg.i = -arg.i;
} else if (fl & MARK_POS) {
prefix++;
} else if (fl & PAD_POS) {
prefix += 2;
} else
pl = 0;
case 'u':
a = format_decimal(arg.i, z);
}
if (xp && p < 0)
goto overflow;
if (xp)
fl &= ~ZERO_PAD;
if (!arg.i && !p) {
a = z;
break;
}
p = MAX(p, z - a + !arg.i);
break;
narrow_c:
case 'c':
*(a = z - (p = 1)) = arg.i;
fl &= ~ZERO_PAD;
break;
case 'm':
if (1)
a = strerror(errno);
else
case 's':
a = arg.p ? arg.p : "(null)";
z = a + strnlen(a, p < 0 ? INT_MAX : p);
if (p < 0 && *z)
goto overflow;
p = z - a;
fl &= ~ZERO_PAD;
break;
case 'C':
if (!arg.i)
goto narrow_c;
wc[0] = arg.i;
wc[1] = 0;
arg.p = wc;
p = -1;
case 'S':
ws = arg.p;
for (i = l = 0;
i < p && *ws && (l = wctomb(mb, *ws++)) >= 0 && l <= p - i; i += l)
;
if (l < 0)
return -1;
if (i > INT_MAX)
goto overflow;
p = i;
pad(f, ' ', w, p, fl);
ws = arg.p;
for (i = 0; i < 0U + p && *ws && i + (l = wctomb(mb, *ws++)) <= p; i += l)
out(f, mb, l);
pad(f, ' ', w, p, fl ^ LEFT_ADJ);
l = w > p ? w : p;
continue;
case 'e':
case 'f':
case 'g':
case 'a':
case 'E':
case 'F':
case 'G':
case 'A':
if (xp && p < 0)
goto overflow;
l = format_float(f, arg.f, w, p, fl, t);
if (l < 0)
goto overflow;
continue;
}
if (p < z - a)
p = z - a;
if (p > INT_MAX - pl)
goto overflow;
if (w < pl + p)
w = pl + p;
if (w > INT_MAX - cnt)
goto overflow;
pad(f, ' ', w, pl + p, fl);
out(f, prefix, pl);
pad(f, '0', w, pl + p, fl ^ ZERO_PAD);
pad(f, '0', p, z - a, 0);
out(f, a, z - a);
pad(f, ' ', w, pl + p, fl ^ LEFT_ADJ);
l = w;
}
if (f)
return cnt;
if (!l10n)
return 0;
for (i = 1; i <= NL_ARGMAX && nl_type[i]; i++)
pop_arg(nl_arg + i, nl_type[i], ap);
for (; i <= NL_ARGMAX && !nl_type[i]; i++)
;
if (i <= NL_ARGMAX)
goto inval;
return 1;
inval:
errno = EINVAL;
return -1;
overflow:
errno = EOVERFLOW;
return -1;
}
static int do_test(const char *fmt, ...) {
va_list ap;
va_start(ap, fmt);
int ret = printf_core(stdout, fmt, &ap, 0, 0);
va_end(ap);
return ret;
}
int main(void) {
// do_test("%.2e", 1500001.0);
double f = 1e-100;
int ret = do_test("%.2147483647g\n", f);
// int ret = do_test("%.100g\n", f);
if (ret < 0) {
perror("printf");
}
// do_test("%f\n", 1.34);
// do_test("%f\n", 10.34);
// do_test("%g\n", .00005);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment