diff --git a/compiler/test/stdlib/float32.test.gr b/compiler/test/stdlib/float32.test.gr index 395dd2579..d340fbf37 100644 --- a/compiler/test/stdlib/float32.test.gr +++ b/compiler/test/stdlib/float32.test.gr @@ -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 @@ -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)) diff --git a/stdlib/float32.gr b/stdlib/float32.gr index 03e73927d..eabcb7cd9 100644 --- a/stdlib/float32.gr +++ b/stdlib/float32.gr @@ -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 @@ -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). * @@ -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 +} diff --git a/stdlib/float32.md b/stdlib/float32.md index a301d8303..f68d1cbd5 100644 --- a/stdlib/float32.md +++ b/stdlib/float32.md @@ -513,6 +513,54 @@ use Float32.{ (>=) } assert 3.0f >= 3.0f ``` +### Float32.**isFinite** + +
+Added in next +No other changes yet. +
+ +```grain +isFinite : (x: Float32) => Bool +``` + +Checks if a float is finite. +All values are finite exept for NaN, infinity or negative infinity. + +Parameters: + +|param|type|description| +|-----|----|-----------| +|`x`|`Float32`|The number to check| + +Returns: + +|type|description| +|----|-----------| +|`Bool`|`true` if the value is finite or `false` otherwise| + +Examples: + +```grain +Float32.isFinite(0.5f) +``` + +```grain +Float32.isFinite(1.0f) +``` + +```grain +Float32.isFinite(Infinityf) == false +``` + +```grain +Float32.isFinite(-Infinityf) == false +``` + +```grain +Float32.isFinite(NaNf) == false +``` + ### Float32.**isNaN**
@@ -956,3 +1004,160 @@ Float32.copySign(3.0f, -1.0f) == -3.0f Float32.copySign(-5.0f, 1.0f) == 5.0f ``` +### Float32.**isClose** + +
+Added in next +No other changes yet. +
+ +```grain +isClose : + (a: Float32, b: Float32, ?relativeTolerance: Float32, + ?absoluteTolerance: Float32) => Bool +``` + +Determines whether two values are considered close to each other using a relative and absolute tolerance. + +Parameters: + +|param|type|description| +|-----|----|-----------| +|`a`|`Float32`|The first value| +|`b`|`Float32`|The second value| +|`?relativeTolerance`|`Float32`|The maximum tolerance to use relative to the larger absolute value `a` or `b`| +|`?absoluteTolerance`|`Float32`|The absolute tolerance to use, regardless of the values of `a` or `b`| + +Returns: + +|type|description| +|----|-----------| +|`Bool`|`true` if the values are considered close to each other or `false` otherwise| + +Examples: + +```grain +Float32.isClose(1.233f, 1.233f) +``` + +```grain +Float32.isClose(1.233f, 1.233000001f) +``` + +```grain +Float32.isClose(8.005f, 8.450f, absoluteTolerance=0.5f) +``` + +```grain +Float32.isClose(4.0f, 4.1f, relativeTolerance=0.025f) +``` + +```grain +Float32.isClose(1.233f, 1.24f) == false +``` + +```grain +Float32.isClose(1.233f, 1.4566f) == false +``` + +```grain +Float32.isClose(8.005f, 8.450f, absoluteTolerance=0.4f) == false +``` + +```grain +Float32.isClose(4.0f, 4.1f, relativeTolerance=0.024f) == false +``` + +### Float32.**sin** + +
+Added in next +No other changes yet. +
+ +```grain +sin : (radians: Float32) => Float32 +``` + +Computes the sine of a float (in radians). + +Parameters: + +|param|type|description| +|-----|----|-----------| +|`radians`|`Float32`|The input in radians| + +Returns: + +|type|description| +|----|-----------| +|`Float32`|The computed sine| + +Examples: + +```grain +Float32.sin(0.0f) == 0.0f +``` + +### Float32.**cos** + +
+Added in next +No other changes yet. +
+ +```grain +cos : (radians: Float32) => Float32 +``` + +Computes the cosine of a float (in radians). + +Parameters: + +|param|type|description| +|-----|----|-----------| +|`radians`|`Float32`|The input in radians| + +Returns: + +|type|description| +|----|-----------| +|`Float32`|The computed cosine| + +Examples: + +```grain +Float32.cos(0.0f) == 1.0f +``` + +### Float32.**tan** + +
+Added in next +No other changes yet. +
+ +```grain +tan : (radians: Float32) => Float32 +``` + +Computes the tangent of a number (in radians). + +Parameters: + +|param|type|description| +|-----|----|-----------| +|`radians`|`Float32`|The input in radians| + +Returns: + +|type|description| +|----|-----------| +|`Float32`|The computed tangent| + +Examples: + +```grain +Float32.tan(0.0f) == 0.0f +``` +