mirror of
https://github.com/jart/cosmopolitan.git
synced 2025-02-01 03:53:33 +00:00
394 lines
11 KiB
C
394 lines
11 KiB
C
/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
|
|
│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│
|
|
╞══════════════════════════════════════════════════════════════════════════════╡
|
|
│ Copyright 2022 Justine Alexandra Roberts Tunney │
|
|
│ │
|
|
│ Permission to use, copy, modify, and/or distribute this software for │
|
|
│ any purpose with or without fee is hereby granted, provided that the │
|
|
│ above copyright notice and this permission notice appear in all copies. │
|
|
│ │
|
|
│ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL │
|
|
│ WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED │
|
|
│ WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE │
|
|
│ AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL │
|
|
│ DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR │
|
|
│ PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER │
|
|
│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │
|
|
│ PERFORMANCE OF THIS SOFTWARE. │
|
|
╚─────────────────────────────────────────────────────────────────────────────*/
|
|
#include "libc/assert.h"
|
|
#include "libc/fmt/fmt.h"
|
|
#include "libc/fmt/fmt.internal.h"
|
|
#include "libc/fmt/internal.h"
|
|
#include "libc/macros.internal.h"
|
|
#include "libc/nexgen32e/bsr.h"
|
|
#include "third_party/gdtoa/gdtoa.h"
|
|
|
|
static const char kSpecialFloats[2][2][4] = {{"INF", "inf"}, {"NAN", "nan"}};
|
|
|
|
static void __fmt_free_dtoa(char **mem) {
|
|
if (*mem) {
|
|
freedtoa(*mem);
|
|
*mem = 0;
|
|
}
|
|
}
|
|
|
|
int __fmt_dtoa(int (*out)(const char *, void *, size_t), void *arg, int d,
|
|
int flags, int prec, int sign, int width, bool longdouble,
|
|
char qchar, unsigned char signbit, const char *alphabet,
|
|
va_list va) {
|
|
union {
|
|
double d;
|
|
uint32_t u[2];
|
|
uint64_t q;
|
|
} pun;
|
|
int c, k, i1, ui, bw, bex, sgn, prec1, decpt;
|
|
char *s, *p, *q, *se, *mem, special[8];
|
|
mem = 0;
|
|
switch (d) {
|
|
case 'F':
|
|
case 'f':
|
|
if (!(flags & FLAGS_PRECISION)) prec = 6;
|
|
if (longdouble) {
|
|
pun.d = va_arg(va, long double);
|
|
} else {
|
|
pun.d = va_arg(va, double);
|
|
}
|
|
FormatDtoa:
|
|
assert(!mem);
|
|
s = mem = dtoa(pun.d, 3, prec, &decpt, &sgn, &se);
|
|
if (decpt == 9999) {
|
|
Format9999:
|
|
bzero(special, sizeof(special));
|
|
p = q = special;
|
|
if (sgn) {
|
|
*q++ = '-';
|
|
} else if (flags & FLAGS_PLUS) {
|
|
*q++ = '+';
|
|
} else if (flags & FLAGS_SPACE) {
|
|
*q++ = ' ';
|
|
}
|
|
memcpy(q, kSpecialFloats[*s == 'N'][d >= 'a'], 4);
|
|
FormatThatThing:
|
|
__fmt_free_dtoa(&mem);
|
|
mem = 0;
|
|
prec = 0;
|
|
flags &= ~(FLAGS_PRECISION | FLAGS_PLUS | FLAGS_SPACE);
|
|
goto FormatString;
|
|
}
|
|
FormatReal:
|
|
if (sgn) sign = '-';
|
|
if (prec > 0) width -= prec;
|
|
if (width > 0) {
|
|
if (sign) --width;
|
|
if (decpt <= 0) {
|
|
--width;
|
|
if (prec > 0) --width;
|
|
} else {
|
|
if (s == se) decpt = 1;
|
|
width -= decpt;
|
|
if (prec > 0) --width;
|
|
}
|
|
}
|
|
if (width > 0 && !(flags & FLAGS_LEFT)) {
|
|
if (flags & FLAGS_ZEROPAD) {
|
|
if (sign) __FMT_PUT(sign);
|
|
sign = 0;
|
|
do
|
|
__FMT_PUT('0');
|
|
while (--width > 0);
|
|
} else {
|
|
do
|
|
__FMT_PUT(' ');
|
|
while (--width > 0);
|
|
}
|
|
}
|
|
if (sign) __FMT_PUT(sign);
|
|
if (decpt <= 0) {
|
|
__FMT_PUT('0');
|
|
if (prec > 0) __FMT_PUT('.');
|
|
while (decpt < 0) {
|
|
__FMT_PUT('0');
|
|
prec--;
|
|
decpt++;
|
|
}
|
|
} else {
|
|
do {
|
|
if ((c = *s)) {
|
|
s++;
|
|
} else {
|
|
c = '0';
|
|
}
|
|
__FMT_PUT(c);
|
|
} while (--decpt > 0);
|
|
if (prec > 0) __FMT_PUT('.');
|
|
}
|
|
while (--prec >= 0) {
|
|
if ((c = *s)) {
|
|
s++;
|
|
} else {
|
|
c = '0';
|
|
}
|
|
__FMT_PUT(c);
|
|
}
|
|
while (--width >= 0) {
|
|
__FMT_PUT(' ');
|
|
}
|
|
__fmt_free_dtoa(&mem);
|
|
break;
|
|
|
|
case 'G':
|
|
case 'g':
|
|
if (!(flags & FLAGS_PRECISION)) prec = 6;
|
|
if (longdouble) {
|
|
pun.d = va_arg(va, long double);
|
|
} else {
|
|
pun.d = va_arg(va, double);
|
|
}
|
|
if (prec < 0) prec = 0;
|
|
assert(!mem);
|
|
s = mem = dtoa(pun.d, prec ? 2 : 0, prec, &decpt, &sgn, &se);
|
|
if (decpt == 9999) goto Format9999;
|
|
c = se - s;
|
|
prec1 = prec;
|
|
if (!prec) {
|
|
prec = c;
|
|
prec1 = c + (s[1] ? 5 : 4);
|
|
}
|
|
if (decpt > -4 && decpt <= prec1) {
|
|
prec = c - decpt;
|
|
if (prec < 0) prec = 0;
|
|
goto FormatReal;
|
|
}
|
|
d -= 2;
|
|
if (prec > c) prec = c;
|
|
--prec;
|
|
goto FormatExpo;
|
|
|
|
case 'e':
|
|
case 'E':
|
|
if (!(flags & FLAGS_PRECISION)) prec = 6;
|
|
if (longdouble) {
|
|
pun.d = va_arg(va, long double);
|
|
} else {
|
|
pun.d = va_arg(va, double);
|
|
}
|
|
if (prec < 0) prec = 0;
|
|
if (!dtoa) {
|
|
p = "?";
|
|
goto FormatThatThing;
|
|
}
|
|
assert(!mem);
|
|
s = mem = dtoa(pun.d, 2, prec + 1, &decpt, &sgn, &se);
|
|
if (decpt == 9999) goto Format9999;
|
|
FormatExpo:
|
|
if (sgn) sign = '-';
|
|
if ((width -= prec + 5) > 0) {
|
|
if (sign) --width;
|
|
if (prec) --width;
|
|
}
|
|
if ((c = --decpt) < 0) c = -c;
|
|
while (c >= 100) {
|
|
--width;
|
|
c /= 10;
|
|
}
|
|
if (width > 0 && !(flags & FLAGS_LEFT)) {
|
|
if (flags & FLAGS_ZEROPAD) {
|
|
if (sign) __FMT_PUT(sign);
|
|
sign = 0;
|
|
do
|
|
__FMT_PUT('0');
|
|
while (--width > 0);
|
|
} else {
|
|
do
|
|
__FMT_PUT(' ');
|
|
while (--width > 0);
|
|
}
|
|
}
|
|
if (sign) __FMT_PUT(sign);
|
|
__FMT_PUT(*s++);
|
|
if (prec) __FMT_PUT('.');
|
|
while (--prec >= 0) {
|
|
if ((c = *s)) {
|
|
s++;
|
|
} else {
|
|
c = '0';
|
|
}
|
|
__FMT_PUT(c);
|
|
}
|
|
__fmt_free_dtoa(&mem);
|
|
__FMT_PUT(d);
|
|
if (decpt < 0) {
|
|
__FMT_PUT('-');
|
|
decpt = -decpt;
|
|
} else {
|
|
__FMT_PUT('+');
|
|
}
|
|
for (c = 2, k = 10; 10 * k <= decpt; c++) k *= 10;
|
|
for (;;) {
|
|
i1 = decpt / k;
|
|
__FMT_PUT(i1 + '0');
|
|
if (--c <= 0) break;
|
|
decpt -= i1 * k;
|
|
decpt *= 10;
|
|
}
|
|
while (--width >= 0) {
|
|
__FMT_PUT(' ');
|
|
}
|
|
break;
|
|
|
|
case 'a':
|
|
alphabet = "0123456789abcdefpx";
|
|
goto FormatBinary;
|
|
case 'A':
|
|
alphabet = "0123456789ABCDEFPX";
|
|
FormatBinary:
|
|
if (longdouble) {
|
|
pun.d = va_arg(va, long double);
|
|
} else {
|
|
pun.d = va_arg(va, double);
|
|
}
|
|
if ((pun.u[1] & 0x7ff00000) == 0x7ff00000) {
|
|
goto FormatDtoa;
|
|
}
|
|
if (pun.u[1] & 0x80000000) {
|
|
sign = '-';
|
|
pun.u[1] &= 0x7fffffff;
|
|
}
|
|
if (pun.d) {
|
|
c = '1';
|
|
bex = (pun.u[1] >> 20) - 1023;
|
|
pun.u[1] &= 0xfffff;
|
|
if (bex == -1023) {
|
|
++bex;
|
|
if (pun.u[1]) {
|
|
do {
|
|
--bex;
|
|
pun.u[1] <<= 1;
|
|
if (pun.u[0] & 0x80000000) pun.u[1] |= 1;
|
|
pun.u[0] <<= 1;
|
|
} while (pun.u[1] < 0x100000);
|
|
} else {
|
|
while (!(pun.u[0] & 0x80000000)) {
|
|
--bex;
|
|
pun.u[0] <<= 1;
|
|
}
|
|
bex -= 21;
|
|
pun.u[1] = pun.u[0] >> 11;
|
|
pun.u[0] <<= 21;
|
|
}
|
|
}
|
|
} else {
|
|
c = '0';
|
|
bex = 0;
|
|
}
|
|
if (flags & FLAGS_PRECISION) {
|
|
if (prec > 13) prec = 13;
|
|
if (pun.d && prec < 13) {
|
|
pun.u[1] |= 0x100000;
|
|
if (prec < 5) {
|
|
ui = 1u << ((5 - prec) * 4 - 1);
|
|
if (pun.u[1] & ui) {
|
|
if (pun.u[1] & ((ui - 1) | (ui << 1)) || pun.u[0]) {
|
|
pun.u[1] += ui;
|
|
BexCheck:
|
|
if (pun.u[1] & 0x200000) {
|
|
++bex;
|
|
pun.u[1] >>= 1;
|
|
}
|
|
}
|
|
}
|
|
} else if (prec == 5) {
|
|
if (pun.u[0] & 0x80000000) {
|
|
BumpIt:
|
|
++pun.u[1];
|
|
goto BexCheck;
|
|
}
|
|
} else {
|
|
i1 = (13 - prec) * 4;
|
|
ui = 1u << (i1 - 1);
|
|
if (pun.u[0] & ui && pun.u[0] & ((ui - 1) | (ui << 1))) {
|
|
pun.u[0] += ui;
|
|
if (!(pun.u[0] >> i1)) goto BumpIt;
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
if ((ui = pun.u[0])) {
|
|
ui = __builtin_ctz(ui);
|
|
prec = 6 + ((32 - ROUNDDOWN(ui, 4)) >> 2) - 1;
|
|
} else if ((ui = pun.u[1] & 0xfffff)) {
|
|
ui = __builtin_ctz(ui);
|
|
prec = (20 - ROUNDDOWN(ui, 4)) >> 2;
|
|
} else {
|
|
prec = 0;
|
|
}
|
|
}
|
|
bw = 1;
|
|
if (bex) {
|
|
if ((i1 = bex) < 0) i1 = -i1;
|
|
while (i1 >= 10) {
|
|
++bw;
|
|
i1 /= 10;
|
|
}
|
|
}
|
|
if (pun.u[1] & 0x80000000) {
|
|
pun.u[1] &= 0x7fffffff;
|
|
if (pun.d || sign) sign = '-';
|
|
}
|
|
if ((width -= bw + 5) > 0) {
|
|
if (sign) --width;
|
|
if (prec) --width;
|
|
}
|
|
if (pun.q && prec > 0) {
|
|
width -= ROUNDUP(bsrl(pun.q) + 1, 4) >> 2;
|
|
}
|
|
if (width > 0 && !(flags & FLAGS_LEFT)) {
|
|
if (flags & FLAGS_ZEROPAD) {
|
|
if (sign) {
|
|
__FMT_PUT(sign);
|
|
sign = 0;
|
|
}
|
|
do
|
|
__FMT_PUT('0');
|
|
while (--width > 0);
|
|
} else {
|
|
do
|
|
__FMT_PUT(' ');
|
|
while (--width > 0);
|
|
}
|
|
}
|
|
if (sign) __FMT_PUT(sign);
|
|
__FMT_PUT('0');
|
|
__FMT_PUT(alphabet[17]);
|
|
__FMT_PUT(c);
|
|
if (prec > 0) __FMT_PUT('.');
|
|
while (prec-- > 0) {
|
|
__FMT_PUT(alphabet[(pun.q >> 48) & 0xf]);
|
|
pun.q <<= 4;
|
|
}
|
|
__FMT_PUT(alphabet[16]);
|
|
if (bex < 0) {
|
|
__FMT_PUT('-');
|
|
bex = -bex;
|
|
} else {
|
|
__FMT_PUT('+');
|
|
}
|
|
for (c = 1; 10 * c <= bex;) c *= 10;
|
|
for (;;) {
|
|
i1 = bex / c;
|
|
__FMT_PUT('0' + i1);
|
|
if (!--bw) break;
|
|
bex -= i1 * c;
|
|
bex *= 10;
|
|
}
|
|
break;
|
|
|
|
FormatString:
|
|
if (__fmt_stoa(out, arg, p, flags, prec, width, signbit, qchar) == -1) {
|
|
return -1;
|
|
}
|
|
break;
|
|
}
|
|
return 0;
|
|
}
|