Files
ww/lib/math/floats.ww
Hojun-Cho 88f3d67b28 lib/math: re-fold F64_EXPBIAS to const f64info/f32info struct (#40)
Restore the Hare-faithful floatinfo struct defs (ref/hare/math/floats.ha
:117,126), removing the Drew-flattened-(b) #129-era bypass (F64_EXPBIAS:int
scalar). #149 (db7523e) now lowers cross-module &def to LEAQ, so the struct
exports are addressable via the stof consumer pattern (&math.f64info ->
f: *floatinfo -> f.expbias). First real consumer of A.2 struct-composite
static-init (DATA byte-validated: f64info/f32info 40B each).

ADD export def f64info/f32info: floatinfo (hex masks value-identical to
Hare's (1<<52)-1 etc.; A.2 helper folds bare literals only, documented
at-site). DELETE export def F64_EXPBIAS (zero consumers). KEEP NAN_BITS/
INF_BITS u64 sentinels (ruling b: def NAN=0.0/0.0 blocked by #147) +
F64_EXPONENT_BIAS:u64 (bit-ops alias, Hare keeps both).

Test 952: pointer-param row reads &math.f64info + &math.f32info through
*math.floatinfo, all 5 fields each (pointer-param not direct field-read,
dodges #150). Make test 184/184 incl 990-997 byte-id + combined_ww_fresh.
lib/math not compiler-imported -> no regen.

Unblocks fold-4 stof.ha.
2026-05-27 10:55:31 +09:00

274 lines
10 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.
// floats — f64 classification, sign, bit-reinterpret core, and the f64
// decompose half (subnormal-normalize + frexp). Ported from
// ref/hare/math/floats.ha (fold-1: classify/sign/bits; fold-2a:
// issubnormalf64/normalizef64/frexpf64; strconv-foundation fold-1a:
// F32 bit-layout + f32bits/f32frombits + floatinfo struct type;
// fold-1b: NAN_BITS/INF_BITS sentinels; γ-cleanup: f64info/f32info
// instances re-folded once #149 lowered &math.f64info). frexpf64's
// zero guard `n == 0f64` rides the #103
// fix (no-decimal f64 literal now materialized into XMM) and its
// (f64, i64) tuple return rides the #105 fix (tuple f64-word read).
// The ldexp/modfrac/nextafter family stays deferred (need f64 DIVIDE).
package math;
// Returns the binary representation of the given f64.
// ref/hare/math/floats.ha:5. Parens around &n are load-bearing: ww's `:`
// cast binds tighter than unary `&`, so Hare's `*(&n: *u64)` would parse
// as `*(&(n: *u64))`; `(&n): *u64` reinterprets the address as intended.
export fn f64bits(n: f64) u64 = {
return *((&n): *u64);
};
// Returns the binary representation of the given f32.
// ref/hare/math/floats.ha:8
export fn f32bits(n: f32) u32 = {
return *((&n): *u32);
};
// Returns f64 with the given binary representation.
// ref/hare/math/floats.ha:11
export fn f64frombits(n: u64) f64 = {
return *((&n): *f64);
};
// Returns f32 with the given binary representation.
// ref/hare/math/floats.ha:14
export fn f32frombits(n: u32) f32 = {
return *((&n): *f32);
};
// ref/hare/math/floats.ha:17,20,23 declare these as untyped int. ww has
// no untyped def (every def carries a type) and routes shift/bitwise
// through unify_arith, which rejects mixed operand types (cmd/wcc/
// check.c:769). The bit-structure consts are used only as u64 shift
// amounts and mask widths, so they are typed u64 here — the closest
// stand-in for Hare's untyped-int adapt at those use sites.
// The number of bits in the significand of the binary representation of f64.
export def F64_MANTISSA_BITS: u64 = 52;
// The number of bits in the exponent of the binary representation of f64.
export def F64_EXPONENT_BITS: u64 = 11;
// The bias of the exponent of the binary representation of f64. Subtract this
// from the exponent in the binary representation to get the actual exponent.
export def F64_EXPONENT_BIAS: u64 = 1023;
// Mask with each bit of an f64's mantissa set.
// ref/hare/math/floats.ha:37
export def F64_MANTISSA_MASK: u64 = (1 << F64_MANTISSA_BITS) - 1;
// Mask with each bit of an f64's exponent set.
// ref/hare/math/floats.ha:40
export def F64_EXPONENT_MASK: u64 = (1 << F64_EXPONENT_BITS) - 1;
// The mask that gets an f64's sign.
// ref/hare/math/floats.ha:75
def F64_SIGN_MASK: u64 = 1u64 << 63;
// Mask that clears an f64's exponent field, keeping sign + mantissa.
// ref/hare/math/floats.ha:77. Hare hardcodes the 0x800FFFFFFFFFFFFF binary
// literal because its lexer can't const-fold the expression; ww's #88
// def-const-fold can, so the readable form is kept. floats.ha:79's NOTE
// expression has an `0u64 &` upstream typo (it would yield 0); the value it
// documents is exactly ~(F64_EXPONENT_MASK << F64_MANTISSA_BITS).
def F64_EXP_REMOVAL_MASK: u64 = ~(F64_EXPONENT_MASK << F64_MANTISSA_BITS);
// The f64 bit pattern whose exponent field evaluates to zero (0.5 scale).
// ref/hare/math/floats.ha:84
def F64_EXP_ZERO: u64 = (F64_EXPONENT_BIAS - 1) << F64_MANTISSA_BITS;
// F32 bit-structure constants. ref/hare/math/floats.ha:27,30,33 declare
// these as untyped int; ww has no untyped def, so they ride u32 (matching
// the u32 bit container, the same way the F64 family rides u64 — see
// the note above F64_MANTISSA_BITS).
// The number of bits in the significand of the binary representation of f32.
// ref/hare/math/floats.ha:27
export def F32_MANTISSA_BITS: u32 = 23u32;
// The number of bits in the exponent of the binary representation of f32.
// ref/hare/math/floats.ha:30
export def F32_EXPONENT_BITS: u32 = 8u32;
// The bias of the exponent of the binary representation of f32. Subtract this
// from the exponent in the binary representation to get the actual exponent.
// ref/hare/math/floats.ha:33
export def F32_EXPONENT_BIAS: u32 = 127u32;
// Mask with each bit of an f32's mantissa set.
// ref/hare/math/floats.ha:43
export def F32_MANTISSA_MASK: u32 = (1u32 << F32_MANTISSA_BITS) - 1u32;
// Mask with each bit of an f32's exponent set.
// ref/hare/math/floats.ha:46
export def F32_EXPONENT_MASK: u32 = (1u32 << F32_EXPONENT_BITS) - 1u32;
// The mask that gets an f32's sign.
// ref/hare/math/floats.ha:87
def F32_SIGN_MASK: u32 = 1u32 << 31;
// Mask that clears an f32's exponent field, keeping sign + mantissa.
// ref/hare/math/floats.ha:92. Hare hardcodes the binary literal (its
// lexer can't const-fold the expression); ww's #88 def-const-fold can,
// so the readable form is kept (same call as F64_EXP_REMOVAL_MASK).
def F32_EXP_REMOVAL_MASK: u32 = ~(F32_EXPONENT_MASK << F32_MANTISSA_BITS);
// The f32 bit pattern whose exponent field evaluates to zero (0.5 scale).
// ref/hare/math/floats.ha:95
def F32_EXP_ZERO: u32 = (F32_EXPONENT_BIAS - 1u32) << F32_MANTISSA_BITS;
// floatinfo — IEEE-754 shape parameters for a binary float type, passed
// to width-generic helpers in strconv (eisel_lemire, floatbits, hex_to_bits,
// mkfloat). ref/hare/math/floats.ha:101. Hare's `int` maps to ww's `int`
// (machine word, 8B; project_int_machine_word_derived_limits), so the
// expbias field stays `int` — that keeps the fold-4 stof port byte-for-byte
// against ref/hare/strconv/stof.ha:248,288 (`let e: int = 0` arithmetic
// against `f.expbias` of the same type, no cast at use site).
export type floatinfo = struct {
// Bits in significand.
mantbits: u64,
// Bits in exponent.
expbits: u64,
// Bias of exponent.
expbias: int,
// Mask for mantissa.
mantmask: u64,
// Mask for exponent.
expmask: u64,
};
// floatinfo instances for the f64 / f32 types, consumed by the
// width-generic strconv helpers via &math.f64info (cross-module
// address-of, lowered since #149). ref/hare/math/floats.ha:117,126.
// Hare spells the masks (1 << 52) - 1 / (1 << 23) - 1; the #129 A.2
// struct-composite static-init path folds only bare-literal field
// initializers, not const-fold expressions, so the value-identical hex
// literals are used here (0xFFFFFFFFFFFFF == (1<<52)-1, 0x7FFFFF ==
// (1<<23)-1 — same hex-literal style as the NAN_BITS/INF_BITS sentinels
// below). expbias rides `int` (the field type) with no suffix.
export def f64info: floatinfo = floatinfo {
mantbits = 52u64,
expbits = 11u64,
expbias = 1023,
mantmask = 0xFFFFFFFFFFFFFu64,
expmask = 0x7FFu64,
};
export def f32info: floatinfo = floatinfo {
mantbits = 23u64,
expbits = 8u64,
expbias = 127,
mantmask = 0x7FFFFFu64,
expmask = 0xFFu64,
};
// IEEE-754 quiet-NaN and positive-Infinity f64 bit sentinels.
// ref/hare/math/floats.ha:137,141. Hare exports `def NAN = 0.0/0.0;` and
// `def INF = 1.0/0.0;` (untyped float def-fold); ww's cgen doesn't lower
// `def: f64 = expr;` (the symbol comes out undefined at link time — see
// #129). Callers materialize the f64 sentinel via f64frombits(NAN_BITS)
// / f64frombits(INF_BITS); same bit-exact value, one extra reinterpret.
// 0x7FF8000000000000 is the IEEE-754 binary64 quiet-NaN (sign=0, exp=
// all-ones, mantissa MSB=1, rest=0); 0x7FF0000000000000 is +Infinity
// (sign=0, exp=all-ones, mantissa=0). Re-fold to `def NAN: f64 = ...`
// when #129 closes (γ-cleanup pattern per amalloc-drop precedent).
export def NAN_BITS: u64 = 0x7FF8000000000000u64;
export def INF_BITS: u64 = 0x7FF0000000000000u64;
// Returns true if the given floating-point number is NaN.
// ref/hare/math/floats.ha:144 (Hare's expression body inlined into a
// block: ww has no expression-bodied fn form, only brace blocks).
export fn isnan(n: f64) bool = {
return n != n;
};
// Returns true if the given floating-point number is infinite.
// ref/hare/math/floats.ha:147
export fn isinf(n: f64) bool = {
const bits = f64bits(n);
const mant = bits & F64_MANTISSA_MASK;
const exp = bits >> F64_MANTISSA_BITS & F64_EXPONENT_MASK;
return exp == F64_EXPONENT_MASK && mant == 0;
};
// Returns true if the given f64 is subnormal.
// ref/hare/math/floats.ha:179
export fn issubnormalf64(n: f64) bool = {
const bits = f64bits(n);
const mant = bits & F64_MANTISSA_MASK;
const exp = bits >> F64_MANTISSA_BITS & F64_EXPONENT_MASK;
return exp == 0 && mant != 0;
};
// Returns the absolute value of f64 n.
// ref/hare/math/floats.ha:195
export fn absf64(n: f64) f64 = {
if (isnan(n)) {
return n;
};
return f64frombits(f64bits(n) & ~F64_SIGN_MASK);
};
// Returns 1 if x is positive and -1 if x is negative. Note that zero is also
// signed.
// ref/hare/math/floats.ha:212
export fn signf64(x: f64) i64 = {
if (f64bits(x) & F64_SIGN_MASK == 0) {
return 1i64;
} else {
return -1i64;
};
};
// Returns whether or not x is positive.
// ref/hare/math/floats.ha:231
export fn ispositivef64(x: f64) bool = {
return signf64(x) == 1i64;
};
// Returns whether or not x is negative.
// ref/hare/math/floats.ha:237
export fn isnegativef64(x: f64) bool = {
return signf64(x) == -1i64;
};
// Returns x, but with the sign of y.
// ref/hare/math/floats.ha:243
export fn copysignf64(x: f64, y: f64) f64 = {
return f64frombits((f64bits(x) & ~F64_SIGN_MASK) |
(f64bits(y) & F64_SIGN_MASK));
};
// Takes a potentially subnormal f64 n and returns a normal f64 normal_float
// and an exponent exp such that n == normal_float * 2^{exp}.
// ref/hare/math/floats.ha:256
export fn normalizef64(n: f64) (f64, i64) = {
if (issubnormalf64(n)) {
const factor = 1i64 << (F64_MANTISSA_BITS: i64);
const normal_float = (n * (factor: f64));
return (normal_float, -(F64_MANTISSA_BITS: i64));
};
return (n, 0);
};
// Breaks a f64 down into its mantissa and exponent. The mantissa will be
// between 0.5 and 1.
// ref/hare/math/floats.ha:278
export fn frexpf64(n: f64) (f64, i64) = {
if (isnan(n) || isinf(n) || n == 0f64) {
return (n, 0);
};
const normalized = normalizef64(n);
const normal_float = normalized.0;
const normalization_exp = normalized.1;
const bits = f64bits(normal_float);
const raw_exp: u64 = (bits >> F64_MANTISSA_BITS) & F64_EXPONENT_MASK;
const exp: i64 = normalization_exp +
(raw_exp: i64) - (F64_EXPONENT_BIAS: i64) + 1;
const mantissa: f64 =
f64frombits((bits & F64_EXP_REMOVAL_MASK) | F64_EXP_ZERO);
return (mantissa, exp);
};