// Mirrors ref/hare/strconv/decimal.ha (Hare in turn ports Go's // lib/strconv/decimal.go). Pure integer arithmetic; no f32/f64 // references (#121 residual-guard SAFE). // // Spelling divergences from Hare (mechanical, ww-side parser shape): // - Hare `let a = X, b = Y;` → two single `let` statements // (ww parser doesn't accept comma-separated bindings). // - Hare `tbl[lo..]` open-ended slice → direct indexing // `tbl[lo + i]` at point-of-use (equivalent algorithm; no // allocation, no aliasing). ww `[lo:hi]` uses `:`; `..` form // is not parsed. // - Hare `0z`/`1z` size literals → ww has no `z` suffix; pre-bind // `let SZ_ZERO: size = (0u64: size);` etc. at function entry // ("hoisted size casts as local consts" — ww `T: type` casts // embedded inside expressions confuse the parser). // - Hare `~0u64` typed-suffix literal → ww parser rejects `~` on // typed-suffix; route via a named zero local + `~zero`. // - Hare `for (cond; afterthought)` 2-clause → ww 3-clause // `for (init; cond; post)` (when continue is used; the post // must run each iteration) or inline-the-afterthought in body // (when no continue exists in the loop). // - Hare `fn foo() T = if (cond) {...} else expr;` expression body // → ww requires a `{}` block body throughout. // - Hare bare `assert(cond)` builtin → `assert(cond, msg)`; // wwstage cgen has no `assert` intercept (deferred fold). // - In-file instances of the above hoist pattern: `i_sz` (line 93) // hoists a per-iteration size cast out of a for-loop comparison // (bullet 3 sub-case — the size-cast hoist applied inside a loop // body, not just at function entry); `lowbit_lit` (line 242) // decomposes Hare's `(nd > 0 && d.digits[nd - 1] & 1 != 0)` into // a stepwise boolean local to dodge ww parser precedence on mixed // `&` / `&&` / `!=` within a single expression. // // CGEN class closures consumed (post-prereqs): // - #131 (4acab6e) — `len(d.digits)` compile-time-folds cs==ww // - #134 (36bf603) — `d.digits[nd] >= 5u8` picks JAE (unsigned) // - #133 (3986818) — `d.digits[i] += 1u8` load-op-store BOTH // stages // - #135 (ade6840) — `(*d).digits[i]` read+write N_DOT-base addr // // Drew CGEN-SAFE invariants: // - #129: module-level decls here are integer-literal defs only. // - #128: digits is fundamental [800]u8, zero-init only. // - #121: zero float ops. // - Drew watch-item `*d = decimal{...};` reset (line 110 in Hare): // pointer-deref reset to composite-literal probed cs==ww // byte-id safe. package strconv; import os; // ref/hare/strconv/decimal.ha:5. def maxshift: u8 = 60u8; // ref/hare/strconv/decimal.ha:6. def decimal_point_range: u16 = 2047u16; // ref/hare/strconv/decimal.ha:8-26. Field layout 1:1. The 800-digit // bound covers subnormal doubles (min exp -1074, max mantissa 4e16 // → at most 767 digits; 800 leaves headroom). export type decimal = struct { digits: [800]u8, nd: size, dp: i32, negative: bool, truncated: bool, }; // ref/hare/strconv/decimal.ha:29-33. Strip trailing zeros. fn trim(d: *decimal) void = { let SZ_ZERO: size = (0u64: size); let SZ_ONE: size = (1u64: size); for (d.nd > SZ_ZERO && d.digits[d.nd - SZ_ONE] == 0u8) { d.nd -= SZ_ONE; }; }; // ref/hare/strconv/decimal.ha:35-55. Compute the digit-count // increase for a left-shift `shift` (consults left_shift_table + // pow5_table from stof_data.ww, bb6f840). Uses `continue` so the // loop stays in 3-clause form for byte-id-correct post-increment. fn leftshift_newdigits(d: *decimal, shift: u32) u32 = { shift &= 63u32; let x_a: u32 = (left_shift_table[shift]: u32); let x_b: u32 = (left_shift_table[shift + 1u32]: u32); let nn: u32 = x_a >> 11u32; let pow5_a: u32 = 0x7FFu32 & x_a; let pow5_b: u32 = 0x7FFu32 & x_b; let n: u32 = pow5_b - pow5_a; for (let i: u32 = 0u32; i < n; i += 1u32) { let i_sz: size = (i: size); if (i_sz >= d.nd) { return nn - 1u32; } else if (d.digits[i] == pow5_table[pow5_a + i]) { continue; } else if (d.digits[i] < pow5_table[pow5_a + i]) { return nn - 1u32; } else { return nn; }; }; return nn; }; // ref/hare/strconv/decimal.ha:57-91. Shift `d` left by k bits. fn leftshift(d: *decimal, k: u32) void = { let SZ_ONE: size = (1u64: size); let SZ_BOUND: size = (len(d.digits): size); let kU64: u64 = (k: u64); let MAXSHIFT_U32: u32 = (maxshift: u32); assert(k <= MAXSHIFT_U32, "strconv.leftshift: k > maxshift"); if (d.nd == (0u64: size)) { return; }; let nn: u32 = leftshift_newdigits(d, k); let r: int = (d.nd: int) - 1; let w: size = (r: size) + (nn: size); let n: u64 = 0u64; for (r >= 0) { n += (d.digits[r]: u64) << kU64; let quo: u64 = n / 10u64; let rem: u64 = n - 10u64 * quo; if (w < SZ_BOUND) { d.digits[w] = (rem: u8); } else if (rem != 0u64) { d.truncated = true; }; n = quo; r -= 1; w -= SZ_ONE; }; for (n > 0u64) { let quo: u64 = n / 10u64; let rem: u64 = n - 10u64 * quo; if (w < SZ_BOUND) { d.digits[w] = (rem: u8); } else if (rem != 0u64) { d.truncated = true; }; n = quo; w -= SZ_ONE; }; d.nd += (nn: size); if (d.nd > SZ_BOUND) { d.nd = SZ_BOUND; }; d.dp += (nn: i32); trim(d); }; // ref/hare/strconv/decimal.ha:93-134. Shift `d` right by k bits. // Two outer Hare 2-clause loops (`for (cond; r += 1)`) are inlined // as `for (cond) { ... r += SZ_ONE; }` since neither uses continue. fn rightshift(d: *decimal, k: u32) void = { let SZ_ZERO: size = (0u64: size); let SZ_ONE: size = (1u64: size); let SZ_BOUND: size = (len(d.digits): size); let kU64: u64 = (k: u64); let r: size = SZ_ZERO; let w: size = SZ_ZERO; let n: u64 = 0u64; for ((n >> kU64) == 0u64) { if (r >= d.nd) { if (n == 0u64) { d.nd = SZ_ZERO; return; }; for ((n >> kU64) == 0u64) { n *= 10u64; r += SZ_ONE; }; break; }; n = n * 10u64 + (d.digits[r]: u64); r += SZ_ONE; }; d.dp -= (r: i32) - 1; if (d.dp < -(decimal_point_range: i32)) { // Drew-watch-item: pointer-deref reset to composite // literal — probed cs==ww byte-id safe in pre-flight. *d = decimal { ... }; return; }; let mask: u64 = (1u64 << kU64) - 1u64; for (r < d.nd) { let dig: u64 = n >> kU64; n &= mask; d.digits[w] = (dig: u8); w += SZ_ONE; n = n * 10u64 + (d.digits[r]: u64); r += SZ_ONE; }; for (n > 0u64) { let dig: u64 = n >> kU64; n &= mask; if (w < SZ_BOUND) { d.digits[w] = (dig: u8); w += SZ_ONE; } else if (dig > 0u64) { d.truncated = true; }; n *= 10u64; }; d.nd = w; trim(d); }; // ref/hare/strconv/decimal.ha:138-153. Shift right (k < 0) or left // (k > 0). Hardware shifts cap at 60 bits without losing top // digits, so break large shifts into maxshift-sized chunks. fn decimal_shift(d: *decimal, k: int) void = { let MAXSHIFT_INT: int = (maxshift: int); let MAXSHIFT_U32: u32 = (maxshift: u32); if (d.nd == (0u64: size)) { return; }; if (k > 0) { for (k > MAXSHIFT_INT) { leftshift(d, MAXSHIFT_U32); k -= MAXSHIFT_INT; }; leftshift(d, (k: u32)); } else if (k < 0) { for (k < -MAXSHIFT_INT) { rightshift(d, MAXSHIFT_U32); k += MAXSHIFT_INT; }; rightshift(d, ((-k): u32)); }; }; // ref/hare/strconv/decimal.ha:155-160. Banker's rounding decision: // at the exact half (digit==5, no more digits) round to even (the // preceding digit's low bit decides); past-half rounds up; below- // half rounds down. Hare's expression-bodied `if` re-shaped as a // block per ww parser. fn should_round_up(d: *decimal, nd: uint) bool = { let nd_sz: size = (nd: size); let SZ_ONE: size = (1u64: size); let U_ONE: uint = (1u32: uint); let U_ZERO: uint = (0u32: uint); if (nd_sz < d.nd) { if (d.digits[nd] == 5u8 && (nd_sz + SZ_ONE) == d.nd) { let lowbit_lit: bool = false; if (nd > U_ZERO) { if ((d.digits[nd - U_ONE] & 1u8) != 0u8) { lowbit_lit = true; }; }; return d.truncated || lowbit_lit; } else { return d.digits[nd] >= 5u8; }; }; return false; }; // ref/hare/strconv/decimal.ha:162-166. Round to `nd` digits. fn round(d: *decimal, nd: uint) void = { if ((nd: size) >= d.nd) { return; }; if (should_round_up(d, nd)) { roundup(d, nd); } else { rounddown(d, nd); }; }; // ref/hare/strconv/decimal.ha:168-172. Truncate to `nd` digits. fn rounddown(d: *decimal, nd: uint) void = { if ((nd: size) >= d.nd) { return; }; d.nd = (nd: size); trim(d); }; // ref/hare/strconv/decimal.ha:174-186. Round up to `nd` digits; // propagate carry. If all 9s, the result is a single 1 with the // decimal point advanced. fn roundup(d: *decimal, nd: uint) void = { let SZ_ONE: size = (1u64: size); if ((nd: size) >= d.nd) { return; }; for (let i: int = (nd: int) - 1; i >= 0; i -= 1) { if (d.digits[i] < 9u8) { d.digits[i] += 1u8; d.nd = (i: size) + SZ_ONE; return; }; }; d.digits[0] = 1u8; d.nd = SZ_ONE; d.dp += 1; }; // ref/hare/strconv/decimal.ha:188-202. Read `d` as the integer // rounded to `d.dp` digits. Returns 0 if `d.dp <= 0`; returns // ~0u64 if `d.dp > 18` (exceeds u64 range). Hare's two 2-clause // loops (`for (cond; i += 1)`) are inlined per the spelling // divergence at file top. fn decimal_round(d: *decimal) u64 = { let SZ_ZERO: size = (0u64: size); let SZ_ONE: size = (1u64: size); if (d.nd == SZ_ZERO || d.dp < 0) { return 0u64; }; if (d.dp > 18) { // Hare's `~0u64` doesn't parse on a typed-suffix literal // in ww; route via a named zero. let zero: u64 = 0u64; return ~zero; }; let dp_sz: size = ((d.dp: uint): size); let i: size = SZ_ZERO; let n: u64 = 0u64; for (i < dp_sz && i < d.nd) { n = n * 10u64 + (d.digits[i]: u64); i += SZ_ONE; }; for (i < dp_sz) { n *= 10u64; i += SZ_ONE; }; if (should_round_up(d, (d.dp: uint))) { n += 1u64; }; return n; };