lib/strconv: f64tos Ryū shortest float→string (#106 fold-5a)
Graduate f64tos from the lossy fixed-point placeholder to Ryū shortest- round-trippable (ftos.ha:432 + ftos_ryu.ha). f64tos(n:f64) str, G-format (void/NONE) — the faithful documented subset (full parametric fftosf is dead code for G/void/NONE → deferred #64). Ryū core decomposed: struct- RETURN + scalar params (Hare's r128 idiom; avoids tuple-ABI #163-166). f64 powers tables [15][2]/[13][2]u64 (2D #156). Zero float literals. Both E+F encode paths reachable+tested, no dead code. Graduation (lib-note "don't keep both"): old lossy f64tos deleted; fmt fprintf_f64_huge "huge"→"9.5e18" (improvement). 5 value-faithful filed- bug dodges (byte-id, documented): #167/#169/#170/#43/#144. Test 908. Make test 186/186 incl 990-997 byte-id + combined_ww_fresh. Drew's strconv 5-fold plan — primary completion (f64tos). f32tos coda #67 (behind #143); parametric ftosf #64.
This commit is contained in:
549
lib/strconv/ftos.ww
Normal file
549
lib/strconv/ftos.ww
Normal file
@@ -0,0 +1,549 @@
|
||||
// strconv — float→string via Ryū (shortest round-trippable decimal).
|
||||
// Mirrors ref/hare/strconv/ftos_ryu.ha (the algorithm core) +
|
||||
// ref/hare/strconv/ftos.ha:432 (the f64tos driver). Ryū: Ulf Adams,
|
||||
// https://doi.org/10.1145/3192366.3192369 — Hare translated it from the
|
||||
// reference C (https://github.com/ulfjack/ryu); ww follows Hare.
|
||||
//
|
||||
// SCOPE — the f64tos shortest-representation subset (Hare's ffmt::G,
|
||||
// prec=void, fflags::NONE). Two deferrals:
|
||||
// - f32tos (ftos.ha:448) + its f32 Ryū sub-path (f32todecf32 +
|
||||
// mulpow5inv/pow5_divpow2 + mulshift32 + the *32 helpers + the
|
||||
// F32_POW5_*_BITCOUNT defs) → fold-5b (task #67), gated on the #143
|
||||
// f32-arg-push cgen fix: f32tos must call math.f32bits(n), passing
|
||||
// an f32 arg, which spills MOVSD (cstage) vs MOVSS (wwstage) →
|
||||
// 990-997 byte-id break. Not isolatable (f32bits IS the reinterpret),
|
||||
// only fixable. f32tos IS a real Hare entry — it ships in #67.
|
||||
// - the parametric fftosf/ffmt/fflags/ftosf surface → task #64 (needs
|
||||
// io::handle/memio + a `(size|io::error)?` per appendrune (#158);
|
||||
// for G/void/NONE the ffmt/fflags/precision/multiprecision-fallback
|
||||
// machinery is provably dead code — `ok` is always true → init_dec/
|
||||
// compute_round/round unreachable — which bootstrap-coverage rejects).
|
||||
// The lib note blesses "a documented subset". This file ships ZERO float
|
||||
// literals — Ryū is all bit/integer arithmetic on f64bits(n) — so the
|
||||
// wwdump TK_FLOAT embedding concern is moot.
|
||||
//
|
||||
// Decomposition divergences (the #163-166 tuple/struct-ABI cluster —
|
||||
// ww's partial tuple support miscompiles the shapes Hare uses; the
|
||||
// WORKING shapes, struct-RETURN + scalar-PARAMS, are this algorithm's
|
||||
// own idiom: ftos_ryu.ha:12 already uses `struct r128` not a tuple for
|
||||
// u128mul, and fold-4/stof.ww decomposed likewise):
|
||||
// - `mulshiftall64`'s tuple param `mul:(u64,u64)` → two scalar params
|
||||
// `mul0,mul1` (#163: tuple-as-param reads garbage); its 3-tuple
|
||||
// return `(u64,u64,u64)` → 24B struct `ryuv` (#164: 3-tuple return
|
||||
// reads 0; struct-RETURN is byte-id-clean — r128 precedent). NO
|
||||
// struct-as-PARAM anywhere (#165: 16B struct-param diverges cs≠ww).
|
||||
// - `f64computeinvpow5`/`f64computepow5` keep their 2-tuple `(u64,u64)`
|
||||
// return (call-return 2-tuple + `.0`/`.1` is byte-id-clean — the
|
||||
// math/floats.ww frexpf64 precedent).
|
||||
// - dead `mulshift64` (tuple-param, never called) + dead
|
||||
// `F32/F64_DECIMAL_DIGITS` dropped.
|
||||
//
|
||||
// Spelling divergences (mechanical, ww parser/cgen; cite ftos_ryu.ha):
|
||||
// - scalar-PARAM mutation (`m<<=1`, `value*=…`) → copy-to-local
|
||||
// (stof.ww hex_to_bits precedent).
|
||||
// - `&&=` → `x = x && y`. `ibool=if(b)1 else 0` expr-body → block.
|
||||
// comma `let a=…, b=…` → split. `if/else` expr-yield → pre-bound
|
||||
// local + block. `assert()` → `os.assert(cond,msg)`.
|
||||
// - 2D row-bind `mul=TBL[base]` → direct double-index `TBL[base][0/1]`
|
||||
// (#155 / #156, stof.ww eisel_lemire precedent).
|
||||
// - ibool's u8 result + the u8 BITCOUNT defs cast explicitly to u32/u64
|
||||
// at each use (Hare promotes; ww is strict — int-machine-word note).
|
||||
// - a `(N: uint)` cast embedded inside an array subscript `[ ]` is
|
||||
// rejected by the ww parser → hoist to a named local before the
|
||||
// index (decimal.ww "hoist size casts" note); see init_dec_mant_exp
|
||||
// + encode_e_dec.
|
||||
|
||||
package strconv;
|
||||
|
||||
import math;
|
||||
import os;
|
||||
|
||||
// ref/hare/strconv/ftos_ryu.ha:33. (hi:lo) >> s, low 64 bits. Hare's
|
||||
// "TODO: use 128-bit integers" — ww has no u128; pure-u64 decomposition.
|
||||
// (u128mul + the r128 struct live in stof.ww, fold-4's first consumer;
|
||||
// reused in-package here.)
|
||||
fn u128rshift(lo: u64, hi: u64, s: u32) u64 = {
|
||||
os.assert(s <= 64u32, "strconv.u128rshift: s > 64");
|
||||
return (hi << (64u64 - (s: u64))) | (lo >> (s: u64));
|
||||
};
|
||||
|
||||
// ref/hare/strconv/ftos_ryu.ha:39. Largest p with 5^p | value.
|
||||
fn pow5fac(v: u64) u32 = {
|
||||
let value: u64 = v;
|
||||
let m_inv_5: u64 = 14757395258967641293u64; // 5 * m_inv_5 == 1 (mod 2^64)
|
||||
let n_div_5: u64 = 3689348814741910323u64;
|
||||
let count: u32 = 0u32;
|
||||
for (true) {
|
||||
os.assert(value != 0u64, "strconv.pow5fac: value == 0");
|
||||
value *= m_inv_5;
|
||||
if (value > n_div_5) { break; };
|
||||
count += 1u32;
|
||||
};
|
||||
return count;
|
||||
};
|
||||
|
||||
// ref/hare/strconv/ftos_ryu.ha:64.
|
||||
fn ibool(b: bool) u8 = {
|
||||
if (b) { return 1u8; };
|
||||
return 0u8;
|
||||
};
|
||||
|
||||
// ref/hare/strconv/ftos_ryu.ha:66-67.
|
||||
fn pow5multiple(v: u64, p: u32) bool = { return pow5fac(v) >= p; };
|
||||
|
||||
// ref/hare/strconv/ftos_ryu.ha:69.
|
||||
fn pow2multiple(v: u64, p: u32) bool = {
|
||||
os.assert(v > 0u64, "strconv.pow2multiple: v == 0");
|
||||
os.assert(p < 64u32, "strconv.pow2multiple: p >= 64");
|
||||
return (v & ((1u64 << (p: u64)) - 1u64)) == 0u64;
|
||||
};
|
||||
|
||||
// ref/hare/strconv/ftos_ryu.ha:89. The (v+, v-rounded, v-) triple.
|
||||
// Decomposed: tuple param → mul0/mul1 scalars (#163); 3-tuple return →
|
||||
// this struct (#164). The `mm_shift==1` `if/else`-yield → pre-bound
|
||||
// `v_minus` + block.
|
||||
type ryuv = struct { vp: u64, vr: u64, vm: u64 };
|
||||
|
||||
fn mulshiftall64(m: u64, mul0: u64, mul1: u64, j: i32, mm_shift: u32) ryuv = {
|
||||
let mm: u64 = m << 1u64;
|
||||
let r0: r128 = u128mul(mm, mul0);
|
||||
let r1: r128 = u128mul(mm, mul1);
|
||||
let lo: u64 = r0.lo;
|
||||
let tmp: u64 = r0.hi;
|
||||
let mid: u64 = tmp + r1.lo;
|
||||
let hi: u64 = r1.hi + (ibool(mid < tmp): u64);
|
||||
let lo2: u64 = lo + mul0;
|
||||
let mid2: u64 = mid + mul1 + (ibool(lo2 < lo): u64);
|
||||
let hi2: u64 = hi + (ibool(mid2 < mid): u64);
|
||||
let v_plus: u64 = u128rshift(mid2, hi2, ((j - 64 - 1): u32));
|
||||
let v_minus: u64 = 0u64;
|
||||
if (mm_shift == 1u32) {
|
||||
let lo3: u64 = lo - mul0;
|
||||
let mid3: u64 = mid - mul1 - (ibool(lo3 > lo): u64);
|
||||
let hi3: u64 = hi - (ibool(mid3 > mid): u64);
|
||||
v_minus = u128rshift(mid3, hi3, ((j - 64 - 1): u32));
|
||||
} else {
|
||||
let lo3: u64 = lo + lo;
|
||||
let mid3: u64 = mid + mid + (ibool(lo3 < lo): u64);
|
||||
let hi3: u64 = hi + hi + (ibool(mid3 < mid): u64);
|
||||
let lo4: u64 = lo3 - mul0;
|
||||
let mid4: u64 = mid3 - mul1 - (ibool(lo4 > lo3): u64);
|
||||
let hi4: u64 = hi3 - (ibool(mid4 > mid3): u64);
|
||||
v_minus = u128rshift(mid4, hi4, ((j - 64): u32));
|
||||
};
|
||||
let v_rounded: u64 = u128rshift(mid, hi, ((j - 64 - 1): u32));
|
||||
return ryuv { vp = v_plus, vr = v_rounded, vm = v_minus };
|
||||
};
|
||||
|
||||
// ref/hare/strconv/ftos_ryu.ha:140.
|
||||
fn log2pow5(e: u32) u32 = {
|
||||
os.assert(e <= 3528u32, "strconv.log2pow5: e > 3528");
|
||||
return (e * 1217359u32) >> 19u32;
|
||||
};
|
||||
|
||||
// ref/hare/strconv/ftos_ryu.ha:145-147.
|
||||
fn ceil_log2pow5(e: u32) u32 = { return log2pow5(e) + 1u32; };
|
||||
fn pow5bits(e: u32) u32 = { return ceil_log2pow5(e); };
|
||||
|
||||
// ref/hare/strconv/ftos_ryu.ha:149.
|
||||
fn log10pow2(e: u32) u32 = {
|
||||
os.assert(e <= 1650u32, "strconv.log10pow2: e > 1650");
|
||||
return (e * 78913u32) >> 18u32;
|
||||
};
|
||||
|
||||
// ref/hare/strconv/ftos_ryu.ha:154.
|
||||
fn log10pow5(e: u32) u32 = {
|
||||
os.assert(e <= 2620u32, "strconv.log10pow5: e > 2620");
|
||||
return (e * 732923u32) >> 20u32;
|
||||
};
|
||||
|
||||
// ref/hare/strconv/ftos_ryu.ha:224. Returns the (low, high) split of the
|
||||
// inverse power of five. 2-tuple kept (works); row-bind → double-index.
|
||||
fn f64computeinvpow5(i: u32) (u64, u64) = {
|
||||
let base: u32 = (i + (POW5_TABLE_SZ: u32) - 1u32) / (POW5_TABLE_SZ: u32);
|
||||
let base2: u32 = base * (POW5_TABLE_SZ: u32);
|
||||
let off: u32 = base2 - i;
|
||||
if (off == 0u32) {
|
||||
return (F64_POW5_INV_SPLIT2[base][0], F64_POW5_INV_SPLIT2[base][1]);
|
||||
};
|
||||
let m: u64 = POW5_TABLE[off];
|
||||
let r1: r128 = u128mul(m, F64_POW5_INV_SPLIT2[base][1]);
|
||||
let r0: r128 = u128mul(m, F64_POW5_INV_SPLIT2[base][0] - 1u64);
|
||||
let high1: u64 = r1.hi;
|
||||
let low1: u64 = r1.lo;
|
||||
let high0: u64 = r0.hi;
|
||||
let low0: u64 = r0.lo;
|
||||
let sum: u64 = high0 + low1;
|
||||
if (sum < high0) {
|
||||
high1 += 1u64;
|
||||
};
|
||||
let delta: u32 = pow5bits(base2) - pow5bits(i);
|
||||
let res0: u64 = u128rshift(low0, sum, delta) + 1u64 +
|
||||
(((POW5_INV_OFFSETS[i / 16u32] >> ((i % 16u32) << 1u32)) & 3u32): u64);
|
||||
let res1: u64 = u128rshift(sum, high1, delta);
|
||||
return (res0, res1);
|
||||
};
|
||||
|
||||
// ref/hare/strconv/ftos_ryu.ha:246.
|
||||
fn f64computepow5(i: u32) (u64, u64) = {
|
||||
let base: u32 = i / (POW5_TABLE_SZ: u32);
|
||||
let base2: u32 = base * (POW5_TABLE_SZ: u32);
|
||||
let off: u32 = i - base2;
|
||||
if (off == 0u32) {
|
||||
return (F64_POW5_SPLIT2[base][0], F64_POW5_SPLIT2[base][1]);
|
||||
};
|
||||
let m: u64 = POW5_TABLE[off];
|
||||
let r1: r128 = u128mul(m, F64_POW5_SPLIT2[base][1]);
|
||||
let r0: r128 = u128mul(m, F64_POW5_SPLIT2[base][0]);
|
||||
let high1: u64 = r1.hi;
|
||||
let low1: u64 = r1.lo;
|
||||
let high0: u64 = r0.hi;
|
||||
let low0: u64 = r0.lo;
|
||||
let sum: u64 = high0 + low1;
|
||||
if (sum < high0) {
|
||||
high1 += 1u64;
|
||||
};
|
||||
let delta: u32 = pow5bits(i) - pow5bits(base2);
|
||||
let res0: u64 = u128rshift(low0, sum, delta) +
|
||||
(((POW5_OFFSETS[i / 16u32] >> ((i % 16u32) << 1u32)) & 3u32): u64);
|
||||
let res1: u64 = u128rshift(sum, high1, delta);
|
||||
return (res0, res1);
|
||||
};
|
||||
|
||||
// ref/hare/strconv/ftos_ryu.ha:267. Shortest decimal of an f64:
|
||||
// value == mantissa * 10^exponent. `exponent` rides i64 not Hare's i32
|
||||
// (ftos_ryu.ha:269): a 16B struct-return with a NARROW (i32) second
|
||||
// field unpacks MOVL in wwstage vs MOVQ in cstage (store-width cs≠ww
|
||||
// byte-id split, #169); an 8B i64 field unpacks MOVQ in both. The value
|
||||
// always fits i32 (cast at the init_dec_mant_exp call site).
|
||||
type decf64 = struct { mantissa: u64, exponent: i64 };
|
||||
|
||||
// ref/hare/strconv/ftos_ryu.ha:272. `mantissa`/`exponent` are the raw
|
||||
// IEEE-754 fields of an f64.
|
||||
fn f64todecf64(mantissa: u64, exponent: u32) decf64 = {
|
||||
let e2: i32 = (math.F64_EXPONENT_BIAS + math.F64_MANTISSA_BITS + 2u64): i32;
|
||||
let m2: u64 = 0u64;
|
||||
if (exponent == 0u32) {
|
||||
e2 = 1i32 - e2;
|
||||
m2 = mantissa;
|
||||
} else {
|
||||
e2 = (exponent: i32) - e2;
|
||||
m2 = (1u64 << math.F64_MANTISSA_BITS) | mantissa;
|
||||
};
|
||||
let accept_bounds: bool = (m2 & 1u64) == 0u64;
|
||||
let mv: u64 = 4u64 * m2;
|
||||
let mm_shift: u32 = ibool(mantissa != 0u64 || exponent <= 1u32): u32;
|
||||
let vp: u64 = 0u64;
|
||||
let vr: u64 = 0u64;
|
||||
let vm: u64 = 0u64;
|
||||
let e10: i32 = 0i32;
|
||||
let vm_trailing_zeros: bool = false;
|
||||
let vr_trailing_zeros: bool = false;
|
||||
if (e2 >= 0i32) {
|
||||
let q: u32 = log10pow2(e2: u32) - (ibool(e2 > 3i32): u32);
|
||||
e10 = q: i32;
|
||||
let k: u32 = (F64_POW5_INV_BITCOUNT: u32) + pow5bits(q) - 1u32;
|
||||
let i: i32 = -e2 + ((q + k): i32);
|
||||
let pow5 = f64computeinvpow5(q);
|
||||
let res: ryuv = mulshiftall64(m2, pow5.0, pow5.1, i, mm_shift);
|
||||
vp = res.vp; vr = res.vr; vm = res.vm;
|
||||
if (q <= 21u32) {
|
||||
if ((mv - 5u64 * (mv / 5u64)) == 0u64) {
|
||||
vr_trailing_zeros = pow5multiple(mv, q);
|
||||
} else if (accept_bounds) {
|
||||
vm_trailing_zeros = pow5multiple(mv - 1u64 - (mm_shift: u64), q);
|
||||
} else {
|
||||
vp -= (ibool(pow5multiple(mv + 2u64, q)): u64);
|
||||
};
|
||||
};
|
||||
} else {
|
||||
let q: u32 = log10pow5((-e2): u32) - (ibool(-e2 > 1i32): u32);
|
||||
e10 = e2 + (q: i32);
|
||||
let i: i32 = -e2 - (q: i32);
|
||||
let k: i32 = (pow5bits(i: u32): i32) - (F64_POW5_BITCOUNT: i32);
|
||||
let j: i32 = (q: i32) - k;
|
||||
let pow5 = f64computepow5(i: u32);
|
||||
let res: ryuv = mulshiftall64(m2, pow5.0, pow5.1, j, mm_shift);
|
||||
vp = res.vp; vr = res.vr; vm = res.vm;
|
||||
if (q <= 1u32) {
|
||||
vr_trailing_zeros = true;
|
||||
if (accept_bounds) {
|
||||
vm_trailing_zeros = mm_shift == 1u32;
|
||||
} else {
|
||||
vp -= 1u64;
|
||||
};
|
||||
} else if (q < 63u32) {
|
||||
vr_trailing_zeros = pow2multiple(mv, q);
|
||||
};
|
||||
};
|
||||
let removed: i32 = 0i32;
|
||||
let last_removed_digit: u8 = 0u8;
|
||||
let output: u64 = 0u64;
|
||||
if (vm_trailing_zeros || vr_trailing_zeros) {
|
||||
for (true) {
|
||||
let vpby10: u64 = vp / 10u64;
|
||||
let vmby10: u64 = vm / 10u64;
|
||||
if (vpby10 <= vmby10) { break; };
|
||||
let vmmod10: u32 = (vm: u32) - 10u32 * (vmby10: u32);
|
||||
let vrby10: u64 = vr / 10u64;
|
||||
let vrmod10: u32 = (vr: u32) - 10u32 * (vrby10: u32);
|
||||
vm_trailing_zeros = vm_trailing_zeros && (vmmod10 == 0u32);
|
||||
vr_trailing_zeros = vr_trailing_zeros && (last_removed_digit == 0u8);
|
||||
last_removed_digit = (vrmod10: u8);
|
||||
vr = vrby10; vp = vpby10; vm = vmby10;
|
||||
removed += 1i32;
|
||||
};
|
||||
if (vm_trailing_zeros) {
|
||||
for (true) {
|
||||
let vmby10: u64 = vm / 10u64;
|
||||
let vmmod10: u32 = (vm: u32) - 10u32 * (vmby10: u32);
|
||||
if (vmmod10 != 0u32) { break; };
|
||||
let vpby10: u64 = vp / 10u64;
|
||||
let vrby10: u64 = vr / 10u64;
|
||||
let vrmod10: u32 = (vr: u32) - 10u32 * (vrby10: u32);
|
||||
vr_trailing_zeros = vr_trailing_zeros && (last_removed_digit == 0u8);
|
||||
last_removed_digit = (vrmod10: u8);
|
||||
vr = vrby10; vp = vpby10; vm = vmby10;
|
||||
removed += 1i32;
|
||||
};
|
||||
};
|
||||
if (vr_trailing_zeros && last_removed_digit == 5u8 && (vr & 1u64) == 0u64) {
|
||||
last_removed_digit = 4u8; // round to even
|
||||
};
|
||||
let cond1: bool = (vr == vm) && ((!accept_bounds) || (!vm_trailing_zeros));
|
||||
let cond2: bool = last_removed_digit >= 5u8;
|
||||
output = vr + (ibool(cond1 || cond2): u64);
|
||||
} else {
|
||||
let round_up: bool = false;
|
||||
let vpby100: u64 = vp / 100u64;
|
||||
let vmby100: u64 = vm / 100u64;
|
||||
if (vpby100 > vmby100) {
|
||||
let vrby100: u64 = vr / 100u64;
|
||||
let vrmod100: u32 = (vr: u32) - 100u32 * (vrby100: u32);
|
||||
round_up = vrmod100 >= 50u32;
|
||||
vr = vrby100; vp = vpby100; vm = vmby100;
|
||||
removed += 2i32;
|
||||
};
|
||||
for (true) {
|
||||
let vmby10: u64 = vm / 10u64;
|
||||
let vpby10: u64 = vp / 10u64;
|
||||
if (vpby10 <= vmby10) { break; };
|
||||
let vrby10: u64 = vr / 10u64;
|
||||
let vrmod10: u32 = (vr: u32) - 10u32 * (vrby10: u32);
|
||||
round_up = vrmod10 >= 5u32;
|
||||
vr = vrby10; vp = vpby10; vm = vmby10;
|
||||
removed += 1i32;
|
||||
};
|
||||
output = vr + (ibool(vr == vm || round_up): u64);
|
||||
};
|
||||
let exp: i32 = e10 + removed;
|
||||
return decf64 { exponent = (exp: i64), mantissa = output };
|
||||
};
|
||||
|
||||
// ==== G-format encode layer (ftos.ha) — only the ffmt::G / prec=void /
|
||||
// fflags::NONE-REACHABLE logic. The SHOW_POINT/precision/E-vs-uppercase
|
||||
// arms (ftos.ha:88-105, 127-145, 170-213's zeros/caps) are UNREACHABLE
|
||||
// for G/void/NONE (ffpoint(NONE)=false, prec is never uint, f is always
|
||||
// G) and are NOT ported — porting them stubbed would be untested dead
|
||||
// code. The parametric ftosf/ffmt/fflags surface is deferred (task #64;
|
||||
// needs a parametric consumer + io::handle + #158). ====
|
||||
|
||||
// ref/hare/strconv/ftos.ha:49. Decimal digit-count of n (n <= 1e17).
|
||||
fn declen(n: u64) uint = {
|
||||
os.assert(n <= 100000000000000000u64, "strconv.declen: n > 1e17");
|
||||
if (n >= 100000000000000000u64) { return (18u32: uint); };
|
||||
if (n >= 10000000000000000u64) { return (17u32: uint); };
|
||||
if (n >= 1000000000000000u64) { return (16u32: uint); };
|
||||
if (n >= 100000000000000u64) { return (15u32: uint); };
|
||||
if (n >= 10000000000000u64) { return (14u32: uint); };
|
||||
if (n >= 1000000000000u64) { return (13u32: uint); };
|
||||
if (n >= 100000000000u64) { return (12u32: uint); };
|
||||
if (n >= 10000000000u64) { return (11u32: uint); };
|
||||
if (n >= 1000000000u64) { return (10u32: uint); };
|
||||
if (n >= 100000000u64) { return (9u32: uint); };
|
||||
if (n >= 10000000u64) { return (8u32: uint); };
|
||||
if (n >= 1000000u64) { return (7u32: uint); };
|
||||
if (n >= 100000u64) { return (6u32: uint); };
|
||||
if (n >= 10000u64) { return (5u32: uint); };
|
||||
if (n >= 1000u64) { return (4u32: uint); };
|
||||
if (n >= 100u64) { return (3u32: uint); };
|
||||
if (n >= 10u64) { return (2u32: uint); };
|
||||
return (1u32: uint);
|
||||
};
|
||||
|
||||
// ref/hare/strconv/ftos.ha:217. Lay the Ryū shortest (mantissa,exponent)
|
||||
// into the decimal `d`. `mantissa` is mutated in Hare → local `mant`.
|
||||
fn init_dec_mant_exp(d: *decimal, mantissa: u64, exponent: i32) void = {
|
||||
// Hoisted uint casts: ww parser rejects a `(N: uint)` cast embedded
|
||||
// inside an array subscript (decimal.ww "hoist size casts" note).
|
||||
let U_ZERO: uint = (0u32: uint);
|
||||
let U_ONE: uint = (1u32: uint);
|
||||
let mant: u64 = mantissa;
|
||||
let dl: uint = declen(mant);
|
||||
let i: uint = U_ZERO;
|
||||
for (i < dl) {
|
||||
d.digits[dl - i - U_ONE] = (mant % 10u64): u8;
|
||||
mant /= 10u64;
|
||||
i += U_ONE;
|
||||
};
|
||||
d.nd = (dl: size);
|
||||
d.dp = (dl: i32) + exponent;
|
||||
};
|
||||
|
||||
// ref/hare/strconv/ftos.ha:71. writestr → buffer-cursor adaptation (the
|
||||
// *tos static-buffer convention replaces Hare's io::handle sink).
|
||||
fn putstr(buf: []u8, out: i32, s: str) i32 = {
|
||||
let o: i32 = out;
|
||||
let k: i32 = 0i32;
|
||||
for (k < s.len) {
|
||||
buf[o] = s[k];
|
||||
o += 1i32;
|
||||
k += 1i32;
|
||||
};
|
||||
return o;
|
||||
};
|
||||
|
||||
// ref/hare/strconv/ftos.ha:109. Fixed-point render (G/void/NONE-reachable
|
||||
// logic only). Writes into `buf` at cursor `out`, returns the new cursor.
|
||||
fn encode_f_dec(d: *decimal, buf: []u8, out: i32) i32 = {
|
||||
let o: i32 = out;
|
||||
let lo: i32 = 0i32;
|
||||
if (d.dp <= 0i32) { lo = d.dp - 1i32; };
|
||||
let hi: i32 = d.dp;
|
||||
if ((d.nd: i32) > d.dp) { hi = (d.nd: i32); };
|
||||
if (hi > (d.nd: i32) && d.dp <= 0i32) {
|
||||
hi = (d.nd: i32);
|
||||
} else if (hi > d.dp && d.dp > 0i32) {
|
||||
hi = d.dp;
|
||||
if ((d.nd: i32) > d.dp) { hi = (d.nd: i32); };
|
||||
};
|
||||
let i: i32 = lo;
|
||||
for (i < hi) {
|
||||
if (i == d.dp) {
|
||||
buf[o] = 46u8; // '.'
|
||||
o += 1i32;
|
||||
};
|
||||
if (0i32 <= i && i < (d.nd: i32)) {
|
||||
buf[o] = (d.digits[i] + 48u8): u8;
|
||||
} else {
|
||||
buf[o] = 48u8; // '0'
|
||||
};
|
||||
o += 1i32;
|
||||
i += 1i32;
|
||||
};
|
||||
return o;
|
||||
};
|
||||
|
||||
// ref/hare/strconv/ftos.ha:160. Scientific render (G/void/NONE-reachable
|
||||
// logic only): no precision zeros, lowercase 'e', no '+'/two-digit pad.
|
||||
fn encode_e_dec(d: *decimal, buf: []u8, out: i32) i32 = {
|
||||
let o: i32 = out;
|
||||
os.assert(d.nd > (0u64: size), "strconv.encode_e_dec: nd == 0");
|
||||
buf[o] = (d.digits[0] + 48u8): u8;
|
||||
o += 1i32;
|
||||
if ((d.nd: i32) > 1i32) {
|
||||
buf[o] = 46u8; // '.'
|
||||
o += 1i32;
|
||||
};
|
||||
let i: size = (1u64: size);
|
||||
for (i < d.nd) {
|
||||
buf[o] = (d.digits[i] + 48u8): u8;
|
||||
o += 1i32;
|
||||
i += (1u64: size);
|
||||
};
|
||||
buf[o] = 101u8; // 'e'
|
||||
o += 1i32;
|
||||
let e: i32 = d.dp - 1i32;
|
||||
if (e < 0i32) {
|
||||
e = -e;
|
||||
buf[o] = 45u8; // '-'
|
||||
o += 1i32;
|
||||
};
|
||||
// Hoisted uint casts (ww parser rejects `(N: uint)` inside `[ ]`).
|
||||
let U_ONE: uint = (1u32: uint);
|
||||
let U_TWO: uint = (2u32: uint);
|
||||
let U_THREE: uint = (3u32: uint);
|
||||
let ebuf: [3]u8 = [0u8, 0u8, 0u8]; // exponents are at most 3 digits
|
||||
let l: uint = declen(e: u64);
|
||||
let k: uint = (0u32: uint);
|
||||
for (k < l) {
|
||||
ebuf[U_TWO - k] = (e % 10i32): u8;
|
||||
e /= 10i32;
|
||||
k += U_ONE;
|
||||
};
|
||||
let m: uint = U_THREE - l;
|
||||
for (m < U_THREE) {
|
||||
buf[o] = (ebuf[m] + 48u8): u8;
|
||||
o += 1i32;
|
||||
m += U_ONE;
|
||||
};
|
||||
return o;
|
||||
};
|
||||
|
||||
// ref/hare/strconv/ftos.ha:432. f64 → shortest base-10 str. Returns a
|
||||
// view into a static buffer overwritten on the next call (the *tos
|
||||
// convention; see strings.dup to retain). Equivalent to Hare's ftosf
|
||||
// with format G + precision void. The fftosf G/void/NONE path is inlined
|
||||
// (the parametric surface is deferred — task #64).
|
||||
//
|
||||
// Max output is 24 (ftos.ha:434): sign + digit + '.' + 16 digits + 'e' +
|
||||
// exp-sign + 3 exp-digits. Sized 32 not 24: a no-rhs [24]u8 module buffer
|
||||
// emits 4 DATAW in wwstage vs 2 in cstage (#43, the size-16/24 emitletdataw
|
||||
// split); 32 emits 2 in both (byte-id). The extra 8 bytes are unused.
|
||||
let f64tos_buf: [32]u8;
|
||||
|
||||
export fn f64tos(n: f64) str = {
|
||||
let bits: u64 = math.f64bits(n);
|
||||
let mantissa: u64 = bits & math.F64_MANTISSA_MASK;
|
||||
let exponent: u32 = ((bits >> math.F64_MANTISSA_BITS) & math.F64_EXPONENT_MASK): u32;
|
||||
let sign: bool = (bits >> (math.F64_EXPONENT_BITS + math.F64_MANTISSA_BITS)) > 0u64;
|
||||
let special: bool = exponent == (math.F64_EXPONENT_MASK: u32);
|
||||
|
||||
let o: i32 = 0i32;
|
||||
let r: str;
|
||||
r.ptr = &f64tos_buf[0];
|
||||
// NaN carries no sign prefix (ftos.ha:331-333, before sign handling).
|
||||
if (special && mantissa != 0u64) {
|
||||
o = putstr(f64tos_buf[0:32], o, "nan");
|
||||
r.len = o;
|
||||
return r;
|
||||
};
|
||||
if (sign) {
|
||||
f64tos_buf[o] = 45u8; // '-'
|
||||
o += 1i32;
|
||||
};
|
||||
if (special) {
|
||||
o = putstr(f64tos_buf[0:32], o, "infinity");
|
||||
r.len = o;
|
||||
return r;
|
||||
};
|
||||
if (exponent == 0u32 && mantissa == 0u64) {
|
||||
f64tos_buf[o] = 48u8; // '0' (encode_zero, G/void/NONE)
|
||||
o += 1i32;
|
||||
r.len = o;
|
||||
return r;
|
||||
};
|
||||
|
||||
let d = decimal { ... };
|
||||
// Reads of d.nd / d.dp ride a *decimal pointer: wwstage resolves a
|
||||
// scalar-field read of a LOCAL struct (`d.nd`) to a bogus global
|
||||
// symbol (`nd(SB)`), but a pointer-deref field read (`pd.nd`) lowers
|
||||
// correctly in both stages (the stof.ww/decimal.ww *decimal precedent)
|
||||
// — #170. The init/trim/encode calls already took &d; route via pd.
|
||||
let pd: *decimal = &d;
|
||||
let dd: decf64 = f64todecf64(mantissa, exponent);
|
||||
init_dec_mant_exp(pd, dd.mantissa, (dd.exponent: i32));
|
||||
// ok = !ffpoint(NONE) || ... is always true → no multiprecision
|
||||
// fallback (ftos.ha:365). f == G → trim (ftos.ha:386).
|
||||
trim(pd);
|
||||
if (pd.nd == (0u64: size)) {
|
||||
f64tos_buf[o] = 48u8; // rounded to zero
|
||||
o += 1i32;
|
||||
} else if (pd.dp < -1i32 || (pd.dp - (pd.nd: i32)) > 2i32) {
|
||||
o = encode_e_dec(pd, f64tos_buf[0:32], o);
|
||||
} else {
|
||||
o = encode_f_dec(pd, f64tos_buf[0:32], o);
|
||||
};
|
||||
r.len = o;
|
||||
return r;
|
||||
};
|
||||
104
lib/strconv/ftos_data.ww
Normal file
104
lib/strconv/ftos_data.ww
Normal file
@@ -0,0 +1,104 @@
|
||||
// strconv — Ryū float→string lookup tables + bit-count constants.
|
||||
// Mirrors ref/hare/strconv/ftos_ryu.ha:159-222 byte-exact. Pure data
|
||||
// fold (strconv #106 fold-5): no logic, consumed by ftos.ww's
|
||||
// f64computeinvpow5 / f64computepow5 (the Ryū power-of-five cores).
|
||||
//
|
||||
// File-organisation divergence: Hare keeps these tables INLINE in
|
||||
// ftos_ryu.ha. ww splits data from logic into ftos_data.ww (mirroring
|
||||
// the stof.ww / stof_data.ww split) — same `package strconv`, so the
|
||||
// tables stay visible to ftos.ww with no qualification.
|
||||
//
|
||||
// Spelling divergences (same as stof_data.ww, candidate #130 + rule-12):
|
||||
// - Hare `const TBL = [...]` → ww module-level `let` (ww has no
|
||||
// module-`const` keyword; the values are never written).
|
||||
// - every literal carries its element-width suffix (`u64`/`u32`):
|
||||
// cstage rejects bare integer literals in `[N]uXX` init while
|
||||
// wwstage accepts them; the suffixed form is the only shape both
|
||||
// stages agree on.
|
||||
// - the [N][2]u64 tables stay faithful 2D (rule-12, not flattened);
|
||||
// the 2D module-level static-init + double-index read landed in
|
||||
// #156 (cbeffea), proven by stof_data.ww's powers_of_ten[596][2]u64.
|
||||
|
||||
package strconv;
|
||||
|
||||
// ref/hare/strconv/ftos_ryu.ha:159-160. Bit-counts of the split
|
||||
// power-of-five tables. Defined u8 (faithful); ftos.ww casts to u32/i32
|
||||
// at each use site (Hare promotes a u8 def inside mixed-width arithmetic;
|
||||
// ww is strict — explicit cast, project_int_machine_word_derived_limits).
|
||||
// The F32_POW5_*_BITCOUNT siblings (ftos_ryu.ha:162-163) land in fold-5b
|
||||
// (task #67) with their only consumer, f32todecf32 — omitted here to keep
|
||||
// this fold dead-code-free.
|
||||
def F64_POW5_INV_BITCOUNT: u8 = 125u8;
|
||||
def F64_POW5_BITCOUNT: u8 = 125u8;
|
||||
|
||||
// ref/hare/strconv/ftos_ryu.ha:165-181.
|
||||
let F64_POW5_INV_SPLIT2: [15][2]u64 = [
|
||||
[1u64, 2305843009213693952u64],
|
||||
[5955668970331000884u64, 1784059615882449851u64],
|
||||
[8982663654677661702u64, 1380349269358112757u64],
|
||||
[7286864317269821294u64, 2135987035920910082u64],
|
||||
[7005857020398200553u64, 1652639921975621497u64],
|
||||
[17965325103354776697u64, 1278668206209430417u64],
|
||||
[8928596168509315048u64, 1978643211784836272u64],
|
||||
[10075671573058298858u64, 1530901034580419511u64],
|
||||
[597001226353042382u64, 1184477304306571148u64],
|
||||
[1527430471115325346u64, 1832889850782397517u64],
|
||||
[12533209867169019542u64, 1418129833677084982u64],
|
||||
[5577825024675947042u64, 2194449627517475473u64],
|
||||
[11006974540203867551u64, 1697873161311732311u64],
|
||||
[10313493231639821582u64, 1313665730009899186u64],
|
||||
[12701016819766672773u64, 2032799256770390445u64],
|
||||
];
|
||||
|
||||
// ref/hare/strconv/ftos_ryu.ha:183-188.
|
||||
let POW5_INV_OFFSETS: [19]u32 = [
|
||||
0x54544554u32, 0x04055545u32, 0x10041000u32, 0x00400414u32, 0x40010000u32, 0x41155555u32,
|
||||
0x00000454u32, 0x00010044u32, 0x40000000u32, 0x44000041u32, 0x50454450u32, 0x55550054u32,
|
||||
0x51655554u32, 0x40004000u32, 0x01000001u32, 0x00010500u32, 0x51515411u32, 0x05555554u32,
|
||||
0x00000000u32,
|
||||
];
|
||||
|
||||
// ref/hare/strconv/ftos_ryu.ha:190-204.
|
||||
let F64_POW5_SPLIT2: [13][2]u64 = [
|
||||
[0u64, 1152921504606846976u64],
|
||||
[0u64, 1490116119384765625u64],
|
||||
[1032610780636961552u64, 1925929944387235853u64],
|
||||
[7910200175544436838u64, 1244603055572228341u64],
|
||||
[16941905809032713930u64, 1608611746708759036u64],
|
||||
[13024893955298202172u64, 2079081953128979843u64],
|
||||
[6607496772837067824u64, 1343575221513417750u64],
|
||||
[17332926989895652603u64, 1736530273035216783u64],
|
||||
[13037379183483547984u64, 2244412773384604712u64],
|
||||
[1605989338741628675u64, 1450417759929778918u64],
|
||||
[9630225068416591280u64, 1874621017369538693u64],
|
||||
[665883850346957067u64, 1211445438634777304u64],
|
||||
[14931890668723713708u64, 1565756531257009982u64],
|
||||
];
|
||||
|
||||
// ref/hare/strconv/ftos_ryu.ha:206-211.
|
||||
let POW5_OFFSETS: [21]u32 = [
|
||||
0x00000000u32, 0x00000000u32, 0x00000000u32, 0x00000000u32, 0x40000000u32, 0x59695995u32,
|
||||
0x55545555u32, 0x56555515u32, 0x41150504u32, 0x40555410u32, 0x44555145u32, 0x44504540u32,
|
||||
0x45555550u32, 0x40004000u32, 0x96440440u32, 0x55565565u32, 0x54454045u32, 0x40154151u32,
|
||||
0x55559155u32, 0x51405555u32, 0x00000105u32,
|
||||
];
|
||||
|
||||
// ref/hare/strconv/ftos_ryu.ha:213. Divisor/index stride in
|
||||
// f64computeinvpow5 / f64computepow5 (ftos.ww). Kept as a def for those
|
||||
// arithmetic uses; POW5_TABLE's dimension below must be a literal (cstage
|
||||
// rejects a def-named array length — "array length must be an integer
|
||||
// literal"; wwstage accepts it but emits an empty DATAW — divergence
|
||||
// #167, so the literal `26` is the only shape both stages agree on;
|
||||
// matches decimal.ww's `[800]u8` array-dimension-literal precedent).
|
||||
def POW5_TABLE_SZ: u8 = 26u8;
|
||||
|
||||
// ref/hare/strconv/ftos_ryu.ha:215-222. 5^0 .. 5^25 (the 5^26 entry is
|
||||
// commented out in Hare too — it lives implicitly in the SPLIT2 tables).
|
||||
let POW5_TABLE: [26]u64 = [
|
||||
1u64, 5u64, 25u64, 125u64, 625u64, 3125u64, 15625u64, 78125u64,
|
||||
390625u64, 1953125u64, 9765625u64, 48828125u64, 244140625u64,
|
||||
1220703125u64, 6103515625u64, 30517578125u64, 152587890625u64,
|
||||
762939453125u64, 3814697265625u64, 19073486328125u64, 95367431640625u64,
|
||||
476837158203125u64, 2384185791015625u64, 11920928955078125u64,
|
||||
59604644775390625u64, 298023223876953125u64,
|
||||
];
|
||||
@@ -261,91 +261,12 @@ export fn stou8(s: str, b: base) (u8 | invalid | overflow) = {
|
||||
return 0: invalid;
|
||||
};
|
||||
|
||||
// f64tos — convert v to a decimal string. Returns owned str; release
|
||||
// via os.free. Mirrors Hare's strconv::f64tos (current ww impl is
|
||||
// fixed-point only, max 6 fractional digits, no NaN/Inf support —
|
||||
// see graduate-to-Ryū note below).
|
||||
//
|
||||
// Surface:
|
||||
//
|
||||
// - finite values only. NaN/±Inf detection needs an f64→u64 bit
|
||||
// reinterpret cast that the cgen doesn't expose yet.
|
||||
// - fixed-point only, up to 6 fractional digits. Trailing zeros
|
||||
// after the decimal point are trimmed. Trailing '.' is dropped.
|
||||
// - magnitudes ≥ 9e18 (overflows i64 in the integer-part cast)
|
||||
// fall back to the literal token "huge". Hare would print these
|
||||
// in scientific notation via Ryū; we will graduate when the
|
||||
// compiler grows the bit-reinterpret cast.
|
||||
//
|
||||
// Round-trip is therefore lossy past 6 fractional digits.
|
||||
//
|
||||
// No float literals in the body — 990's wwdump diff requires this
|
||||
// file's TK_FLOAT count to match between C and ww front-ends, and
|
||||
// the ww-side wwdump currently skips TK_FLOAT.fval while the C side
|
||||
// %g-formats it. Same trick lib/ww/lex/lex.ww's parsef64 uses:
|
||||
// build f64 constants via int-to-f64 casts.
|
||||
let f64tos_buf: [64]u8;
|
||||
|
||||
export fn f64tos(v: f64) str = {
|
||||
let out: i32 = 0;
|
||||
let f: f64 = v;
|
||||
let zero: f64 = 0: f64;
|
||||
if (f < zero) {
|
||||
f64tos_buf[out] = 45u8; // '-'
|
||||
out += 1;
|
||||
f = -f;
|
||||
};
|
||||
// 9e18 is comfortably under I64_MAX (9.22e18). Past this the
|
||||
// `f: i64` cast wraps and the integer part comes back as garbage.
|
||||
let cap: f64 = 9000000000000000000i64: f64;
|
||||
if (f >= cap) {
|
||||
let s: str = "huge";
|
||||
let k: i32 = 0;
|
||||
for (k < s.len) { f64tos_buf[out] = s[k]; out += 1; k += 1; };
|
||||
let r: str;
|
||||
r.ptr = &f64tos_buf[0];
|
||||
r.len = out;
|
||||
return r;
|
||||
};
|
||||
let ip: i64 = f: i64;
|
||||
// Fractional part scaled to 6 decimal digits, with round-to-
|
||||
// nearest via +0.5. (f64 compound assigns mis-lower in cgen —
|
||||
// use the explicit form, as the rest of lib does.)
|
||||
let frac: f64 = f - (ip: f64);
|
||||
let scale: f64 = 1000000: f64;
|
||||
frac = frac * scale;
|
||||
let half: f64 = (1: f64) / (2: f64);
|
||||
let fp: i64 = (frac + half): i64;
|
||||
// Carry: e.g. 0.9999996 rounds fp up to 1000000 and the integer
|
||||
// part needs to advance.
|
||||
if (fp >= 1000000) {
|
||||
ip += 1;
|
||||
fp = 0;
|
||||
};
|
||||
let intstr: str = i64tos(ip, base.DEC);
|
||||
let k: i32 = 0;
|
||||
for (k < intstr.len) { f64tos_buf[out] = intstr.ptr[k]; out += 1; k += 1; };
|
||||
if (fp != 0) {
|
||||
f64tos_buf[out] = 46u8; // '.'
|
||||
out += 1;
|
||||
let fracstr: str = u64tos(fp: u64, base.DEC);
|
||||
// Pad fractional to 6 digits with leading zeros (e.g. 0.05 →
|
||||
// fp=50000, fracstr="50000", pad one '0' before).
|
||||
let z: i32 = 6 - fracstr.len;
|
||||
for (z > 0) { f64tos_buf[out] = 48u8; out += 1; z -= 1; };
|
||||
k = 0;
|
||||
for (k < fracstr.len) { f64tos_buf[out] = fracstr.ptr[k]; out += 1; k += 1; };
|
||||
// Trim trailing zeros in the fractional part.
|
||||
for (out > 0) {
|
||||
if (f64tos_buf[out - 1] != 48u8) { break; };
|
||||
out -= 1;
|
||||
};
|
||||
};
|
||||
let r: str;
|
||||
r.ptr = &f64tos_buf[0];
|
||||
r.len = out;
|
||||
return r;
|
||||
};
|
||||
// f64tos — graduated to the Ryū shortest-round-trippable implementation
|
||||
// in ftos.ww (strconv #106 fold-5). The old lossy fixed-point version
|
||||
// (6 fractional digits, "huge" fallback ≥9e18, no NaN/Inf) was deleted
|
||||
// here per the lib-note graduation rule ("replace in one go, don't keep
|
||||
// both"); ftos.ww's f64tos is the live one. f32tos follows in fold-5b
|
||||
// (task #67, gated on the #143 f32-arg-push cgen fix).
|
||||
|
||||
// strerror — convert an strconv error to a user-readable string.
|
||||
// Returns owned str; release via os.free. Mirrors Hare's
|
||||
|
||||
93
lib/strconv/test/ftostest.ww
Normal file
93
lib/strconv/test/ftostest.ww
Normal file
@@ -0,0 +1,93 @@
|
||||
// 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 is NOT exercised here: it is deferred to fold-5b (task #67),
|
||||
// gated on the #143 f32-arg-push cs≠ww cgen fix.
|
||||
//
|
||||
// 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;
|
||||
|
||||
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);
|
||||
};
|
||||
|
||||
// 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(); };
|
||||
};
|
||||
|
||||
export fn main() i32 = {
|
||||
signalled = 1; f64tos_fixed();
|
||||
signalled = 2; f64tos_scientific();
|
||||
signalled = 3; f64tos_special();
|
||||
os.exit(0);
|
||||
return 0;
|
||||
};
|
||||
Reference in New Issue
Block a user