Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(stdlib): Add isFinite, isClose, sin, cos, tan to Float64 #2166

Merged
merged 3 commits into from
Oct 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading