Files
ww/lib/strconv/stof.ww
Hojun-Cho aadc6618f0 lib: banner purge + WHY-only comment sweep (rule 8)
Every // ---- section banner dies (132 -> 0): names carry the WHAT.
Narration deleted (filename restatements, run-with lines, what-the-
next-line-does); every ref/hare cite, task cite, divergence, ABI/
layout contract, and ownership qualifier kept (borrowed-view lines
restored where the sweep over-cut). Comment-only proven: all 442
walk-workdir .s and 32 import-probe .s byte-identical before/after;
libbyteid 56-roster all-ID.
2026-08-08 21:10:18 +09:00

699 lines
20 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 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)
};
// 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 = {
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;
};
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;
};
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)
};