30 lib test files: package <mod> -> <mod>_test, the sanctioned Go-over-Hare departure (CLAUDE.md rule-9 carve-out; white-box testing deferred, not forbidden). decimaltest retired per .ai/rob-16-decimaltest-ruling.md: Hare ships no decimal_test.ha (engine covered transitively); coverage migrated losslessly into stoftest rows (all-9s carry, nd>19 pure-decimal) + ftostest f64_roundtrip mirroring ref/hare strconv ftos_test.ha tcsf64; k=0 micro-gap documented at site. regex_test keeps its white-box internal probes under a documented rule-7 divergence (rehome = task #9). 922_decimal_run + its 989 byte-id row removed with the fixture.
192 lines
7.6 KiB
Plaintext
192 lines
7.6 KiB
Plaintext
// ftostest — exercises lib/strconv/ftos.ww (Hare ftos.ha / ftos_ryu.ha
|
|
// Ryū port). Run with `out/bin/ww run lib/strconv/test/ftostest.ww`.
|
|
// Same signalled-then-os.exit(signalled+10) pattern as stoftest/
|
|
// decimaltest: a non-zero exit names the failing @test (1..N → 11..N+10).
|
|
//
|
|
// Ports ref/hare/strconv/+test/ftos_test.ha's ffmt::G / prec=void /
|
|
// fflags::NONE rows (the f64tos cases) verbatim — these are exactly
|
|
// strconv.f64tos's output. encode_f_dec is covered by 13.37/1100/0.011/…
|
|
// and encode_e_dec by 1e4/1.1e4/1e-3/1.1e-3/1e-4. nan/±infinity via
|
|
// math.f64frombits of the IEEE bit patterns (ww math has no f64 NAN/INF
|
|
// const — see math/floats.ww).
|
|
//
|
|
// f32tos (fold-5b, task #67) is exercised by f32tos_fixed/_scientific/
|
|
// _extremes/_special below. The G/void rows mirror ftos_test.ha's tcs (the
|
|
// "pass for both f32 and f64" set, lines 9/183); the _extremes rows mirror
|
|
// tcsf32 (ftos_test.ha:218-221, the f32-EXCLUSIVE denormal/min/max — full
|
|
// f32 mantissa). f32 values use the `f32` literal suffix or f32frombits.
|
|
//
|
|
// Lives in lib/strconv/test/ so `import strconv` resolves to the
|
|
// DIRECTORY (full package), not the strconv.ww FILE — same rationale as
|
|
// stoftest/decimaltest.
|
|
|
|
package strconv_test;
|
|
|
|
import strconv;
|
|
import os;
|
|
import math;
|
|
|
|
let signalled: i32 = 0;
|
|
fn fail() void = { os.exit(signalled + 10); };
|
|
|
|
fn streq(a: str, b: str) bool = {
|
|
if (a.len != b.len) { return false; };
|
|
let k: i32 = 0i32;
|
|
for (k < a.len) {
|
|
if (a[k] != b[k]) { return false; };
|
|
k += 1i32;
|
|
};
|
|
return true;
|
|
};
|
|
|
|
fn chk(n: f64, want: str) bool = {
|
|
return streq(f64tos(n), want);
|
|
};
|
|
|
|
// rt64 — bit-exact round-trip: render then parse back must reproduce the
|
|
// exact bits. The parse-back re-runs strconv's decimal slow path.
|
|
fn rt64(x: f64) bool = {
|
|
match (stof64(f64tos(x), base.DEC)) {
|
|
case let v: f64 => { return math.f64bits(v) == math.f64bits(x); };
|
|
case let e: invalid => { return false; };
|
|
case let e: overflow => { return false; };
|
|
};
|
|
return false;
|
|
};
|
|
|
|
// ftos_test.ha:57-64 (G/void/NONE-equivalent) — fixed-point renders.
|
|
@test fn f64tos_fixed() void = {
|
|
if (!chk(13.37, "13.37")) { fail(); };
|
|
if (!chk(12345.0, "12345")) { fail(); };
|
|
if (!chk(1100.0, "1100")) { fail(); };
|
|
if (!chk(100.0, "100")) { fail(); };
|
|
if (!chk(10.0, "10")) { fail(); };
|
|
if (!chk(1.0, "1")) { fail(); };
|
|
if (!chk(0.3, "0.3")) { fail(); };
|
|
if (!chk(0.1, "0.1")) { fail(); };
|
|
if (!chk(0.01, "0.01")) { fail(); };
|
|
if (!chk(0.011, "0.011")) { fail(); };
|
|
if (!chk(1.414, "1.414")) { fail(); };
|
|
if (!chk(-6345.972, "-6345.972")) { fail(); };
|
|
};
|
|
|
|
// ftos_test.ha:78-90 — scientific renders (the shortest-G E-dispatch).
|
|
@test fn f64tos_scientific() void = {
|
|
if (!chk(10000.0, "1e4")) { fail(); };
|
|
if (!chk(11000.0, "1.1e4")) { fail(); };
|
|
if (!chk(1000.0, "1e3")) { fail(); };
|
|
if (!chk(0.001, "1e-3")) { fail(); };
|
|
if (!chk(0.0011, "1.1e-3")) { fail(); };
|
|
if (!chk(0.0001, "1e-4")) { fail(); };
|
|
// dp=-2 < -1 → G dispatches to E (ftos_test.ha:62 is the ffmt::F row;
|
|
// shortest-G of 0.0031415 is scientific).
|
|
if (!chk(0.0031415, "3.1415e-3")) { fail(); };
|
|
};
|
|
|
|
// ftos_test.ha:12/16/27 — zero, ±infinity, nan.
|
|
@test fn f64tos_special() void = {
|
|
if (!chk(0.0, "0")) { fail(); };
|
|
// sign bit via (1u64 << 63), not the 0x8000000000000000 literal:
|
|
// wwstage mis-emits the I64_MIN-magnitude literal (#144 family).
|
|
let negzero: f64 = math.f64frombits(1u64 << 63u64);
|
|
if (!chk(negzero, "-0")) { fail(); };
|
|
let inf: f64 = math.f64frombits(0x7FF0000000000000u64);
|
|
if (!chk(inf, "infinity")) { fail(); };
|
|
let ninf: f64 = math.f64frombits(0xFFF0000000000000u64);
|
|
if (!chk(ninf, "-infinity")) { fail(); };
|
|
let nan: f64 = math.f64frombits(0x7FF8000000000000u64);
|
|
if (!chk(nan, "nan")) { fail(); };
|
|
};
|
|
|
|
fn chkf32(n: f32, want: str) bool = {
|
|
return streq(f32tos(n), want);
|
|
};
|
|
|
|
// ftos_test.ha tcs G/void rows (the "pass for both f32 and f64" set) —
|
|
// fixed-point renders. 1.0/1.5/0.1/100/0.5 are the fold-5a probe's 5/5
|
|
// value-validated f32todecf32 pairs.
|
|
@test fn f32tos_fixed() void = {
|
|
if (!chkf32(13.37f32, "13.37")) { fail(); };
|
|
if (!chkf32(-13.37f32, "-13.37")) { fail(); }; // sign prefix + encode_f at o=1
|
|
// 2^25: the smallest f32 with e2 >= 0 (q == 0) — the ONLY runtime cover
|
|
// of f32todecf32's e2>=0 / q<=9 trailing-zero block (the f32-specific
|
|
// `q <= 9` + pow5multiple32 arm). External shortest-round-trip check.
|
|
if (!chkf32(33554432.0f32, "33554432")) { fail(); };
|
|
if (!chkf32(12345.0f32, "12345")) { fail(); };
|
|
if (!chkf32(1100.0f32, "1100")) { fail(); };
|
|
if (!chkf32(100.0f32, "100")) { fail(); };
|
|
if (!chkf32(10.0f32, "10")) { fail(); };
|
|
if (!chkf32(1.0f32, "1")) { fail(); };
|
|
if (!chkf32(1.5f32, "1.5")) { fail(); };
|
|
if (!chkf32(0.5f32, "0.5")) { fail(); };
|
|
if (!chkf32(0.1f32, "0.1")) { fail(); };
|
|
if (!chkf32(0.01f32, "0.01")) { fail(); };
|
|
if (!chkf32(0.011f32, "0.011")) { fail(); };
|
|
};
|
|
|
|
// ftos_test.ha tcs G/void rows — scientific (shortest-G E-dispatch).
|
|
@test fn f32tos_scientific() void = {
|
|
if (!chkf32(10000.0f32, "1e4")) { fail(); };
|
|
if (!chkf32(11000.0f32, "1.1e4")) { fail(); };
|
|
if (!chkf32(1000.0f32, "1e3")) { fail(); };
|
|
if (!chkf32(0.001f32, "1e-3")) { fail(); };
|
|
if (!chkf32(0.0011f32, "1.1e-3")) { fail(); };
|
|
if (!chkf32(0.0001f32, "1e-4")) { fail(); };
|
|
};
|
|
|
|
// ftos_test.ha:218-221 (tcsf32) — the f32-EXCLUSIVE denormal/min-normal/
|
|
// max-normal rows (distinct outputs from f64): exercise the full f32
|
|
// mantissa (8 sig digits) + 2-digit exponent. Built via f32frombits of the
|
|
// IEEE-754 bit patterns (ww math has no F32_MIN_*/MAX_NORMAL const).
|
|
@test fn f32tos_extremes() void = {
|
|
let minsub: f32 = math.f32frombits(1u32); // 2^-149
|
|
if (!chkf32(minsub, "1e-45")) { fail(); };
|
|
let minnorm: f32 = math.f32frombits(0x00800000u32); // 2^-126
|
|
if (!chkf32(minnorm, "1.1754944e-38")) { fail(); };
|
|
let maxnorm: f32 = math.f32frombits(0x7F7FFFFFu32);
|
|
if (!chkf32(maxnorm, "3.4028235e38")) { fail(); };
|
|
};
|
|
|
|
// ftos_test.ha:11/15/26 — zero, ±infinity, nan (f32 bit patterns).
|
|
@test fn f32tos_special() void = {
|
|
if (!chkf32(0.0f32, "0")) { fail(); };
|
|
// sign bit via (1u32 << 31), mirroring the f64 test's I64_MIN-literal
|
|
// avoidance (#144 family).
|
|
let negzero: f32 = math.f32frombits(1u32 << 31u32);
|
|
if (!chkf32(negzero, "-0")) { fail(); };
|
|
let inf: f32 = math.f32frombits(0x7F800000u32);
|
|
if (!chkf32(inf, "infinity")) { fail(); };
|
|
let ninf: f32 = math.f32frombits(0xFF800000u32);
|
|
if (!chkf32(ninf, "-infinity")) { fail(); };
|
|
let nan: f32 = math.f32frombits(0x7FC00000u32);
|
|
if (!chkf32(nan, "nan")) { fail(); };
|
|
};
|
|
|
|
// ftos_test.ha:196-204 (tcsf64) — Hare's f64-exclusive extremes, here as a
|
|
// bit-exact ROUND-TRIP (f64bits(stof64(f64tos(x)))==f64bits(x)): the parse-
|
|
// back re-runs strconv's decimal engine end-to-end, preserving the coverage
|
|
// the retired white-box decimaltest.ww gave directly (#16). Extremes built
|
|
// via f64frombits (ww math has no F64_MIN_*/MAX_NORMAL const); exact and
|
|
// 17-digit-halfway values as literals.
|
|
@test fn f64_roundtrip() void = {
|
|
if (!rt64(9007199254740991.0)) { fail(); }; // 2^53-1
|
|
if (!rt64(90071992547409915.0)) { fail(); }; // 17-digit halfway pair
|
|
if (!rt64(90071992547409925.0)) { fail(); };
|
|
if (!rt64(math.f64frombits(0x0000000000000001u64))) { fail(); }; // F64_MIN_SUBNORMAL (5e-324)
|
|
if (!rt64(math.f64frombits(0x0010000000000000u64))) { fail(); }; // F64_MIN_NORMAL
|
|
if (!rt64(math.f64frombits(0x7FEFFFFFFFFFFFFFu64))) { fail(); }; // F64_MAX_NORMAL
|
|
};
|
|
|
|
export fn main() i32 = {
|
|
signalled = 1; f64tos_fixed();
|
|
signalled = 2; f64tos_scientific();
|
|
signalled = 3; f64tos_special();
|
|
signalled = 4; f32tos_fixed();
|
|
signalled = 5; f32tos_scientific();
|
|
signalled = 6; f32tos_extremes();
|
|
signalled = 7; f32tos_special();
|
|
signalled = 8; f64_roundtrip();
|
|
os.exit(0);
|
|
return 0;
|
|
};
|