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.
699 lines
20 KiB
Plaintext
699 lines
20 KiB
Plaintext
// 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)
|
||
};
|