hare

The Hare programming language
git clone https://git.torresjrjr.com/hare.git
Log | Files | Refs | README | LICENSE

commit e29ec221cd43655821a89857c8706fc41cb94421
parent 3c362bec22fb22ac81f7929365770cc612e0e29f
Author: Vlad-Stefan Harbuz <vlad@vladh.net>
Date:   Tue, 17 Aug 2021 17:37:53 +0200

Add math.ha and clean up floats.ha

Signed-off-by: Vlad-Stefan Harbuz <vlad@vladh.net>

Diffstat:
Mmath/floats.ha | 457++++++++++++++++++++++++++++++++++++++++++++-----------------------------------
Amath/ints.ha | 138+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Amath/math.ha | 690+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mscripts/gen-stdlib | 2+-
Mstdlib.mk | 12++++++++----
5 files changed, 1090 insertions(+), 209 deletions(-)

diff --git a/math/floats.ha b/math/floats.ha @@ -1,144 +1,5 @@ use types; -// The floating point value representing Not a Number, i.e. an undefined or -// unrepresentable value. You cannot test if a number is NaN by comparing to -// this value; see [[isnan]] instead. -export def NAN: f32 = 0.0 / 0.0; - -// The floating point value representing positive infinity. Use -[[INF]] for -// negative infinity. -export def INF: f32 = 1.0 / 0.0; - -// Returns true if the given f64 is NaN. -export fn isnanf64(n: f64) bool = n != n; - -// Returns true if the given f32 is NaN. -export fn isnanf32(n: f32) bool = n != n; - -// Returns true if the given floating-point number is NaN. -export fn isnan(n: types::floating) bool = { - return match (n) { - f: f64 => isnanf64(f), - f: f32 => isnanf32(f), - }; -}; - -@test fn isnan() void = { - assert(isnan(NAN)); - assert(isnan(-NAN)); - assert(isnan(f64frombits(0xfffabcdef1234567))); - assert(!isnan(INF)); - assert(!isnan(1.23f32)); -}; - -// Returns true if the given floating-point number is infinite. -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; -}; - -@test fn isinf() void = { - assert(isinf(INF)); - assert(isinf(-INF)); - assert(!isinf(NAN)); - assert(!isinf(1.23)); - assert(!isinf(-1.23f32)); -}; - -// Returns true if the given floating-point number is normal. -export fn isnormal(n: types::floating) bool = { - return match (n) { - n: f32 => isnormalf32(n), - n: f64 => isnormalf64(n), - }; -}; - -// Returns true if the given f32 is normal. -export fn isnormalf32(n: f32) bool = { - const bits = f32bits(n); - const mant = bits & F32_MANTISSA_MASK; - const exp = bits >> F32_MANTISSA_BITS & F32_EXPONENT_MASK; - return exp != F32_EXPONENT_MASK && (exp > 0 || mant == 0); -}; - -// Returns true if the given f64 is normal. -export fn isnormalf64(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 && (exp > 0 || mant == 0); -}; - -// Returns true if the given floating-point number is subnormal. -export fn issubnormal(n: types::floating) bool = { - return match (n) { - n: f32 => issubnormalf32(n), - n: f64 => issubnormalf64(n), - }; -}; - -// Returns true if the given f32 is subnormal. -export fn issubnormalf32(n: f32) bool = { - const bits = f32bits(n); - const mant = bits & F32_MANTISSA_MASK; - const exp = bits >> F32_MANTISSA_BITS & F32_EXPONENT_MASK; - return exp == 0 && mant != 0; -}; - -// Returns true if the given f64 is subnormal. -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; -}; - -@test fn float_normality() void = { - assert(isnormal(0.0)); - assert(isnormal(1.0)); - assert(!isnormal(NAN)); - assert(!isnormal(INF)); - assert(!isnormal(1.0e-310)); - assert(!isnormal(1.0e-40f32)); - - assert(isnormalf32(1.0)); - assert(isnormalf32(0.0)); - assert(!isnormalf32(NAN)); - assert(!isnormalf32(INF)); - assert(!isnormalf32(-1.0e-40)); - assert(isnormalf32(-1.0e-50)); - - assert(isnormalf64(1.0)); - assert(isnormalf64(0.0)); - assert(!isnormalf64(NAN)); - assert(!isnormalf64(INF)); - assert(!isnormalf64(-1.0e-320)); - assert(isnormalf64(-1.0e-330)); - - assert(issubnormal(1.0e-320)); - assert(issubnormal(1.0e-42f32)); - assert(!issubnormal(NAN)); - assert(!issubnormal(INF)); - assert(!issubnormal(1.0)); - assert(!issubnormal(0.0)); - - assert(issubnormalf32(1.0e-45)); - assert(issubnormalf32(-1.0e-39)); - assert(!issubnormalf32(-NAN)); - assert(!issubnormalf32(-INF)); - assert(!issubnormalf32(0.0)); - assert(!issubnormalf32(-1.0e-49)); - - assert(issubnormalf64(5.0e-324)); - assert(issubnormalf64(-2.0e-310)); - assert(!issubnormalf64(-NAN)); - assert(!issubnormalf64(-INF)); - assert(!issubnormalf64(-1.0e-400)); - assert(!issubnormalf64(0.0)); -}; - // Returns the binary representation of the given f64. export fn f64bits(n: f64) u64 = *(&n: *u64); @@ -235,7 +96,8 @@ def F32_SIGN_MASK: u32 = 1u32 << 31; def F32_EXP_REMOVAL_MASK: u32 = 0b10000000011111111111111111111111; // The f32 that contains only an exponent that evaluates to zero. -def F32_EXP_ZERO: u32 = ((F32_EXPONENT_BIAS: u32) - 1) << (F32_MANTISSA_BITS: u32); +def F32_EXP_ZERO: u32 = + ((F32_EXPONENT_BIAS: u32) - 1) << (F32_MANTISSA_BITS: u32); // Contains information about the structure of a specific floating point number // type. @@ -270,36 +132,179 @@ export const f32info: floatinfo = floatinfo { expmask = (1 << 8) - 1, }; -// Returns the absolute value of n. +// The floating point value representing Not a Number, i.e. an undefined or +// unrepresentable value. You cannot test if a number is NaN by comparing to +// this value; see [[isnan]] instead. +export def NAN: f32 = 0.0 / 0.0; + +// The floating point value representing positive infinity. Use -[[INF]] for +// negative infinity. +export def INF: f32 = 1.0 / 0.0; + +// Returns true if the given floating-point number is NaN. +export fn isnan(n: f64) bool = n != n; + +@test fn isnan() void = { + assert(isnan(NAN)); + assert(isnan(-NAN)); + assert(isnan(f64frombits(0xfffabcdef1234567))); + assert(!isnan(INF)); + assert(!isnan(1.23f32)); +}; + +// Returns true if the given floating-point number is infinite. +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 floating-point number is positive infinity. +export fn isposinf(n: f64) bool = { + return isinf(n) && signf64(n) == 1i64; +}; + +// Returns true if the given floating-point number is negative infinity. +export fn isneginf(n: f64) bool = { + return isinf(n) && signf64(n) == -1i64; +}; + +@test fn isinf() void = { + assert(isinf(INF)); + assert(isinf(-INF)); + assert(!isinf(NAN)); + assert(!isinf(1.23)); + assert(!isinf(-1.23f32)); + assert(isposinf(math::INF)); + assert(!isposinf(-math::INF)); + assert(isneginf(-math::INF)); + assert(!isneginf(math::INF)); +}; + +// Returns true if the given floating-point number is normal. +export fn isnormal(n: types::floating) bool = { + return match (n) { + n: f32 => isnormalf32(n), + n: f64 => isnormalf64(n), + }; +}; + +// Returns true if the given f64 is normal. +export fn isnormalf64(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 && (exp > 0 || mant == 0); +}; + +// Returns true if the given f32 is normal. +export fn isnormalf32(n: f32) bool = { + const bits = f32bits(n); + const mant = bits & F32_MANTISSA_MASK; + const exp = bits >> F32_MANTISSA_BITS & F32_EXPONENT_MASK; + return exp != F32_EXPONENT_MASK && (exp > 0 || mant == 0); +}; + +// Returns true if the given floating-point number is subnormal. +export fn issubnormal(n: types::floating) bool = { + return match (n) { + n: f32 => issubnormalf32(n), + n: f64 => issubnormalf64(n), + }; +}; + +// Returns true if the given f64 is subnormal. +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 true if the given f32 is subnormal. +export fn issubnormalf32(n: f32) bool = { + const bits = f32bits(n); + const mant = bits & F32_MANTISSA_MASK; + const exp = bits >> F32_MANTISSA_BITS & F32_EXPONENT_MASK; + return exp == 0 && mant != 0; +}; + +@test fn float_normality() void = { + assert(isnormal(0.0)); + assert(isnormal(1.0)); + assert(!isnormal(NAN)); + assert(!isnormal(INF)); + assert(!isnormal(1.0e-310)); + assert(!isnormal(1.0e-40f32)); + + assert(isnormalf32(1.0)); + assert(isnormalf32(0.0)); + assert(!isnormalf32(NAN)); + assert(!isnormalf32(INF)); + assert(!isnormalf32(-1.0e-40)); + assert(isnormalf32(-1.0e-50)); + + assert(isnormalf64(1.0)); + assert(isnormalf64(0.0)); + assert(!isnormalf64(NAN)); + assert(!isnormalf64(INF)); + assert(!isnormalf64(-1.0e-320)); + assert(isnormalf64(-1.0e-330)); + + assert(issubnormal(1.0e-320)); + assert(issubnormal(1.0e-42f32)); + assert(!issubnormal(NAN)); + assert(!issubnormal(INF)); + assert(!issubnormal(1.0)); + assert(!issubnormal(0.0)); + + assert(issubnormalf32(1.0e-45)); + assert(issubnormalf32(-1.0e-39)); + assert(!issubnormalf32(-NAN)); + assert(!issubnormalf32(-INF)); + assert(!issubnormalf32(0.0)); + assert(!issubnormalf32(-1.0e-49)); + + assert(issubnormalf64(5.0e-324)); + assert(issubnormalf64(-2.0e-310)); + assert(!issubnormalf64(-NAN)); + assert(!issubnormalf64(-INF)); + assert(!issubnormalf64(-1.0e-400)); + assert(!issubnormalf64(0.0)); +}; + +// Returns the absolute value of f64 n. export fn absf64(n: f64) f64 = { return f64frombits(f64bits(n) & ~F64_SIGN_MASK); }; -// Returns the absolute value of n. +// Returns the absolute value of f32 n. export fn absf32(n: f32) f32 = { return f32frombits(f32bits(n) & ~F32_SIGN_MASK); }; -// Returns the absolute value of n. -export fn abs(n: types::floating) f64 = { +// Returns the absolute value of floating-point number n. +export fn absf(n: types::floating) f64 = { return match (n) { n: f64 => absf64(n), n: f32 => (absf32(n): f64), }; }; -@test fn test_abs() void = { +@test fn absf() void = { assert(absf64(2.0f64) == 2.0f64); assert(absf32(2.0f32) == 2.0f32); - assert(abs(2.0f64) == 2.0f64); - assert(abs(2.0f32) == 2.0f64); - assert(abs(-2.0f64) == 2.0f64); - assert(abs(-2.0f32) == 2.0f32); - assert(abs(0f32) == 0f32); - assert(abs(0f64) == 0f64); + assert(absf(2.0f64) == 2.0f64); + assert(absf(2.0f32) == 2.0f64); + assert(absf(-2.0f64) == 2.0f64); + assert(absf(-2.0f32) == 2.0f32); + assert(absf(0f32) == 0f32); + assert(absf(0f64) == 0f64); }; -// Returns 1 if x is positive and -1 if x is negative. Note that zero is also signed. +// Returns 1 if x is positive and -1 if x is negative. Note that zero is also +// signed. export fn signf64(x: f64) i64 = { if (f64bits(x) & F64_SIGN_MASK == 0) { return 1i64; @@ -308,7 +313,8 @@ export fn signf64(x: f64) i64 = { }; }; -// Returns 1 if x is positive and -1 if x is negative. Note that zero is also signed. +// Returns 1 if x is positive and -1 if x is negative. Note that zero is also +// signed. export fn signf32(x: f32) i64 = { if (f32bits(x) & F32_SIGN_MASK == 0) { return 1i64; @@ -317,21 +323,54 @@ export fn signf32(x: f32) i64 = { }; }; -// Returns 1 if x is positive and -1 if x is negative. Note that zero is also signed. -export fn sign(x: types::floating) i64 = { +// Returns 1 if x is positive and -1 if x is negative. Note that zero is also +// signed. +export fn signf(x: types::floating) i64 = { return match (x) { n: f64 => signf64(n), n: f32 => signf32(n), }; }; -@test fn test_sign() void = { - assert(sign(0.0f64) == 1i64); - assert(sign(-0.0f64) == -1i64); - assert(sign(0.0f32) == 1i64); - assert(sign(-0.0f32) == -1i64); - assert(sign(1.5f64) == 1i64); - assert(sign(-1.5f64) == -1i64); +@test fn signf() void = { + assert(signf(0.0f64) == 1i64); + assert(signf(-0.0f64) == -1i64); + assert(signf(0.0f32) == 1i64); + assert(signf(-0.0f32) == -1i64); + assert(signf(1.5f64) == 1i64); + assert(signf(-1.5f64) == -1i64); + assert(ispositive(1.0f64)); + assert(!ispositive(-1.0f64)); + assert(isnegative(-1.0f64)); + assert(!isnegative(1.0f64)); +}; + +// Returns whether or not x is positive. +export fn ispositivef64(x: f64) bool = signf64(x) == 1i64; + +// Returns whether or not x is positive. +export fn ispositivef32(x: f32) bool = signf32(x) == 1i32; + +// Returns whether or not x is positive. +export fn ispositive(x: types::floating) bool = { + return match (x) { + n: f64 => ispositivef64(n), + n: f32 => ispositivef32(n), + }; +}; + +// Returns whether or not x is negative. +export fn isnegativef64(x: f64) bool = signf64(x) == -1i64; + +// Returns whether or not x is negative. +export fn isnegativef32(x: f32) bool = signf32(x) == -1i32; + +// Returns whether or not x is negative. +export fn isnegative(x: types::floating) bool = { + return match (x) { + n: f64 => isnegativef64(n), + n: f32 => isnegativef32(n), + }; }; // Returns x, but with the sign of y. @@ -354,7 +393,7 @@ export fn copysign(x: types::floating, y: types::floating) f64 = { }; }; -@test fn test_copysign() void = { +@test fn copysign() void = { assert(copysign(100.0f64, 1.0f64) == 100.0f64); assert(copysign(100.0f64, -1.0f64) == -100.0f64); assert(copysign(100.0f32, 1.0f32) == 100.0f32); @@ -362,9 +401,9 @@ export fn copysign(x: types::floating, y: types::floating) f64 = { assert(copysign(100.0f64, 0.0f64) == 100.0f64); assert(copysign(100.0f64, -0.0f64) == -100.0f64); assert(copysign(0.0f64, 100.0f64) == 0.0f64); - assert(sign(copysign(0.0f64, 100.0f64)) > 0); + assert(signf(copysign(0.0f64, 100.0f64)) > 0); assert(copysign(0.0f64, -100.0f64) == 0.0f64); - assert(sign(copysign(0.0f64, -100.0f64)) < 0); + assert(signf(copysign(0.0f64, -100.0f64)) < 0); }; // Takes a potentially subnormal f64 n and returns a normal f64 normal_float @@ -389,7 +428,7 @@ export fn normalizef32(n: f32) (f64, i64) = { return (n, 0); }; -@test fn test_normalize() void = { +@test fn normalize() void = { let res = normalizef64(5.0e-320); assert(res.0 > F64_MIN_NORMAL); assert(res.1 < 0i64); @@ -398,8 +437,8 @@ export fn normalizef32(n: f32) (f64, i64) = { assert(res.1 == 0i64); }; -// Breaks a f64 down into its mantissa and exponent. The mantissa will be between 0.5 and -// 1. +// Breaks a f64 down into its mantissa and exponent. The mantissa will be +// between 0.5 and 1. export fn frexpf64(n: f64) (f64, i64) = { if (isnan(n) || isinf(n) || n == 0f64) { return (n, 0); @@ -411,12 +450,13 @@ export fn frexpf64(n: f64) (f64, i64) = { 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); + const mantissa: f64 = + f64frombits((bits & F64_EXP_REMOVAL_MASK) | F64_EXP_ZERO); return (mantissa, exp); }; -// Breaks a f32 down into its mantissa and exponent. The mantissa will be between 0.5 and -// 1. +// Breaks a f32 down into its mantissa and exponent. The mantissa will be +// between 0.5 and 1. export fn frexpf32(n: f32) (f64, i64) = { if (isnan(n) || isinf(n) || n == 0f32) { return (n, 0); @@ -429,12 +469,13 @@ export fn frexpf32(n: f32) (f64, i64) = { (F32_EXPONENT_MASK: u32)); const exp: i64 = normalization_exp + (raw_exp: i64) - (F32_EXPONENT_BIAS: i64) + 1; - const mantissa: f32 = f32frombits((bits & F32_EXP_REMOVAL_MASK) | F32_EXP_ZERO); + const mantissa: f32 = + f32frombits((bits & F32_EXP_REMOVAL_MASK) | F32_EXP_ZERO); return (mantissa, exp); }; -// Breaks a float down into its mantissa and exponent. The mantissa will be between 0.5 -// and 1. +// Breaks a float down into its mantissa and exponent. The mantissa will be +// between 0.5 and 1. export fn frexp(n: types::floating) (f64, i64) = { return match (n) { n: f64 => frexpf64(n), @@ -442,7 +483,7 @@ export fn frexp(n: types::floating) (f64, i64) = { }; }; -@test fn test_frexp() void = { +@test fn frexp() void = { let res = frexp(3.0f64); assert(res.0 == 0.75f64); assert(res.1 == 2i64); @@ -464,7 +505,8 @@ export fn ldexpf64(mantissa: f64, exp: i64) f64 = { const normal_float = normalized.0; const normalization_exp = normalized.1; const bits = f64bits(normal_float); - const mantissa_exp = (((bits >> F64_MANTISSA_BITS) & F64_EXPONENT_MASK): i64) - + const mantissa_exp = + (((bits >> F64_MANTISSA_BITS) & F64_EXPONENT_MASK): i64) - (F64_EXPONENT_BIAS: i64); let res_exp = exp + normalization_exp + mantissa_exp; // Underflow @@ -503,7 +545,8 @@ export fn ldexpf32(mantissa: f32, exp: i64) f32 = { const normal_float = normalized.0; const normalization_exp = normalized.1; const bits = f32bits(normal_float); - const mantissa_exp = (((bits >> F32_MANTISSA_BITS) & F32_EXPONENT_MASK): i32) - + const mantissa_exp = + (((bits >> F32_MANTISSA_BITS) & F32_EXPONENT_MASK): i32) - (F32_EXPONENT_BIAS: i32); let res_exp = exp + normalization_exp + mantissa_exp; // Underflow @@ -533,7 +576,7 @@ export fn ldexpf32(mantissa: f32, exp: i64) f32 = { return subnormal_factor * f32frombits(res); }; -@test fn test_frexp_ldexp() void = { +@test fn frexp_ldexp() void = { const tests64: [_]f64 = [INF, -INF, 0.0, 1.0, -1.0, 2.42, 123456789.0, F64_MIN_NORMAL, F64_MAX_NORMAL, @@ -545,7 +588,7 @@ export fn ldexpf32(mantissa: f32, exp: i64) f32 = { }; assert(ldexpf64(1.0f64, -1076i64) == 0.0f64); assert(ldexpf64(-1.0f64, -1076i64) == -0.0f64); - assert(sign(ldexpf64(-1.0f64, -1076i64)) < 0); + assert(signf(ldexpf64(-1.0f64, -1076i64)) < 0); assert(ldexpf64(2.0f64, 1024i64) == INF); assert(ldexpf64(-2.0f64, 1024i64) == -INF); @@ -560,22 +603,22 @@ export fn ldexpf32(mantissa: f32, exp: i64) f32 = { }; assert(ldexpf32(1.0f32, -1076i32) == 0.0f32); assert(ldexpf32(-1.0f32, -1076i32) == -0.0f32); - assert(sign(ldexpf32(-1.0f32, -1076i32)) < 0); + assert(signf(ldexpf32(-1.0f32, -1076i32)) < 0); assert(ldexpf32(2.0f32, 1024i32) == INF); assert(ldexpf32(-2.0f32, 1024i32) == -INF); }; // Returns the integer and fractional parts of an f64. -export fn modf64(n: f64) (f64, f64) = { +export fn modf64(n: f64) (i64, f64) = { if (n < 1.0f64) { if (n < 0.0f64) { let positive_parts = modf64(-n); return (-positive_parts.0, -positive_parts.1); }; if (n == 0.0f64) { - return (n, n); + return ((n: i64), n); }; - return (0.0f64, n); + return (0i64, n); }; let bits = f64bits(n); const exp = (((bits >> F64_MANTISSA_BITS) & F64_EXPONENT_MASK): i64) - @@ -589,20 +632,20 @@ export fn modf64(n: f64) (f64, f64) = { }; const int_part = f64frombits(bits); const frac_part = n - int_part; - return (int_part, frac_part); + return ((int_part: i64), frac_part); }; // Returns the integer and fractional parts of an f32. -export fn modf32(n: f32) (f32, f32) = { +export fn modf32(n: f32) (i32, f32) = { if (n < 1.0f32) { if (n < 0.0f32) { let positive_parts = modf32(-n); return (-positive_parts.0, -positive_parts.1); }; if (n == 0.0f32) { - return (n, n); + return ((n: i32), n); }; - return (0.0f32, n); + return (0i32, n); }; let bits = f32bits(n); const exp = (((bits >> F32_MANTISSA_BITS) & F32_EXPONENT_MASK): i32) - @@ -616,45 +659,51 @@ export fn modf32(n: f32) (f32, f32) = { }; const int_part = f32frombits(bits); const frac_part = n - int_part; - return (int_part, frac_part); + return ((int_part: i32), frac_part); }; -@test fn test_modf() void = { +@test fn modf() void = { // 64 let res = modf64(1.75f64); - assert(res.0 == 1.0f64); + assert(res.0 == 1i64); assert(res.1 == 0.75f64); res = modf64(0.75f64); - assert(res.0 == 0.0f64); + assert(res.0 == 0i64); assert(res.1 == 0.75f64); res = modf64(-0.75f64); - assert(res.0 == -0.0f64); + assert(res.0 == -0i64); assert(res.1 == -0.75f64); res = modf64(0.0f64); - assert(res.0 == 0.0f64); + assert(res.0 == 0i64); assert(res.1 == 0.0f64); - assert(sign(res.1) > 0); + assert(signf(res.1) > 0); res = modf64(-0.0f64); - assert(res.0 == -0.0f64); + assert(res.0 == -0i64); assert(res.1 == -0.0f64); - assert(sign(res.1) < 0); + assert(signf(res.1) < 0); + res = modf64(23.50f64); + assert(res.0 == 23i64); + assert(res.1 == 0.50f64); // 32 let res = modf32(1.75f32); - assert(res.0 == 1.0f32); + assert(res.0 == 1i32); assert(res.1 == 0.75f32); res = modf32(0.75f32); - assert(res.0 == 0.0f32); + assert(res.0 == 0i32); assert(res.1 == 0.75f32); res = modf32(-0.75f32); - assert(res.0 == -0.0f32); + assert(res.0 == -0i32); assert(res.1 == -0.75f32); res = modf32(0.0f32); - assert(res.0 == 0.0f32); + assert(res.0 == 0i32); assert(res.1 == 0.0f32); - assert(sign(res.1) > 0); + assert(signf(res.1) > 0); res = modf32(-0.0f32); - assert(res.0 == -0.0f32); - assert(res.0 == -0.0f32); - assert(sign(res.1) < 0); + assert(res.0 == -0i32); + assert(res.1 == -0.0f32); + assert(signf(res.1) < 0); + res = modf32(23.50f32); + assert(res.0 == 23i32); + assert(res.1 == 0.50f32); }; diff --git a/math/ints.ha b/math/ints.ha @@ -0,0 +1,138 @@ +use types; + +// Returns the absolute value of signed integer n. +export fn absi8(n: i8) i8 = { + if (n < 0i8) { + return -n; + } else { + return n; + }; +}; + +// Returns the absolute value of signed integer n. +export fn absi16(n: i16) i16 = { + if (n < 0i16) { + return -n; + } else { + return n; + }; +}; + +// Returns the absolute value of signed integer n. +export fn absi32(n: i32) i32 = { + if (n < 0i32) { + return -n; + } else { + return n; + }; +}; + +// Returns the absolute value of signed integer n. +export fn absi64(n: i64) i64 = { + if (n < 0i64) { + return -n; + } else { + return n; + }; +}; + +// Returns the absolute value of signed integer n. +export fn absi(n: types::integer) i64 = { + return match (n) { + n: i8 => (absi8(n): i64), + n: i16 => (absi16(n): i64), + n: i32 => (absi32(n): i64), + n: i64 => (absi64(n): i64), + }; +}; + +@test fn absi() void = { + assert(absi8(2i8) == 2i8); + assert(absi8(-2i8) == 2i8); + assert(absi16(2i16) == 2i16); + assert(absi16(-2i16) == 2i16); + assert(absi32(2i32) == 2i32); + assert(absi32(-2i32) == 2i32); + assert(absi64(2i64) == 2i64); + assert(absi64(-2i64) == 2i64); + assert(absi(2i8) == 2i64); + assert(absi(-2i8) == 2i64); + assert(absi(2i16) == 2i64); + assert(absi(-2i16) == 2i64); + assert(absi(2i32) == 2i64); + assert(absi(-2i32) == 2i64); + assert(absi(2i64) == 2i64); + assert(absi(-2i64) == 2i64); +}; + +// Return 1 if n is positive, -1 if it's negative and 0 if it's 0. +export fn signi8(n: i8) i8 = { + if (n > 0i8) { + return 1i8; + }; + if (n < 0i8) { + return -1i8; + }; + return 0i8; +}; + +// Return 1 if n is positive, -1 if it's negative and 0 if it's 0. +export fn signi16(n: i16) i16 = { + if (n > 0i16) { + return 1i16; + }; + if (n < 0i16) { + return -1i16; + }; + return 0i16; +}; + +// Return 1 if n is positive, -1 if it's negative and 0 if it's 0. +export fn signi32(n: i32) i32 = { + if (n > 0i32) { + return 1i32; + }; + if (n < 0i32) { + return -1i32; + }; + return 0i32; +}; + +// Return 1 if n is positive, -1 if it's negative and 0 if it's 0. +export fn signi64(n: i64) i64 = { + if (n > 0i64) { + return 1i64; + }; + if (n < 0i64) { + return -1i64; + }; + return 0i64; +}; + +// Return 1 if n is positive, -1 if it's negative and 0 if it's 0. +export fn signi(n: types::integer) i64 = { + return match (n) { + n: i8 => (signi8(n): i64), + n: i16 => (signi16(n): i64), + n: i32 => (signi32(n): i64), + n: i64 => (signi64(n): i64), + }; +}; + +@test fn signi() void = { + assert(signi8(2i8) == 1i8); + assert(signi8(-2i8) == -1i8); + assert(signi8(0i8) == 0i8); + assert(signi16(2i16) == 1i16); + assert(signi16(-2i16) == -1i16); + assert(signi16(0i16) == 0i16); + assert(signi32(2i32) == 1i32); + assert(signi32(-2i32) == -1i32); + assert(signi32(0i32) == 0i32); + assert(signi64(2i64) == 1i64); + assert(signi64(-2i64) == -1i64); + assert(signi64(0i64) == 0i64); + assert(signi(2i16) == 1i64); + assert(signi(-2i16) == -1i64); + assert(signi(0i16) == 0i64); +}; diff --git a/math/math.ha b/math/math.ha @@ -0,0 +1,690 @@ +// Sections of the code below, in particular log() and exp(), are based on Go's +// implementation, which is, in turn, based on FreeBSD's. The original C code, +// as well as the respective comments and constants are from +// /usr/src/lib/msun/src/e_log.c. +// +// The FreeBSD copyright notice: +// ==================================================== +// Copyright (C) 1993 by Sun Microsystems, Inc. All rights reserved. +// +// Developed at SunPro, a Sun Microsystems, Inc. business. +// Permission to use, copy, modify, and distribute this +// software is freely granted, provided that this notice +// is preserved. +// ==================================================== +// +// The Go copyright notice: +// ==================================================== +// Copyright (c) 2009 The Go Authors. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// ==================================================== + +use types; + +// Returns whether x and y are within tol of each other. +export fn eqwithinf64(x: f64, y: f64, tol: f64) bool = { + return absf64(x - y) < tol; +}; + +// Returns whether x and y are within tol of each other. +export fn eqwithinf32(x: f32, y: f32, tol: f32) bool = { + return absf32(x - y) < tol; +}; + +// Returns whether x and y are within tol of each other. +export fn eqwithin(x: types::floating, y: types::floating, + tol: types::floating) bool = { + return match (x) { + n: f64 => eqwithinf64(n, y as f64, tol as f64), + n: f32 => eqwithinf32(n, y as f32, tol as f32), + }; +}; + +@test fn equalwithin() void = { + assert(eqwithin(1.0f64, 2.0f64, 2.0f64)); + assert(eqwithin(1.0f32, 2.0f32, 2.0f32)); + assert(!eqwithin(1.0005f32, 1.0004f32, 0.00001f32)); +}; + +// e - https://oeis.org/A001113 +export def E: f64 = 2.71828182845904523536028747135266249775724709369995957496696763; +// pi - https://oeis.org/A000796 +export def PI: f64 = 3.14159265358979323846264338327950288419716939937510582097494459; +// phi - https://oeis.org/A001622 +export def PHI: f64 = 1.61803398874989484820458683436563811772030917980576286213544862; +// sqrt(2) - https://oeis.org/A002193 +def SQRT_2: f64 = 1.41421356237309504880168872420969807856967187537694807317667974; +// sqrt(e) - https://oeis.org/A019774 +def SQRT_E: f64 = 1.64872127070012814684865078781416357165377610071014801157507931; +// sqrt(pi) - https://oeis.org/A002161 +def SQRT_PI: f64 = 1.77245385090551602729816748334114518279754945612238712821380779; +// sqrt(phi) - https://oeis.org/A139339 +def SQRT_PHI: f64 = 1.27201964951406896425242246173749149171560804184009624861664038; +// ln(2) - https://oeis.org/A002162 +def LN_2: f64 = 0.693147180559945309417232121458176568075500134360255254120680009; +def LN2_HI: f64 = 6.93147180369123816490e-01; +def LN2_LO: f64 = 1.90821492927058770002e-10; +// log_{2}(e) +def LOG2_E: f64 = 1.0f64 / LN_2; +// ln(10) - https://oeis.org/A002392 +def LN_10: f64 = 2.30258509299404568401799145468436420760110148862877297603332790; +// log_{10}(e) +def LOG10_E: f64 = 1.0f64 / LN_10; + +// __ieee754_log(x) +// Return the logarithm of x +// +// Method : +// 1. Argument Reduction: find k and f such that +// x = 2**k * (1+f), +// where sqrt(2)/2 < 1+f < sqrt(2) . +// +// 2. Approximation of log(1+f). +// Let s = f/(2+f) ; based on log(1+f) = log(1+s) - log(1-s) +// = 2s + 2/3 s**3 + 2/5 s**5 + ....., +// = 2s + s*R +// We use a special Reme algorithm on [0,0.1716] to generate +// a polynomial of degree 14 to approximate R. The maximum error +// of this polynomial approximation is bounded by 2**-58.45. In +// other words, +// 2 4 6 8 10 12 14 +// R(z) ~ L1*s +L2*s +L3*s +L4*s +L5*s +L6*s +L7*s +// (the values of L1 to L7 are listed in the program) and +// | 2 14 | -58.45 +// | L1*s +...+L7*s - R(z) | <= 2 +// | | +// Note that 2s = f - s*f = f - hfsq + s*hfsq, where hfsq = f*f/2. +// In order to guarantee error in log below 1ulp, we compute log by +// log(1+f) = f - s*(f - R) (if f is not too large) +// log(1+f) = f - (hfsq - s*(hfsq+R)). (better accuracy) +// +// 3. Finally, log(x) = k*Ln2 + log(1+f). +// = k*Ln2_hi+(f-(hfsq-(s*(hfsq+R)+k*Ln2_lo))) +// Here Ln2 is split into two floating point number: +// Ln2_hi + Ln2_lo, +// where n*Ln2_hi is always exact for |n| < 2000. +// +// Special cases: +// log(x) is NaN with signal if x < 0 (including -INF) ; +// log(+INF) is +INF; log(0) is -INF with signal; +// log(NaN) is that NaN with no signal. +// +// Accuracy: +// according to an error analysis, the error is always less than +// 1 ulp (unit in the last place). +// +// Constants: +// The hexadecimal values are the intended ones for the following +// constants. The decimal values may be used, provided that the +// compiler will convert from decimal to binary accurately enough +// to produce the hexadecimal values shown. + +// Returns the natural logarithm of x. +export fn logf64(x: f64) f64 = { + const L1 = 6.666666666666735130e-01; // 3FE55555 55555593 + const L2 = 3.999999999940941908e-01; // 3FD99999 9997FA04 + const L3 = 2.857142874366239149e-01; // 3FD24924 94229359 + const L4 = 2.222219843214978396e-01; // 3FCC71C5 1D8E78AF + const L5 = 1.818357216161805012e-01; // 3FC74664 96CB03DE + const L6 = 1.531383769920937332e-01; // 3FC39A09 D078C69F + const L7 = 1.479819860511658591e-01; // 3FC2F112 DF3E5244 + + // special cases + if (isnan(x) || isposinf(x)) { + return x; + } else if (x < 0.0f64) { + return NAN; + } else if (x == 0.0f64) { + return -INF; + }; + + // Reduce + const f1_and_ki = frexp(x); + let f1 = f1_and_ki.0; + let ki = f1_and_ki.1; + if (f1 < (SQRT_2 / 2.0f64)) { + f1 *= 2.0f64; + ki -= 1i64; + }; + let f = f1 - 1.0f64; + let k = (ki: f64); + + // Compute + const s = f / (2.0f64 + f); + const s2 = s * s; + const s4 = s2 * s2; + const t1 = s2 * (L1 + s4 * (L3 + s4 * (L5 + s4 * L7))); + const t2 = s4 * (L2 + s4 * (L4 + s4 * L6)); + const R = t1 + t2; + const hfsq = 0.5f64 * f * f; + return k * LN2_HI - ((hfsq - (s * (hfsq + R) + k * LN2_LO)) - f); +}; + +@test fn logf64() void = { + assert(logf64(E) == 1.0f64); + assert(logf64(54.598150033144239078110261202860878402790f64) == 4.0f64); + assert(isnan(logf64(-1.0f64))); + assert(isposinf(logf64(INF))); + assert(isneginf(logf64(0.0f64))); + assert(isnan(logf64(NAN))); +}; + +// exp(x) +// Returns the exponential of x. +// +// Method +// 1. Argument reduction: +// Reduce x to an r so that |r| <= 0.5*ln2 ~ 0.34658. +// Given x, find r and integer k such that +// +// x = k*ln2 + r, |r| <= 0.5*ln2. +// +// Here r will be represented as r = hi-lo for better +// accuracy. +// +// 2. Approximation of exp(r) by a special rational function on +// the interval [0,0.34658]: +// Write +// R(r**2) = r*(exp(r)+1)/(exp(r)-1) = 2 + r*r/6 - r**4/360 + ... +// We use a special Remez algorithm on [0,0.34658] to generate +// a polynomial of degree 5 to approximate R. The maximum error +// of this polynomial approximation is bounded by 2**-59. In +// other words, +// R(z) ~ 2.0 + P1*z + P2*z**2 + P3*z**3 + P4*z**4 + P5*z**5 +// (where z=r*r, and the values of P1 to P5 are listed below) +// and +// | 5 | -59 +// | 2.0+P1*z+...+P5*z - R(z) | <= 2 +// | | +// The computation of exp(r) thus becomes +// 2*r +// exp(r) = 1 + ------- +// R - r +// r*R1(r) +// = 1 + r + ----------- (for better accuracy) +// 2 - R1(r) +// where +// 2 4 10 +// R1(r) = r - (P1*r + P2*r + ... + P5*r ). +// +// 3. Scale back to obtain exp(x): +// From step 1, we have +// exp(x) = 2**k * exp(r) +// +// Special cases: +// exp(INF) is INF, exp(NaN) is NaN; +// exp(-INF) is 0, and +// for finite argument, only exp(0)=1 is exact. +// +// Accuracy: +// according to an error analysis, the error is always less than +// 1 ulp (unit in the last place). +// +// Misc. info. +// For IEEE double +// if x > 7.09782712893383973096e+02 then exp(x) overflow +// if x < -7.45133219101941108420e+02 then exp(x) underflow +// +// Constants: +// The hexadecimal values are the intended ones for the following +// constants. The decimal values may be used, provided that the +// compiler will convert from decimal to binary accurately enough +// to produce the hexadecimal values shown. + +// Returns e^r * 2^k where r = hi - lo and |r| <= (ln(2) / 2). +export fn expmultif64(hi: f64, lo: f64, k: i64) f64 = { + const P1 = 1.66666666666666657415e-01; // 0x3FC55555; 0x55555555 + const P2 = -2.77777777770155933842e-03; // 0xBF66C16C; 0x16BEBD93 + const P3 = 6.61375632143793436117e-05; // 0x3F11566A; 0xAF25DE2C + const P4 = -1.65339022054652515390e-06; // 0xBEBBBD41; 0xC5D26BF1 + const P5 = 4.13813679705723846039e-08; // 0x3E663769; 0x72BEA4D0 + + let r = hi - lo; + let t = r * r; + let c = r - t * (P1 + t * (P2 + t * (P3 + t * (P4 + t * P5)))); + let y = 1.0f64 - ((lo - (r * c) / (2.0f64 - c)) - hi); + return ldexpf64(y, k); +}; + +// Returns e^x. +export fn expf64(x: f64) f64 = { + const overflow = 7.09782712893383973096e+02; + const underflow = -7.45133219101941108420e+02; + const near_zero = 1.0f64 / ((1i64 << 28i64): f64); + + // Special cases + if (isnan(x) || isposinf(x)) { + return x; + } else if (isneginf(x)) { + return 0.0f64; + } else if (x > overflow) { + return INF; + } else if (x < underflow) { + return 0.0f64; + } else if (-near_zero < x && x < near_zero) { + return 1.0f64 + x; + }; + + // Reduce; computed as r = hi - lo for extra precision. + let k = 0i64; + if (x < 0.0f64) { + k = (((LOG2_E * x) - 0.5): i64); + } else if (x > 0.0f64) { + k = (((LOG2_E * x) + 0.5): i64); + }; + let hi = x - ((k: f64) * LN2_HI); + let lo = (k: f64) * LN2_LO; + + // Compute + return expmultif64(hi, lo, k); +}; + +// Returns 2^x. +export fn exp2f64(x: f64) f64 = { + const overflow = 1.0239999999999999e+03; + const underflow = -1.0740e+03; + + // Special cases + if (isnan(x) || isposinf(x)) { + return x; + } else if (isneginf(x)) { + return 0.0f64; + } else if (x > overflow) { + return INF; + } else if (x < underflow) { + return 0.0f64; + }; + + // Argument reduction; x = r×lg(e) + k with |r| ≤ ln(2)/2. + // Computed as r = hi - lo for extra precision. + let k = 0i64; + if (x > 0.0f64) { + k = ((x + 0.5): i64); + } else if (x < 0.0f64) { + k = ((x - 0.5): i64); + }; + let t = x - (k: f64); + let hi = t * LN2_HI; + let lo = -t * LN2_LO; + + // Compute + return expmultif64(hi, lo, k); +}; + +@test fn expf64() void = { + assert(expf64(1.0f64) == E); + assert(isnan(expf64(NAN))); + assert(isinf(expf64(INF))); + assert(expf64(-INF) == 0.0f64); + assert(isinf(expf64(99999.0f64))); + assert(expf64(-99999.0f64) == 0.0f64); + assert(expf64(0.5e-20) == 1.0f64); +}; + +@test fn exp2f64() void = { + assert(exp2f64(0.0f64) == 1.0f64); + assert(exp2f64(3.0f64) == 8.0f64); + assert(exp2f64(-2.0f64) == 0.25f64); + assert(!isinf(exp2f64(256.0f64))); + assert(isinf(exp2f64(99999.0f64))); + assert(exp2f64(-99999.0f64) == 0.0f64); + assert(isnan(exp2f64(NAN))); + assert(isinf(exp2f64(INF))); + assert(exp2f64(-INF) == 0.0f64); +}; + +// __ieee754_sqrt(x) +// Return correctly rounded sqrt. +// ----------------------------------------- +// | Use the hardware sqrt if you have one | +// ----------------------------------------- +// Method: +// Bit by bit method using integer arithmetic. (Slow, but portable) +// 1. Normalization +// Scale x to y in [1,4) with even powers of 2: +// find an integer k such that 1 <= (y=x*2**(2k)) < 4, then +// sqrt(x) = 2**k * sqrt(y) +// 2. Bit by bit computation +// Let q = sqrt(y) truncated to i bit after binary point (q = 1), +// i 0 +// i+1 2 +// s = 2*q , and y = 2 * ( y - q ). (1) +// i i i i +// +// To compute q from q , one checks whether +// i+1 i +// +// -(i+1) 2 +// (q + 2 ) <= y. (2) +// i +// -(i+1) +// If (2) is false, then q = q ; otherwise q = q + 2 . +// i+1 i i+1 i +// +// With some algebraic manipulation, it is not difficult to see +// that (2) is equivalent to +// -(i+1) +// s + 2 <= y (3) +// i i +// +// The advantage of (3) is that s and y can be computed by +// i i +// the following recurrence formula: +// if (3) is false +// +// s = s , y = y ; (4) +// i+1 i i+1 i +// +// otherwise, +// -i -(i+1) +// s = s + 2 , y = y - s - 2 (5) +// i+1 i i+1 i i +// +// One may easily use induction to prove (4) and (5). +// Note. Since the left hand side of (3) contain only i+2 bits, +// it is not necessary to do a full (53-bit) comparison +// in (3). +// 3. Final rounding +// After generating the 53 bits result, we compute one more bit. +// Together with the remainder, we can decide whether the +// result is exact, bigger than 1/2ulp, or less than 1/2ulp +// (it will never equal to 1/2ulp). +// The rounding mode can be detected by checking whether +// huge + tiny is equal to huge, and whether huge - tiny is +// equal to huge for some floating point number "huge" and "tiny". + +// Returns the square root of x. +export fn sqrtf64(x: f64) f64 = { + // Special cases + if (x == 0.0f64) { + return x; + } else if (isnan(x) || isposinf(x)) { + return x; + } else if (x < 0.0f64) { + return NAN; + }; + + let bits = f64bits(x); + + // Normalize x + let exp = (((bits >> F64_MANTISSA_BITS) & F64_EXPONENT_MASK): i64); + if (exp == 0i64) { + // Subnormal x + for (bits & (1 << F64_MANTISSA_BITS) == 0) { + bits <<= 1; + exp -= 1; + }; + exp += 1; + }; + // Unbias exponent + exp -= (F64_EXPONENT_BIAS: i64); + bits = bits & ~(F64_EXPONENT_MASK << F64_MANTISSA_BITS); + bits = bits | (1u64 << (F64_MANTISSA_BITS: u64)); + // Odd exp, double x to make it even + if (exp & 1i64 == 1i64) { + bits <<= 1; + }; + // exp = exp/2, exponent of square root + exp >>= 1; + // Generate sqrt(x) bit by bit + bits <<= 1; + // q = sqrt(x) + let q = 0u64; + let s = 0u64; + // r = moving bit from MSB to LSB + let r = ((1u64 << (F64_MANTISSA_BITS + 1u64)): u64); + for (r != 0) { + let t = s + r; + if (t <= bits) { + s = t + r; + bits -= t; + q += r; + }; + bits <<= 1u64; + r >>= 1u64; + }; + // Final rounding + if (bits != 0) { + // Remainder, result not exact + // Round according to extra bit + q += q & 1; + }; + // significand + biased exponent + bits = (q >> 1) + ( + ((exp - 1i64 + (F64_EXPONENT_BIAS: i64)): u64) << + F64_MANTISSA_BITS); + return f64frombits(bits); +}; + +@test fn sqrt() void = { + assert(sqrtf64(2.0f64) == SQRT_2); + assert(sqrtf64(4.0f64) == 2.0f64); + assert(sqrtf64(16.0f64) == 4.0f64); + assert(sqrtf64(65536.0f64) == 256.0f64); + assert(sqrtf64(powf64(123.0f64, 2.0f64)) == 123.0f64); + assert(sqrtf64(0.0f64) == 0.0f64); + assert(isnan(sqrtf64(NAN))); + assert(isposinf(sqrtf64(INF))); + assert(isnan(sqrtf64(-2.0f64))); +}; + +fn is_f64_odd_int(x: f64) bool = { + let x_int_frac = modf64(x); + let x_int = x_int_frac.0; + let x_frac = x_int_frac.1; + let has_no_frac = (x_frac == 0.0f64); + let is_odd = ((x_int & 1i64) == 1i64); + return has_no_frac && is_odd; +}; + +// Returns x^p. +export fn powf64(x: f64, p: f64) f64 = { + if (x == 1.0f64 || p == 0.0f64) { + return 1.0f64; + } else if (p == 1.0f64) { + return x; + } else if (isnan(x)) { + return NAN; + } else if (isnan(p)) { + return NAN; + } else if (x == 0.0f64) { + if (p < 0.0f64) { + if (is_f64_odd_int(p)) { + return copysignf64(INF, x); + } else { + return INF; + }; + } else if (p > 0.0f64) { + if (is_f64_odd_int(p)) { + return x; + } else { + return 0.0f64; + }; + }; + } else if (isinf(p)) { + if (x == -1.0f64) { + return 1.0f64; + } else if ((absf64(x) < 1.0f64) == isposinf(p)) { + return 0.0f64; + }; + return INF; + } else if (isinf(x)) { + if (isneginf(x)) { + return powf64(-0.0f64, -p); + } else if (p < 0.0f64) { + return 0.0f64; + } else if (p > 0.0f64) { + return INF; + }; + } else if (p == 0.5f64) { + return sqrtf64(x); + } else if (p == -0.5f64) { + return 1.0f64 / sqrtf64(x); + }; + + let x_parts = frexp(x); + let x_mantissa = x_parts.0; + let x_exp = x_parts.1; + + let p_int_frac = modf64(absf64(p)); + let p_int = p_int_frac.0; + let p_frac = p_int_frac.1; + + let res_mantissa = 1.0f64; + let res_exp = 0i64; + + // The method used later in this function doesn't apply to fractional + // powers, so we have to handle these separately with + // x^p = e^{p * ln(x)} + if (p_frac != 0.0f64) { + if (p_frac > 0.5f64) { + p_frac -= 1.0f64; + p_int += 1i64; + }; + res_mantissa = expf64(p_frac * logf64(x)); + }; + + // Repeatedly square our number x, for each bit in our power p. + // If the current bit is 1 in p, add the respective power of x to our + // result. + for (let i = p_int; i != 0; i >>= 1) { + // Check for over/underflow. + if (x_exp <= -1i64 << (F64_EXPONENT_BITS: i64)) { + return 0.0f64; + }; + if (x_exp >= 1i64 << (F64_EXPONENT_BITS: i64)) { + return INF; + }; + // Perform squaring. + if (i & 1i64 == 1i64) { + res_mantissa *= x_mantissa; + res_exp += x_exp; + }; + x_mantissa *= x_mantissa; + x_exp <<= 1; + // Correct mantisa to be in [0.5, 1). + if (x_mantissa < 0.5f64) { + x_mantissa += x_mantissa; + x_exp -= 1; + }; + }; + + if (p < 0.0f64) { + res_mantissa = 1.0f64 / res_mantissa; + res_exp = -res_exp; + }; + + let res = ldexpf64(res_mantissa, res_exp); + return res; +}; + +@test fn powf64() void = { + // Positive integer + assert(powf64(2.0f64, 2.0f64) == 4.0f64); + assert(powf64(3.0f64, 3.0f64) == 27.0f64); + // Very large positive integer + assert(!isinf(powf64(2.0f64, 1020.0f64))); + assert(isinf(powf64(2.0f64, 1050.0f64))); + // Negative integer + assert(powf64(2.0f64, -1.0f64) == 0.5f64); + assert(powf64(2.0f64, -2.0f64) == 0.25f64); + // Very small negative integer + assert(powf64(2.0f64, -1020.0f64) > 0.0f64); + assert(powf64(2.0f64, -1080.0f64) == 0.0f64); + // Positive fractional powers + assert(eqwithin(powf64(2.0f64, 1.5f64), + 2.8284271247461900976033774f64, + 1e-10f64)); + assert(eqwithin(powf64(2.0f64, 5.5f64), + 45.254833995939041561654039f64, + 1e-10f64)); + // Negative fractional powers + assert(eqwithin(powf64(2.0f64, -1.5f64), + 0.3535533905932737622004221f64, + 1e-10f64)); + assert(eqwithin(powf64(2.0f64, -5.5f64), + 0.0220970869120796101375263f64, + 1e-10f64)); + + // Special cases + // pow(x, ±0) = 1 for any x + assert(powf64(123.0f64, 0.0f64) == 1.0f64); + // pow(1, y) = 1 for any y + assert(powf64(1.0f64, 123.0f64) == 1.0f64); + // pow(x, 1) = x for any x + assert(powf64(123.0f64, 1.0f64) == 123.0f64); + // pow(NaN, y) = NaN + assert(isnan(powf64(NAN, 123.0f64))); + // pow(x, NaN) = NaN + assert(isnan(powf64(123.0f64, NAN))); + // pow(±0, y) = ±Inf for y an odd integer < 0 + assert(isposinf(powf64(0.0f64, -3.0f64))); + assert(isneginf(powf64(-0.0f64, -3.0f64))); + // pow(±0, -Inf) = +Inf + assert(isposinf(powf64(0.0f64, -INF))); + assert(isposinf(powf64(-0.0f64, -INF))); + // pow(±0, +Inf) = +0 + assert(powf64(0.0f64, INF) == 0.0f64); + assert(powf64(-0.0f64, INF) == 0.0f64); + // pow(±0, y) = +Inf for finite y < 0 and not an odd integer + assert(isposinf(powf64(0.0f64, -2.0f64))); + assert(isposinf(powf64(-0.0f64, -2.0f64))); + //pow(±0, y) = ±0 for y an odd integer > 0 + assert(powf64(0.0f64, 123.0f64) == 0.0f64); + let neg_zero = powf64(-0.0f64, 123.0f64); + assert(neg_zero == 0.0f64); + assert(isnegative(neg_zero)); + // pow(±0, y) = +0 for finite y > 0 and not an odd integer + assert(powf64(0.0f64, 8.0f64) == 0.0f64); + // pow(-1, ±Inf) = 1 + assert(powf64(-1.0f64, INF) == 1.0f64); + assert(powf64(-1.0f64, -INF) == 1.0f64); + // pow(x, +Inf) = +Inf for |x| > 1 + assert(isposinf(powf64(123.0f64, INF))); + // pow(x, -Inf) = +0 for |x| > 1 + assert(powf64(123.0f64, -INF) == 0.0f64); + // pow(x, +Inf) = +0 for |x| < 1 + assert(powf64(0.5f64, INF) == 0.0f64); + assert(powf64(-0.5f64, INF) == 0.0f64); + // pow(x, -Inf) = +Inf for |x| < 1 + assert(isposinf(powf64(0.5f64, -INF))); + assert(isposinf(powf64(-0.5f64, -INF))); + // pow(+Inf, y) = +Inf for y > 0 + assert(isposinf(powf64(INF, 123.0f64))); + // pow(+Inf, y) = +0 for y < 0 + assert(powf64(INF, -1.0f64) == 0.0f64); + // pow(-Inf, y) = pow(-0, -y) + assert(powf64(-INF, 123.0f64) == powf64(-0.0f64, -123.0f64)); + // pow(x, y) = NaN for finite x < 0 and finite non-integer y + assert(isnan(powf64(-2.0f64, 1.23f64))); + // sqrt + assert(powf64(4.0f64, 0.5f64) == sqrtf64(4.0f64)); + assert(powf64(4.0f64, 0.5f64) == 2.0f64); + assert(powf64(4.0f64, -0.5f64) == (1.0f64 / sqrtf64(4.0f64))); + assert(powf64(4.0f64, -0.5f64) == (1.0f64 / 2.0f64)); +}; diff --git a/scripts/gen-stdlib b/scripts/gen-stdlib @@ -550,7 +550,7 @@ linux_vdso() { math() { printf '# math\n' gen_srcs math \ - floats.ha + math.ha floats.ha ints.ha gen_ssa math types } diff --git a/stdlib.mk b/stdlib.mk @@ -844,9 +844,11 @@ $(HARECACHE)/linux/vdso/linux_vdso.ssa: $(stdlib_linux_vdso_srcs) $(stdlib_rt) $ # math # math stdlib_math_srcs= \ - $(STDLIB)/math/floats.ha + $(STDLIB)/math/math.ha \ + $(STDLIB)/math/floats.ha \ + $(STDLIB)/math/ints.ha -$(HARECACHE)/math/math.ssa: $(stdlib_math_srcs) $(stdlib_rt) +$(HARECACHE)/math/math.ssa: $(stdlib_math_srcs) $(stdlib_rt) $(stdlib_types) @printf 'HAREC \t$@\n' @mkdir -p $(HARECACHE)/math @HARECACHE=$(HARECACHE) $(HAREC) $(HAREFLAGS) -o $@ -Nmath \ @@ -2061,9 +2063,11 @@ $(TESTCACHE)/linux/vdso/linux_vdso.ssa: $(testlib_linux_vdso_srcs) $(testlib_rt) # math # math testlib_math_srcs= \ - $(STDLIB)/math/floats.ha + $(STDLIB)/math/math.ha \ + $(STDLIB)/math/floats.ha \ + $(STDLIB)/math/ints.ha -$(TESTCACHE)/math/math.ssa: $(testlib_math_srcs) $(testlib_rt) +$(TESTCACHE)/math/math.ssa: $(testlib_math_srcs) $(testlib_rt) $(testlib_types) @printf 'HAREC \t$@\n' @mkdir -p $(TESTCACHE)/math @HARECACHE=$(TESTCACHE) $(HAREC) $(TESTHAREFLAGS) -o $@ -Nmath \