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