diff options
| author | 2026-10-12 08:46:23 +0330 | |
|---|---|---|
| committer | 2026-10-12 08:46:23 +0330 | |
| commit | ecc350ca5c2eb50be8309c5f14f8bf0d87db9a02 (patch) | |
| tree | 70e0af76a1b13affff7c6cc8aac7b3e6ef960478 /src/core/fops.h | |
a
Diffstat (limited to 'src/core/fops.h')
| -rw-r--r-- | src/core/fops.h | 1048 |
1 files changed, 1048 insertions, 0 deletions
diff --git a/src/core/fops.h b/src/core/fops.h new file mode 100644 index 0000000..9213603 --- /dev/null +++ b/src/core/fops.h @@ -0,0 +1,1048 @@ +/* IEEE-754 binary64, packed in one 64-bit word. + a float on the stack is the raw 64 bits. the odd/even tag is only for the + instruction stream, so a float literal is two words: opcode 600 (the even + word 1200) and then the raw bits, which the decoder must consume itself + because bit 0 is often set. + + two arithmetic paths, chosen when the image is built: + - hardware (the default). fop_* are volatile host doubles, so gcc emits + the FPU (x86_64 SSE2, AArch64 FP, RV64 D). the arm64 jit and the llvm + backend do not call these for 601..609; they emit fadd/fmul/fcmp/scvtf + and the matching llvm double ops. bare metal turns the FPU on in the + boot stub and compiles this file with a hard-float ABI. + - soft (compile with -DZY_SOFTFLOAT). integer IEEE, round ties to even, + no host double. a port with no FPU selects this. print and parse stay + on the integer side either way, so the text does not depend on libc. + + print and parse are integer, on both paths, so the text does not depend + on libc or the host libm. opcode 610 prints six digits after the dot and + rounds a tie in the next digit away from zero. opcode 618 (the word 1236) + takes (bits prec mode --): mode 0 is the shortest spelling that parses + back to the same bits, mode 1 is fixed (prec digits after the dot) and + mode 2 is scientific (prec significant digits). prec is capped at 17. + the parser keeps the digits it is given, rounds ties to even, and accepts + an exponent, inf and nan. */ +#ifndef FOPS_H +#define FOPS_H +#include <stdint.h> + +#ifdef ZY_SOFTFLOAT +/* ---- soft-float: binary64 in integer registers, selected with -DZY_SOFTFLOAT ---- */ +typedef struct { uint64_t sig; int exp; int sign; int cls; } FopSF; /* cls 0 finite, 1 inf, 2 nan */ + +static int fop_clz(uint64_t x) +{ + int n = 0; + if (!x) return 64; + if (!(x & 0xffffffff00000000ull)) { n += 32; x <<= 32; } + if (!(x & 0xffff000000000000ull)) { n += 16; x <<= 16; } + if (!(x & 0xff00000000000000ull)) { n += 8; x <<= 8; } + if (!(x & 0xf000000000000000ull)) { n += 4; x <<= 4; } + if (!(x & 0xc000000000000000ull)) { n += 2; x <<= 2; } + if (!(x & 0x8000000000000000ull)) n++; + return n; +} +static int fop_clz128(unsigned __int128 x) +{ + uint64_t hi = (uint64_t)(x >> 64); + if (hi) return fop_clz(hi); + return 64 + fop_clz((uint64_t)x); +} +static void fop_dec(uint64_t u, FopSF *o) +{ + int eb = (int)((u >> 52) & 0x7ff); + uint64_t frac = u & 0x000fffffffffffffull; + o->sign = (int)(u >> 63); + o->sig = frac; + o->exp = 0; + if (eb == 0x7ff) { o->cls = frac ? 2 : 1; return; } + o->cls = 0; + if (!eb) { o->exp = -1074; return; } + o->sig = frac | (1ull << 52); + o->exp = eb - 1023 - 52; +} +/* value = sig * 2^exp, ties to even. sticky is bits below sig. + a magnitude below 2^-1075 is a zero; that exact power is the tie between + zero and the smallest subnormal, and it rounds back to zero. */ +static uint64_t fop_sf_round(int sign, int exp, uint64_t sig, int sticky) +{ + int round = 0, bit, topw; + uint64_t sgn = (uint64_t)sign << 63; + if (!sig) return sgn; + topw = exp + 63 - fop_clz(sig); + if (topw < -1075) return sgn; + if (topw == -1075) return (sticky || (sig & (sig - 1))) ? sgn | 1 : sgn; + while (sig < (1ull << 52) && exp > -1074) { sig <<= 1; exp--; } + while (sig >= (1ull << 53) || (exp < -1074 && sig)) { + bit = (int)(sig & 1); + sig >>= 1; + exp++; + sticky = sticky || round; + round = bit; + } + if (round && (sticky || (sig & 1))) { + sig++; + if (sig >= (1ull << 53)) { sig >>= 1; exp++; } + } + if (!sig) return sgn; + if (exp > 971) return sgn | 0x7ff0000000000000ull; /* exp + 52 > 1023 */ + if (sig < (1ull << 52)) return sgn | sig; + return sgn | ((uint64_t)(exp + 52 + 1023) << 52) | (sig & 0x000fffffffffffffull); +} +static uint64_t fop_add(uint64_t x, uint64_t y) +{ + FopSF a, b; + unsigned __int128 as, bs, sum; + int sh, sticky = 0, sign, lz; + uint64_t top; + fop_dec(x, &a); + fop_dec(y, &b); + if (a.cls == 2 || b.cls == 2) return 0x7ff8000000000000ull; + if (a.cls == 1 && b.cls == 1) { + if (a.sign != b.sign) return 0x7ff8000000000000ull; + return ((uint64_t)a.sign << 63) | 0x7ff0000000000000ull; + } + if (a.cls == 1) return ((uint64_t)a.sign << 63) | 0x7ff0000000000000ull; + if (b.cls == 1) return ((uint64_t)b.sign << 63) | 0x7ff0000000000000ull; + if (!a.sig && !b.sig) return (a.sign && b.sign) ? 0x8000000000000000ull : 0; + if (!a.sig) return y; + if (!b.sig) return x; + as = (unsigned __int128)a.sig << 60; + bs = (unsigned __int128)b.sig << 60; + if (a.exp < b.exp) { FopSF t = a; a = b; b = t; as = bs; bs = (unsigned __int128)b.sig << 60; } + sh = a.exp - b.exp; + if (sh >= 128) { sticky = b.sig != 0; bs = 0; } + else if (sh) { + sticky = (bs & (((unsigned __int128)1 << sh) - 1)) != 0; + bs >>= sh; + } + if (a.sign == b.sign) { sum = as + bs; sign = a.sign; } + else if (as == bs) return 0; + else if (as > bs) { sum = as - bs; sign = a.sign; } + else { sum = bs - as; sign = b.sign; } + lz = fop_clz128(sum); + sum <<= lz; + top = (uint64_t)(sum >> 64); + if ((uint64_t)sum) sticky = 1; + return fop_sf_round(sign, a.exp + 4 - lz, top, sticky); +} +static uint64_t fop_sub(uint64_t x, uint64_t y) { return fop_add(x, y ^ 0x8000000000000000ull); } +static uint64_t fop_mul(uint64_t x, uint64_t y) +{ + FopSF a, b; + unsigned __int128 prod; + int sign, lz, sticky = 0; + uint64_t top; + fop_dec(x, &a); + fop_dec(y, &b); + sign = a.sign ^ b.sign; + if (a.cls == 2 || b.cls == 2) return 0x7ff8000000000000ull; + if ((a.cls == 1 && !b.sig) || (b.cls == 1 && !a.sig)) return 0x7ff8000000000000ull; + if (a.cls == 1 || b.cls == 1) return ((uint64_t)sign << 63) | 0x7ff0000000000000ull; + if (!a.sig || !b.sig) return (uint64_t)sign << 63; + prod = (unsigned __int128)a.sig * b.sig; + lz = fop_clz128(prod); + prod <<= lz; + top = (uint64_t)(prod >> 64); + if ((uint64_t)prod) sticky = 1; + return fop_sf_round(sign, a.exp + b.exp + 64 - lz, top, sticky); +} +static uint64_t fop_div(uint64_t x, uint64_t y) +{ + FopSF a, b; + unsigned __int128 num, q128; + int sign, lz, sticky; + fop_dec(x, &a); + fop_dec(y, &b); + sign = a.sign ^ b.sign; + if (a.cls == 2 || b.cls == 2) return 0x7ff8000000000000ull; + if (a.cls == 1 && b.cls == 1) return 0x7ff8000000000000ull; + if (a.cls == 1) return ((uint64_t)sign << 63) | 0x7ff0000000000000ull; + if (!b.sig || b.cls == 1) { + if (!b.sig && !a.sig) return 0x7ff8000000000000ull; + if (!b.sig) return ((uint64_t)sign << 63) | 0x7ff0000000000000ull; + return (uint64_t)sign << 63; /* finite / inf */ + } + if (!a.sig) return (uint64_t)sign << 63; + /* (sig << 64) / sig fits in 128 bits even for a subnormal divisor of 1. */ + num = (unsigned __int128)a.sig << 64; + q128 = num / b.sig; + sticky = (num % b.sig) != 0; + lz = fop_clz128(q128); + q128 <<= lz; + if ((uint64_t)q128) sticky = 1; + return fop_sf_round(sign, a.exp - b.exp - lz, (uint64_t)(q128 >> 64), sticky); +} +static int fop_eq(uint64_t x, uint64_t y) +{ + FopSF a, b; + fop_dec(x, &a); + fop_dec(y, &b); + if (a.cls == 2 || b.cls == 2) return 0; + if (!a.cls && !b.cls && !a.sig && !b.sig) return 1; + return x == y; +} +static int fop_lt(uint64_t x, uint64_t y) +{ + FopSF a, b; + int mag; + fop_dec(x, &a); + fop_dec(y, &b); + if (a.cls == 2 || b.cls == 2) return 0; + if (a.cls == 1 && b.cls == 1) return a.sign > b.sign; + if (a.cls == 1) return a.sign; + if (b.cls == 1) return !b.sign; + if (!a.sig && !b.sig) return 0; + if (a.sign != b.sign) return a.sign; + if (a.exp != b.exp) mag = a.exp < b.exp; + else mag = a.sig < b.sig; + if (a.exp == b.exp && a.sig == b.sig) return 0; + return a.sign ? !mag : mag; +} +static int fop_le(uint64_t x, uint64_t y) { return fop_eq(x, y) || fop_lt(x, y); } +static uint64_t fop_i2f(int64_t i) +{ + uint64_t mag; + if (!i) return 0; + if (i < 0) { + mag = (i == (int64_t)((uint64_t)1 << 63)) ? ((uint64_t)1 << 63) : (uint64_t)(-i); + return fop_sf_round(1, 0, mag, 0); + } + return fop_sf_round(0, 0, (uint64_t)i, 0); +} +static int64_t fop_f2i(uint64_t u) +{ + FopSF a; + uint64_t mag; + fop_dec(u, &a); + if (a.cls == 2 || !a.sig) return 0; + if (a.cls == 1 || a.exp >= 63) return a.sign ? (int64_t)((uint64_t)1 << 63) : 9223372036854775807LL; + if (a.exp >= 0) { + if (a.exp > 0 && a.sig > (~0ull >> a.exp)) return a.sign ? (int64_t)((uint64_t)1 << 63) : 9223372036854775807LL; + mag = a.sig << a.exp; + } else { + if (-a.exp >= 64) return 0; + mag = a.sig >> -a.exp; + } + if (!a.sign) { + if (mag > 9223372036854775807ull) return 9223372036854775807LL; + return (int64_t)mag; + } + if (mag > 9223372036854775808ull) return (int64_t)((uint64_t)1 << 63); + if (mag == 9223372036854775808ull) return (int64_t)((uint64_t)1 << 63); + return -(int64_t)mag; +} + +#else +static double fop_d(uint64_t u) +{ + union { uint64_t u; double d; } x; + x.u = u; + return x.d; +} +static uint64_t fop_u(double d) +{ + union { uint64_t u; double d; } x; + x.d = d; + return x.u; +} +static uint64_t fop_add(uint64_t x, uint64_t y) +{ + volatile double a = fop_d(x), b = fop_d(y), c = a + b; + return fop_u(c); +} +static uint64_t fop_sub(uint64_t x, uint64_t y) +{ + volatile double a = fop_d(x), b = fop_d(y), c = a - b; + return fop_u(c); +} +static uint64_t fop_mul(uint64_t x, uint64_t y) +{ + volatile double a = fop_d(x), b = fop_d(y), c = a * b; + return fop_u(c); +} +static uint64_t fop_div(uint64_t x, uint64_t y) +{ + volatile double a = fop_d(x), b = fop_d(y), c = a / b; + return fop_u(c); +} +static int fop_eq(uint64_t x, uint64_t y) +{ + volatile double a = fop_d(x), b = fop_d(y); + return a == b; +} +static int fop_lt(uint64_t x, uint64_t y) +{ + volatile double a = fop_d(x), b = fop_d(y); + return a < b; +} +static int fop_le(uint64_t x, uint64_t y) +{ + volatile double a = fop_d(x), b = fop_d(y); + return a <= b; +} +static uint64_t fop_i2f(int64_t i) +{ + volatile double d = (double)i; + return fop_u(d); +} +/* trunc toward 0. values outside int64 saturate, and a nan becomes 0. + tests stay in range, where this matches a hardware fcvtzs. */ +static int64_t fop_f2i(uint64_t x) +{ + volatile double d = fop_d(x); + if (!(d == d)) return 0; + if (d >= 9223372036854775808.0) return 9223372036854775807LL; + if (d <= -9223372036854775808.0) return (int64_t)((uint64_t)1 << 63); + return (int64_t)d; +} +#endif + +/* binary64 <-> binary32, in integer arithmetic so every build agrees (no libm, + no compiler runtime). f2s rounds to nearest even and takes a double's bits to + a single's bits (in the low 32); s2f is exact. a nan keeps its sign and the top + of its payload, and comes out quiet. */ +static uint64_t fop_f2s(uint64_t x) +{ + uint64_t sign = (x >> 63) << 31, frac = x & 0xFFFFFFFFFFFFFull, sig, q, rem, half; + int e = (int)((x >> 52) & 0x7FF), E, shift; + if (e == 0x7FF) { + if (!frac) return sign | 0x7F800000u; + return sign | 0x7FC00000u | (frac >> 29); + } + if (e == 0) return sign; /* zero, or a subnormal double: far below the smallest single */ + E = e - 1023; + sig = frac | (1ull << 52); + if (E > 127) return sign | 0x7F800000u; + shift = 29; + if (E < -126) { shift = 29 + (-126 - E); if (shift > 54) return sign; } + q = sig >> shift; + rem = sig & ((1ull << shift) - 1); + half = 1ull << (shift - 1); + if (rem > half || (rem == half && (q & 1))) q++; + if (shift == 29) { + if (q == (1ull << 24)) { q >>= 1; E++; if (E > 127) return sign | 0x7F800000u; } + return sign | ((uint64_t)(E + 127) << 23) | (q & 0x7FFFFF); + } + return sign | q; /* a subnormal; if it rounded up to the smallest normal these are that normal's bits */ +} +static uint64_t fop_s2f(uint64_t x) +{ + uint64_t sign, frac; + int e, p; + x &= 0xFFFFFFFFull; + sign = (x >> 31) << 63; + frac = x & 0x7FFFFF; + e = (int)((x >> 23) & 0xFF); + if (e == 0xFF) { + if (!frac) return sign | 0x7FF0000000000000ull; + return sign | 0x7FF8000000000000ull | (frac << 29); + } + if (e == 0) { + if (!frac) return sign; + p = 22; + while (!(frac >> p)) p--; + frac = (frac << (52 - p)) & 0xFFFFFFFFFFFFFull; + return sign | ((uint64_t)(p - 149 + 1023) << 52) | frac; + } + return sign | ((uint64_t)(e - 127 + 1023) << 52) | (frac << 29); +} + +/* decimal conversion in base 2^32. 96 limbs is room for a 120-digit + significand times 5^445, which covers every double and the digits a + 127-character literal can hold. copies go through a volatile store so a + freestanding build does not emit memcpy. */ +#define FOP_BI 96 +typedef struct { uint32_t d[FOP_BI]; int n; } FopBi; + +static void bi_norm(FopBi *a) +{ + while (a->n > 0 && !a->d[a->n - 1]) a->n--; +} +static void bi_clear(FopBi *a) { a->n = 0; } +static void bi_copy(FopBi *dst, const FopBi *src) +{ + volatile uint32_t *w = dst->d; + int i; + dst->n = src->n; + for (i = 0; i < src->n; i++) w[i] = src->d[i]; +} +static void bi_set(FopBi *a, uint64_t v) +{ + a->n = 0; + if (!v) return; + a->d[0] = (uint32_t)v; + a->n = 1; + if (v >> 32) { a->d[1] = (uint32_t)(v >> 32); a->n = 2; } +} +static void bi_add_u32(FopBi *a, uint32_t v) +{ + uint64_t c = v; + int i = 0; + if (!v) return; + while (c) { + if (i >= FOP_BI) return; + if (i == a->n) a->d[a->n++] = 0; + c += a->d[i]; + a->d[i++] = (uint32_t)c; + c >>= 32; + } +} +static void bi_mul_u32(FopBi *a, uint32_t m) +{ + uint64_t c = 0; + int i; + if (!m || !a->n) { a->n = 0; return; } + for (i = 0; i < a->n; i++) { + c += (uint64_t)a->d[i] * m; + a->d[i] = (uint32_t)c; + c >>= 32; + } + if (c && a->n < FOP_BI) a->d[a->n++] = (uint32_t)c; +} +static void bi_mul(FopBi *a, const FopBi *b) +{ + FopBi r; + int i, j; + uint64_t c; + if (!a->n || !b->n) { a->n = 0; return; } + bi_clear(&r); + r.n = a->n + b->n; + if (r.n > FOP_BI) r.n = FOP_BI; + for (i = 0; i < r.n; i++) r.d[i] = 0; + for (i = 0; i < a->n; i++) { + c = 0; + for (j = 0; j < b->n && i + j < FOP_BI; j++) { + c += (uint64_t)r.d[i + j] + (uint64_t)a->d[i] * b->d[j]; + r.d[i + j] = (uint32_t)c; + c >>= 32; + } + if (c && i + b->n < r.n) r.d[i + b->n] = (uint32_t)c; + } + bi_norm(&r); + bi_copy(a, &r); +} +static void bi_pow5(FopBi *out, int e) +{ + FopBi base, acc; + bi_set(&acc, 1); + bi_set(&base, 5); + if (e < 0) e = 0; + while (e) { + if (e & 1) bi_mul(&acc, &base); + e >>= 1; + if (e) bi_mul(&base, &base); + } + bi_copy(out, &acc); +} +static int bi_bitlen(const FopBi *a) +{ + uint32_t x; + int n; + if (!a->n) return 0; + x = a->d[a->n - 1]; + n = (a->n - 1) * 32; + while (x) { n++; x >>= 1; } + return n; +} +static int bi_bit(const FopBi *a, int i) +{ + if (i < 0 || (i >> 5) >= a->n) return 0; + return (int)((a->d[i >> 5] >> (i & 31)) & 1); +} +static int bi_low_any(const FopBi *a, int nbits) +{ + int i, w, b; + if (nbits <= 0) return 0; + w = nbits >> 5; + b = nbits & 31; + for (i = 0; i < w && i < a->n; i++) if (a->d[i]) return 1; + if (b && w < a->n && (a->d[w] & ((1u << b) - 1))) return 1; + return 0; +} +static uint64_t bi_extract(const FopBi *a, int lo, int nbits) +{ + uint64_t r = 0; + int i; + if (nbits > 63) nbits = 63; + for (i = 0; i < nbits; i++) if (bi_bit(a, lo + i)) r |= (uint64_t)1 << i; + return r; +} +static void bi_shl(FopBi *a, int s) +{ + int w, b, i; + uint32_t prev; + if (s <= 0 || !a->n) return; + w = s >> 5; + b = s & 31; + if (a->n + w + (b ? 1 : 0) > FOP_BI) { + for (i = 0; i < FOP_BI; i++) a->d[i] = 0xffffffffu; + a->n = FOP_BI; + return; + } + if (b) { + prev = 0; + for (i = 0; i < a->n; i++) { + uint32_t cur = a->d[i]; + a->d[i] = (cur << b) | prev; + prev = cur >> (32 - b); + } + if (prev) a->d[a->n++] = prev; + } + if (w) { + for (i = a->n - 1; i >= 0; i--) a->d[i + w] = a->d[i]; + for (i = 0; i < w; i++) a->d[i] = 0; + a->n += w; + } +} +static int bi_cmp(const FopBi *a, const FopBi *b) +{ + int i; + if (a->n != b->n) return a->n < b->n ? -1 : 1; + for (i = a->n - 1; i >= 0; i--) if (a->d[i] != b->d[i]) return a->d[i] < b->d[i] ? -1 : 1; + return 0; +} +static void bi_sub(FopBi *a, const FopBi *b) +{ + uint64_t br = 0; + int i; + for (i = 0; i < a->n; i++) { + uint64_t av = a->d[i]; + uint64_t bv = (uint64_t)(i < b->n ? b->d[i] : 0) + br; + if (av < bv) { a->d[i] = (uint32_t)(av + (1ull << 32) - bv); br = 1; } + else { a->d[i] = (uint32_t)(av - bv); br = 0; } + } + bi_norm(a); +} +/* q = floor(num/den), rem = num % den. den must be nonzero. */ +static void bi_divmod(const FopBi *num, const FopBi *den, FopBi *q, FopBi *rem) +{ + int i, nb = bi_bitlen(num); + bi_clear(q); + bi_clear(rem); + for (i = nb - 1; i >= 0; i--) { + bi_shl(rem, 1); + if (bi_bit(num, i)) bi_add_u32(rem, 1); + if (bi_cmp(rem, den) >= 0) { + bi_sub(rem, den); + { + int w = i >> 5; + while (q->n <= w) q->d[q->n++] = 0; + q->d[w] |= 1u << (i & 31); + } + } + } + bi_norm(q); +} +/* half up: remainder >= half the divisor increments the quotient. */ +static void bi_div_halfup(const FopBi *num, const FopBi *den, FopBi *q) +{ + FopBi rem, twice; + bi_divmod(num, den, q, &rem); + bi_copy(&twice, &rem); + bi_shl(&twice, 1); + if (bi_cmp(&twice, den) >= 0) bi_add_u32(q, 1); +} +static uint32_t bi_div10(FopBi *a) +{ + uint64_t rem = 0; + int i; + for (i = a->n - 1; i >= 0; i--) { + rem = (rem << 32) | a->d[i]; + a->d[i] = (uint32_t)(rem / 10); + rem %= 10; + } + bi_norm(a); + return (uint32_t)rem; +} +static int bi_to_digits(const FopBi *a, char *dig) +{ + FopBi x; + char rev[400]; + int n = 0, i; + bi_copy(&x, a); + if (!x.n) { dig[0] = '0'; dig[1] = 0; return 1; } + while (x.n && n < 390) rev[n++] = (char)('0' + (int)bi_div10(&x)); + for (i = 0; i < n; i++) dig[i] = rev[n - 1 - i]; + dig[n] = 0; + return n; +} +/* right shift by s, then +1 if the highest discarded bit was set. */ +static void bi_shr_halfup(const FopBi *a, int s, FopBi *dst) +{ + int up, w, b, i, len; + if (s <= 0) { bi_copy(dst, a); return; } + len = bi_bitlen(a); + up = bi_bit(a, s - 1); + if (s >= len) { + bi_clear(dst); + if (up) bi_set(dst, 1); + return; + } + w = s >> 5; + b = s & 31; + dst->n = 0; + if (!b) { + for (i = w; i < a->n; i++) dst->d[dst->n++] = a->d[i]; + } else { + for (i = w; i < a->n; i++) { + uint32_t cur = a->d[i] >> b; + if (i + 1 < a->n) cur |= a->d[i + 1] << (32 - b); + dst->d[dst->n++] = cur; + } + } + bi_norm(dst); + if (up) bi_add_u32(dst, 1); +} + +/* q * 2^scale, plus a nonzero fraction below bit 0 when extra is set. + round ties to even. a pure power of two at 2^-1075 is the tie between + zero and the smallest subnormal, and it rounds back to zero. */ +static uint64_t fop_qbits(const FopBi *q, int scale, int extra, int neg) +{ + uint64_t sgn = (uint64_t)neg << 63; + int top, unbiased, low, roundb, sticky; + uint64_t sig; + if (!q->n) return sgn; + top = bi_bitlen(q) - 1; + unbiased = top + scale; + if (unbiased > 1023) return sgn | 0x7ff0000000000000ull; + if (unbiased < -1075) return sgn; + if (unbiased == -1075) { + if (!extra && !bi_low_any(q, top)) return sgn; + return sgn | 1; + } + if (unbiased < -1022) { + int rsh = -(scale + 1074); + if (rsh >= 0) { + sig = bi_extract(q, rsh, top - rsh + 1); + roundb = rsh > 0 ? bi_bit(q, rsh - 1) : 0; + sticky = extra || (rsh > 1 && bi_low_any(q, rsh - 1)); + } else { + sig = bi_extract(q, 0, top + 1) << (-rsh); + roundb = 0; + sticky = extra; + } + if (roundb && (sticky || (sig & 1))) sig++; + if (sig >= (1ull << 52)) return sgn | (1ull << 52); + return sgn | sig; + } + low = top - 52; + if (low <= 0) { + sig = bi_extract(q, 0, top + 1); + if (low < 0) sig <<= -low; + roundb = 0; + sticky = extra; + } else { + sig = bi_extract(q, low, 53); + roundb = bi_bit(q, low - 1); + sticky = extra || bi_low_any(q, low - 1); + } + if (roundb && (sticky || (sig & 1))) { + sig++; + if (sig == (1ull << 53)) { + sig >>= 1; + unbiased++; + if (unbiased > 1023) return sgn | 0x7ff0000000000000ull; + } + } + return sgn | ((uint64_t)(unbiased + 1023) << 52) | (sig & 0xfffffffffffffull); +} + +/* sig * 10^exp10, with sticky meaning the true value is strictly larger. */ +static uint64_t fop_dec_bits(const FopBi *sig, int exp10, int neg, int sticky) +{ + FopBi num, den, q, rem; + int bexp, approx, target, sh, guard = 0; + int sgn = neg ? 1 : 0; + if (!sig->n) return sgn ? 0x8000000000000000ull : 0; + if (exp10 >= 0) { + bi_copy(&num, sig); + bi_pow5(&den, exp10); + bi_mul(&num, &den); + bi_set(&den, 1); + bexp = exp10; + } else { + bi_copy(&num, sig); + bi_pow5(&den, -exp10); + bexp = exp10; + } + approx = bi_bitlen(&num) - bi_bitlen(&den) + bexp; + if (approx > 1080) return sgn ? 0xfff0000000000000ull : 0x7ff0000000000000ull; + if (approx < -1140) return sgn ? 0x8000000000000000ull : 0; +again: + if (approx < -902) target = -1088; + else target = approx - 80; + target -= guard * 32; + sh = bexp - target; + { + FopBi n2, d2; + bi_copy(&n2, &num); + bi_copy(&d2, &den); + if (sh >= 0) bi_shl(&n2, sh); + else bi_shl(&d2, -sh); + bi_divmod(&n2, &d2, &q, &rem); + { + int top = q.n ? bi_bitlen(&q) - 1 : 0; + int low = top - 52; + int unbiased = q.n ? top + target : -9999; + if (q.n && unbiased >= -1022 && low < 4 && guard < 4) { guard++; goto again; } + } + return fop_qbits(&q, target, sticky || rem.n != 0, sgn); + } +} + +/* parse directly from byte text or vm words, without truncating a lexer buffer. */ +typedef struct { const char *bytes; const int64_t *words; uint64_t length; } FopText; +static char fop_char(FopText *s, uint64_t at) +{ + if (s->words) return at < s->length ? (char)(s->words[at] & 255) : 0; + return s->bytes[at]; +} +static int fop_word(FopText *s, uint64_t p, const char *w) +{ + while (*w) { + char c = fop_char(s, p); + if (c >= 'A' && c <= 'Z') c = (char)(c - 'A' + 'a'); + if (c != *w) return 0; + p++; + w++; + } + return fop_char(s, p) == 0; +} + +static uint64_t fop_parse_text(FopText *s) +{ + uint64_t p = 0; + int neg = 0, exp10 = 0, nexp = 0, eneg = 0, frac = 0, saw = 0, nd = 0, sticky = 0; + int dec; + FopBi sig; + if (fop_char(s, p) == '+') p++; + else if (fop_char(s, p) == '-') { neg = 1; p++; } + if (fop_word(s, p, "inf")) return neg ? 0xfff0000000000000ull : 0x7ff0000000000000ull; + if (fop_word(s, p, "nan")) return neg ? 0xfff8000000000000ull : 0x7ff8000000000000ull; + bi_clear(&sig); + while (fop_char(s, p) == '.' || (fop_char(s, p) >= '0' && fop_char(s, p) <= '9')) { + if (fop_char(s, p) == '.') { + if (frac) break; + frac = 1; + p++; + continue; + } + saw = 1; + if (fop_char(s, p) == '0' && !sig.n) { + if (frac) exp10--; + } else if (nd < 80) { + bi_mul_u32(&sig, 10); + bi_add_u32(&sig, (uint32_t)(fop_char(s, p) - '0')); + nd++; + if (frac) exp10--; + } else { + if (fop_char(s, p) != '0') sticky = 1; + if (!frac) exp10++; + } + p++; + } + if (fop_char(s, p) == 'e' || fop_char(s, p) == 'E') { + p++; + if (fop_char(s, p) == '+') p++; + else if (fop_char(s, p) == '-') { eneg = 1; p++; } + while (fop_char(s, p) >= '0' && fop_char(s, p) <= '9') { + if (nexp < 1000000) nexp = nexp * 10 + (fop_char(s, p) - '0'); + p++; + } + if (eneg) exp10 -= nexp; + else exp10 += nexp; + } + if (!saw || !sig.n) return neg ? 0x8000000000000000ull : 0; + dec = exp10 + nd - 1; + if (dec > 309) return neg ? 0xfff0000000000000ull : 0x7ff0000000000000ull; + if (dec < -326) return neg ? 0x8000000000000000ull : 0; + return fop_dec_bits(&sig, exp10, neg, sticky); +} + +static uint64_t fop_parse(const char *bytes) +{ + FopText text = { bytes, 0, 0 }; + return fop_parse_text(&text); +} +static uint64_t fop_parse_words(const int64_t *words, uint64_t length) +{ + FopText text = { 0, words, length }; + return length ? fop_parse_text(&text) : 0; +} + +static void fop_decode(uint64_t bits, uint64_t *sig, int *e2, int *neg, int *cls) +{ + uint64_t exp = (bits >> 52) & 0x7ff; + uint64_t frac = bits & 0x000fffffffffffffull; + *neg = (int)(bits >> 63); + *cls = 0; + *sig = 0; + *e2 = 0; + if (exp == 0x7ff) { *cls = frac ? 2 : 1; return; } + if (!exp) { + *sig = frac; + *e2 = -1074; + return; + } + *sig = frac | (1ull << 52); + *e2 = (int)exp - 1023 - 52; +} + +/* x ? 10^k. x = sig * 2^e2, sig > 0. */ +static int fop_cmp10(uint64_t sig, int e2, int k) +{ + FopBi a, b; + int q; + if (k >= 0) { + bi_set(&a, sig); + bi_pow5(&b, k); + if (e2 >= k) bi_shl(&a, e2 - k); + else bi_shl(&b, k - e2); + return bi_cmp(&a, &b); + } + q = -k; + bi_set(&a, sig); + bi_pow5(&b, q); + bi_mul(&a, &b); + if (e2 + q >= 0) { + bi_shl(&a, e2 + q); + if (!a.n) return -1; + if (a.n == 1 && a.d[0] == 1) return 0; + return 1; + } + bi_set(&b, 1); + bi_shl(&b, -(e2 + q)); + return bi_cmp(&a, &b); +} + +/* no __builtin_clzll: a freestanding riscv link has no __clzdi2. */ +static int fop_nlz(uint64_t x) +{ + int n = 0; + if (!x) return 64; + if (!(x & 0xffffffff00000000ull)) { n += 32; x <<= 32; } + if (!(x & 0xffff000000000000ull)) { n += 16; x <<= 16; } + if (!(x & 0xff00000000000000ull)) { n += 8; x <<= 8; } + if (!(x & 0xf000000000000000ull)) { n += 4; x <<= 4; } + if (!(x & 0xc000000000000000ull)) { n += 2; x <<= 2; } + if (!(x & 0x8000000000000000ull)) n++; + return n; +} +static int fop_decexp(uint64_t sig, int e2) +{ + int log2 = 63 - fop_nlz(sig) + e2; + int est, guard = 0; + if (log2 >= 0) est = (int)(((unsigned __int128)log2 * 1292913986ull) >> 32); + else { + unsigned __int128 m = (unsigned __int128)(-log2) * 1292913986ull; + est = -(int)(m >> 32); + if (m & 0xffffffffull) est--; + } + while (guard++ < 12 && fop_cmp10(sig, e2, est + 1) >= 0) est++; + guard = 0; + while (guard++ < 12 && fop_cmp10(sig, e2, est) < 0) est--; + return est; +} + +/* round_half_up(sig * 2^e2 * 10^scale) as an integer. */ +static void fop_scale(uint64_t sig, int e2, int scale, FopBi *mag) +{ + FopBi num, den; + int i, q10; + if (scale >= 0) { + bi_set(&num, sig); + for (i = 0; i < scale; i++) bi_mul_u32(&num, 5); + if (e2 + scale >= 0) bi_shl(&num, e2 + scale); + else { bi_shr_halfup(&num, -(e2 + scale), mag); return; } + bi_copy(mag, &num); + return; + } + q10 = -scale; + bi_set(&num, sig); + bi_pow5(&den, q10); + if (e2 >= q10) bi_shl(&num, e2 - q10); + else bi_shl(&den, q10 - e2); + bi_div_halfup(&num, &den, mag); +} + +static int fop_putu(char *out, int n, int v) +{ + char b[16]; + int i = 0, j; + if (v < 0) v = -v; + if (!v) { out[n++] = '0'; return n; } + while (v) { b[i++] = (char)('0' + v % 10); v /= 10; } + for (j = i - 1; j >= 0; j--) out[n++] = b[j]; + return n; +} + +/* plain when the exponent is in [-4, 15], otherwise scientific. */ +static int fop_spell(const char *dig, int nd, int dec, int neg, int scientific, char *out) +{ + int n = 0, i; + if (neg) out[n++] = '-'; + if (!scientific) { + if (dec >= 0) { + int whole = dec + 1; + if (nd <= whole) { + for (i = 0; i < nd; i++) out[n++] = dig[i]; + for (; i < whole; i++) out[n++] = '0'; + } else { + for (i = 0; i < whole; i++) out[n++] = dig[i]; + out[n++] = '.'; + for (; i < nd; i++) out[n++] = dig[i]; + } + } else { + out[n++] = '0'; + out[n++] = '.'; + for (i = 0; i < -dec - 1; i++) out[n++] = '0'; + for (i = 0; i < nd; i++) out[n++] = dig[i]; + } + } else { + out[n++] = dig[0]; + if (nd > 1) { + out[n++] = '.'; + for (i = 1; i < nd; i++) out[n++] = dig[i]; + } + out[n++] = 'e'; + out[n++] = dec < 0 ? '-' : '+'; + n = fop_putu(out, n, dec < 0 ? -dec : dec); + } + out[n] = 0; + return n; +} + +static void fop_sigdigs(uint64_t sig, int e2, int prec, char *dig, int *dec) +{ + int d = fop_decexp(sig, e2); + FopBi mag; + char tmp[48]; + int nd = 0, i, guard = 0; + while (guard++ < 8) { + int zeros = 1; + fop_scale(sig, e2, prec - 1 - d, &mag); + nd = bi_to_digits(&mag, tmp); + if (nd == prec) break; + if (nd == prec + 1) { + for (i = 1; i < nd; i++) if (tmp[i] != '0') zeros = 0; + if (tmp[0] == '1' && zeros) { + d++; + dig[0] = '1'; + for (i = 1; i < prec; i++) dig[i] = '0'; + dig[prec] = 0; + *dec = d; + return; + } + } + if (nd > prec) d += nd - prec; + else d -= prec - nd; + } + if (nd > prec) nd = prec; + for (i = 0; i < prec - nd; i++) dig[i] = '0'; + for (i = 0; i < nd && i < prec; i++) dig[prec - nd + i] = tmp[i]; + dig[prec] = 0; + *dec = d; +} + +static int fop_format_fixed(uint64_t sig, int e2, int neg, int prec, char *out) +{ + FopBi t, mag; + char dig[400]; + int n = 0, nd, i, whole; + bi_set(&t, sig ? sig : 0); + if (!sig) bi_clear(&mag); + else { + int i5; + for (i5 = 0; i5 < prec; i5++) bi_mul_u32(&t, 5); + if (e2 + prec >= 0) { bi_shl(&t, e2 + prec); bi_copy(&mag, &t); } + else bi_shr_halfup(&t, -(e2 + prec), &mag); + } + if (neg) out[n++] = '-'; + nd = bi_to_digits(&mag, dig); + if (nd <= prec) { + out[n++] = '0'; + if (prec) { + out[n++] = '.'; + for (i = 0; i < prec - nd; i++) out[n++] = '0'; + for (i = 0; i < nd; i++) out[n++] = dig[i]; + } + } else { + whole = nd - prec; + for (i = 0; i < whole && n < 380; i++) out[n++] = dig[i]; + if (prec && n < 390) { + out[n++] = '.'; + for (i = 0; i < prec; i++) out[n++] = dig[whole + i]; + } + } + out[n] = 0; + return n; +} + +static int fop_special_text(int cls, int neg, char *out) +{ + int n = 0; + if (cls == 2) { out[0] = 'n'; out[1] = 'a'; out[2] = 'n'; out[3] = 0; return 3; } + if (neg) out[n++] = '-'; + out[n++] = 'i'; out[n++] = 'n'; out[n++] = 'f'; + out[n] = 0; + return n; +} + +/* mode 0 shortest, 1 fixed, 2 scientific. out must hold 400 bytes. */ +static int fop_format_mode(uint64_t bits, int64_t mode, int64_t prec, char *out) +{ + uint64_t sig; + int e2, neg, cls, n, k, dec, scientific; + char dig[24], buf[80]; + fop_decode(bits, &sig, &e2, &neg, &cls); + if (cls) return fop_special_text(cls, neg, out); + if (mode == 1) { + if (prec < 0) prec = 0; + if (prec > 17) prec = 17; + return fop_format_fixed(sig, e2, neg, (int)prec, out); + } + if (!sig) { + if (mode == 2) { + int p = (int)prec; + int i; + if (p < 1) p = 1; + if (p > 17) p = 17; + n = 0; + if (neg) out[n++] = '-'; + out[n++] = '0'; + if (p > 1) { + out[n++] = '.'; + for (i = 1; i < p; i++) out[n++] = '0'; + } + out[n++] = 'e'; out[n++] = '+'; out[n++] = '0'; + out[n] = 0; + return n; + } + out[0] = '0'; + if (neg) { out[0] = '-'; out[1] = '0'; out[2] = 0; return 2; } + out[1] = 0; + return 1; + } + if (mode == 2) { + int p = (int)prec; + if (p < 1) p = 1; + if (p > 17) p = 17; + fop_sigdigs(sig, e2, p, dig, &dec); + return fop_spell(dig, p, dec, neg, 1, out); + } + for (k = 1; k <= 18; k++) { + fop_sigdigs(sig, e2, k, dig, &dec); + scientific = !(dec >= -4 && dec <= 15); + fop_spell(dig, k, dec, neg, scientific, buf); + if (k == 18 || fop_parse(buf) == bits) return fop_spell(dig, k, dec, neg, scientific, out); + } + return fop_spell(dig, 18, dec, neg, 1, out); +} + +static int fop_format(uint64_t bits, char *out) +{ + return fop_format_mode(bits, 1, 6, out); +} +#endif |
