Skip to content

Commit

Permalink
feat(stdlib): Add isFinite, isClose, sin, cos, tan to Float…
Browse files Browse the repository at this point in the history
…64 (#2166)
  • Loading branch information
spotandjake authored Oct 7, 2024
1 parent 0894dc5 commit fa728d2
Show file tree
Hide file tree
Showing 3 changed files with 521 additions and 0 deletions.
208 changes: 208 additions & 0 deletions compiler/test/stdlib/float64.test.gr
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,16 @@ assert !(5.0d >= 22.0d)
assert !(5.0d < -17.0d)
assert !(5.0d <= 4.0d)

// isFinite
assert Float64.isFinite(NaNd) == false
assert Float64.isFinite(Infinityd) == false
assert Float64.isFinite(-Infinityd) == false
assert Float64.isFinite(1.0d)
assert Float64.isFinite(0.0d)
assert Float64.isFinite(-1.0d)
assert Float64.isFinite(25.76d)
assert Float64.isFinite(-25.00d)

// isNaN
assert Float64.isNaN(NaNd)
assert Float64.isNaN(1.0d) == false
Expand Down Expand Up @@ -211,3 +221,201 @@ assert Float64.isNaN(Float64.copySign(NaNd, 1.0d))
assert Float64.isNaN(Float64.copySign(NaNd, -1.0d))
assert Float64.copySign(1.0d, NaNd) == 1.0d
assert Float64.copySign(1.0d, -NaNd) == -1.0d

// Float64.isClose
assert Float64.isClose(1.0d, 1.0d)
assert Float64.isClose(
1.0d,
1.0d,
relativeTolerance=0.5d,
absoluteTolerance=0.5d
)
assert Float64.isClose(
1.0d,
1.0d,
relativeTolerance=0.0d,
absoluteTolerance=0.0d
)
assert Float64.isClose(0.0d, 0.0d)
assert Float64.isClose(
0.0d,
0.0d,
relativeTolerance=0.5d,
absoluteTolerance=0.5d
)
assert Float64.isClose(
0.0d,
0.0d,
relativeTolerance=0.0d,
absoluteTolerance=0.0d
)
assert Float64.isClose(0.0d, 0.1d) == false
assert Float64.isClose(0.0d, 0.000000001d) == false
assert Float64.isClose(0.0d, 0.00000001d, absoluteTolerance=1e-9d) == false
assert Float64.isClose(0.0d, 0.000000001d, absoluteTolerance=1e-9d)
assert Float64.isClose(-0.0d, 0.000000001d) == false
assert Float64.isClose(-0.0d, 0.00000001d, absoluteTolerance=1e-9d) == false
assert Float64.isClose(-0.0d, 0.000000001d, absoluteTolerance=1e-9d)
assert Float64.isClose(1.1d, 1.10000001d, absoluteTolerance=1e-9d) == false
assert Float64.isClose(1.1d, 1.100000001d, absoluteTolerance=1e-9d)
assert Float64.isClose(Infinityd, Infinityd)
assert Float64.isClose(-Infinityd, -Infinityd)
assert Float64.isClose(Infinityd, -Infinityd) == false
assert Float64.isClose(NaNd, NaNd) == false

// Float64.sin - 0 to pi/2
assert Float64.sin(0.0d) == 0.0d
assert Float64.isClose(Float64.sin(Float64.pi / 6.0d), 0.5d)
assert Float64.isClose(
Float64.sin(Float64.pi / 4.0d),
Float64.sqrt(2.0d) / 2.0d
)
assert Float64.isClose(
Float64.sin(Float64.pi / 3.0d),
Float64.sqrt(3.0d) / 2.0d
)
assert Float64.isClose(Float64.sin(Float64.pi / 2.0d), 1.0d)
// Float64.sin - pi/2 to 2pi
assert Float64.isClose(
Float64.sin(2.0d * Float64.pi / 3.0d),
Float64.sqrt(3.0d) / 2.0d
)
assert Float64.isClose(
Float64.sin(3.0d * Float64.pi / 4.0d),
Float64.sqrt(2.0d) / 2.0d
)
assert Float64.isClose(Float64.sin(5.0d * Float64.pi / 6.0d), 0.5d)
// Note: This has an absolute error of 1e-15 because `Float64.pi` is not exact
assert Float64.isClose(Float64.sin(Float64.pi), 0.0d, absoluteTolerance=1e-15d)
// Float64.sin - 2pi to 3pi/2
assert Float64.isClose(Float64.sin(7.0d * Float64.pi / 6.0d), -0.5d)
assert Float64.isClose(
Float64.sin(5.0d * Float64.pi / 4.0d),
Float64.sqrt(2.0d) / -2.0d
)
assert Float64.isClose(
Float64.sin(4.0d * Float64.pi / 3.0d),
Float64.sqrt(3.0d) / -2.0d
)
assert Float64.isClose(Float64.sin(3.0d * Float64.pi / 2.0d), -1.0d)
// Float64.sin - 3pi/2 to 0
assert Float64.isClose(
Float64.sin(5.0d * Float64.pi / 3.0d),
Float64.sqrt(3.0d) / -2.0d
)
assert Float64.isClose(
Float64.sin(7.0d * Float64.pi / 4.0d),
Float64.sqrt(2.0d) / -2.0d
)
assert Float64.isClose(Float64.sin(11.0d * Float64.pi / 6.0d), -0.5d)
// Note: This has an absolute error of 1e-15 because `Float64.pi` is not exact
assert Float64.isClose(
Float64.sin(2.0d * Float64.pi),
0.0d,
absoluteTolerance=1e-15d
)
// Float64.sin - special cases
assert Float64.sin(0.5d) == Float64.sin(0.5d)
assert Float64.sin(0.25d) == Float64.sin(0.25d)
assert Float64.isClose( // Note: We lose a lot of precision here do to ieee754 representation
Float64.sin(1.7976931348623157e+308d),
0.0049619d,
absoluteTolerance=1e7d
) // Max F64
assert Float64.isClose(
Float64.sin(-1.7976931348623157e+308d),
0.00496d,
absoluteTolerance=1e7d
) // Min F64
assert Float64.isNaN(Float64.sin(Infinityd))
assert Float64.isNaN(Float64.sin(-Infinityd))
assert Float64.isNaN(Float64.sin(NaNd))

// Float64.cos - 0 to pi/2
assert Float64.cos(0.0d) == 1.0d
assert Float64.isClose(
Float64.cos(Float64.pi / 6.0d),
Float64.sqrt(3.0d) / 2.0d
)
assert Float64.isClose(
Float64.cos(Float64.pi / 4.0d),
Float64.sqrt(2.0d) / 2.0d
)
assert Float64.isClose(Float64.cos(Float64.pi / 3.0d), 0.5d)
// Note: This has an absolute error of 1e-15 because `Float64.pi` is not exact
assert Float64.isClose(
Float64.cos(Float64.pi / 2.0d),
0.0d,
absoluteTolerance=1e-15d
)
// Float64.cos - pi/2 to 2pi
assert Float64.isClose(Float64.cos(2.0d * Float64.pi / 3.0d), -0.5d)
assert Float64.isClose(
Float64.cos(3.0d * Float64.pi / 4.0d),
Float64.sqrt(2.0d) / -2.0d
)
assert Float64.isClose(
Float64.cos(5.0d * Float64.pi / 6.0d),
Float64.sqrt(3.0d) / -2.0d
)
assert Float64.isClose(Float64.cos(Float64.pi), -1.0d)
// Float64.cos - 2pi to 3pi/2
assert Float64.isClose(
Float64.cos(7.0d * Float64.pi / 6.0d),
Float64.sqrt(3.0d) / -2.0d
)
assert Float64.isClose(
Float64.cos(5.0d * Float64.pi / 4.0d),
Float64.sqrt(2.0d) / -2.0d
)
assert Float64.isClose(Float64.cos(4.0d * Float64.pi / 3.0d), -0.5d)
// Note: This has an absolute error of 1e-15 because `Float64.pi` is not exact
assert Float64.isClose(
Float64.cos(3.0d * Float64.pi / 2.0d),
0.0d,
absoluteTolerance=1e-15d
)
// Float64.cos - 3pi/2 to 0
assert Float64.isClose(Float64.cos(5.0d * Float64.pi / 3.0d), 0.5d)
assert Float64.isClose(
Float64.cos(7.0d * Float64.pi / 4.0d),
Float64.sqrt(2.0d) / 2.0d
)
assert Float64.isClose(
Float64.cos(11.0d * Float64.pi / 6.0d),
Float64.sqrt(3.0d) / 2.0d
)
assert Float64.isClose(Float64.cos(2.0d * Float64.pi), 1.0d)
// Float64.cos - special cases
assert Float64.cos(0.5d) == Float64.cos(0.5d)
assert Float64.cos(0.25d) == Float64.cos(0.25d)
assert Float64.isNaN(Float64.cos(Infinityd))
assert Float64.isNaN(Float64.cos(-Infinityd))
assert Float64.isNaN(Float64.cos(NaNd))

// Float64.tan - base cases
assert Float64.tan(0.0d) == 0.0d
assert Float64.isClose(
Float64.tan(Float64.pi / 6.0d),
1.0d / Float64.sqrt(3.0d)
)
assert Float64.isClose(Float64.tan(Float64.pi / 4.0d), 1.0d)
assert Float64.isClose(Float64.tan(Float64.pi / 3.0d), Float64.sqrt(3.0d))
// Note: one might expect this to produce infinity but instead we produce 16331239353195370 because pi can not be represented accurately in iee754, This logic follows c
assert Float64.isClose(Float64.tan(Float64.pi / 2.0d), 16331239353195370.0d)
// Float64.tan - special cases
assert Float64.tan(0.5d) == Float64.tan(0.5d)
assert Float64.tan(0.25d) == Float64.tan(0.25d)
assert Float64.isClose( // Note: We lose a lot of precision here do to ieee754 representation
Float64.tan(1.7976931348623157e+308d),
-0.00496201587d,
absoluteTolerance=1e7d
) // Max F64
assert Float64.isClose(
Float64.tan(-1.7976931348623157e+308d),
-0.00496201587d,
absoluteTolerance=1e7d
) // Max F64
assert Float64.isNaN(Float64.tan(Infinityd))
assert Float64.isNaN(Float64.tan(-Infinityd))
assert Float64.isNaN(Float64.tan(NaNd))
108 changes: 108 additions & 0 deletions stdlib/float64.gr
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ use Numbers.{
coerceNumberToFloat64 as fromNumber,
coerceFloat64ToNumber as toNumber,
}
from "runtime/math/trig" include Trig
use Trig.{ sin, cos, tan }

@unsafe
let _VALUE_OFFSET = 8n
Expand Down Expand Up @@ -269,6 +271,29 @@ provide let (>=) = (x: Float64, y: Float64) => {
xv >= yv
}

/**
* Checks if a float is finite.
* All values are finite exept for NaN, infinity or negative infinity.
*
* @param x: The number to check
* @returns `true` if the value is finite or `false` otherwise
*
* @example Float64.isFinite(0.5d)
* @example Float64.isFinite(1.0d)
* @example Float64.isFinite(Infinityd) == false
* @example Float64.isFinite(-Infinityd) == false
* @example Float64.isFinite(NaNd) == false
*
* @since v0.7.0
*/
@unsafe
provide let isFinite = (x: Float64) => {
// uses the fact that all finite floats minus themselves are zero
// (NaN - NaN == NaN, inf - inf == NaN,
// -inf - -inf == NaN, inf - -inf == inf, -inf - inf == -inf)
x - x == 0.0d
}

/**
* Checks if the value is a float NaN value (Not A Number).
*
Expand Down Expand Up @@ -485,3 +510,86 @@ provide let copySign = (x: Float64, y: Float64) => {
let ptr = newFloat64(WasmF64.copySign(xv, yv))
WasmI32.toGrain(ptr): Float64
}

/**
* Determines whether two values are considered close to each other using a relative and absolute tolerance.
*
* @param a: The first value
* @param b: The second value
* @param relativeTolerance: The maximum tolerance to use relative to the larger absolute value `a` or `b`
* @param absoluteTolerance: The absolute tolerance to use, regardless of the values of `a` or `b`
* @returns `true` if the values are considered close to each other or `false` otherwise
*
* @example Float64.isClose(1.233d, 1.233d)
* @example Float64.isClose(1.233d, 1.233000001d)
* @example Float64.isClose(8.005d, 8.450d, absoluteTolerance=0.5d)
* @example Float64.isClose(4.0d, 4.1d, relativeTolerance=0.025d)
* @example Float64.isClose(1.233d, 1.24d) == false
* @example Float64.isClose(1.233d, 1.4566d) == false
* @example Float64.isClose(8.005d, 8.450d, absoluteTolerance=0.4d) == false
* @example Float64.isClose(4.0d, 4.1d, relativeTolerance=0.024d) == false
*
* @since v0.7.0
*/
provide let isClose = (a, b, relativeTolerance=1e-9d, absoluteTolerance=0.0d) => {
if (a == b) {
true
} else if (isFinite(a) && isFinite(b)) {
abs(a - b) <=
max(relativeTolerance * max(abs(a), abs(b)), absoluteTolerance)
} else {
// NaN and infinities which were not equal
false
}
}

/**
* Computes the sine of a float (in radians).
*
* @param radians: The input in radians
* @returns The computed sine
*
* @example Float64.sin(0.0d) == 0.0d
*
* @since v0.7.0
*/
@unsafe
provide let sin = (radians: Float64) => {
let xval = WasmF64.load(WasmI32.fromGrain(radians), _VALUE_OFFSET)
let value = sin(xval)
WasmI32.toGrain(newFloat64(value)): Float64
}

/**
* Computes the cosine of a float (in radians).
*
* @param radians: The input in radians
* @returns The computed cosine
*
* @example Float64.cos(0.0d) == 1.0d
*
* @since v0.7.0
*/
@unsafe
provide let cos = (radians: Float64) => {
let xval = WasmF64.load(WasmI32.fromGrain(radians), _VALUE_OFFSET)
let value = cos(xval)
WasmI32.toGrain(newFloat64(value)): Float64
}

/**
* Computes the tangent of a number (in radians).
*
* @param radians: The input in radians
* @returns The computed tangent
*
* @example Float64.tan(0.0d) == 0.0d
*
* @since v0.7.0
*/
@unsafe
provide let tan = (radians: Float64) => {
let xval = WasmF64.load(WasmI32.fromGrain(radians), _VALUE_OFFSET)
let value = tan(xval)
WasmI32.toGrain(newFloat64(value)): Float64
}
Loading

0 comments on commit fa728d2

Please sign in to comment.