lib/strconv: decimal arbitrary-precision arithmetic (#106 fold-3)
Port ref/hare/strconv/decimal.ha (~202 LOC Hare) → 314 LOC lib/strconv/decimal.ww — decimal struct + 11 fns (trim, decimal_shift, leftshift, leftshift_newdigits, rightshift, round, decimal_round, helpers). 1:1 mechanical Hare-fidelity with 8 documented spelling-divergences. Shared engine for stof (fold-4) + ftos (fold-5). Built atop 5 wwstage cgen prereqs (#131/#133-expanded/#134/#135/#138) that closed gate-blind silent miscompiles surfaced by the port. Test 922_decimal_run + lib/strconv/test/decimaltest.ww (6 @test fns covering all 11 impl fns).
This commit is contained in:
9
Makefile
9
Makefile
@@ -113,7 +113,7 @@ $(BIN)/wwdump_ww: selfhost/cmd/wwdump/main.ww \
|
||||
selfhost/cmd/wcc/cgen.ww selfhost/cmd/wcc/cgenexpr.ww \
|
||||
selfhost/cmd/wcc/cgenstmt.ww selfhost/cmd/wcc/cgenutil.ww \
|
||||
selfhost/cmd/wcc/cgendecl.ww \
|
||||
lib/os/os.ww lib/rt/malloc.ww lib/time/time.ww lib/strconv/strconv.ww lib/strconv/stof_data.ww lib/strings/strings.ww lib/bytes/bytes.ww lib/encoding/utf8/utf8.ww lib/ascii/ascii.ww lib/io/io.ww lib/memio/memio.ww lib/fmt/fmt.ww \
|
||||
lib/os/os.ww lib/rt/malloc.ww lib/time/time.ww lib/strconv/strconv.ww lib/strconv/stof_data.ww lib/strconv/decimal.ww lib/strings/strings.ww lib/bytes/bytes.ww lib/encoding/utf8/utf8.ww lib/ascii/ascii.ww lib/io/io.ww lib/memio/memio.ww lib/fmt/fmt.ww \
|
||||
$(BIN)/ww $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \
|
||||
$(LIB)/libwwrt.a | $(BIN)
|
||||
@mkdir -p $(BIN)/wwdump_ww.d
|
||||
@@ -135,7 +135,7 @@ $(BIN)/w6c_ww: selfhost/cmd/w6c/main.ww \
|
||||
selfhost/cmd/wcc/cgen.ww selfhost/cmd/wcc/cgenexpr.ww \
|
||||
selfhost/cmd/wcc/cgenstmt.ww selfhost/cmd/wcc/cgenutil.ww \
|
||||
selfhost/cmd/wcc/cgendecl.ww \
|
||||
lib/os/os.ww lib/rt/malloc.ww lib/time/time.ww lib/strconv/strconv.ww lib/strconv/stof_data.ww lib/strings/strings.ww lib/bytes/bytes.ww lib/encoding/utf8/utf8.ww lib/ascii/ascii.ww lib/io/io.ww lib/memio/memio.ww lib/fmt/fmt.ww \
|
||||
lib/os/os.ww lib/rt/malloc.ww lib/time/time.ww lib/strconv/strconv.ww lib/strconv/stof_data.ww lib/strconv/decimal.ww lib/strings/strings.ww lib/bytes/bytes.ww lib/encoding/utf8/utf8.ww lib/ascii/ascii.ww lib/io/io.ww lib/memio/memio.ww lib/fmt/fmt.ww \
|
||||
$(BIN)/ww $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \
|
||||
$(LIB)/libwwrt.a | $(BIN)
|
||||
@mkdir -p $(BIN)/w6c_ww.d
|
||||
@@ -322,6 +322,7 @@ TESTS = $(BIN)/test_smoke $(BIN)/test_lex $(BIN)/test_parse $(BIN)/test_check \
|
||||
$(BIN)/test_intdiv_signed \
|
||||
$(BIN)/test_strings_run \
|
||||
$(BIN)/test_hex_run $(BIN)/test_utf8_run $(BIN)/test_bytes_run \
|
||||
$(BIN)/test_decimal_run \
|
||||
$(BIN)/test_memio_run $(BIN)/test_temp_run $(BIN)/test_getopt_run \
|
||||
$(BIN)/test_base32_run $(BIN)/test_base64_run \
|
||||
$(BIN)/test_adler32_run $(BIN)/test_crc16_run \
|
||||
@@ -1031,6 +1032,10 @@ $(BIN)/test_bytes_run: test/wcc/967_bytes_run.c $(BIN)/ww $(BIN)/w6c \
|
||||
$(BIN)/w6a $(BIN)/w6l $(LIB)/libwwrt.a | $(BIN)
|
||||
$(CC) $(CFLAGS) -o $@ $<
|
||||
|
||||
$(BIN)/test_decimal_run: test/wcc/922_decimal_run.c $(BIN)/ww $(BIN)/w6c \
|
||||
$(BIN)/w6a $(BIN)/w6l $(LIB)/libwwrt.a | $(BIN)
|
||||
$(CC) $(CFLAGS) -o $@ $<
|
||||
|
||||
$(BIN)/test_strings_run: test/wcc/966_strings_run.c $(BIN)/ww $(BIN)/w6c \
|
||||
$(BIN)/w6a $(BIN)/w6l $(LIB)/libwwrt.a | $(BIN)
|
||||
$(CC) $(CFLAGS) -o $@ $<
|
||||
|
||||
314
lib/strconv/decimal.ww
Normal file
314
lib/strconv/decimal.ww
Normal file
@@ -0,0 +1,314 @@
|
||||
// strconv — arbitrary-precision decimal engine for float↔string
|
||||
// conversion. 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 → `os.assert(cond, msg)`;
|
||||
// wwstage cgen has no `assert` intercept (deferred fold).
|
||||
//
|
||||
// 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);
|
||||
os.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;
|
||||
};
|
||||
212
lib/strconv/test/decimaltest.ww
Normal file
212
lib/strconv/test/decimaltest.ww
Normal file
@@ -0,0 +1,212 @@
|
||||
// decimaltest — exercises lib/strconv/decimal.ww (Hare decimal.ha port).
|
||||
// Run with `out/bin/ww run lib/strconv/test/decimaltest.ww`. Same
|
||||
// signalled-then-fail()-with-+10 pattern as bytestest / hex / utf8.
|
||||
// Non-zero exit pinpoints the failing scenario.
|
||||
//
|
||||
// Hare has no decimal_test.ha (decimal is exercised transitively via
|
||||
// ftos_test.ha). ww-authored probes per
|
||||
// feedback_test_match_hare_source ("ww-authored where Hare doesn't
|
||||
// ship direct tests"). Coverage per Drew design pre-flight:
|
||||
// trim (RHS-zeros), decimal_shift (k=0/k>0/k<0/|k|>maxshift),
|
||||
// leftshift (small/edge), rightshift (small/break-out), round (at
|
||||
// various dp), decimal_round (small + dp>18 + dp<0).
|
||||
//
|
||||
// Lives in lib/strconv/test/ (not lib/strconv/) so `import strconv`
|
||||
// resolves to the lib/strconv DIRECTORY rather than shadowing on
|
||||
// the strconv.ww FILE — pulls in decimal.ww + stof_data.ww +
|
||||
// strconv.ww as the unified `package strconv` compilation.
|
||||
|
||||
package strconv;
|
||||
|
||||
import strconv;
|
||||
import os;
|
||||
|
||||
let signalled: i32 = 0;
|
||||
fn fail() void = { os.exit(signalled + 10); };
|
||||
|
||||
// Seeds `d` with `vals` as the leading digits + `dp` as the decimal
|
||||
// point, leaving negative/truncated false. Each test starts from a
|
||||
// known state.
|
||||
fn seed(d: *decimal, vals: []u8, dp: i32) void = {
|
||||
let SZ_ONE: size = (1u64: size);
|
||||
let i: size = (0u64: size);
|
||||
for (i < (vals.len: size)) {
|
||||
d.digits[i] = vals[i];
|
||||
i += SZ_ONE;
|
||||
};
|
||||
d.nd = (vals.len: size);
|
||||
d.dp = dp;
|
||||
};
|
||||
|
||||
// ---- trim -------------------------------------------------------------
|
||||
// ref/hare/strconv/decimal.ha:29.
|
||||
|
||||
@test fn trim_cases() void = {
|
||||
let d: decimal;
|
||||
let vals: [5]u8;
|
||||
vals[0] = 1u8; vals[1] = 2u8; vals[2] = 3u8; vals[3] = 0u8; vals[4] = 0u8;
|
||||
seed(&d, vals[0:5], 3);
|
||||
trim(&d);
|
||||
if (d.nd != (3u64: size)) { fail(); };
|
||||
if (d.dp != 3) { fail(); };
|
||||
|
||||
// all-zero case: nd shrinks to 0.
|
||||
let zeros: [3]u8;
|
||||
seed(&d, zeros[0:3], 2);
|
||||
trim(&d);
|
||||
if (d.nd != (0u64: size)) { fail(); };
|
||||
};
|
||||
|
||||
// ---- leftshift / leftshift_newdigits ---------------------------------
|
||||
// ref/hare/strconv/decimal.ha:35,57.
|
||||
|
||||
@test fn leftshift_small_cases() void = {
|
||||
// Shift "5" by 1 → "10" → trim collapses to nd=1 with dp+=1 (Hare
|
||||
// trims trailing zero of the high-shifted byte).
|
||||
let d: decimal;
|
||||
d.digits[0] = 5u8;
|
||||
d.nd = (1u64: size);
|
||||
d.dp = 1;
|
||||
leftshift(&d, 1u32);
|
||||
if (d.dp != 2) { fail(); };
|
||||
if (d.digits[0] != 1u8) { fail(); };
|
||||
// post-trim nd is 1 (trailing 0 stripped).
|
||||
if (d.nd != (1u64: size)) { fail(); };
|
||||
|
||||
// Shift "123" by 1 → "246" (no trim — no trailing zero).
|
||||
let v3: [3]u8;
|
||||
v3[0] = 1u8; v3[1] = 2u8; v3[2] = 3u8;
|
||||
seed(&d, v3[0:3], 3);
|
||||
leftshift(&d, 1u32);
|
||||
if (d.nd != (3u64: size)) { fail(); };
|
||||
if (d.dp != 3) { fail(); };
|
||||
if (d.digits[0] != 2u8) { fail(); };
|
||||
if (d.digits[1] != 4u8) { fail(); };
|
||||
if (d.digits[2] != 6u8) { fail(); };
|
||||
|
||||
// nd == 0 no-op (early-return arm of leftshift).
|
||||
let z: decimal;
|
||||
leftshift(&z, 1u32);
|
||||
if (z.nd != (0u64: size)) { fail(); };
|
||||
};
|
||||
|
||||
// ---- rightshift -----------------------------------------------------
|
||||
// ref/hare/strconv/decimal.ha:93.
|
||||
|
||||
@test fn rightshift_small_cases() void = {
|
||||
// Shift "10" (dp=2) right by 1 → "5" (dp=1).
|
||||
let d: decimal;
|
||||
let vals: [2]u8;
|
||||
vals[0] = 1u8; vals[1] = 0u8;
|
||||
seed(&d, vals[0:2], 2);
|
||||
rightshift(&d, 1u32);
|
||||
if (d.nd != (1u64: size)) { fail(); };
|
||||
if (d.dp != 1) { fail(); };
|
||||
if (d.digits[0] != 5u8) { fail(); };
|
||||
};
|
||||
|
||||
// ---- decimal_shift --------------------------------------------------
|
||||
// ref/hare/strconv/decimal.ha:138.
|
||||
|
||||
@test fn decimal_shift_cases() void = {
|
||||
// k=0 no-op.
|
||||
let d: decimal;
|
||||
let v3: [3]u8; v3[0] = 1u8; v3[1] = 2u8; v3[2] = 3u8;
|
||||
seed(&d, v3[0:3], 3);
|
||||
decimal_shift(&d, 0);
|
||||
if (d.nd != (3u64: size)) { fail(); };
|
||||
if (d.dp != 3) { fail(); };
|
||||
if (d.digits[0] != 1u8) { fail(); };
|
||||
|
||||
// k=2 small positive: 123 << 2 = 492 → digits 4,9,2; dp=3.
|
||||
seed(&d, v3[0:3], 3);
|
||||
decimal_shift(&d, 2);
|
||||
if (d.dp != 3) { fail(); };
|
||||
if (d.digits[0] != 4u8) { fail(); };
|
||||
if (d.digits[1] != 9u8) { fail(); };
|
||||
if (d.digits[2] != 2u8) { fail(); };
|
||||
|
||||
// k>maxshift triggers the break-up loop. Exact digits are checked
|
||||
// transitively by stof in fold-4; verify dp grew.
|
||||
let v: [3]u8; v[0] = 1u8; v[1] = 0u8; v[2] = 0u8;
|
||||
seed(&d, v[0:3], 3);
|
||||
decimal_shift(&d, 70);
|
||||
if (d.dp <= 3) { fail(); };
|
||||
};
|
||||
|
||||
// ---- should_round_up + round + decimal_round ------------------------
|
||||
// ref/hare/strconv/decimal.ha:155,162,188.
|
||||
|
||||
@test fn round_cases() void = {
|
||||
// rounddown — "1234" rounded to 2 digits → "12".
|
||||
let d: decimal;
|
||||
let v: [4]u8; v[0] = 1u8; v[1] = 2u8; v[2] = 3u8; v[3] = 4u8;
|
||||
seed(&d, v[0:4], 4);
|
||||
round(&d, (2u32: uint));
|
||||
if (d.nd != (2u64: size)) { fail(); };
|
||||
if (d.digits[0] != 1u8) { fail(); };
|
||||
if (d.digits[1] != 2u8) { fail(); };
|
||||
|
||||
// roundup — "1259" rounded to 2 digits → "13" (digit[2]==5 →
|
||||
// should_round_up, digit[1]=2 → +1 → digit[1]=3).
|
||||
let r: [4]u8; r[0] = 1u8; r[1] = 2u8; r[2] = 5u8; r[3] = 9u8;
|
||||
seed(&d, r[0:4], 4);
|
||||
round(&d, (2u32: uint));
|
||||
if (d.nd != (2u64: size)) { fail(); };
|
||||
if (d.digits[0] != 1u8) { fail(); };
|
||||
if (d.digits[1] != 3u8) { fail(); };
|
||||
|
||||
// roundup with all-9s carry — "999" rounded to 2 digits → "1"
|
||||
// with dp incremented.
|
||||
let nines: [3]u8; nines[0] = 9u8; nines[1] = 9u8; nines[2] = 9u8;
|
||||
seed(&d, nines[0:3], 3);
|
||||
round(&d, (2u32: uint));
|
||||
if (d.digits[0] != 1u8) { fail(); };
|
||||
if (d.nd != (1u64: size)) { fail(); };
|
||||
if (d.dp != 4) { fail(); };
|
||||
};
|
||||
|
||||
@test fn decimal_round_cases() void = {
|
||||
// dp<0 → 0.
|
||||
let d: decimal;
|
||||
let v: [3]u8; v[0] = 1u8; v[1] = 2u8; v[2] = 3u8;
|
||||
seed(&d, v[0:3], -1);
|
||||
if (decimal_round(&d) != 0u64) { fail(); };
|
||||
|
||||
// nd==0 → 0.
|
||||
let z: decimal;
|
||||
z.dp = 5;
|
||||
if (decimal_round(&z) != 0u64) { fail(); };
|
||||
|
||||
// dp>18 → ~0u64 (all-bits-set).
|
||||
let big: decimal;
|
||||
let one: [1]u8; one[0] = 1u8;
|
||||
seed(&big, one[0:1], 19);
|
||||
let zero_u: u64 = 0u64;
|
||||
if (decimal_round(&big) != ~zero_u) { fail(); };
|
||||
|
||||
// "123" with dp=3 → 123.
|
||||
seed(&d, v[0:3], 3);
|
||||
if (decimal_round(&d) != 123u64) { fail(); };
|
||||
|
||||
// "12" with dp=4 → 1200 (multiply by 10 for each missing digit).
|
||||
let two: [2]u8; two[0] = 1u8; two[1] = 2u8;
|
||||
seed(&d, two[0:2], 4);
|
||||
if (decimal_round(&d) != 1200u64) { fail(); };
|
||||
|
||||
// "126" with dp=2 → 13 (rounded up via should_round_up at dp=2:
|
||||
// digit[2]==6 >= 5).
|
||||
let r: [3]u8; r[0] = 1u8; r[1] = 2u8; r[2] = 6u8;
|
||||
seed(&d, r[0:3], 2);
|
||||
if (decimal_round(&d) != 13u64) { fail(); };
|
||||
};
|
||||
|
||||
export fn main() i32 = {
|
||||
signalled = 1; trim_cases();
|
||||
signalled = 2; leftshift_small_cases();
|
||||
signalled = 3; rightshift_small_cases();
|
||||
signalled = 4; decimal_shift_cases();
|
||||
signalled = 5; round_cases();
|
||||
signalled = 6; decimal_round_cases();
|
||||
return 0;
|
||||
};
|
||||
@@ -2704,6 +2704,321 @@ export fn rpad(s: str, p: rune, maxlen: i32) str = {
|
||||
return frombytes(buf);
|
||||
};
|
||||
|
||||
// strconv — arbitrary-precision decimal engine for float↔string
|
||||
// conversion. 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 → `os.assert(cond, msg)`;
|
||||
// wwstage cgen has no `assert` intercept (deferred fold).
|
||||
//
|
||||
// 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);
|
||||
os.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;
|
||||
};
|
||||
|
||||
// strconv — stof/ftos lookup tables. Mirrors ref/hare/strconv/stof_data.ha
|
||||
// byte-exact. Pure-data fold (strconv #106 fold-2, was fold-3 before drew
|
||||
// re-sequenced 2026-05-26): no logic, exercised transitively when fold-3's
|
||||
|
||||
@@ -727,6 +727,321 @@ export fn exists(path: str) bool = {
|
||||
return r >= 0i64;
|
||||
};
|
||||
|
||||
// strconv — arbitrary-precision decimal engine for float↔string
|
||||
// conversion. 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 → `os.assert(cond, msg)`;
|
||||
// wwstage cgen has no `assert` intercept (deferred fold).
|
||||
//
|
||||
// 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);
|
||||
os.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;
|
||||
};
|
||||
|
||||
// strconv — stof/ftos lookup tables. Mirrors ref/hare/strconv/stof_data.ha
|
||||
// byte-exact. Pure-data fold (strconv #106 fold-2, was fold-3 before drew
|
||||
// re-sequenced 2026-05-26): no logic, exercised transitively when fold-3's
|
||||
|
||||
@@ -727,6 +727,321 @@ export fn exists(path: str) bool = {
|
||||
return r >= 0i64;
|
||||
};
|
||||
|
||||
// strconv — arbitrary-precision decimal engine for float↔string
|
||||
// conversion. 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 → `os.assert(cond, msg)`;
|
||||
// wwstage cgen has no `assert` intercept (deferred fold).
|
||||
//
|
||||
// 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);
|
||||
os.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;
|
||||
};
|
||||
|
||||
// strconv — stof/ftos lookup tables. Mirrors ref/hare/strconv/stof_data.ha
|
||||
// byte-exact. Pure-data fold (strconv #106 fold-2, was fold-3 before drew
|
||||
// re-sequenced 2026-05-26): no logic, exercised transitively when fold-3's
|
||||
|
||||
57
test/wcc/922_decimal_run.c
Normal file
57
test/wcc/922_decimal_run.c
Normal file
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
* 922_decimal_run — execute the lib/strconv/test/decimaltest fixture
|
||||
* under the C-side `ww run` driver and assert exit 0.
|
||||
*
|
||||
* Same thin-wrapper shape as 967_bytes_run / 968_utf8_run / 979_hex_run:
|
||||
* decimaltest.ww carries its own `export fn main()` that drives the
|
||||
* @test fns and signals which case failed via the exit code (signalled
|
||||
* + 10, so cases 1..6 → exit 11..16).
|
||||
*
|
||||
* The test fixture lives in lib/strconv/test/ (not lib/strconv/) so
|
||||
* `import strconv` resolves to the lib/strconv DIRECTORY rather than
|
||||
* shadowing on the strconv.ww FILE — same-dir resolution would pull
|
||||
* in only strconv.ww and miss decimal.ww + stof_data.ww. From the
|
||||
* test/ subdir, the search-path lookup falls through srcd and reaches
|
||||
* the libdir's directory hit, expanding the full strconv package.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/wait.h>
|
||||
|
||||
static int
|
||||
runwait(const char *cmd)
|
||||
{
|
||||
int rc = system(cmd);
|
||||
if (rc == -1) return -1;
|
||||
if (WIFEXITED(rc)) return WEXITSTATUS(rc);
|
||||
return 1;
|
||||
}
|
||||
|
||||
int
|
||||
main(void)
|
||||
{
|
||||
const char *bin = getenv("BIN");
|
||||
if (!bin) bin = "out/bin";
|
||||
char absbin[1024];
|
||||
if (bin[0] != '/') {
|
||||
char cwd[1024];
|
||||
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
|
||||
snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin);
|
||||
bin = absbin;
|
||||
}
|
||||
char cwd[1024];
|
||||
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
|
||||
|
||||
const char *src = "lib/strconv/test/decimaltest.ww";
|
||||
char path[1024], cmd[2048];
|
||||
snprintf(path, sizeof path, "%s/%s", cwd, src);
|
||||
snprintf(cmd, sizeof cmd, "%s/ww run %s", bin, path);
|
||||
int rc = runwait(cmd);
|
||||
if (rc != 0) {
|
||||
fprintf(stderr, "decimal_run FAIL: %s exited %d\n", src, rc);
|
||||
return 1;
|
||||
}
|
||||
printf("decimal_run: %s ok\n", src);
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user