The two local-binds were #168 dodges: a CSE before `% 10u32` to avoid the signed-IDIVQ-on-call-result shape that #168 has now fixed. Inline to the natural form, faithful to ftos_ryu.ha:418-421,444-445 — this exercises #168 in real ported code. The dividends are zero-extended u32 (always positive as 64-bit), so IDIVQ and DIVQ agree on the value; the fix is a cs==ww byte-id shape correction, not a value change. strconv is compiler-imported, so this regenerates the w6c/wwdump/smoke amalgamations.
794 lines
28 KiB
Plaintext
794 lines
28 KiB
Plaintext
// 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 + f32tos shortest-representation subset (Hare's
|
|
// ffmt::G, prec=void, fflags::NONE). f32tos (ftos.ha:448) + its f32 Ryū
|
|
// sub-path (f32todecf32 + mulpow5inv/pow5_divpow2 + mulshift32 + the *32
|
|
// helpers, reusing the shared u64-core + the f64 SPLIT2 tables — the f32
|
|
// path has no separate tables, matching ftos_ryu.ha) ship here in fold-5b
|
|
// (task #67): the gating #143 f32-arg-push cgen fix landed (aff7725, MOVSS
|
|
// both stages), so f32tos's math.f32bits(n) call — passing an f32 arg — is
|
|
// now byte-id-clean. One deferral remains:
|
|
// - 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 };
|
|
};
|
|
|
|
// ==== f32 Ryū sub-path (ftos_ryu.ha). The *32 helpers below mirror their
|
|
// u64 siblings at 32-bit width; they reuse the SHARED f64computeinvpow5/
|
|
// f64computepow5 (and thus the f64 SPLIT2 tables) per ftos_ryu.ha — there
|
|
// is no separate f32 table. Same scalar-PARAM-mutation → copy-to-local,
|
|
// comma-split, assert → os.assert, expr-yield → block divergences as the
|
|
// f64 path above. ====
|
|
|
|
// ref/hare/strconv/ftos_ryu.ha:52. Largest p with 5^p | value (32-bit).
|
|
fn pow5fac32(v: u32) u32 = {
|
|
let value: u32 = v;
|
|
let count: u32 = 0u32;
|
|
for (true) {
|
|
os.assert(value != 0u32, "strconv.pow5fac32: value == 0");
|
|
let q: u32 = value / 5u32;
|
|
let r: u32 = value % 5u32;
|
|
if (r != 0u32) { break; };
|
|
value = q;
|
|
count += 1u32;
|
|
};
|
|
return count;
|
|
};
|
|
|
|
// ref/hare/strconv/ftos_ryu.ha:67.
|
|
fn pow5multiple32(v: u32, p: u32) bool = { return pow5fac32(v) >= p; };
|
|
|
|
// ref/hare/strconv/ftos_ryu.ha:75.
|
|
fn pow2multiple32(v: u32, p: u32) bool = {
|
|
os.assert(v > 0u32, "strconv.pow2multiple32: v == 0");
|
|
os.assert(p < 32u32, "strconv.pow2multiple32: p >= 32");
|
|
return (v & ((1u32 << p) - 1u32)) == 0u32;
|
|
};
|
|
|
|
// ref/hare/strconv/ftos_ryu.ha:121. `m * a_lo` etc. carry an explicit
|
|
// (m: u64) cast (Hare promotes the u32 operand; ww is strict). The bound
|
|
// assert inlines U32_MAX's value: ww's types.U32_MAX is package-private
|
|
// (lib/types/types.ww — no `export`), so Hare's `types::U32_MAX` can't be
|
|
// referenced cross-package.
|
|
fn mulshift32(m: u32, a: u64, s: u32) u32 = {
|
|
os.assert(s > 32u32, "strconv.mulshift32: s <= 32");
|
|
let a_lo: u64 = (a: u32): u64;
|
|
let a_hi: u64 = a >> 32u64;
|
|
let b0: u64 = (m: u64) * a_lo;
|
|
let b1: u64 = (m: u64) * a_hi;
|
|
let sum: u64 = (b0 >> 32u64) + b1;
|
|
let ss: u64 = sum >> ((s: u64) - 32u64);
|
|
os.assert(ss <= 4294967295u64, "strconv.mulshift32: ss > U32_MAX");
|
|
return ss: u32;
|
|
};
|
|
|
|
// ref/hare/strconv/ftos_ryu.ha:130.
|
|
fn mulpow5inv_divpow2(m: u32, q: u32, j: i32) u32 = {
|
|
let pow5 = f64computeinvpow5(q);
|
|
return mulshift32(m, pow5.1 + 1u64, (j: u32));
|
|
};
|
|
|
|
// ref/hare/strconv/ftos_ryu.ha:135.
|
|
fn mulpow5_divpow2(m: u32, i: u32, j: i32) u32 = {
|
|
let pow5 = f64computepow5(i);
|
|
return mulshift32(m, pow5.1, (j: u32));
|
|
};
|
|
|
|
// ref/hare/strconv/ftos_ryu.ha:387. `exponent` rides i64 not Hare's i32,
|
|
// for the same reason decf64 does: widening the field to a full second
|
|
// eightbyte SIDESTEPS the #169 narrow-i32-field struct-return unpack (a
|
|
// narrow i32 there unpacks MOVL wwstage vs MOVQ cstage). The value always
|
|
// fits i32 (cast at the init_dec_mant_exp call site). `mantissa` stays u32
|
|
// (Hare's width); the {u32, pad, i64} layout's first eightbyte holds
|
|
// mantissa@0 + 4B pad and reads cleanly — byte-id CONFIRMED by the 990-997
|
|
// gate (0-diff cs vs ww), not relied on as an ABI guarantee.
|
|
type decf32 = struct { mantissa: u32, exponent: i64 };
|
|
|
|
// ref/hare/strconv/ftos_ryu.ha:392. Shortest decimal of an f32:
|
|
// value == mantissa * 10^exponent. `mantissa`/`exponent` are the raw
|
|
// IEEE-754 fields of an f32.
|
|
fn f32todecf32(mantissa: u32, exponent: u32) decf32 = {
|
|
let e2: i32 = (math.F32_EXPONENT_BIAS + math.F32_MANTISSA_BITS + 2u32): i32;
|
|
let m2: u32 = 0u32;
|
|
if (exponent == 0u32) {
|
|
e2 = 1i32 - e2;
|
|
m2 = mantissa;
|
|
} else {
|
|
e2 = (exponent: i32) - e2;
|
|
m2 = (1u32 << math.F32_MANTISSA_BITS) | mantissa;
|
|
};
|
|
let accept_bounds: bool = (m2 & 1u32) == 0u32;
|
|
let mv: u32 = 4u32 * m2;
|
|
let mp: u32 = mv + 2u32;
|
|
let mm_shift: u32 = ibool(mantissa != 0u32 || exponent <= 1u32): u32;
|
|
let mm: u32 = mv - 1u32 - mm_shift;
|
|
let vr: u32 = 0u32;
|
|
let vp: u32 = 0u32;
|
|
let vm: u32 = 0u32;
|
|
let e10: i32 = 0i32;
|
|
let vm_trailing_zeroes: bool = false;
|
|
let vr_trailing_zeroes: bool = false;
|
|
let last_removed_digit: u8 = 0u8;
|
|
if (e2 >= 0i32) {
|
|
let q: u32 = log10pow2(e2: u32);
|
|
e10 = q: i32;
|
|
let k: u32 = (F32_POW5_INV_BITCOUNT: u32) + pow5bits(q) - 1u32;
|
|
let i: i32 = -e2 + ((q + k): i32);
|
|
vr = mulpow5inv_divpow2(mv, q, i);
|
|
vp = mulpow5inv_divpow2(mp, q, i);
|
|
vm = mulpow5inv_divpow2(mm, q, i);
|
|
if (q != 0u32 && (vp - 1u32) / 10u32 <= vm / 10u32) {
|
|
let l: u32 = (F32_POW5_INV_BITCOUNT: u32) + pow5bits(q - 1u32) - 1u32;
|
|
last_removed_digit = (mulpow5inv_divpow2(mv, q - 1u32,
|
|
-e2 + ((q + l): i32) - 1i32) % 10u32): u8;
|
|
};
|
|
if (q <= 9u32) {
|
|
if (mv % 5u32 == 0u32) {
|
|
vr_trailing_zeroes = pow5multiple32(mv, q);
|
|
} else if (accept_bounds) {
|
|
vm_trailing_zeroes = pow5multiple32(mm, q);
|
|
} else {
|
|
vp -= (ibool(pow5multiple32(mp, q)): u32);
|
|
};
|
|
};
|
|
} else {
|
|
let q: u32 = log10pow5((-e2): u32);
|
|
e10 = (q: i32) + e2;
|
|
let i: u32 = (-e2 - (q: i32)): u32;
|
|
let k: u32 = pow5bits(i) - (F32_POW5_BITCOUNT: u32);
|
|
let j: i32 = (q: i32) - (k: i32);
|
|
vr = mulpow5_divpow2(mv, i, j);
|
|
vp = mulpow5_divpow2(mp, i, j);
|
|
vm = mulpow5_divpow2(mm, i, j);
|
|
if (q != 0u32 && (vp - 1u32) / 10u32 <= vm / 10u32) {
|
|
j = (q: i32) - 1i32 - ((pow5bits(i + 1u32): i32) - (F32_POW5_BITCOUNT: i32));
|
|
last_removed_digit = (mulpow5_divpow2(mv, (i + 1u32), j) % 10u32): u8;
|
|
};
|
|
if (q <= 1u32) {
|
|
vr_trailing_zeroes = true;
|
|
if (accept_bounds) {
|
|
vm_trailing_zeroes = mm_shift == 1u32;
|
|
} else {
|
|
vp -= 1u32;
|
|
};
|
|
} else if (q < 31u32) {
|
|
vr_trailing_zeroes = pow2multiple32(mv, q - 1u32);
|
|
};
|
|
};
|
|
let removed: i32 = 0i32;
|
|
let output: u32 = 0u32;
|
|
if (vm_trailing_zeroes || vr_trailing_zeroes) {
|
|
for ((vp / 10u32) > (vm / 10u32)) {
|
|
vm_trailing_zeroes = vm_trailing_zeroes && ((vm - (vm / 10u32) * 10u32) == 0u32);
|
|
vr_trailing_zeroes = vr_trailing_zeroes && (last_removed_digit == 0u8);
|
|
last_removed_digit = (vr % 10u32): u8;
|
|
vr /= 10u32;
|
|
vp /= 10u32;
|
|
vm /= 10u32;
|
|
removed += 1i32;
|
|
};
|
|
if (vm_trailing_zeroes) {
|
|
for ((vm % 10u32) == 0u32) {
|
|
vr_trailing_zeroes = vr_trailing_zeroes && (last_removed_digit == 0u8);
|
|
last_removed_digit = (vr % 10u32): u8;
|
|
vr /= 10u32;
|
|
vp /= 10u32;
|
|
vm /= 10u32;
|
|
removed += 1i32;
|
|
};
|
|
};
|
|
if (vr_trailing_zeroes && last_removed_digit == 5u8 && vr % 2u32 == 0u32) {
|
|
last_removed_digit = 4u8; // round to even
|
|
};
|
|
let cond1: bool = (vr == vm) && ((!accept_bounds) || (!vm_trailing_zeroes));
|
|
let cond2: bool = last_removed_digit >= 5u8;
|
|
output = vr + (ibool(cond1 || cond2): u32);
|
|
} else {
|
|
for ((vp / 10u32) > (vm / 10u32)) {
|
|
last_removed_digit = (vr % 10u32): u8;
|
|
vr /= 10u32;
|
|
vp /= 10u32;
|
|
vm /= 10u32;
|
|
removed += 1i32;
|
|
};
|
|
output = vr + (ibool(vr == vm || last_removed_digit >= 5u8): u32);
|
|
};
|
|
let exp: i32 = e10 + removed;
|
|
return decf32 { mantissa = output, exponent = (exp: i64) };
|
|
};
|
|
|
|
// ==== 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;
|
|
};
|
|
|
|
// ref/hare/strconv/ftos.ha:448. f32 → shortest base-10 str. Same static-
|
|
// buffer convention + G/void/NONE-inlined path as f64tos. f32bits(n)
|
|
// passes an f32 arg → MOVSS both stages post-#143 (aff7725); this is the
|
|
// piece fold-5b was gated on.
|
|
//
|
|
// Hare sizes this [14]u8 (ftos.ha:451: 1 + 1 + 1 + 7 + 1 + 1 + 2). Sized
|
|
// 32 to reuse f64tos's proven byte-id-clean band: a no-rhs [N]u8 module
|
|
// buffer at the size-16/24 band emits divergent DATAW counts cs≠ww (#43);
|
|
// 32 emits 2 DATAW in both. The unused tail bytes are harmless.
|
|
let f32tos_buf: [32]u8;
|
|
|
|
export fn f32tos(n: f32) str = {
|
|
let bits: u32 = math.f32bits(n);
|
|
let mantissa: u32 = bits & math.F32_MANTISSA_MASK;
|
|
let exponent: u32 = (bits >> math.F32_MANTISSA_BITS) & math.F32_EXPONENT_MASK;
|
|
let sign: bool = (bits >> (math.F32_EXPONENT_BITS + math.F32_MANTISSA_BITS)) > 0u32;
|
|
let special: bool = exponent == math.F32_EXPONENT_MASK;
|
|
|
|
let o: i32 = 0i32;
|
|
let r: str;
|
|
r.ptr = &f32tos_buf[0];
|
|
// NaN carries no sign prefix (ftos.ha:331-333, before sign handling).
|
|
if (special && mantissa != 0u32) {
|
|
o = putstr(f32tos_buf[0:32], o, "nan");
|
|
r.len = o;
|
|
return r;
|
|
};
|
|
if (sign) {
|
|
f32tos_buf[o] = 45u8; // '-'
|
|
o += 1i32;
|
|
};
|
|
if (special) {
|
|
o = putstr(f32tos_buf[0:32], o, "infinity");
|
|
r.len = o;
|
|
return r;
|
|
};
|
|
if (exponent == 0u32 && mantissa == 0u32) {
|
|
f32tos_buf[o] = 48u8; // '0' (encode_zero, G/void/NONE)
|
|
o += 1i32;
|
|
r.len = o;
|
|
return r;
|
|
};
|
|
|
|
let d = decimal { ... };
|
|
// *decimal pointer for the field reads (the #170 dodge; see f64tos).
|
|
let pd: *decimal = &d;
|
|
let dd: decf32 = f32todecf32(mantissa, exponent);
|
|
init_dec_mant_exp(pd, (dd.mantissa: u64), (dd.exponent: i32));
|
|
trim(pd);
|
|
if (pd.nd == (0u64: size)) {
|
|
f32tos_buf[o] = 48u8; // rounded to zero
|
|
o += 1i32;
|
|
} else if (pd.dp < -1i32 || (pd.dp - (pd.nd: i32)) > 2i32) {
|
|
o = encode_e_dec(pd, f32tos_buf[0:32], o);
|
|
} else {
|
|
o = encode_f_dec(pd, f32tos_buf[0:32], o);
|
|
};
|
|
r.len = o;
|
|
return r;
|
|
};
|