// 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. package strconv_test; import strconv; import math; fn fstreq(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 fstreq(strconv.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 (strconv.stof64(strconv.f64tos(x), strconv.base.DEC)) { case let v: f64 => { return math.f64bits(v) == math.f64bits(x); }; case let e: strconv.invalid => { return false; }; case let e: strconv.overflow => { return false; }; }; return false; }; fn rt32(x: f32) bool = { match (strconv.stof32(strconv.f32tos(x), strconv.base.DEC)) { case let v: f32 => { return math.f32bits(v) == math.f32bits(x); }; case let e: strconv.invalid => { return false; }; case let e: strconv.overflow => { return false; }; }; return false; }; // ftos_test.ha:57-64 (G/void/NONE-equivalent) — fixed-point renders. @test fn f64tos_fixed() void = { assert(!(!chk(13.37, "13.37"))); assert(!(!chk(12345.0, "12345"))); assert(!(!chk(1100.0, "1100"))); assert(!(!chk(100.0, "100"))); assert(!(!chk(10.0, "10"))); assert(!(!chk(1.0, "1"))); assert(!(!chk(0.3, "0.3"))); assert(!(!chk(0.1, "0.1"))); assert(!(!chk(0.01, "0.01"))); assert(!(!chk(0.011, "0.011"))); assert(!(!chk(1.414, "1.414"))); assert(!(!chk(-6345.972, "-6345.972"))); }; // ftos_test.ha:78-90 — scientific renders (the shortest-G E-dispatch). @test fn f64tos_scientific() void = { assert(!(!chk(10000.0, "1e4"))); assert(!(!chk(11000.0, "1.1e4"))); assert(!(!chk(1000.0, "1e3"))); assert(!(!chk(0.001, "1e-3"))); assert(!(!chk(0.0011, "1.1e-3"))); assert(!(!chk(0.0001, "1e-4"))); // dp=-2 < -1 → G dispatches to E (ftos_test.ha:62 is the ffmt::F row; // shortest-G of 0.0031415 is scientific). assert(!(!chk(0.0031415, "3.1415e-3"))); }; // ftos_test.ha:12/16/27 — zero, ±infinity, nan. @test fn f64tos_special() void = { assert(!(!chk(0.0, "0"))); // 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); assert(!(!chk(negzero, "-0"))); let inf: f64 = math.f64frombits(0x7FF0000000000000u64); assert(!(!chk(inf, "infinity"))); let ninf: f64 = math.f64frombits(0xFFF0000000000000u64); assert(!(!chk(ninf, "-infinity"))); let nan: f64 = math.f64frombits(0x7FF8000000000000u64); assert(!(!chk(nan, "nan"))); }; fn chkf32(n: f32, want: str) bool = { return fstreq(strconv.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 = { assert(!(!chkf32(13.37f32, "13.37"))); assert(!(!chkf32(-13.37f32, "-13.37"))); // 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. assert(!(!chkf32(33554432.0f32, "33554432"))); assert(!(!chkf32(12345.0f32, "12345"))); assert(!(!chkf32(1100.0f32, "1100"))); assert(!(!chkf32(100.0f32, "100"))); assert(!(!chkf32(10.0f32, "10"))); assert(!(!chkf32(1.0f32, "1"))); assert(!(!chkf32(1.5f32, "1.5"))); assert(!(!chkf32(0.5f32, "0.5"))); assert(!(!chkf32(0.1f32, "0.1"))); assert(!(!chkf32(0.01f32, "0.01"))); assert(!(!chkf32(0.011f32, "0.011"))); }; // ftos_test.ha tcs G/void rows — scientific (shortest-G E-dispatch). @test fn f32tos_scientific() void = { assert(!(!chkf32(10000.0f32, "1e4"))); assert(!(!chkf32(11000.0f32, "1.1e4"))); assert(!(!chkf32(1000.0f32, "1e3"))); assert(!(!chkf32(0.001f32, "1e-3"))); assert(!(!chkf32(0.0011f32, "1.1e-3"))); assert(!(!chkf32(0.0001f32, "1e-4"))); }; // 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 assert(!(!chkf32(minsub, "1e-45"))); let minnorm: f32 = math.f32frombits(0x00800000u32); // 2^-126 assert(!(!chkf32(minnorm, "1.1754944e-38"))); let maxnorm: f32 = math.f32frombits(0x7F7FFFFFu32); assert(!(!chkf32(maxnorm, "3.4028235e38"))); }; @test fn f32_roundtrip_boundaries() void = { assert(!(!rt32(math.f32frombits(0x00000001u32)))); assert(!(!rt32(math.f32frombits(0x007FFFFFu32)))); assert(!(!rt32(math.f32frombits(0x00800000u32)))); assert(!(!rt32(math.f32frombits(0x00800001u32)))); assert(!(!rt32(math.f32frombits(0x7F7FFFFFu32)))); assert(!(!rt32(math.f32frombits(0x807FFFFFu32)))); assert(!(!rt32(math.f32frombits(0x80800000u32)))); }; // ftos_test.ha:11/15/26 — zero, ±infinity, nan (f32 bit patterns). @test fn f32tos_special() void = { assert(!(!chkf32(0.0f32, "0"))); // sign bit via (1u32 << 31), mirroring the f64 test's I64_MIN-literal // avoidance (#144 family). let negzero: f32 = math.f32frombits(1u32 << 31u32); assert(!(!chkf32(negzero, "-0"))); let inf: f32 = math.f32frombits(0x7F800000u32); assert(!(!chkf32(inf, "infinity"))); let ninf: f32 = math.f32frombits(0xFF800000u32); assert(!(!chkf32(ninf, "-infinity"))); let nan: f32 = math.f32frombits(0x7FC00000u32); assert(!(!chkf32(nan, "nan"))); }; // 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 = { assert(!(!rt64(9007199254740991.0))); // 2^53-1 assert(!(!rt64(90071992547409915.0))); // 17-digit halfway pair assert(!(!rt64(90071992547409925.0))); assert(!(!rt64(math.f64frombits(0x0000000000000001u64)))); // F64_MIN_SUBNORMAL (5e-324) assert(!(!rt64(math.f64frombits(0x000FFFFFFFFFFFFFu64)))); // F64_MAX_SUBNORMAL assert(!(!rt64(math.f64frombits(0x0010000000000000u64)))); // F64_MIN_NORMAL assert(!(!rt64(math.f64frombits(0x0010000000000001u64)))); // next normal assert(!(!rt64(math.f64frombits(0x800FFFFFFFFFFFFFu64)))); // negative max subnormal assert(!(!rt64(math.f64frombits(0x8010000000000000u64)))); // negative min normal assert(!(!rt64(math.f64frombits(0x7FEFFFFFFFFFFFFFu64)))); // F64_MAX_NORMAL };