lib/strconv: stof.ha port — Eisel-Lemire string→float (#106 fold-4)
stof64/stof32 (f64|f32 | invalid | overflow) via Eisel-Lemire fast-path (powers_of_ten[596][2]u64 + eisel_lemire 128-bit multiply) + decimal slow-path fallback (decimal.ww, fold-3). 16 fns + faithful powers_of_ten (byte-identical to Hare). u128 via pure-u64 64×64→128 (ftos_ryu.ha). Consumes &math.f64info (γ-cleanup), tagged-float-return (PREREQ-2 #157), 2D double-index (PREREQ-1 #156). 13 documented spelling-divergences (rule-9, each cites stof.ha): #155 (po10 double-index + per-field struct-copy), #161 (compound-assign explicit form), #144 (-0.0 via 1u64<<63), #158, #138, test-only #143/parsef64. Test 909 (DEC+hex+NaN/Inf/invalid/overflow, bit-exact, cstage ww run). Make test 185/185 incl 990-997 byte-id + combined_ww_fresh. Makefile: stof.ww added to w6c_ww/wwdump_ww deps (freshness, fold-3 precedent). Drew's strconv 5-fold plan 4/5. Followup #162 (wwstage lexer parsef64 1-ULP — could adopt stof64).
This commit is contained in:
700
lib/strconv/stof.ww
Normal file
700
lib/strconv/stof.ww
Normal file
@@ -0,0 +1,700 @@
|
||||
// strconv — string-to-float. Mirrors ref/hare/strconv/stof.ha
|
||||
// (Hare in turn adapts Go): Eisel-Lemire fast path [1] with the
|
||||
// Simple-Decimal-Conversion slow path [2] (decimal.ww) as fallback.
|
||||
// [1]: https://nigeltao.github.io/blog/2020/eisel-lemire.html
|
||||
// [2]: https://nigeltao.github.io/blog/2020/parse-number-f64-simple.html
|
||||
//
|
||||
// The Eisel-Lemire fast path (`eisel_lemire` + the `powers_of_ten`
|
||||
// table in stof_data.ww + the three call sites: floatbits's d.nd<=19
|
||||
// block, stof64/stof32's !truncated block) is a pure speed
|
||||
// optimisation — it returns the same correctly-rounded value the
|
||||
// decimal slow path (decimal_parse → floatbits) computes, or void to
|
||||
// defer. Its prereqs landed: the 2D `[596][2]u64` static-init +
|
||||
// double-index read (#156) and the tagged float-variant return-pack
|
||||
// (#157, which the public `(f64|invalid|overflow)` return needs).
|
||||
//
|
||||
// Spelling divergences from Hare (mechanical, ww parser/cgen shape):
|
||||
// - str scan index rides `i32` (ww `str.len: i32` + `invalid = !i32`
|
||||
// payload), not Hare's `size`/`len(s)`. lib CLAUDE.md str-index note.
|
||||
// - char literals kept faithful (`buf[i] == '.'`, `c - '0'`); probed
|
||||
// byte-id + value-correct both stages.
|
||||
// - Hare `?` error-propagation → nested statement-`match` with all-
|
||||
// return arms + a `case void => void` continuation. ww's `?`
|
||||
// lowering and a bound `match`-expression with mixed yield/return
|
||||
// arms both diverge cs≠ww (the latter wwstage-checker-rejected);
|
||||
// strconv.ww's stoi32 set the explicit-match precedent.
|
||||
// - Hare `for (cond; afterthought)` 2-clause + `continue` → ww
|
||||
// 2-clause `for (cond)` with the afterthought inlined at body end
|
||||
// AND before each `continue` (ww has no empty-init 3-clause
|
||||
// `for (; c; p)`; #138 post-skip is dodged since 2-clause has no
|
||||
// post). decimal.ww set the inline-afterthought precedent.
|
||||
// - Hare `if`/`switch`-expression yield → explicit if-statements +
|
||||
// pre-bound scalar locals (ww has no expression-bodied if).
|
||||
// - Hare fn-pointer-in-tuple + `switch yield` selecting the digit
|
||||
// predicate in fast_parse → a `base==HEX` bool + an `isdigitbase`
|
||||
// helper that branches to ascii.isdigit/isxdigit (no fn-ptr, no
|
||||
// tuple, no switch).
|
||||
// - struct-param field MUTATION (hex_to_bits mutates its by-value
|
||||
// `p`) → copy p's fields to scalar locals at entry; ww miscompiles
|
||||
// + diverges on writing a by-value struct param's fields (filed).
|
||||
// - default arg dropped: Hare `b: base = base::DEC` → callers pass
|
||||
// base explicitly (no lib fn ships a default arg; strconv.ww
|
||||
// stoi64 precedent). The base param is normalised through a local
|
||||
// `bb` (param reassignment avoided).
|
||||
// - `math::NAN`/`math::INF` (f32) absent in ww math → materialised
|
||||
// via f32frombits of the IEEE-754 f32 bit patterns (same honest
|
||||
// construction as math/floats.ww's NAN_BITS/INF_BITS).
|
||||
// - narrowing int→i32 assignments carry explicit casts (ww `int` is
|
||||
// an 8B machine word; project_int_machine_word_derived_limits).
|
||||
// - `r128`/`u128mul` live here (fold-4 is first consumer); fold-5
|
||||
// ftos (Ryū) shares them in-package.
|
||||
|
||||
package strconv;
|
||||
|
||||
import ascii;
|
||||
import math;
|
||||
import os;
|
||||
import strings;
|
||||
|
||||
// ref/hare/strconv/ftos_ryu.ha:12. 64×64→128 result halves.
|
||||
type r128 = struct {
|
||||
hi: u64,
|
||||
lo: u64,
|
||||
};
|
||||
|
||||
// ref/hare/strconv/ftos_ryu.ha:18. 64×64→128 via 32-bit decomposition
|
||||
// (Hare's own "TODO: use 128-bit integers when implemented" — ww has
|
||||
// no u128; the decomposition is the portable shape both stages agree
|
||||
// on). Comma let-bindings split per decimal.ww divergence.
|
||||
fn u128mul(a: u64, b: u64) r128 = {
|
||||
let a0: u64 = (a: u32): u64;
|
||||
let a1: u64 = a >> 32u64;
|
||||
let b0: u64 = (b: u32): u64;
|
||||
let b1: u64 = b >> 32u64;
|
||||
let p00: u64 = a0 * b0;
|
||||
let p01: u64 = a0 * b1;
|
||||
let p10: u64 = a1 * b0;
|
||||
let p11: u64 = a1 * b1;
|
||||
let p00_lo: u64 = (p00: u32): u64;
|
||||
let p00_hi: u64 = p00 >> 32u64;
|
||||
let mid1: u64 = p10 + p00_hi;
|
||||
let mid1_lo: u64 = (mid1: u32): u64;
|
||||
let mid1_hi: u64 = mid1 >> 32u64;
|
||||
let mid2: u64 = p01 + mid1_lo;
|
||||
let mid2_lo: u64 = (mid2: u32): u64;
|
||||
let mid2_hi: u64 = mid2 >> 32u64;
|
||||
let r_hi: u64 = p11 + mid1_hi + mid2_hi;
|
||||
let r_lo: u64 = (mid2_lo << 32u64) | p00_lo;
|
||||
return r128 { hi = r_hi, lo = r_lo };
|
||||
};
|
||||
|
||||
// ref/hare/strconv/stof.ha:14.
|
||||
fn todig(c: u8) u8 = {
|
||||
if ('0' <= c && c <= '9') { return c - '0'; };
|
||||
if ('a' <= c && c <= 'f') { return c - 'a' + 10u8; };
|
||||
if ('A' <= c && c <= 'F') { return c - 'A' + 10u8; };
|
||||
abort("strconv.todig: unreachable");
|
||||
return 0u8; // unreachable; rt_abort is void-typed (path-cov)
|
||||
};
|
||||
|
||||
@symbol("rt_abort") fn abort(msg: str) void;
|
||||
|
||||
// ref/hare/strconv/stof.ha:25.
|
||||
type fast_parsed_float = struct {
|
||||
mantissa: u64,
|
||||
exponent: i32,
|
||||
negative: bool,
|
||||
truncated: bool,
|
||||
};
|
||||
|
||||
// Digit-class predicate selector for fast_parse — replaces Hare's
|
||||
// fn-pointer-in-tuple (`&ascii::isdigit` / `&ascii::isxdigit`).
|
||||
fn isdigitbase(c: rune, ishex: bool) bool = {
|
||||
if (ishex) { return ascii.isxdigit(c); };
|
||||
return ascii.isdigit(c);
|
||||
};
|
||||
|
||||
// ref/hare/strconv/stof.ha:32.
|
||||
fn fast_parse(s: str, b: base) (fast_parsed_float | invalid) = {
|
||||
let buf: []u8 = strings.toutf8(s);
|
||||
let i: i32 = 0;
|
||||
let neg: bool = false;
|
||||
let trunc: bool = false;
|
||||
if (buf[i] == '-') {
|
||||
neg = true;
|
||||
i += 1;
|
||||
} else if (buf[i] == '+') {
|
||||
i += 1;
|
||||
};
|
||||
|
||||
let ishex: bool = (b == base.HEX);
|
||||
let expchr: rune = 'e';
|
||||
let max_ndmant: int = 19;
|
||||
if (ishex) {
|
||||
expchr = 'p';
|
||||
max_ndmant = 16;
|
||||
};
|
||||
let bnum: u64 = (b: i32): u64;
|
||||
|
||||
let sawdot: bool = false;
|
||||
let sawdigits: bool = false;
|
||||
let nd: int = 0;
|
||||
let ndmant: int = 0;
|
||||
let dp: int = 0;
|
||||
let mant: u64 = 0u64;
|
||||
let exp: i32 = 0i32;
|
||||
for (i < s.len) {
|
||||
if (buf[i] == '.') {
|
||||
if (sawdot) { return i: invalid; };
|
||||
sawdot = true;
|
||||
dp = nd;
|
||||
} else if (isdigitbase(buf[i]: rune, ishex)) {
|
||||
sawdigits = true;
|
||||
if (buf[i] == '0' && nd == 0) {
|
||||
dp -= 1;
|
||||
i += 1;
|
||||
continue;
|
||||
};
|
||||
nd += 1;
|
||||
if (ndmant < max_ndmant) {
|
||||
mant = mant * bnum + (todig(buf[i]): u64);
|
||||
ndmant += 1;
|
||||
} else if (buf[i] != '0') {
|
||||
trunc = true;
|
||||
};
|
||||
} else {
|
||||
break;
|
||||
};
|
||||
i += 1;
|
||||
};
|
||||
if (!sawdigits) { return i: invalid; };
|
||||
if (!sawdot) {
|
||||
dp = nd;
|
||||
};
|
||||
if (b == base.HEX) {
|
||||
dp *= 4;
|
||||
ndmant *= 4;
|
||||
};
|
||||
if (i < s.len && ascii.tolower(buf[i]: rune) == expchr) {
|
||||
i += 1;
|
||||
if (i >= s.len) { return i: invalid; };
|
||||
let expsign: int = 1;
|
||||
if (buf[i] == '+') {
|
||||
i += 1;
|
||||
} else if (buf[i] == '-') {
|
||||
expsign = -1;
|
||||
i += 1;
|
||||
};
|
||||
if (i >= s.len || !ascii.isdigit(buf[i]: rune)) {
|
||||
return i: invalid;
|
||||
};
|
||||
let e: int = 0;
|
||||
for (i < s.len && ascii.isdigit(buf[i]: rune)) {
|
||||
if (e < 10000) {
|
||||
e = e * 10 + ((buf[i] - '0'): int);
|
||||
};
|
||||
i += 1;
|
||||
};
|
||||
dp += e * expsign;
|
||||
} else if (b == base.HEX) {
|
||||
return i: invalid; // hex floats must have an exponent
|
||||
};
|
||||
if (i != s.len) { return i: invalid; };
|
||||
if (mant != 0u64) {
|
||||
exp = (dp - ndmant): i32;
|
||||
};
|
||||
return fast_parsed_float {
|
||||
mantissa = mant,
|
||||
exponent = exp,
|
||||
negative = neg,
|
||||
truncated = trunc,
|
||||
};
|
||||
};
|
||||
|
||||
// ref/hare/strconv/stof.ha:115. Fills the slow-path decimal `d`.
|
||||
fn decimal_parse(d: *decimal, s: str) (void | invalid) = {
|
||||
let i: i32 = 0;
|
||||
let buf: []u8 = strings.toutf8(s);
|
||||
d.negative = false;
|
||||
d.truncated = false;
|
||||
if (buf[0] == '+') {
|
||||
i += 1;
|
||||
} else if (buf[0] == '-') {
|
||||
d.negative = true;
|
||||
i += 1;
|
||||
};
|
||||
let sawdot: bool = false;
|
||||
let sawdigits: bool = false;
|
||||
for (i < s.len) {
|
||||
if (buf[i] == '.') {
|
||||
if (sawdot) { return i: invalid; };
|
||||
sawdot = true;
|
||||
d.dp = (d.nd: i32);
|
||||
} else if (ascii.isdigit(buf[i]: rune)) {
|
||||
sawdigits = true;
|
||||
if (buf[i] == '0' && d.nd == (0u64: size)) {
|
||||
d.dp -= 1;
|
||||
i += 1;
|
||||
continue;
|
||||
};
|
||||
if (d.nd < (len(d.digits): size)) {
|
||||
d.digits[d.nd] = buf[i] - '0';
|
||||
d.nd += (1u64: size);
|
||||
} else if (buf[i] != '0') {
|
||||
d.truncated = true;
|
||||
};
|
||||
} else {
|
||||
break;
|
||||
};
|
||||
i += 1;
|
||||
};
|
||||
if (!sawdigits) { return i: invalid; };
|
||||
if (!sawdot) {
|
||||
d.dp = (d.nd: i32);
|
||||
};
|
||||
if (i < s.len && (buf[i] == 'e' || buf[i] == 'E')) {
|
||||
i += 1;
|
||||
if (i >= s.len) { return i: invalid; };
|
||||
let expsign: int = 1;
|
||||
if (buf[i] == '+') {
|
||||
i += 1;
|
||||
} else if (buf[i] == '-') {
|
||||
expsign = -1;
|
||||
i += 1;
|
||||
};
|
||||
if (i >= s.len || !ascii.isdigit(buf[i]: rune)) {
|
||||
return i: invalid;
|
||||
};
|
||||
let e: int = 0;
|
||||
for (i < s.len && ascii.isdigit(buf[i]: rune)) {
|
||||
if (e < 10000) {
|
||||
e = e * 10 + ((buf[i] - '0'): int);
|
||||
};
|
||||
i += 1;
|
||||
};
|
||||
d.dp += (e * expsign): i32;
|
||||
};
|
||||
if (i != s.len) { return i: invalid; };
|
||||
return;
|
||||
};
|
||||
|
||||
// ref/hare/strconv/stof.ha:173. Count of leading zero bits in n>0.
|
||||
fn leading_zeroes(n: u64) uint = {
|
||||
os.assert(n > 0u64, "strconv.leading_zeroes: n == 0");
|
||||
let b: u64 = 0u64;
|
||||
if ((n & 0xFFFFFFFF00000000u64) > 0u64) {
|
||||
n >>= 32u64;
|
||||
b |= 32u64;
|
||||
};
|
||||
if ((n & 0xFFFF0000u64) > 0u64) {
|
||||
n >>= 16u64;
|
||||
b |= 16u64;
|
||||
};
|
||||
if ((n & 0xFF00u64) > 0u64) {
|
||||
n >>= 8u64;
|
||||
b |= 8u64;
|
||||
};
|
||||
if ((n & 0xF0u64) > 0u64) {
|
||||
n >>= 4u64;
|
||||
b |= 4u64;
|
||||
};
|
||||
if ((n & 0xCu64) > 0u64) {
|
||||
n >>= 2u64;
|
||||
b |= 2u64;
|
||||
};
|
||||
if ((n & 0x2u64) > 0u64) {
|
||||
n >>= 1u64;
|
||||
b |= 1u64;
|
||||
};
|
||||
return ((63u64 - b): uint);
|
||||
};
|
||||
|
||||
// ref/hare/strconv/stof.ha:203. Eisel-Lemire fast path: a correctly-
|
||||
// rounded f64/f32 from (mantissa, exp10) when the 128-bit product is
|
||||
// unambiguous, else void → caller falls to the decimal slow path.
|
||||
// Divergences at-site: `mantissa <<= clz` (scalar-param mutate) → local
|
||||
// `mnt`; whole-struct local reassign `x = merged` copies only the first
|
||||
// word in cgen → per-field `x.hi = …; x.lo = …` (#155); `po10 =
|
||||
// powers_of_ten[i]` row-bind → direct double-index (#155, A2); bitwise-
|
||||
// vs-compare fully parenthesised; comma let-bindings split.
|
||||
fn eisel_lemire(
|
||||
mantissa: u64,
|
||||
exp10: i32,
|
||||
neg: bool,
|
||||
f: *math.floatinfo,
|
||||
) (u64 | void) = {
|
||||
if (mantissa == 0u64 || exp10 > 288 || exp10 < -307) {
|
||||
return;
|
||||
};
|
||||
let idx: i32 = exp10 + 307;
|
||||
let clz: uint = leading_zeroes(mantissa);
|
||||
let mnt: u64 = mantissa << (clz: u64);
|
||||
let shift: u64 = 64u64 - f.mantbits - 3u64;
|
||||
let mask: u64 = (1u64 << shift) - 1u64;
|
||||
// log(10)/log(2) ≈ 217706 / 65536; x / 65536 = x >> 16.
|
||||
let exp: int = (217706 * (exp10: int)) >> 16;
|
||||
let e2: u64 = ((exp + f.expbias + 64): u64) - (clz: u64);
|
||||
let x: r128 = u128mul(mnt, powers_of_ten[idx][1]);
|
||||
if ((x.hi & mask) == mask && (x.lo + mnt) < mnt) {
|
||||
let y: r128 = u128mul(mnt, powers_of_ten[idx][0]);
|
||||
let merged: r128 = r128 { hi = x.hi, lo = x.lo + y.hi };
|
||||
if (merged.lo < x.lo) {
|
||||
// local-struct-field compound-assign drops the load in
|
||||
// wwstage (sets =1, not +=1) — explicit form, byte-id.
|
||||
merged.hi = merged.hi + 1u64;
|
||||
};
|
||||
if ((merged.hi & mask) == mask && (merged.lo + 1u64) == 0u64 &&
|
||||
(y.lo + mnt) < mnt) {
|
||||
return;
|
||||
};
|
||||
x.hi = merged.hi;
|
||||
x.lo = merged.lo;
|
||||
};
|
||||
let msb: u64 = x.hi >> 63u64;
|
||||
let mant: u64 = x.hi >> (msb + shift);
|
||||
e2 -= 1u64 ^ msb;
|
||||
if (x.lo == 0u64 && (x.hi & mask) == 0u64 && (mant & 3u64) == 1u64) {
|
||||
return;
|
||||
};
|
||||
mant += mant & 1u64;
|
||||
mant >>= 1u64;
|
||||
if ((mant >> (f.mantbits + 1u64)) > 0u64) {
|
||||
mant >>= 1u64;
|
||||
e2 += 1u64;
|
||||
};
|
||||
if (e2 <= 0u64 || e2 >= (1u64 << f.expbits) - 1u64) {
|
||||
return;
|
||||
};
|
||||
return mkfloat(mant, (e2: uint), neg, f);
|
||||
};
|
||||
|
||||
// ref/hare/strconv/stof.ha:247. Slow-path: decimal `d` → IEEE bits.
|
||||
fn floatbits(d: *decimal, f: *math.floatinfo) (u64 | overflow) = {
|
||||
let e: int = 0;
|
||||
let m: u64 = 0u64;
|
||||
let powtab: [19]i8 = [
|
||||
0i8, 3i8, 6i8, 9i8, 13i8, 16i8, 19i8, 23i8, 26i8, 29i8,
|
||||
33i8, 36i8, 39i8, 43i8, 46i8, 49i8, 53i8, 56i8, 59i8,
|
||||
];
|
||||
if (d.nd == (0u64: size) || d.dp < -326) {
|
||||
if (d.negative) {
|
||||
return mkfloat(0u64, (0u32: uint), d.negative, f);
|
||||
};
|
||||
return 0u64;
|
||||
} else if (d.dp > 310) {
|
||||
return overflow{};
|
||||
};
|
||||
if (d.nd <= (19u64: size)) {
|
||||
let dmant: u64 = 0u64;
|
||||
let i: size = (0u64: size);
|
||||
for (i < d.nd) {
|
||||
dmant = 10u64 * dmant + (d.digits[i]: u64);
|
||||
i += (1u64: size);
|
||||
};
|
||||
let exp10: i32 = d.dp - (d.nd: i32);
|
||||
match (eisel_lemire(dmant, exp10, d.negative, f)) {
|
||||
case let r: u64 => { return r; };
|
||||
case void => void;
|
||||
};
|
||||
};
|
||||
for (d.dp > 0) {
|
||||
let n: int = 0;
|
||||
if ((d.dp: uint) >= (len(powtab): uint)) {
|
||||
n = (maxshift: int);
|
||||
} else {
|
||||
n = (powtab[d.dp]: int);
|
||||
};
|
||||
decimal_shift(d, -n);
|
||||
e += n;
|
||||
};
|
||||
for (d.dp <= 0) {
|
||||
let n: int = 0;
|
||||
if (d.dp == 0) {
|
||||
if (d.digits[0] >= 5u8) { break; };
|
||||
if (d.digits[0] < 2u8) { n = 2; } else { n = 1; };
|
||||
} else if ((-d.dp) >= (len(powtab): i32)) {
|
||||
n = (maxshift: int);
|
||||
} else {
|
||||
n = (powtab[-d.dp]: int);
|
||||
};
|
||||
decimal_shift(d, n);
|
||||
e -= n;
|
||||
};
|
||||
e -= 1;
|
||||
if (e <= -f.expbias + 1) {
|
||||
let nn: int = -f.expbias - e + 1;
|
||||
decimal_shift(d, -nn);
|
||||
e += nn;
|
||||
};
|
||||
if (e + f.expbias >= ((1u64 << f.expbits): int) - 1) {
|
||||
return overflow{};
|
||||
};
|
||||
decimal_shift(d, (f.mantbits: int) + 1);
|
||||
m = decimal_round(d);
|
||||
if (m == (2u64 << f.mantbits)) {
|
||||
m >>= 1u64;
|
||||
e += 1;
|
||||
if (e + f.expbias >= ((1u64 << f.expbits): int) - 1) {
|
||||
return overflow{};
|
||||
};
|
||||
};
|
||||
if ((m & (1u64 << f.mantbits)) == 0u64) {
|
||||
e = -f.expbias;
|
||||
};
|
||||
return mkfloat(m, ((e + f.expbias): uint), d.negative, f);
|
||||
};
|
||||
|
||||
// ref/hare/strconv/stof.ha:311. Assemble sign|exp|mantissa.
|
||||
fn mkfloat(m: u64, e: uint, negative: bool, f: *math.floatinfo) u64 = {
|
||||
let n: u64 = m & ((1u64 << f.mantbits) - 1u64);
|
||||
n |= ((e: u64) & ((1u64 << f.expbits) - 1u64)) << f.mantbits;
|
||||
if (negative) {
|
||||
n |= 1u64 << (f.mantbits + f.expbits);
|
||||
};
|
||||
return n;
|
||||
};
|
||||
|
||||
// ref/hare/strconv/stof.ha:320. Exact f64 powers of ten 1e0..1e22 (all
|
||||
// exactly representable; see stof64exact).
|
||||
let f64pow10: [23]f64 = [
|
||||
1.0e0, 1.0e1, 1.0e2, 1.0e3, 1.0e4, 1.0e5, 1.0e6, 1.0e7, 1.0e8, 1.0e9,
|
||||
1.0e10, 1.0e11, 1.0e12, 1.0e13, 1.0e14, 1.0e15, 1.0e16, 1.0e17, 1.0e18,
|
||||
1.0e19, 1.0e20, 1.0e21, 1.0e22,
|
||||
];
|
||||
|
||||
// ref/hare/strconv/stof.ha:326.
|
||||
fn stof64exact(mant: u64, exp: i32, neg: bool) (f64 | void) = {
|
||||
if (mant >> math.F64_MANTISSA_BITS != 0u64) { return; };
|
||||
let n: f64 = (mant: i64): f64;
|
||||
if (neg) {
|
||||
n = -n;
|
||||
};
|
||||
if (exp == 0i32) {
|
||||
return n;
|
||||
};
|
||||
if (-22i32 <= exp && exp <= 22i32) {
|
||||
if (exp >= 0i32) {
|
||||
// f64 compound-assign mis-lowers in cgen — explicit
|
||||
// form (strconv.ww f64tos precedent).
|
||||
n = n * f64pow10[exp];
|
||||
} else {
|
||||
n = n / f64pow10[-exp];
|
||||
};
|
||||
} else {
|
||||
return;
|
||||
};
|
||||
return n;
|
||||
};
|
||||
|
||||
// ref/hare/strconv/stof.ha:345. Exact f32 powers of ten 1e0..1e10.
|
||||
let f32pow10: [11]f32 = [
|
||||
1.0e0f32, 1.0e1f32, 1.0e2f32, 1.0e3f32, 1.0e4f32, 1.0e5f32, 1.0e6f32,
|
||||
1.0e7f32, 1.0e8f32, 1.0e9f32, 1.0e10f32,
|
||||
];
|
||||
|
||||
// ref/hare/strconv/stof.ha:349.
|
||||
fn stof32exact(mant: u64, exp: i32, neg: bool) (f32 | void) = {
|
||||
if (mant >> (math.F32_MANTISSA_BITS: u64) != 0u64) { return; };
|
||||
let n: f32 = (mant: i32): f32;
|
||||
if (neg) {
|
||||
n = -n;
|
||||
};
|
||||
if (exp == 0i32) {
|
||||
return n;
|
||||
};
|
||||
if (-10i32 <= exp && exp <= 10i32) {
|
||||
if (exp >= 0i32) {
|
||||
// f32 compound-assign mis-lowers in cgen — explicit form.
|
||||
n = n * f32pow10[exp];
|
||||
} else {
|
||||
n = n / (f64pow10[-exp]: f32);
|
||||
};
|
||||
} else {
|
||||
return;
|
||||
};
|
||||
return n;
|
||||
};
|
||||
|
||||
// ref/hare/strconv/stof.ha:369. Adapted from Go's atofHex. The by-value
|
||||
// `p` is mutated in Hare; ww copies its fields to scalar locals (struct
|
||||
// param field-write miscompiles + diverges — filed).
|
||||
fn hex_to_bits(p: fast_parsed_float, info: *math.floatinfo) (u64 | overflow) = {
|
||||
let pmant: u64 = p.mantissa;
|
||||
let pexp: i32 = p.exponent;
|
||||
let pneg: bool = p.negative;
|
||||
let ptrunc: bool = p.truncated;
|
||||
let max_exp: int = ((1u64 << info.expbits): int) - info.expbias - 2;
|
||||
let min_exp: int = -info.expbias + 1;
|
||||
pexp += (info.mantbits: i32);
|
||||
|
||||
// Shift left until a leading 1 bit followed by mantbits + 2 rounding.
|
||||
for (pmant != 0u64 && pmant >> (info.mantbits + 2u64) == 0u64) {
|
||||
pmant <<= 1u64;
|
||||
pexp -= 1;
|
||||
};
|
||||
if (ptrunc) {
|
||||
pmant |= 1u64;
|
||||
};
|
||||
// Too many bits: shift right (sticky-or the dropped bit).
|
||||
for (pmant >> (3u64 + info.mantbits) != 0u64) {
|
||||
pmant = (pmant >> 1u64) | (pmant & 1u64);
|
||||
pexp += 1;
|
||||
};
|
||||
// Denormalise if the exponent is small.
|
||||
for (pmant > 1u64 && pexp < (min_exp: i32) - 2) {
|
||||
pmant = (pmant >> 1u64) | (pmant & 1u64);
|
||||
pexp += 1;
|
||||
};
|
||||
// Round to even.
|
||||
let round: u64 = pmant & 3u64;
|
||||
pmant >>= 2u64;
|
||||
round |= pmant & 1u64;
|
||||
pexp += 2;
|
||||
if (round == 3u64) {
|
||||
pmant += 1u64;
|
||||
if (pmant == 1u64 << (1u64 + info.mantbits)) {
|
||||
pmant >>= 1u64;
|
||||
pexp += 1;
|
||||
};
|
||||
};
|
||||
// Denormal or zero.
|
||||
if (pmant >> info.mantbits == 0u64) {
|
||||
pexp = (-info.expbias): i32;
|
||||
};
|
||||
if (pexp > (max_exp: i32)) {
|
||||
return overflow{};
|
||||
};
|
||||
let bits: u64 = pmant & info.mantmask;
|
||||
bits |= (((pexp + (info.expbias: i32)): u64) & info.expmask) << info.mantbits;
|
||||
if (pneg) {
|
||||
bits |= 1u64 << (info.mantbits + info.expbits);
|
||||
};
|
||||
return bits;
|
||||
};
|
||||
|
||||
// ref/hare/strconv/stof.ha:425. "nan"/"infinity"/±"infinity",
|
||||
// case-insensitive. ww math has no f32 NAN/INF consts → f32frombits of
|
||||
// the IEEE-754 f32 bit patterns (qNaN 0x7FC00000, ±Inf 0x7F800000 /
|
||||
// 0xFF800000).
|
||||
fn special(s: str) (f32 | void) = {
|
||||
if (ascii.strcasecmp(s, "nan") == 0) {
|
||||
return math.f32frombits(0x7FC00000u32);
|
||||
} else if (ascii.strcasecmp(s, "infinity") == 0) {
|
||||
return math.f32frombits(0x7F800000u32);
|
||||
} else if (ascii.strcasecmp(s, "+infinity") == 0) {
|
||||
return math.f32frombits(0x7F800000u32);
|
||||
} else if (ascii.strcasecmp(s, "-infinity") == 0) {
|
||||
return math.f32frombits(0xFF800000u32);
|
||||
};
|
||||
return;
|
||||
};
|
||||
|
||||
// ref/hare/strconv/stof.ha:445. Parse `s` as f64 (base DEC or HEX). See
|
||||
// the module note: the EL fast path is HELD; the decimal fallback gives
|
||||
// correct results meanwhile.
|
||||
export fn stof64(s: str, b: base) (f64 | invalid | overflow) = {
|
||||
let bb: base = b;
|
||||
if (bb == base.DEFAULT) {
|
||||
bb = base.DEC;
|
||||
} else if (bb == base.HEX_LOWER) {
|
||||
bb = base.HEX;
|
||||
};
|
||||
os.assert(bb == base.DEC || bb == base.HEX,
|
||||
"strconv.stof64: base must be DEC or HEX");
|
||||
|
||||
if (s.len == 0) {
|
||||
return 0: invalid;
|
||||
};
|
||||
|
||||
match (special(s)) {
|
||||
case let f: f32 => { return (f: f64); };
|
||||
case void => void;
|
||||
};
|
||||
|
||||
match (fast_parse(s, bb)) {
|
||||
case let p: fast_parsed_float => {
|
||||
if (bb == base.HEX) {
|
||||
match (hex_to_bits(p, &math.f64info)) {
|
||||
case let bits: u64 => { return math.f64frombits(bits); };
|
||||
case let eo: overflow => { return eo; };
|
||||
};
|
||||
} else if (!p.truncated) {
|
||||
match (stof64exact(p.mantissa, p.exponent, p.negative)) {
|
||||
case let n: f64 => { return n; };
|
||||
case void => void;
|
||||
};
|
||||
match (eisel_lemire(p.mantissa, p.exponent, p.negative,
|
||||
&math.f64info)) {
|
||||
case let n: u64 => { return math.f64frombits(n); };
|
||||
case void => void;
|
||||
};
|
||||
};
|
||||
let d = decimal { ... };
|
||||
match (decimal_parse(&d, s)) {
|
||||
case let ei: invalid => { return ei; };
|
||||
case void => void;
|
||||
};
|
||||
match (floatbits(&d, &math.f64info)) {
|
||||
case let n: u64 => { return math.f64frombits(n); };
|
||||
case let eo: overflow => { return eo; };
|
||||
};
|
||||
};
|
||||
case let ei: invalid => { return ei; };
|
||||
};
|
||||
return 0: invalid; // unreachable (path-cov)
|
||||
};
|
||||
|
||||
// ref/hare/strconv/stof.ha:491. Parse `s` as f32 (base DEC or HEX).
|
||||
export fn stof32(s: str, b: base) (f32 | invalid | overflow) = {
|
||||
let bb: base = b;
|
||||
if (bb == base.DEFAULT) {
|
||||
bb = base.DEC;
|
||||
} else if (bb == base.HEX_LOWER) {
|
||||
bb = base.HEX;
|
||||
};
|
||||
os.assert(bb == base.DEC || bb == base.HEX,
|
||||
"strconv.stof32: base must be DEC or HEX");
|
||||
|
||||
if (s.len == 0) {
|
||||
return 0: invalid;
|
||||
};
|
||||
|
||||
match (special(s)) {
|
||||
case let f: f32 => { return f; };
|
||||
case void => void;
|
||||
};
|
||||
|
||||
match (fast_parse(s, bb)) {
|
||||
case let p: fast_parsed_float => {
|
||||
if (bb == base.HEX) {
|
||||
match (hex_to_bits(p, &math.f32info)) {
|
||||
case let bits: u64 => {
|
||||
return math.f32frombits(bits: u32);
|
||||
};
|
||||
case let eo: overflow => { return eo; };
|
||||
};
|
||||
} else if (!p.truncated) {
|
||||
match (stof32exact(p.mantissa, p.exponent, p.negative)) {
|
||||
case let n: f32 => { return n; };
|
||||
case void => void;
|
||||
};
|
||||
match (eisel_lemire(p.mantissa, p.exponent, p.negative,
|
||||
&math.f32info)) {
|
||||
case let n: u64 => { return math.f32frombits(n: u32); };
|
||||
case void => void;
|
||||
};
|
||||
};
|
||||
let d = decimal { ... };
|
||||
match (decimal_parse(&d, s)) {
|
||||
case let ei: invalid => { return ei; };
|
||||
case void => void;
|
||||
};
|
||||
match (floatbits(&d, &math.f32info)) {
|
||||
case let n: u64 => { return math.f32frombits(n: u32); };
|
||||
case let eo: overflow => { return eo; };
|
||||
};
|
||||
};
|
||||
case let ei: invalid => { return ei; };
|
||||
};
|
||||
return 0: invalid; // unreachable (path-cov)
|
||||
};
|
||||
Reference in New Issue
Block a user