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 Float32 #2168

Merged
merged 1 commit 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
228 changes: 228 additions & 0 deletions compiler/test/stdlib/float32.test.gr
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,16 @@ assert compare(nan, nan) == 0
assert compare(1.0f, nan) > 0
assert compare(nan, 1.0f) < 0

// isFinite
assert Float32.isFinite(NaNf) == false
assert Float32.isFinite(Infinityf) == false
assert Float32.isFinite(-Infinityf) == false
assert Float32.isFinite(1.0f)
assert Float32.isFinite(0.0f)
assert Float32.isFinite(-1.0f)
assert Float32.isFinite(25.76f)
assert Float32.isFinite(-25.00f)

// isNaN
assert Float32.isNaN(NaNf)
assert Float32.isNaN(1.0f) == false
Expand Down Expand Up @@ -205,3 +215,221 @@ assert Float32.isNaN(Float32.copySign(NaNf, 1.0f))
assert Float32.isNaN(Float32.copySign(NaNf, -1.0f))
assert Float32.copySign(1.0f, NaNf) == 1.0f
assert Float32.copySign(1.0f, -NaNf) == -1.0f

// Float32.isClose
assert Float32.isClose(1.0f, 1.0f)
assert Float32.isClose(
1.0f,
1.0f,
relativeTolerance=0.5f,
absoluteTolerance=0.5f
)
assert Float32.isClose(
1.0f,
1.0f,
relativeTolerance=0.0f,
absoluteTolerance=0.0f
)
assert Float32.isClose(0.0f, 0.0f)
assert Float32.isClose(
0.0f,
0.0f,
relativeTolerance=0.5f,
absoluteTolerance=0.5f
)
assert Float32.isClose(
0.0f,
0.0f,
relativeTolerance=0.0f,
absoluteTolerance=0.0f
)
assert Float32.isClose(0.0f, 0.1f) == false
assert Float32.isClose(0.0f, 0.000000001f) == false
assert Float32.isClose(0.0f, 0.00000001f, absoluteTolerance=1e-9f) == false
assert Float32.isClose(0.0f, 0.000000001f, absoluteTolerance=1e-9f)
assert Float32.isClose(-0.0f, 0.000000001f) == false
assert Float32.isClose(-0.0f, 0.00000001f, absoluteTolerance=1e-9f) == false
assert Float32.isClose(-0.0f, 0.000000001f, absoluteTolerance=1e-9f)
assert Float32.isClose(1.1f, 1.1000001f, absoluteTolerance=1e-10f) == false
assert Float32.isClose(1.1f, 1.100000001f, absoluteTolerance=1e-9f)
assert Float32.isClose(Infinityf, Infinityf)
assert Float32.isClose(-Infinityf, -Infinityf)
assert Float32.isClose(Infinityf, -Infinityf) == false
assert Float32.isClose(NaNf, NaNf) == false

// Float32.sin - 0 to pi/2
assert Float32.sin(0.0f) == 0.0f
assert Float32.isClose(Float32.sin(Float32.pi / 6.0f), 0.5f)
assert Float32.isClose(
Float32.sin(Float32.pi / 4.0f),
Float32.sqrt(2.0f) / 2.0f
)
assert Float32.isClose(
Float32.sin(Float32.pi / 3.0f),
Float32.sqrt(3.0f) / 2.0f,
absoluteTolerance=1e-5f
)
assert Float32.isClose(Float32.sin(Float32.pi / 2.0f), 1.0f)
// Float32.sin - pi/2 to 2pi
assert Float32.isClose(
Float32.sin(2.0f * Float32.pi / 3.0f),
Float32.sqrt(3.0f) / 2.0f
)
assert Float32.isClose(
Float32.sin(3.0f * Float32.pi / 4.0f),
Float32.sqrt(2.0f) / 2.0f
)
assert Float32.isClose(
Float32.sin(5.0f * Float32.pi / 6.0f),
0.5f,
absoluteTolerance=1e-5f
)
// Note: This has an absolute error of 1e-15 because `Float32.pi` is not exact
assert Float32.isClose(Float32.sin(Float32.pi), 0.0f, absoluteTolerance=1e-6f)
// Float32.sin - 2pi to 3pi/2
assert Float32.isClose(
Float32.sin(7.0f * Float32.pi / 6.0f),
-0.5f,
absoluteTolerance=1e-5f
)
assert Float32.isClose(
Float32.sin(5.0f * Float32.pi / 4.0f),
Float32.sqrt(2.0f) / -2.0f,
absoluteTolerance=1e-5f
)
assert Float32.isClose(
Float32.sin(4.0f * Float32.pi / 3.0f),
Float32.sqrt(3.0f) / -2.0f,
absoluteTolerance=1e-5f
)
assert Float32.isClose(Float32.sin(3.0f * Float32.pi / 2.0f), -1.0f)
// Float32.sin - 3pi/2 to 0
assert Float32.isClose(
Float32.sin(5.0f * Float32.pi / 3.0f),
Float32.sqrt(3.0f) / -2.0f,
absoluteTolerance=1e-5f
)
assert Float32.isClose(
Float32.sin(7.0f * Float32.pi / 4.0f),
Float32.sqrt(2.0f) / -2.0f,
absoluteTolerance=1e-5f
)
assert Float32.isClose(
Float32.sin(11.0f * Float32.pi / 6.0f),
-0.5f,
absoluteTolerance=1e-5f
)
// Note: This has an absolute error of 1e-5 because `Float32.pi` is not exact
assert Float32.isClose(
Float32.sin(2.0f * Float32.pi),
0.0f,
absoluteTolerance=1e-5f
)
// Float32.sin - special cases
assert Float32.sin(0.5f) == Float32.sin(0.5f)
assert Float32.sin(0.25f) == Float32.sin(0.25f)
assert Float32.isNaN(Float32.sin(Infinityf))
assert Float32.isNaN(Float32.sin(-Infinityf))
assert Float32.isNaN(Float32.sin(NaNf))

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

// Float32.tan - base cases
assert Float32.tan(0.0f) == 0.0f
assert Float32.isClose(
Float32.tan(Float32.pi / 6.0f),
1.0f / Float32.sqrt(3.0f)
)
assert Float32.isClose(Float32.tan(Float32.pi / 4.0f), 1.0f)
assert Float32.isClose(
Float32.tan(Float32.pi / 3.0f),
Float32.sqrt(3.0f),
absoluteTolerance=1e-5f
)
// Float32.tan - special cases
assert Float32.tan(0.5f) == Float32.tan(0.5f)
assert Float32.tan(0.25f) == Float32.tan(0.25f)
assert Float32.isNaN(Float32.tan(Infinityf))
assert Float32.isNaN(Float32.tan(-Infinityf))
assert Float32.isNaN(Float32.tan(NaNf))
111 changes: 111 additions & 0 deletions stdlib/float32.gr
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ use Numbers.{
coerceNumberToFloat32 as fromNumber,
coerceFloat32ToNumber as toNumber,
}
from "runtime/math/trig" include Trig
use Trig.{ sin, cos, tan }

@unsafe
let _VALUE_OFFSET = 4n
Expand Down Expand Up @@ -274,6 +276,29 @@ provide let (>=) = (x: Float32, y: Float32) => {
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 Float32.isFinite(0.5f)
* @example Float32.isFinite(1.0f)
* @example Float32.isFinite(Infinityf) == false
* @example Float32.isFinite(-Infinityf) == false
* @example Float32.isFinite(NaNf) == false
*
* @since v0.7.0
*/
@unsafe
provide let isFinite = (x: Float32) => {
// 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.0f
}

/**
* Checks if the value is a float NaN value (Not A Number).
*
Expand Down Expand Up @@ -490,3 +515,89 @@ provide let copySign = (x: Float32, y: Float32) => {
let ptr = newFloat32(WasmF32.copySign(xv, yv))
WasmI32.toGrain(ptr): Float32
}

/**
* 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 Float32.isClose(1.233f, 1.233f)
* @example Float32.isClose(1.233f, 1.233000001f)
* @example Float32.isClose(8.005f, 8.450f, absoluteTolerance=0.5f)
* @example Float32.isClose(4.0f, 4.1f, relativeTolerance=0.025f)
* @example Float32.isClose(1.233f, 1.24f) == false
* @example Float32.isClose(1.233f, 1.4566f) == false
* @example Float32.isClose(8.005f, 8.450f, absoluteTolerance=0.4f) == false
* @example Float32.isClose(4.0f, 4.1f, relativeTolerance=0.024f) == false
*
* @since v0.7.0
*/
provide let isClose = (a, b, relativeTolerance=1e-9f, absoluteTolerance=0.0f) => {
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 Float32.sin(0.0f) == 0.0f
*
* @since v0.7.0
*/
@unsafe
provide let sin = (radians: Float32) => {
// TODO(#2167): Implement Float32 optimized trig functions
let xval = WasmF32.load(WasmI32.fromGrain(radians), _VALUE_OFFSET)
let value = sin(WasmF64.promoteF32(xval))
WasmI32.toGrain(newFloat32(WasmF32.demoteF64(value))): Float32
}

/**
* Computes the cosine of a float (in radians).
*
* @param radians: The input in radians
* @returns The computed cosine
*
* @example Float32.cos(0.0f) == 1.0f
*
* @since v0.7.0
*/
@unsafe
provide let cos = (radians: Float32) => {
// TODO(#2167): Implement Float32 optimized trig functions
let xval = WasmF32.load(WasmI32.fromGrain(radians), _VALUE_OFFSET)
let value = cos(WasmF64.promoteF32(xval))
WasmI32.toGrain(newFloat32(WasmF32.demoteF64(value))): Float32
}

/**
* Computes the tangent of a number (in radians).
*
* @param radians: The input in radians
* @returns The computed tangent
*
* @example Float32.tan(0.0f) == 0.0f
*
* @since v0.7.0
*/
@unsafe
provide let tan = (radians: Float32) => {
// TODO(#2167): Implement Float32 optimized trig functions
let xval = WasmF32.load(WasmI32.fromGrain(radians), _VALUE_OFFSET)
let value = tan(WasmF64.promoteF32(xval))
WasmI32.toGrain(newFloat32(WasmF32.demoteF64(value))): Float32
}
Loading
Loading