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:
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;
|
||||
};
|
||||
Reference in New Issue
Block a user