Skip to content

Commit bdb4641

Browse files
authored
feat(stdlib): Add isFinite, isClose, sin, cos, tan to Float32 (#2168)
1 parent fa728d2 commit bdb4641

File tree

3 files changed

+544
-0
lines changed

3 files changed

+544
-0
lines changed

compiler/test/stdlib/float32.test.gr

Lines changed: 228 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,16 @@ assert compare(nan, nan) == 0
110110
assert compare(1.0f, nan) > 0
111111
assert compare(nan, 1.0f) < 0
112112

113+
// isFinite
114+
assert Float32.isFinite(NaNf) == false
115+
assert Float32.isFinite(Infinityf) == false
116+
assert Float32.isFinite(-Infinityf) == false
117+
assert Float32.isFinite(1.0f)
118+
assert Float32.isFinite(0.0f)
119+
assert Float32.isFinite(-1.0f)
120+
assert Float32.isFinite(25.76f)
121+
assert Float32.isFinite(-25.00f)
122+
113123
// isNaN
114124
assert Float32.isNaN(NaNf)
115125
assert Float32.isNaN(1.0f) == false
@@ -205,3 +215,221 @@ assert Float32.isNaN(Float32.copySign(NaNf, 1.0f))
205215
assert Float32.isNaN(Float32.copySign(NaNf, -1.0f))
206216
assert Float32.copySign(1.0f, NaNf) == 1.0f
207217
assert Float32.copySign(1.0f, -NaNf) == -1.0f
218+
219+
// Float32.isClose
220+
assert Float32.isClose(1.0f, 1.0f)
221+
assert Float32.isClose(
222+
1.0f,
223+
1.0f,
224+
relativeTolerance=0.5f,
225+
absoluteTolerance=0.5f
226+
)
227+
assert Float32.isClose(
228+
1.0f,
229+
1.0f,
230+
relativeTolerance=0.0f,
231+
absoluteTolerance=0.0f
232+
)
233+
assert Float32.isClose(0.0f, 0.0f)
234+
assert Float32.isClose(
235+
0.0f,
236+
0.0f,
237+
relativeTolerance=0.5f,
238+
absoluteTolerance=0.5f
239+
)
240+
assert Float32.isClose(
241+
0.0f,
242+
0.0f,
243+
relativeTolerance=0.0f,
244+
absoluteTolerance=0.0f
245+
)
246+
assert Float32.isClose(0.0f, 0.1f) == false
247+
assert Float32.isClose(0.0f, 0.000000001f) == false
248+
assert Float32.isClose(0.0f, 0.00000001f, absoluteTolerance=1e-9f) == false
249+
assert Float32.isClose(0.0f, 0.000000001f, absoluteTolerance=1e-9f)
250+
assert Float32.isClose(-0.0f, 0.000000001f) == false
251+
assert Float32.isClose(-0.0f, 0.00000001f, absoluteTolerance=1e-9f) == false
252+
assert Float32.isClose(-0.0f, 0.000000001f, absoluteTolerance=1e-9f)
253+
assert Float32.isClose(1.1f, 1.1000001f, absoluteTolerance=1e-10f) == false
254+
assert Float32.isClose(1.1f, 1.100000001f, absoluteTolerance=1e-9f)
255+
assert Float32.isClose(Infinityf, Infinityf)
256+
assert Float32.isClose(-Infinityf, -Infinityf)
257+
assert Float32.isClose(Infinityf, -Infinityf) == false
258+
assert Float32.isClose(NaNf, NaNf) == false
259+
260+
// Float32.sin - 0 to pi/2
261+
assert Float32.sin(0.0f) == 0.0f
262+
assert Float32.isClose(Float32.sin(Float32.pi / 6.0f), 0.5f)
263+
assert Float32.isClose(
264+
Float32.sin(Float32.pi / 4.0f),
265+
Float32.sqrt(2.0f) / 2.0f
266+
)
267+
assert Float32.isClose(
268+
Float32.sin(Float32.pi / 3.0f),
269+
Float32.sqrt(3.0f) / 2.0f,
270+
absoluteTolerance=1e-5f
271+
)
272+
assert Float32.isClose(Float32.sin(Float32.pi / 2.0f), 1.0f)
273+
// Float32.sin - pi/2 to 2pi
274+
assert Float32.isClose(
275+
Float32.sin(2.0f * Float32.pi / 3.0f),
276+
Float32.sqrt(3.0f) / 2.0f
277+
)
278+
assert Float32.isClose(
279+
Float32.sin(3.0f * Float32.pi / 4.0f),
280+
Float32.sqrt(2.0f) / 2.0f
281+
)
282+
assert Float32.isClose(
283+
Float32.sin(5.0f * Float32.pi / 6.0f),
284+
0.5f,
285+
absoluteTolerance=1e-5f
286+
)
287+
// Note: This has an absolute error of 1e-15 because `Float32.pi` is not exact
288+
assert Float32.isClose(Float32.sin(Float32.pi), 0.0f, absoluteTolerance=1e-6f)
289+
// Float32.sin - 2pi to 3pi/2
290+
assert Float32.isClose(
291+
Float32.sin(7.0f * Float32.pi / 6.0f),
292+
-0.5f,
293+
absoluteTolerance=1e-5f
294+
)
295+
assert Float32.isClose(
296+
Float32.sin(5.0f * Float32.pi / 4.0f),
297+
Float32.sqrt(2.0f) / -2.0f,
298+
absoluteTolerance=1e-5f
299+
)
300+
assert Float32.isClose(
301+
Float32.sin(4.0f * Float32.pi / 3.0f),
302+
Float32.sqrt(3.0f) / -2.0f,
303+
absoluteTolerance=1e-5f
304+
)
305+
assert Float32.isClose(Float32.sin(3.0f * Float32.pi / 2.0f), -1.0f)
306+
// Float32.sin - 3pi/2 to 0
307+
assert Float32.isClose(
308+
Float32.sin(5.0f * Float32.pi / 3.0f),
309+
Float32.sqrt(3.0f) / -2.0f,
310+
absoluteTolerance=1e-5f
311+
)
312+
assert Float32.isClose(
313+
Float32.sin(7.0f * Float32.pi / 4.0f),
314+
Float32.sqrt(2.0f) / -2.0f,
315+
absoluteTolerance=1e-5f
316+
)
317+
assert Float32.isClose(
318+
Float32.sin(11.0f * Float32.pi / 6.0f),
319+
-0.5f,
320+
absoluteTolerance=1e-5f
321+
)
322+
// Note: This has an absolute error of 1e-5 because `Float32.pi` is not exact
323+
assert Float32.isClose(
324+
Float32.sin(2.0f * Float32.pi),
325+
0.0f,
326+
absoluteTolerance=1e-5f
327+
)
328+
// Float32.sin - special cases
329+
assert Float32.sin(0.5f) == Float32.sin(0.5f)
330+
assert Float32.sin(0.25f) == Float32.sin(0.25f)
331+
assert Float32.isNaN(Float32.sin(Infinityf))
332+
assert Float32.isNaN(Float32.sin(-Infinityf))
333+
assert Float32.isNaN(Float32.sin(NaNf))
334+
335+
// Float32.cos - 0 to pi/2
336+
assert Float32.cos(0.0f) == 1.0f
337+
assert Float32.isClose(
338+
Float32.cos(Float32.pi / 6.0f),
339+
Float32.sqrt(3.0f) / 2.0f
340+
)
341+
assert Float32.isClose(
342+
Float32.cos(Float32.pi / 4.0f),
343+
Float32.sqrt(2.0f) / 2.0f
344+
)
345+
assert Float32.isClose(
346+
Float32.cos(Float32.pi / 3.0f),
347+
0.5f,
348+
absoluteTolerance=1e-5f
349+
)
350+
// Note: This has an absolute error of 1e-5 because `Float32.pi` is not exact
351+
assert Float32.isClose(
352+
Float32.cos(Float32.pi / 2.0f),
353+
0.0f,
354+
absoluteTolerance=1e-5f
355+
)
356+
// Float32.cos - pi/2 to 2pi
357+
assert Float32.isClose(
358+
Float32.cos(2.0f * Float32.pi / 3.0f),
359+
-0.5f,
360+
absoluteTolerance=1e-5f
361+
)
362+
assert Float32.isClose(
363+
Float32.cos(3.0f * Float32.pi / 4.0f),
364+
Float32.sqrt(2.0f) / -2.0f
365+
)
366+
assert Float32.isClose(
367+
Float32.cos(5.0f * Float32.pi / 6.0f),
368+
Float32.sqrt(3.0f) / -2.0f,
369+
absoluteTolerance=1e-5f
370+
)
371+
assert Float32.isClose(Float32.cos(Float32.pi), -1.0f)
372+
// Float32.cos - 2pi to 3pi/2
373+
assert Float32.isClose(
374+
Float32.cos(7.0f * Float32.pi / 6.0f),
375+
Float32.sqrt(3.0f) / -2.0f,
376+
absoluteTolerance=1e-5f
377+
)
378+
assert Float32.isClose(
379+
Float32.cos(5.0f * Float32.pi / 4.0f),
380+
Float32.sqrt(2.0f) / -2.0f,
381+
absoluteTolerance=1e-5f
382+
)
383+
assert Float32.isClose(
384+
Float32.cos(4.0f * Float32.pi / 3.0f),
385+
-0.5f,
386+
absoluteTolerance=1e-5f
387+
)
388+
// Note: This has an absolute error of 1e-5 because `Float32.pi` is not exact
389+
assert Float32.isClose(
390+
Float32.cos(3.0f * Float32.pi / 2.0f),
391+
0.0f,
392+
absoluteTolerance=1e-5f
393+
)
394+
// Float32.cos - 3pi/2 to 0
395+
assert Float32.isClose(
396+
Float32.cos(5.0f * Float32.pi / 3.0f),
397+
0.5f,
398+
absoluteTolerance=1e-5f
399+
)
400+
assert Float32.isClose(
401+
Float32.cos(7.0f * Float32.pi / 4.0f),
402+
Float32.sqrt(2.0f) / 2.0f,
403+
absoluteTolerance=1e-5f
404+
)
405+
assert Float32.isClose(
406+
Float32.cos(11.0f * Float32.pi / 6.0f),
407+
Float32.sqrt(3.0f) / 2.0f,
408+
absoluteTolerance=1e-5f
409+
)
410+
assert Float32.isClose(Float32.cos(2.0f * Float32.pi), 1.0f)
411+
// Float32.cos - special cases
412+
assert Float32.cos(0.5f) == Float32.cos(0.5f)
413+
assert Float32.cos(0.25f) == Float32.cos(0.25f)
414+
assert Float32.isNaN(Float32.cos(Infinityf))
415+
assert Float32.isNaN(Float32.cos(-Infinityf))
416+
assert Float32.isNaN(Float32.cos(NaNf))
417+
418+
// Float32.tan - base cases
419+
assert Float32.tan(0.0f) == 0.0f
420+
assert Float32.isClose(
421+
Float32.tan(Float32.pi / 6.0f),
422+
1.0f / Float32.sqrt(3.0f)
423+
)
424+
assert Float32.isClose(Float32.tan(Float32.pi / 4.0f), 1.0f)
425+
assert Float32.isClose(
426+
Float32.tan(Float32.pi / 3.0f),
427+
Float32.sqrt(3.0f),
428+
absoluteTolerance=1e-5f
429+
)
430+
// Float32.tan - special cases
431+
assert Float32.tan(0.5f) == Float32.tan(0.5f)
432+
assert Float32.tan(0.25f) == Float32.tan(0.25f)
433+
assert Float32.isNaN(Float32.tan(Infinityf))
434+
assert Float32.isNaN(Float32.tan(-Infinityf))
435+
assert Float32.isNaN(Float32.tan(NaNf))

stdlib/float32.gr

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ use Numbers.{
2424
coerceNumberToFloat32 as fromNumber,
2525
coerceFloat32ToNumber as toNumber,
2626
}
27+
from "runtime/math/trig" include Trig
28+
use Trig.{ sin, cos, tan }
2729

2830
@unsafe
2931
let _VALUE_OFFSET = 4n
@@ -274,6 +276,29 @@ provide let (>=) = (x: Float32, y: Float32) => {
274276
xv >= yv
275277
}
276278

279+
/**
280+
* Checks if a float is finite.
281+
* All values are finite exept for NaN, infinity or negative infinity.
282+
*
283+
* @param x: The number to check
284+
* @returns `true` if the value is finite or `false` otherwise
285+
*
286+
* @example Float32.isFinite(0.5f)
287+
* @example Float32.isFinite(1.0f)
288+
* @example Float32.isFinite(Infinityf) == false
289+
* @example Float32.isFinite(-Infinityf) == false
290+
* @example Float32.isFinite(NaNf) == false
291+
*
292+
* @since v0.7.0
293+
*/
294+
@unsafe
295+
provide let isFinite = (x: Float32) => {
296+
// uses the fact that all finite floats minus themselves are zero
297+
// (NaN - NaN == NaN, inf - inf == NaN,
298+
// -inf - -inf == NaN, inf - -inf == inf, -inf - inf == -inf)
299+
x - x == 0.0f
300+
}
301+
277302
/**
278303
* Checks if the value is a float NaN value (Not A Number).
279304
*
@@ -490,3 +515,89 @@ provide let copySign = (x: Float32, y: Float32) => {
490515
let ptr = newFloat32(WasmF32.copySign(xv, yv))
491516
WasmI32.toGrain(ptr): Float32
492517
}
518+
519+
/**
520+
* Determines whether two values are considered close to each other using a relative and absolute tolerance.
521+
*
522+
* @param a: The first value
523+
* @param b: The second value
524+
* @param relativeTolerance: The maximum tolerance to use relative to the larger absolute value `a` or `b`
525+
* @param absoluteTolerance: The absolute tolerance to use, regardless of the values of `a` or `b`
526+
* @returns `true` if the values are considered close to each other or `false` otherwise
527+
*
528+
* @example Float32.isClose(1.233f, 1.233f)
529+
* @example Float32.isClose(1.233f, 1.233000001f)
530+
* @example Float32.isClose(8.005f, 8.450f, absoluteTolerance=0.5f)
531+
* @example Float32.isClose(4.0f, 4.1f, relativeTolerance=0.025f)
532+
* @example Float32.isClose(1.233f, 1.24f) == false
533+
* @example Float32.isClose(1.233f, 1.4566f) == false
534+
* @example Float32.isClose(8.005f, 8.450f, absoluteTolerance=0.4f) == false
535+
* @example Float32.isClose(4.0f, 4.1f, relativeTolerance=0.024f) == false
536+
*
537+
* @since v0.7.0
538+
*/
539+
provide let isClose = (a, b, relativeTolerance=1e-9f, absoluteTolerance=0.0f) => {
540+
if (a == b) {
541+
true
542+
} else if (isFinite(a) && isFinite(b)) {
543+
abs(a - b) <=
544+
max(relativeTolerance * max(abs(a), abs(b)), absoluteTolerance)
545+
} else {
546+
// NaN and infinities which were not equal
547+
false
548+
}
549+
}
550+
551+
/**
552+
* Computes the sine of a float (in radians).
553+
*
554+
* @param radians: The input in radians
555+
* @returns The computed sine
556+
*
557+
* @example Float32.sin(0.0f) == 0.0f
558+
*
559+
* @since v0.7.0
560+
*/
561+
@unsafe
562+
provide let sin = (radians: Float32) => {
563+
// TODO(#2167): Implement Float32 optimized trig functions
564+
let xval = WasmF32.load(WasmI32.fromGrain(radians), _VALUE_OFFSET)
565+
let value = sin(WasmF64.promoteF32(xval))
566+
WasmI32.toGrain(newFloat32(WasmF32.demoteF64(value))): Float32
567+
}
568+
569+
/**
570+
* Computes the cosine of a float (in radians).
571+
*
572+
* @param radians: The input in radians
573+
* @returns The computed cosine
574+
*
575+
* @example Float32.cos(0.0f) == 1.0f
576+
*
577+
* @since v0.7.0
578+
*/
579+
@unsafe
580+
provide let cos = (radians: Float32) => {
581+
// TODO(#2167): Implement Float32 optimized trig functions
582+
let xval = WasmF32.load(WasmI32.fromGrain(radians), _VALUE_OFFSET)
583+
let value = cos(WasmF64.promoteF32(xval))
584+
WasmI32.toGrain(newFloat32(WasmF32.demoteF64(value))): Float32
585+
}
586+
587+
/**
588+
* Computes the tangent of a number (in radians).
589+
*
590+
* @param radians: The input in radians
591+
* @returns The computed tangent
592+
*
593+
* @example Float32.tan(0.0f) == 0.0f
594+
*
595+
* @since v0.7.0
596+
*/
597+
@unsafe
598+
provide let tan = (radians: Float32) => {
599+
// TODO(#2167): Implement Float32 optimized trig functions
600+
let xval = WasmF32.load(WasmI32.fromGrain(radians), _VALUE_OFFSET)
601+
let value = tan(WasmF64.promoteF32(xval))
602+
WasmI32.toGrain(newFloat32(WasmF32.demoteF64(value))): Float32
603+
}

0 commit comments

Comments
 (0)