Files
ww/lib/math/floats.ww

161 lines
5.7 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). 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).
// f32 variants stay deferred (#104 blocks the f32 call-arg narrowing), as
// do the ldexp/modfrac/nextafter family (need f64 DIVIDE + the INF const).
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;
// 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;
// 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);
};