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:
2026-05-27 15:39:03 +09:00
parent 81796cd533
commit 0e66073e32
11 changed files with 3219 additions and 762 deletions

View File

@@ -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