Skip to content

Commit f97c011

Browse files
authored
feat(stdlib): Reimplement Number.sin, Number.cos, Number.tan (#2158)
1 parent e99dcba commit f97c011

File tree

15 files changed

+1035
-0
lines changed

15 files changed

+1035
-0
lines changed

compiler/test/stdlib/number.test.gr

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -949,3 +949,111 @@ assert Number.linearMap(
949949
0.5
950950
) ==
951951
150
952+
953+
// Number.sin - 0 to pi/2
954+
assert Number.sin(0) == 0
955+
assert Number.isClose(Number.sin(Number.pi / 6), 1/2)
956+
assert Number.isClose(Number.sin(Number.pi / 4), Number.sqrt(2) / 2)
957+
assert Number.isClose(Number.sin(Number.pi / 3), Number.sqrt(3) / 2)
958+
assert Number.isClose(Number.sin(Number.pi / 2), 1)
959+
// Number.sin - pi/2 to 2pi
960+
assert Number.isClose(Number.sin(2 * Number.pi / 3), Number.sqrt(3) / 2)
961+
assert Number.isClose(Number.sin(3 * Number.pi / 4), Number.sqrt(2) / 2)
962+
assert Number.isClose(Number.sin(5 * Number.pi / 6), 1/2)
963+
// Note: This has an absolute error of 1e-15 because `Number.pi` is not exact
964+
assert Number.isClose(Number.sin(Number.pi), 0, absoluteTolerance=1e-15)
965+
// Number.sin - 2pi to 3pi/2
966+
assert Number.isClose(Number.sin(7 * Number.pi / 6), -1/2)
967+
assert Number.isClose(Number.sin(5 * Number.pi / 4), Number.sqrt(2) / -2)
968+
assert Number.isClose(Number.sin(4 * Number.pi / 3), Number.sqrt(3) / -2)
969+
assert Number.isClose(Number.sin(3 * Number.pi / 2), -1)
970+
// Number.sin - 3pi/2 to 0
971+
assert Number.isClose(Number.sin(5 * Number.pi / 3), Number.sqrt(3) / -2)
972+
assert Number.isClose(Number.sin(7 * Number.pi / 4), Number.sqrt(2) / -2)
973+
assert Number.isClose(Number.sin(11 * Number.pi / 6), -1/2)
974+
// Note: This has an absolute error of 1e-15 because `Number.pi` is not exact
975+
assert Number.isClose(Number.sin(2 * Number.pi), 0, absoluteTolerance=1e-15)
976+
// Number.sin - special cases
977+
assert Number.sin(1/2) == Number.sin(0.5)
978+
assert Number.sin(1/4) == Number.sin(0.25)
979+
assert Number.isClose(Number.sin(18_446_744_073_709_551_615), 0.0235985099) // 2^64-1
980+
assert Number.isClose(Number.sin(-18_446_744_073_709_551_615), -0.0235985099) // -2^64+1
981+
assert Number.isClose(Number.sin(18_446_744_073_709_551_616), 0.0235985099) // 2^64
982+
assert Number.isClose(Number.sin(-18_446_744_073_709_551_616), -0.0235985099) // -2^64
983+
assert Number.isClose( // Note: We lose a lot of precision here do to ieee754 representation
984+
Number.sin(1.7976931348623157e+308),
985+
0.0049619,
986+
absoluteTolerance=1e7
987+
) // Max F64
988+
assert Number.isClose(
989+
Number.sin(-1.7976931348623157e+308),
990+
0.00496,
991+
absoluteTolerance=1e7
992+
) // Max F64
993+
assert Number.isNaN(Number.sin(Infinity))
994+
assert Number.isNaN(Number.sin(-Infinity))
995+
assert Number.isNaN(Number.sin(NaN))
996+
997+
// Number.cos - 0 to pi/2
998+
assert Number.cos(0) == 1
999+
assert Number.isClose(Number.cos(Number.pi / 6), Number.sqrt(3) / 2)
1000+
assert Number.isClose(Number.cos(Number.pi / 4), Number.sqrt(2) / 2)
1001+
assert Number.isClose(Number.cos(Number.pi / 3), 1/2)
1002+
// Note: This has an absolute error of 1e-15 because `Number.pi` is not exact
1003+
assert Number.isClose(Number.cos(Number.pi / 2), 0, absoluteTolerance=1e-15)
1004+
// Number.cos - pi/2 to 2pi
1005+
assert Number.isClose(Number.cos(2 * Number.pi / 3), -1/2)
1006+
assert Number.isClose(Number.cos(3 * Number.pi / 4), Number.sqrt(2) / -2)
1007+
assert Number.isClose(Number.cos(5 * Number.pi / 6), Number.sqrt(3) / -2)
1008+
assert Number.isClose(Number.cos(Number.pi), -1)
1009+
// Number.cos - 2pi to 3pi/2
1010+
assert Number.isClose(Number.cos(7 * Number.pi / 6), Number.sqrt(3) / -2)
1011+
assert Number.isClose(Number.cos(5 * Number.pi / 4), Number.sqrt(2) / -2)
1012+
assert Number.isClose(Number.cos(4 * Number.pi / 3), -1/2)
1013+
// Note: This has an absolute error of 1e-15 because `Number.pi` is not exact
1014+
assert Number.isClose(Number.cos(3 * Number.pi / 2), 0, absoluteTolerance=1e-15)
1015+
// Number.cos - 3pi/2 to 0
1016+
assert Number.isClose(Number.cos(5 * Number.pi / 3), 1/2)
1017+
assert Number.isClose(Number.cos(7 * Number.pi / 4), Number.sqrt(2) / 2)
1018+
assert Number.isClose(Number.cos(11 * Number.pi / 6), Number.sqrt(3) / 2)
1019+
assert Number.isClose(Number.cos(2 * Number.pi), 1)
1020+
// Number.cos - special cases
1021+
assert Number.cos(1/2) == Number.cos(0.5)
1022+
assert Number.cos(1/4) == Number.cos(0.25)
1023+
assert Number.isClose(Number.cos(18_446_744_073_709_551_615), -0.99972151638) // 2^64-1
1024+
assert Number.isClose(Number.cos(-18_446_744_073_709_551_615), -0.99972151638) // -2^64+1
1025+
assert Number.isClose(Number.cos(18_446_744_073_709_551_616), -0.99972151638) // 2^64
1026+
assert Number.isClose(Number.cos(-18_446_744_073_709_551_616), -0.99972151638) // -2^64
1027+
assert Number.isClose(Number.cos(1.7976931348623157e+308), -0.99998768942) // Max F64
1028+
assert Number.isClose(Number.cos(-1.7976931348623157e+308), -0.99998768942) // Max F64
1029+
assert Number.isNaN(Number.cos(Infinity))
1030+
assert Number.isNaN(Number.cos(-Infinity))
1031+
assert Number.isNaN(Number.cos(NaN))
1032+
1033+
// Number.tan - base cases
1034+
assert Number.tan(0) == 0
1035+
assert Number.isClose(Number.tan(Number.pi / 6), 1 / Number.sqrt(3))
1036+
assert Number.isClose(Number.tan(Number.pi / 4), 1)
1037+
assert Number.isClose(Number.tan(Number.pi / 3), Number.sqrt(3))
1038+
// 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
1039+
assert Number.isClose(Number.tan(Number.pi / 2), 16331239353195370)
1040+
// Number.tan - special cases
1041+
assert Number.tan(1/2) == Number.tan(0.5)
1042+
assert Number.tan(1/4) == Number.tan(0.25)
1043+
assert Number.isClose(Number.tan(18_446_744_073_709_551_615), -0.02360508353) // 2^64-1
1044+
assert Number.isClose(Number.tan(-18_446_744_073_709_551_615), 0.02360508353) // -2^64+1
1045+
assert Number.isClose(Number.tan(18_446_744_073_709_551_616), -0.02360508353) // 2^64
1046+
assert Number.isClose(Number.tan(-18_446_744_073_709_551_616), 0.02360508353) // -2^64
1047+
assert Number.isClose( // Note: We lose a lot of precision here do to ieee754 representation
1048+
Number.tan(1.7976931348623157e+308),
1049+
-0.00496201587,
1050+
absoluteTolerance=1e7
1051+
) // Max F64
1052+
assert Number.isClose(
1053+
Number.tan(-1.7976931348623157e+308),
1054+
-0.00496201587,
1055+
absoluteTolerance=1e7
1056+
) // Max F64
1057+
assert Number.isNaN(Number.tan(Infinity))
1058+
assert Number.isNaN(Number.tan(-Infinity))
1059+
assert Number.isNaN(Number.tan(NaN))

stdlib/number.gr

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,8 @@ from "runtime/atoi/parse" include Parse as Atoi
4848
from "runtime/atof/parse" include Parse as Atof
4949
from "runtime/unsafe/tags" include Tags
5050
from "runtime/exception" include Exception
51+
from "runtime/math/trig" include Trig
52+
use Trig.{ sin, cos, tan }
5153

5254
use Atoi.{ type ParseIntError }
5355

@@ -1072,3 +1074,69 @@ provide let linearMap = (inputRange, outputRange, current) => {
10721074
clamp(outputRange, mapped)
10731075
}
10741076
}
1077+
1078+
/**
1079+
* Computes the sine of a number (in radians).
1080+
*
1081+
* @param radians: The input in radians
1082+
* @returns The computed sine
1083+
*
1084+
* @example Number.sin(0) == 0
1085+
*
1086+
* @since v0.7.0
1087+
*/
1088+
@unsafe
1089+
provide let sin = (radians: Number) => {
1090+
use WasmF64.{ (==) }
1091+
let xval = coerceNumberToWasmF64(radians)
1092+
let value = sin(xval)
1093+
return if (!isFloat(radians) && value == WasmF64.trunc(value)) {
1094+
WasmI32.toGrain(reducedInteger(WasmI64.truncF64S(value))): Number
1095+
} else {
1096+
WasmI32.toGrain(newFloat64(value)): Number
1097+
}
1098+
}
1099+
1100+
/**
1101+
* Computes the cosine of a number (in radians).
1102+
*
1103+
* @param radians: The input in radians
1104+
* @returns The computed cosine
1105+
*
1106+
* @example Number.cos(0) == 1
1107+
*
1108+
* @since v0.7.0
1109+
*/
1110+
@unsafe
1111+
provide let cos = (radians: Number) => {
1112+
use WasmF64.{ (==) }
1113+
let xval = coerceNumberToWasmF64(radians)
1114+
let value = cos(xval)
1115+
return if (!isFloat(radians) && value == WasmF64.trunc(value)) {
1116+
WasmI32.toGrain(reducedInteger(WasmI64.truncF64S(value))): Number
1117+
} else {
1118+
WasmI32.toGrain(newFloat64(value)): Number
1119+
}
1120+
}
1121+
1122+
/**
1123+
* Computes the tangent of a number (in radians).
1124+
*
1125+
* @param radians: The input in radians
1126+
* @returns The computed tangent
1127+
*
1128+
* @example Number.tan(0) == 0
1129+
*
1130+
* @since v0.7.0
1131+
*/
1132+
@unsafe
1133+
provide let tan = (radians: Number) => {
1134+
use WasmF64.{ (==) }
1135+
let xval = coerceNumberToWasmF64(radians)
1136+
let value = tan(xval)
1137+
return if (!isFloat(radians) && value == WasmF64.trunc(value)) {
1138+
WasmI32.toGrain(reducedInteger(WasmI64.truncF64S(value))): Number
1139+
} else {
1140+
WasmI32.toGrain(newFloat64(value)): Number
1141+
}
1142+
}

stdlib/number.md

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1566,3 +1566,96 @@ Throws:
15661566
* When `outputRange` is not finite
15671567
* When `outputRange` includes NaN
15681568

1569+
### Number.**sin**
1570+
1571+
<details disabled>
1572+
<summary tabindex="-1">Added in <code>next</code></summary>
1573+
No other changes yet.
1574+
</details>
1575+
1576+
```grain
1577+
sin : (radians: Number) => Number
1578+
```
1579+
1580+
Computes the sine of a number (in radians).
1581+
1582+
Parameters:
1583+
1584+
|param|type|description|
1585+
|-----|----|-----------|
1586+
|`radians`|`Number`|The input in radians|
1587+
1588+
Returns:
1589+
1590+
|type|description|
1591+
|----|-----------|
1592+
|`Number`|The computed sine|
1593+
1594+
Examples:
1595+
1596+
```grain
1597+
Number.sin(0) == 0
1598+
```
1599+
1600+
### Number.**cos**
1601+
1602+
<details disabled>
1603+
<summary tabindex="-1">Added in <code>next</code></summary>
1604+
No other changes yet.
1605+
</details>
1606+
1607+
```grain
1608+
cos : (radians: Number) => Number
1609+
```
1610+
1611+
Computes the cosine of a number (in radians).
1612+
1613+
Parameters:
1614+
1615+
|param|type|description|
1616+
|-----|----|-----------|
1617+
|`radians`|`Number`|The input in radians|
1618+
1619+
Returns:
1620+
1621+
|type|description|
1622+
|----|-----------|
1623+
|`Number`|The computed cosine|
1624+
1625+
Examples:
1626+
1627+
```grain
1628+
Number.cos(0) == 1
1629+
```
1630+
1631+
### Number.**tan**
1632+
1633+
<details disabled>
1634+
<summary tabindex="-1">Added in <code>next</code></summary>
1635+
No other changes yet.
1636+
</details>
1637+
1638+
```grain
1639+
tan : (radians: Number) => Number
1640+
```
1641+
1642+
Computes the tangent of a number (in radians).
1643+
1644+
Parameters:
1645+
1646+
|param|type|description|
1647+
|-----|----|-----------|
1648+
|`radians`|`Number`|The input in radians|
1649+
1650+
Returns:
1651+
1652+
|type|description|
1653+
|----|-----------|
1654+
|`Number`|The computed tangent|
1655+
1656+
Examples:
1657+
1658+
```grain
1659+
Number.tan(0) == 0
1660+
```
1661+

stdlib/runtime/math/kernel/cos.gr

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
/*
2+
* ====================================================
3+
* Copyright (C) 1993 by Sun Microsystems, Inc. All rights reserved.
4+
*
5+
* Developed at SunSoft, a Sun Microsystems, Inc. business.
6+
* Permission to use, copy, modify, and distribute this
7+
* software is freely granted, provided that this notice
8+
* is preserved.
9+
* ====================================================
10+
*/
11+
module Cosine
12+
13+
from "runtime/unsafe/wasmf64" include WasmF64
14+
15+
/*
16+
* Source: https://git.musl-libc.org/cgit/musl/tree/src/math/__cos.c
17+
*
18+
* kernel cos function on [-pi/4, pi/4], pi/4 ~ 0.785398164
19+
* Input x is assumed to be bounded by ~pi/4 in magnitude.
20+
* Input y is the tail of x.
21+
*
22+
* Algorithm
23+
* 1. Since cos(-x) = cos(x), we need only to consider positive x.
24+
* 2. if x < 2^-27 (hx<0x3e400000 0), return 1 with inexact if x!=0.
25+
* 3. cos(x) is approximated by a polynomial of degree 14 on
26+
* [0,pi/4]
27+
* 4 14
28+
* cos(x) ~ 1 - x*x/2 + C1*x + ... + C6*x
29+
* where the remez error is
30+
*
31+
* | 2 4 6 8 10 12 14 | -58
32+
* |cos(x)-(1-.5*x +C1*x +C2*x +C3*x +C4*x +C5*x +C6*x )| <= 2
33+
* | |
34+
*
35+
* 4 6 8 10 12 14
36+
* 4. let r = C1*x +C2*x +C3*x +C4*x +C5*x +C6*x , then
37+
* cos(x) ~ 1 - x*x/2 + r
38+
* since cos(x+y) ~ cos(x) - sin(x)*y
39+
* ~ cos(x) - x*y,
40+
* a correction term is necessary in cos(x) and hence
41+
* cos(x+y) = 1 - (x*x/2 - (r - x*y))
42+
* For better accuracy, rearrange to
43+
* cos(x+y) ~ w + (tmp + (r-x*y))
44+
* where w = 1 - x*x/2 and tmp is a tiny correction term
45+
* (1 - x*x/2 == w + tmp exactly in infinite precision).
46+
* The exactness of w + tmp in infinite precision depends on w
47+
* and tmp having the same precision as x. If they have extra
48+
* precision due to compiler bugs, then the extra precision is
49+
* only good provided it is retained in all terms of the final
50+
* expression for cos(). Retention happens in all cases tested
51+
* under FreeBSD, so don't pessimize things by forcibly clipping
52+
* any extra precision in w.
53+
*/
54+
@unsafe
55+
provide let cos = (x: WasmF64, y: WasmF64) => {
56+
use WasmF64.{ (+), (-), (*) }
57+
let c1 = 4.16666666666666019037e-02W /* 0x3FA55555, 0x5555554C */
58+
let c2 = -1.38888888888741095749e-03W /* 0xBF56C16C, 0x16C15177 */
59+
let c3 = 2.48015872894767294178e-05W /* 0x3EFA01A0, 0x19CB1590 */
60+
let c4 = -2.75573143513906633035e-07W /* 0xBE927E4F, 0x809C52AD */
61+
let c5 = 2.08757232129817482790e-09W /* 0x3E21EE9E, 0xBDB4B1C4 */
62+
let c6 = -1.13596475577881948265e-11W /* 0xBDA8FAE9, 0xBE8838D4 */
63+
64+
let z = x * x
65+
let w = z * z
66+
let r = z * (c1 + z * (c2 + z * c3)) + w * w * (c4 + z * (c5 + z * c6))
67+
let hz = 0.5W * z
68+
let w = 1.0W - hz
69+
w + (1.0W - w - hz + (z * r - x * y))
70+
}

stdlib/runtime/math/kernel/cos.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
---
2+
title: Cosine
3+
---
4+
5+
## Values
6+
7+
Functions and constants included in the Cosine module.
8+
9+
### Cosine.**cos**
10+
11+
```grain
12+
cos : (x: WasmF64, y: WasmF64) => WasmF64
13+
```
14+

0 commit comments

Comments
 (0)