Files
ww/lib/math/floats.ww
Hojun-Cho e28d6b2e9d lib/math: NAN/INF bits + flattened F64_EXPBIAS (#106 fold-1b)
Flattened from Hare's const struct instances per #129 cgen module-let-
init gap; NAN/INF expressed as `def NAN_BITS:u64 = 0x...;` and
`def INF_BITS:u64 = 0x...;` materialized via f64frombits(NAN_BITS) at
use-site. F64_EXPBIAS:int flattened from f64info.expbias (cite
ref/hare/math/floats.ha:117); floatinfo struct definition preserved for
γ-cleanup re-fold when #129 closes. F32_EXPBIAS deferred (option-ii:
stof.ha consumers reach f.expbias via struct-field only).
2026-05-26 19:19:48 +09:00

264 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 + F64_EXPBIAS (flattened from
// f64info per #129)). 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;
// flattened from f64info per #129 (cgen module-level struct-init gap);
// cite ref/hare/math/floats.ha:117. Re-fold to const f64info: floatinfo
// when #129 closes (γ-cleanup pattern per amalloc-drop precedent).
// The existing F64_EXPONENT_BIAS:u64 above is the bit-ops alias; this
// int-typed F64_EXPBIAS is the floatinfo.expbias-field counterpart and
// drops the cast at the construction site (see ref/hare/strconv/stof.ha
// :248,288 for the consumer pattern).
export def F64_EXPBIAS: int = 1023;
// 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).
//
// The companion `f64info` / `f32info` module-scope instances Hare exports
// (ref/hare/math/floats.ha:117,126) are DEFERRED — ww's cgen doesn't
// lower module-level struct lets-with-initializer (the symbol comes out
// undefined at link time). Callers in the fold-4 stof port construct a
// stack-local `floatinfo { ... }` and pass `&info` until that gap closes.
// Repro logged with team lead.
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,
};
// 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);
};