Skip to content

Commit fa728d2

Browse files
authored
feat(stdlib): Add isFinite, isClose, sin, cos, tan to Float64 (#2166)
1 parent 0894dc5 commit fa728d2

File tree

3 files changed

+521
-0
lines changed

3 files changed

+521
-0
lines changed

compiler/test/stdlib/float64.test.gr

Lines changed: 208 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,16 @@ assert !(5.0d >= 22.0d)
116116
assert !(5.0d < -17.0d)
117117
assert !(5.0d <= 4.0d)
118118

119+
// isFinite
120+
assert Float64.isFinite(NaNd) == false
121+
assert Float64.isFinite(Infinityd) == false
122+
assert Float64.isFinite(-Infinityd) == false
123+
assert Float64.isFinite(1.0d)
124+
assert Float64.isFinite(0.0d)
125+
assert Float64.isFinite(-1.0d)
126+
assert Float64.isFinite(25.76d)
127+
assert Float64.isFinite(-25.00d)
128+
119129
// isNaN
120130
assert Float64.isNaN(NaNd)
121131
assert Float64.isNaN(1.0d) == false
@@ -211,3 +221,201 @@ assert Float64.isNaN(Float64.copySign(NaNd, 1.0d))
211221
assert Float64.isNaN(Float64.copySign(NaNd, -1.0d))
212222
assert Float64.copySign(1.0d, NaNd) == 1.0d
213223
assert Float64.copySign(1.0d, -NaNd) == -1.0d
224+
225+
// Float64.isClose
226+
assert Float64.isClose(1.0d, 1.0d)
227+
assert Float64.isClose(
228+
1.0d,
229+
1.0d,
230+
relativeTolerance=0.5d,
231+
absoluteTolerance=0.5d
232+
)
233+
assert Float64.isClose(
234+
1.0d,
235+
1.0d,
236+
relativeTolerance=0.0d,
237+
absoluteTolerance=0.0d
238+
)
239+
assert Float64.isClose(0.0d, 0.0d)
240+
assert Float64.isClose(
241+
0.0d,
242+
0.0d,
243+
relativeTolerance=0.5d,
244+
absoluteTolerance=0.5d
245+
)
246+
assert Float64.isClose(
247+
0.0d,
248+
0.0d,
249+
relativeTolerance=0.0d,
250+
absoluteTolerance=0.0d
251+
)
252+
assert Float64.isClose(0.0d, 0.1d) == false
253+
assert Float64.isClose(0.0d, 0.000000001d) == false
254+
assert Float64.isClose(0.0d, 0.00000001d, absoluteTolerance=1e-9d) == false
255+
assert Float64.isClose(0.0d, 0.000000001d, absoluteTolerance=1e-9d)
256+
assert Float64.isClose(-0.0d, 0.000000001d) == false
257+
assert Float64.isClose(-0.0d, 0.00000001d, absoluteTolerance=1e-9d) == false
258+
assert Float64.isClose(-0.0d, 0.000000001d, absoluteTolerance=1e-9d)
259+
assert Float64.isClose(1.1d, 1.10000001d, absoluteTolerance=1e-9d) == false
260+
assert Float64.isClose(1.1d, 1.100000001d, absoluteTolerance=1e-9d)
261+
assert Float64.isClose(Infinityd, Infinityd)
262+
assert Float64.isClose(-Infinityd, -Infinityd)
263+
assert Float64.isClose(Infinityd, -Infinityd) == false
264+
assert Float64.isClose(NaNd, NaNd) == false
265+
266+
// Float64.sin - 0 to pi/2
267+
assert Float64.sin(0.0d) == 0.0d
268+
assert Float64.isClose(Float64.sin(Float64.pi / 6.0d), 0.5d)
269+
assert Float64.isClose(
270+
Float64.sin(Float64.pi / 4.0d),
271+
Float64.sqrt(2.0d) / 2.0d
272+
)
273+
assert Float64.isClose(
274+
Float64.sin(Float64.pi / 3.0d),
275+
Float64.sqrt(3.0d) / 2.0d
276+
)
277+
assert Float64.isClose(Float64.sin(Float64.pi / 2.0d), 1.0d)
278+
// Float64.sin - pi/2 to 2pi
279+
assert Float64.isClose(
280+
Float64.sin(2.0d * Float64.pi / 3.0d),
281+
Float64.sqrt(3.0d) / 2.0d
282+
)
283+
assert Float64.isClose(
284+
Float64.sin(3.0d * Float64.pi / 4.0d),
285+
Float64.sqrt(2.0d) / 2.0d
286+
)
287+
assert Float64.isClose(Float64.sin(5.0d * Float64.pi / 6.0d), 0.5d)
288+
// Note: This has an absolute error of 1e-15 because `Float64.pi` is not exact
289+
assert Float64.isClose(Float64.sin(Float64.pi), 0.0d, absoluteTolerance=1e-15d)
290+
// Float64.sin - 2pi to 3pi/2
291+
assert Float64.isClose(Float64.sin(7.0d * Float64.pi / 6.0d), -0.5d)
292+
assert Float64.isClose(
293+
Float64.sin(5.0d * Float64.pi / 4.0d),
294+
Float64.sqrt(2.0d) / -2.0d
295+
)
296+
assert Float64.isClose(
297+
Float64.sin(4.0d * Float64.pi / 3.0d),
298+
Float64.sqrt(3.0d) / -2.0d
299+
)
300+
assert Float64.isClose(Float64.sin(3.0d * Float64.pi / 2.0d), -1.0d)
301+
// Float64.sin - 3pi/2 to 0
302+
assert Float64.isClose(
303+
Float64.sin(5.0d * Float64.pi / 3.0d),
304+
Float64.sqrt(3.0d) / -2.0d
305+
)
306+
assert Float64.isClose(
307+
Float64.sin(7.0d * Float64.pi / 4.0d),
308+
Float64.sqrt(2.0d) / -2.0d
309+
)
310+
assert Float64.isClose(Float64.sin(11.0d * Float64.pi / 6.0d), -0.5d)
311+
// Note: This has an absolute error of 1e-15 because `Float64.pi` is not exact
312+
assert Float64.isClose(
313+
Float64.sin(2.0d * Float64.pi),
314+
0.0d,
315+
absoluteTolerance=1e-15d
316+
)
317+
// Float64.sin - special cases
318+
assert Float64.sin(0.5d) == Float64.sin(0.5d)
319+
assert Float64.sin(0.25d) == Float64.sin(0.25d)
320+
assert Float64.isClose( // Note: We lose a lot of precision here do to ieee754 representation
321+
Float64.sin(1.7976931348623157e+308d),
322+
0.0049619d,
323+
absoluteTolerance=1e7d
324+
) // Max F64
325+
assert Float64.isClose(
326+
Float64.sin(-1.7976931348623157e+308d),
327+
0.00496d,
328+
absoluteTolerance=1e7d
329+
) // Min F64
330+
assert Float64.isNaN(Float64.sin(Infinityd))
331+
assert Float64.isNaN(Float64.sin(-Infinityd))
332+
assert Float64.isNaN(Float64.sin(NaNd))
333+
334+
// Float64.cos - 0 to pi/2
335+
assert Float64.cos(0.0d) == 1.0d
336+
assert Float64.isClose(
337+
Float64.cos(Float64.pi / 6.0d),
338+
Float64.sqrt(3.0d) / 2.0d
339+
)
340+
assert Float64.isClose(
341+
Float64.cos(Float64.pi / 4.0d),
342+
Float64.sqrt(2.0d) / 2.0d
343+
)
344+
assert Float64.isClose(Float64.cos(Float64.pi / 3.0d), 0.5d)
345+
// Note: This has an absolute error of 1e-15 because `Float64.pi` is not exact
346+
assert Float64.isClose(
347+
Float64.cos(Float64.pi / 2.0d),
348+
0.0d,
349+
absoluteTolerance=1e-15d
350+
)
351+
// Float64.cos - pi/2 to 2pi
352+
assert Float64.isClose(Float64.cos(2.0d * Float64.pi / 3.0d), -0.5d)
353+
assert Float64.isClose(
354+
Float64.cos(3.0d * Float64.pi / 4.0d),
355+
Float64.sqrt(2.0d) / -2.0d
356+
)
357+
assert Float64.isClose(
358+
Float64.cos(5.0d * Float64.pi / 6.0d),
359+
Float64.sqrt(3.0d) / -2.0d
360+
)
361+
assert Float64.isClose(Float64.cos(Float64.pi), -1.0d)
362+
// Float64.cos - 2pi to 3pi/2
363+
assert Float64.isClose(
364+
Float64.cos(7.0d * Float64.pi / 6.0d),
365+
Float64.sqrt(3.0d) / -2.0d
366+
)
367+
assert Float64.isClose(
368+
Float64.cos(5.0d * Float64.pi / 4.0d),
369+
Float64.sqrt(2.0d) / -2.0d
370+
)
371+
assert Float64.isClose(Float64.cos(4.0d * Float64.pi / 3.0d), -0.5d)
372+
// Note: This has an absolute error of 1e-15 because `Float64.pi` is not exact
373+
assert Float64.isClose(
374+
Float64.cos(3.0d * Float64.pi / 2.0d),
375+
0.0d,
376+
absoluteTolerance=1e-15d
377+
)
378+
// Float64.cos - 3pi/2 to 0
379+
assert Float64.isClose(Float64.cos(5.0d * Float64.pi / 3.0d), 0.5d)
380+
assert Float64.isClose(
381+
Float64.cos(7.0d * Float64.pi / 4.0d),
382+
Float64.sqrt(2.0d) / 2.0d
383+
)
384+
assert Float64.isClose(
385+
Float64.cos(11.0d * Float64.pi / 6.0d),
386+
Float64.sqrt(3.0d) / 2.0d
387+
)
388+
assert Float64.isClose(Float64.cos(2.0d * Float64.pi), 1.0d)
389+
// Float64.cos - special cases
390+
assert Float64.cos(0.5d) == Float64.cos(0.5d)
391+
assert Float64.cos(0.25d) == Float64.cos(0.25d)
392+
assert Float64.isNaN(Float64.cos(Infinityd))
393+
assert Float64.isNaN(Float64.cos(-Infinityd))
394+
assert Float64.isNaN(Float64.cos(NaNd))
395+
396+
// Float64.tan - base cases
397+
assert Float64.tan(0.0d) == 0.0d
398+
assert Float64.isClose(
399+
Float64.tan(Float64.pi / 6.0d),
400+
1.0d / Float64.sqrt(3.0d)
401+
)
402+
assert Float64.isClose(Float64.tan(Float64.pi / 4.0d), 1.0d)
403+
assert Float64.isClose(Float64.tan(Float64.pi / 3.0d), Float64.sqrt(3.0d))
404+
// 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
405+
assert Float64.isClose(Float64.tan(Float64.pi / 2.0d), 16331239353195370.0d)
406+
// Float64.tan - special cases
407+
assert Float64.tan(0.5d) == Float64.tan(0.5d)
408+
assert Float64.tan(0.25d) == Float64.tan(0.25d)
409+
assert Float64.isClose( // Note: We lose a lot of precision here do to ieee754 representation
410+
Float64.tan(1.7976931348623157e+308d),
411+
-0.00496201587d,
412+
absoluteTolerance=1e7d
413+
) // Max F64
414+
assert Float64.isClose(
415+
Float64.tan(-1.7976931348623157e+308d),
416+
-0.00496201587d,
417+
absoluteTolerance=1e7d
418+
) // Max F64
419+
assert Float64.isNaN(Float64.tan(Infinityd))
420+
assert Float64.isNaN(Float64.tan(-Infinityd))
421+
assert Float64.isNaN(Float64.tan(NaNd))

stdlib/float64.gr

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ use Numbers.{
2323
coerceNumberToFloat64 as fromNumber,
2424
coerceFloat64ToNumber as toNumber,
2525
}
26+
from "runtime/math/trig" include Trig
27+
use Trig.{ sin, cos, tan }
2628

2729
@unsafe
2830
let _VALUE_OFFSET = 8n
@@ -269,6 +271,29 @@ provide let (>=) = (x: Float64, y: Float64) => {
269271
xv >= yv
270272
}
271273

274+
/**
275+
* Checks if a float is finite.
276+
* All values are finite exept for NaN, infinity or negative infinity.
277+
*
278+
* @param x: The number to check
279+
* @returns `true` if the value is finite or `false` otherwise
280+
*
281+
* @example Float64.isFinite(0.5d)
282+
* @example Float64.isFinite(1.0d)
283+
* @example Float64.isFinite(Infinityd) == false
284+
* @example Float64.isFinite(-Infinityd) == false
285+
* @example Float64.isFinite(NaNd) == false
286+
*
287+
* @since v0.7.0
288+
*/
289+
@unsafe
290+
provide let isFinite = (x: Float64) => {
291+
// uses the fact that all finite floats minus themselves are zero
292+
// (NaN - NaN == NaN, inf - inf == NaN,
293+
// -inf - -inf == NaN, inf - -inf == inf, -inf - inf == -inf)
294+
x - x == 0.0d
295+
}
296+
272297
/**
273298
* Checks if the value is a float NaN value (Not A Number).
274299
*
@@ -485,3 +510,86 @@ provide let copySign = (x: Float64, y: Float64) => {
485510
let ptr = newFloat64(WasmF64.copySign(xv, yv))
486511
WasmI32.toGrain(ptr): Float64
487512
}
513+
514+
/**
515+
* Determines whether two values are considered close to each other using a relative and absolute tolerance.
516+
*
517+
* @param a: The first value
518+
* @param b: The second value
519+
* @param relativeTolerance: The maximum tolerance to use relative to the larger absolute value `a` or `b`
520+
* @param absoluteTolerance: The absolute tolerance to use, regardless of the values of `a` or `b`
521+
* @returns `true` if the values are considered close to each other or `false` otherwise
522+
*
523+
* @example Float64.isClose(1.233d, 1.233d)
524+
* @example Float64.isClose(1.233d, 1.233000001d)
525+
* @example Float64.isClose(8.005d, 8.450d, absoluteTolerance=0.5d)
526+
* @example Float64.isClose(4.0d, 4.1d, relativeTolerance=0.025d)
527+
* @example Float64.isClose(1.233d, 1.24d) == false
528+
* @example Float64.isClose(1.233d, 1.4566d) == false
529+
* @example Float64.isClose(8.005d, 8.450d, absoluteTolerance=0.4d) == false
530+
* @example Float64.isClose(4.0d, 4.1d, relativeTolerance=0.024d) == false
531+
*
532+
* @since v0.7.0
533+
*/
534+
provide let isClose = (a, b, relativeTolerance=1e-9d, absoluteTolerance=0.0d) => {
535+
if (a == b) {
536+
true
537+
} else if (isFinite(a) && isFinite(b)) {
538+
abs(a - b) <=
539+
max(relativeTolerance * max(abs(a), abs(b)), absoluteTolerance)
540+
} else {
541+
// NaN and infinities which were not equal
542+
false
543+
}
544+
}
545+
546+
/**
547+
* Computes the sine of a float (in radians).
548+
*
549+
* @param radians: The input in radians
550+
* @returns The computed sine
551+
*
552+
* @example Float64.sin(0.0d) == 0.0d
553+
*
554+
* @since v0.7.0
555+
*/
556+
@unsafe
557+
provide let sin = (radians: Float64) => {
558+
let xval = WasmF64.load(WasmI32.fromGrain(radians), _VALUE_OFFSET)
559+
let value = sin(xval)
560+
WasmI32.toGrain(newFloat64(value)): Float64
561+
}
562+
563+
/**
564+
* Computes the cosine of a float (in radians).
565+
*
566+
* @param radians: The input in radians
567+
* @returns The computed cosine
568+
*
569+
* @example Float64.cos(0.0d) == 1.0d
570+
*
571+
* @since v0.7.0
572+
*/
573+
@unsafe
574+
provide let cos = (radians: Float64) => {
575+
let xval = WasmF64.load(WasmI32.fromGrain(radians), _VALUE_OFFSET)
576+
let value = cos(xval)
577+
WasmI32.toGrain(newFloat64(value)): Float64
578+
}
579+
580+
/**
581+
* Computes the tangent of a number (in radians).
582+
*
583+
* @param radians: The input in radians
584+
* @returns The computed tangent
585+
*
586+
* @example Float64.tan(0.0d) == 0.0d
587+
*
588+
* @since v0.7.0
589+
*/
590+
@unsafe
591+
provide let tan = (radians: Float64) => {
592+
let xval = WasmF64.load(WasmI32.fromGrain(radians), _VALUE_OFFSET)
593+
let value = tan(xval)
594+
WasmI32.toGrain(newFloat64(value)): Float64
595+
}

0 commit comments

Comments
 (0)