/* * printf_float.c * Floating-point conversions (%e/%f/%g and uppercase variants) for the * printf family. Kept in its own translation unit -- and therefore its own * archive member -- so that --gc-sections can drop it, along with the * pow/exp/log it drags in, from the many programs that only ever format * integers and strings. vsnprintf reaches this through a weak reference; * see printf_internal.h and lib/libc/Makefile. * Copyright (c) 2026 Daniel Hammer */ #include #include #include #include "printf_internal.h" /* Base-10 exponent of the leading digit of a positive finite value, i.e. the E such that value = m * 10^E with m in [1, 10). No rounding is applied. */ static int _pf_exp10(double v) { int E = (int)floor(log10(v)); double s = v / pow(10.0, (double)E); if (s >= 10.0) E++; else if (s < 1.0) E--; return E; } /* Generate `nsig` significant decimal digits of a positive finite value into `out` (ASCII, no terminator). Returns the base-10 exponent of the leading digit, i.e. value ~= (out[0].out[1..]) * 10^E. Rounding is half-up and may carry into a new leading digit, bumping the returned exponent. */ static int _pf_gendigits(double v, int nsig, char *out) { int E = (int)floor(log10(v)); double scale = v / pow(10.0, (double)E); /* Correct estimate errors so scale lands in [1, 10). */ if (scale >= 10.0) { scale /= 10.0; E++; } else if (scale < 1.0) { scale *= 10.0; E--; } for (int i = 0; i < nsig; i++) { int d = (int)scale; if (d < 0) d = 0; else if (d > 9) d = 9; out[i] = (char)('0' + d); scale = (scale - d) * 10.0; } if (scale >= 5.0) { /* round up based on the next digit */ int i = nsig - 1; while (i >= 0) { if (out[i] == '9') { out[i] = '0'; i--; } else { out[i]++; break; } } if (i < 0) { out[0] = '1'; E++; } /* all nines carried out */ } return E; } /* Append the two-or-more-digit exponent field of %e/%g ("e+NN"). */ static void _pf_put_exp(char *mbuf, int *mlen, int E, int upper) { int m = *mlen; mbuf[m++] = upper ? 'E' : 'e'; int ex = E; char es = '+'; if (ex < 0) { es = '-'; ex = -ex; } mbuf[m++] = es; char eb[8]; int el = 0; if (ex == 0) eb[el++] = '0'; while (ex > 0) { eb[el++] = (char)('0' + ex % 10); ex /= 10; } while (el < 2) eb[el++] = '0'; while (el > 0) mbuf[m++] = eb[--el]; *mlen = m; } /* Format a floating-point value for the %e/%f/%g conversions (and uppercase variants), honoring precision, width, padding and sign flags. */ void _pf_putfloat(struct _pf_state *st, double v, char conv, int precision, int width, char pad, int left_align, int plus, int space, int alt) { int upper = (conv >= 'A' && conv <= 'Z'); char lc = upper ? (char)(conv - 'A' + 'a') : conv; _dbl_bits bits; bits.d = v; int neg = (int)(bits.u >> 63); uint64_t expo = (bits.u >> 52) & 0x7FF; uint64_t mant = bits.u & 0x000FFFFFFFFFFFFFULL; char sign = 0; if (neg) sign = '-'; else if (plus) sign = '+'; else if (space) sign = ' '; /* Infinity / NaN: never zero-padded. */ if (expo == 0x7FF) { const char *word = mant ? (upper ? "NAN" : "nan") : (upper ? "INF" : "inf"); if (mant) sign = 0; char tmp[8]; int tl = 0; if (sign) tmp[tl++] = sign; for (const char *p = word; *p; p++) tmp[tl++] = *p; if (!left_align) for (int w = tl; w < width; w++) _pf_putc(st, ' '); for (int i = 0; i < tl; i++) _pf_putc(st, tmp[i]); if (left_align) for (int w = tl; w < width; w++) _pf_putc(st, ' '); return; } if (precision < 0) precision = 6; if (precision > 340) precision = 340; double av = neg ? -v : v; char mbuf[800]; int mlen = 0; if (lc == 'e') { int nsig = precision + 1; char dig[400]; int E; if (av == 0.0) { for (int i = 0; i < nsig; i++) dig[i] = '0'; E = 0; } else E = _pf_gendigits(av, nsig, dig); mbuf[mlen++] = dig[0]; if (precision > 0 || alt) { mbuf[mlen++] = '.'; for (int i = 1; i <= precision; i++) mbuf[mlen++] = dig[i]; } _pf_put_exp(mbuf, &mlen, E, upper); } else if (lc == 'f') { if (av == 0.0) { mbuf[mlen++] = '0'; if (precision > 0 || alt) { mbuf[mlen++] = '.'; for (int i = 0; i < precision; i++) mbuf[mlen++] = '0'; } } else { int Ef = _pf_exp10(av); int keep = Ef + 1 + precision; if (keep <= 0) { char d1[4]; _pf_gendigits(av, 1, d1); int roundUp = (keep == 0 && d1[0] >= '5'); mbuf[mlen++] = '0'; if (precision > 0 || alt) { mbuf[mlen++] = '.'; for (int i = 0; i < precision; i++) mbuf[mlen++] = '0'; } if (roundUp) mbuf[mlen - 1] = '1'; } else { if (keep > 380) keep = 380; char dig[400]; int E = _pf_gendigits(av, keep, dig); int len = keep; int intlen = (E >= 0) ? (E + 1) : 0; if (intlen == 0) mbuf[mlen++] = '0'; else for (int i = 0; i < intlen; i++) mbuf[mlen++] = (i < len ? dig[i] : '0'); if (precision > 0 || alt) { mbuf[mlen++] = '.'; if (E < 0) { int lead = -E - 1; for (int k = 0; k < precision; k++) { if (k < lead) mbuf[mlen++] = '0'; else { int idx = k - lead; mbuf[mlen++] = (idx < len ? dig[idx] : '0'); } } } else { for (int k = 0; k < precision; k++) { int idx = intlen + k; mbuf[mlen++] = (idx < len ? dig[idx] : '0'); } } } } } } else { /* 'g' / 'G' */ int P = precision; if (P == 0) P = 1; char dig[400]; int E; if (av == 0.0) { E = 0; for (int i = 0; i < P; i++) dig[i] = '0'; } else E = _pf_gendigits(av, P, dig); int len = P; int useExp = (E < -4 || E >= P); int hasDot = 0; if (useExp) { int fp = P - 1; mbuf[mlen++] = dig[0]; if (fp > 0 || alt) { mbuf[mlen++] = '.'; hasDot = 1; for (int i = 1; i <= fp; i++) mbuf[mlen++] = dig[i]; } if (!alt && hasDot) { while (mbuf[mlen - 1] == '0') mlen--; if (mbuf[mlen - 1] == '.') mlen--; } _pf_put_exp(mbuf, &mlen, E, upper); } else { int fp = P - 1 - E; if (fp < 0) fp = 0; int intlen = (E >= 0) ? (E + 1) : 0; if (intlen == 0) mbuf[mlen++] = '0'; else for (int i = 0; i < intlen; i++) mbuf[mlen++] = (i < len ? dig[i] : '0'); if (fp > 0 || alt) { mbuf[mlen++] = '.'; hasDot = 1; if (E < 0) { int lead = -E - 1; for (int k = 0; k < fp; k++) { if (k < lead) mbuf[mlen++] = '0'; else { int idx = k - lead; mbuf[mlen++] = (idx < len ? dig[idx] : '0'); } } } else { for (int k = 0; k < fp; k++) { int idx = intlen + k; mbuf[mlen++] = (idx < len ? dig[idx] : '0'); } } } if (!alt && hasDot) { while (mbuf[mlen - 1] == '0') mlen--; if (mbuf[mlen - 1] == '.') mlen--; } } } int total = (sign ? 1 : 0) + mlen; if (!left_align && pad == '0') { if (sign) _pf_putc(st, sign); for (int w = total; w < width; w++) _pf_putc(st, '0'); for (int i = 0; i < mlen; i++) _pf_putc(st, mbuf[i]); } else if (!left_align) { for (int w = total; w < width; w++) _pf_putc(st, ' '); if (sign) _pf_putc(st, sign); for (int i = 0; i < mlen; i++) _pf_putc(st, mbuf[i]); } else { if (sign) _pf_putc(st, sign); for (int i = 0; i < mlen; i++) _pf_putc(st, mbuf[i]); for (int w = total; w < width; w++) _pf_putc(st, ' '); } }