Ports the subnormal-normalize step of the f64 decompose half from ref/hare/math/floats.ha: issubnormalf64 (floats.ha:179) and normalizef64 (floats.ha:256, the f64-multiply-on-subnormal that yields (f64, i64)). frexpf64 (floats.ha:278) is held back, not ported: its Hare-exact zero guard `n == 0f64` miscompiles. A no-decimal `0f64` literal used as an f64 comparison operand is materialized into a GPR and never moved to XMM, so the UCOMISD reads a stale operand and `n == 0f64` is wrong for every n. Both stages emit this identically, so the byte-id gates are blind to it. `0.0` compiles correctly but substituting it would be a workaround (rule 7), so frexpf64 waits for the cgen fix. normalizef64/issubnormalf64 touch neither the broken literal form nor any tuple-field comparison, so they are correct and land now.
134 lines
4.6 KiB
Plaintext
134 lines
4.6 KiB
Plaintext
// floats — f64 classification, sign, bit-reinterpret core, and the
|
|
// subnormal-normalize step of the f64 decompose half. Ported from
|
|
// ref/hare/math/floats.ha (fold-1: classify/sign/bits; fold-2a:
|
|
// issubnormalf64/normalizef64). frexpf64 (floats.ha:278) is held back: its
|
|
// Hare-exact zero guard `n == 0f64` miscompiles — a no-decimal `0f64`
|
|
// literal in an f64 comparison is materialized into a GPR and never moved
|
|
// to XMM, so the compare reads a stale operand (both stages identically,
|
|
// so the byte-id gates are blind to it). `0.0` would dodge it, but that is
|
|
// a workaround (CLAUDE.md rule 7); frexpf64 lands once the cgen bug is
|
|
// fixed. f32 variants, NAN/INF + magnitude consts, and the
|
|
// ldexp/modfrac/nextafter family (need f64 DIVIDE + the INF const) are
|
|
// deferred to a later fold.
|
|
|
|
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 f64 with the given binary representation.
|
|
// ref/hare/math/floats.ha:11
|
|
export fn f64frombits(n: u64) f64 = {
|
|
return *((&n): *f64);
|
|
};
|
|
|
|
// 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;
|
|
|
|
// 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);
|
|
};
|