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
…32 (#2168)
  • Loading branch information
spotandjake authored Oct 7, 2024
1 parent fa728d2 commit bdb4641
Show file tree
Hide file tree
Showing 3 changed files with 544 additions and 0 deletions.
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

0 comments on commit bdb4641

Please sign in to comment.