sourcework/xetal-libraries/libs/Trig/src/Trig.xtl
1⍝│ Trig: trigonometric and hyperbolic functions not in the base
2⍝│ language, written in closed form from its builtins (s_in, c_os,
3⍝│ a_tan, s_qrt, e_xp, e_xpm1, l_og, l_og1p). The hyperbolic tangent
4⍝│ is the builtin t_anh; Trig keeps no copy of it.
5⍝│ Import it with an alias of your choice: "tr:" u_se< "Trig".
6⍝│ Put libs/Trig/src on XETAL_PATH ("just path"); the reference is libs/Trig/docs.
7⍝│ Names with l: are exported; those under h: are private to this file.
8⍝│
9⍝│ Angles are in radians. Every function works on whole arrays, one
10⍝│ element at a time, and its results are Floats.
11
12⍝━━ Circular
13
14⍝│ Half of pi, the angle of a quarter turn.
15ʰq̲uarter ← { u → 0.5 × p̲i u }
16
17⍝│ t_an x: the tangent, the sine over the cosine. Near an odd multiple
18⍝│ of a quarter turn it is as large as the cosine there is small.
19ˡt̲an ← { x → (s̲in x) ÷ c̲os x }
20
21⍝│ a_sin x: the angle whose sine is x, from -pi/2 to pi/2, by the
22⍝│ half-angle form 2 atan(x / (1 + sqrt(1 - x^2))), whose divisor is
23⍝│ never below 1. Outside -1..1 the square root (s_qrt) refuses
24⍝│ (error[domain]).
25ˡa̲sin ← { x → y ← f̲loat x◆ 2.0 × a̲tan y ÷ 1.0 + s̲qrt 1.0 − y × y }
26
27⍝│ a_cos x: the angle whose cosine is x, from 0 to pi, as
28⍝│ 2 asin(sqrt((1 - x) / 2)), exact near both ends. Outside -1..1 the
29⍝│ square root (s_qrt) refuses (error[domain]).
30ˡa̲cos ← { x → 2.0 × ˡa̲sin s̲qrt 0.5 × 1.0 − f̲loat x }
31
32⍝│ y a_tan2 x: the angle of the point (x, y) from the positive x axis,
33⍝│ from -pi to pi: a_tan (y / x) turned by pi into the left half plane
34⍝│ (up for y >= 0, down for y < 0); on the y axis, pi/2 or -pi/2; at
35⍝│ the origin, 0.
36ˡa̲tan2 ← { y x →
37 b ← f̲loat y
38 a ← f̲loat x
39 z ← f̲loat a = 0.0
40 t ← (1.0 − z) × a̲tan b ÷ a + z
41 up ← (f̲loat b ≥ 0.0) − f̲loat b < 0.0
42 side ← (f̲loat b > 0.0) − f̲loat b < 0.0
43 t + ((p̲i @) × up × f̲loat a < 0.0) + (ʰq̲uarter @) × side × z
44}
45
46⍝━━ Hyperbolic
47
48⍝│ ((k p_ick lo)_ hi): lo where the mask k is 1.0, hi where it is 0.0.
49⍝│ The branch not picked must be finite (0 times an infinity is NaN),
50⍝│ so each caller clamps the argument it gives a branch.
51ʰp̲ick ← { k lo hi → (k × lo) + (1.0 − k) × hi }
52
53⍝│ The sign of x as a Float, 1.0 for 0: the odd functions take it from
54⍝│ their argument and work on its magnitude.
55ʰs̲ign ← { x → 1.0 − 2.0 × f̲loat x < 0.0 }
56
57⍝│ Where the large-argument forms take over: past it e^-a is below
58⍝│ 2e-9 of e^a (and below 1e-16 of 1/a^2 for the inverses).
59ʰb̲ig ← { u → 20.0 }
60
61⍝│ e^a / 2, as e^(a - ln 2) so it does not overflow until the result
62⍝│ does (a up to about 710.47, where e^a alone stops at 709.78).
63ʰh̲alfExp ← { a → e̲xp a − l̲og 2.0 }
64
65⍝│ s_inh x: the hyperbolic sine, (e^x - e^-x) / 2. Up to |x| = 20 as
66⍝│ (m + m / (m + 1)) / 2 with m = e^|x| - 1 (e_xpm1), so no
67⍝│ cancellation near 0; past it e^|x| / 2. Infinite past about 710.47.
68ˡs̲inh ← { x →
69 y ← f̲loat x
70 a ← a̲bs y
71 m ← e̲xpm1 a m̲in ʰb̲ig @
72 k ← f̲loat a ≤ ʰb̲ig @
73 lo ← 0.5 × m + m ÷ m + 1.0
74 hi ← ʰh̲alfExp a m̲ax ʰb̲ig @
75 (ʰs̲ign y) × ((k ʰp̲ick lo)_ hi)
76}
77
78⍝│ c_osh x: the hyperbolic cosine, (e^x + e^-x) / 2, never below 1.
79⍝│ Up to |x| = 20 as 1 + m^2 / (2 (m + 1)) with m = e^|x| - 1, past it
80⍝│ e^|x| / 2. Infinite past about 710.47.
81ˡc̲osh ← { x →
82 a ← a̲bs f̲loat x
83 m ← e̲xpm1 a m̲in ʰb̲ig @
84 k ← f̲loat a ≤ ʰb̲ig @
85 lo ← 1.0 + (m × m) ÷ 2.0 × m + 1.0
86 hi ← ʰh̲alfExp a m̲ax ʰb̲ig @
87 ((k ʰp̲ick lo)_ hi)
88}
89
90⍝│ Where the inverses' large-argument form takes over: past it
91⍝│ sqrt(a^2 + 1) is a to the last bit (and a^2 cannot overflow below).
92ʰh̲uge ← { u → 100000000.0 }
93
94⍝│ ln(2a), for a past h:h_uge: the inverses' large-argument form.
95ʰl̲ogTwice ← { a → (l̲og a) + l̲og 2.0 }
96
97⍝│ a_sinh x: the inverse hyperbolic sine, ln(x + sqrt(x^2 + 1)), for
98⍝│ every x. Up to |x| = 1e8 as log1p(a + a^2 / (1 + sqrt(1 + a^2)))
99⍝│ (l_og1p), so no cancellation near 0; past it ln(2a).
100ˡa̲sinh ← { x →
101 y ← f̲loat x
102 a ← a̲bs y
103 b ← a m̲in ʰh̲uge @
104 k ← f̲loat a ≤ ʰh̲uge @
105 lo ← l̲og1p b + (b × b) ÷ 1.0 + s̲qrt 1.0 + b × b
106 hi ← ʰl̲ogTwice a m̲ax ʰh̲uge @
107 (ʰs̲ign y) × ((k ʰp̲ick lo)_ hi)
108}
109
110⍝│ a_cosh x: the inverse hyperbolic cosine, ln(x + sqrt(x^2 - 1)), for
111⍝│ x of 1 or more, from 0 up. Up to 1e8 as log1p(t + sqrt(t) sqrt(t + 2))
112⍝│ with t = x - 1, exact near 1; past it ln(2x). Below 1 the square
113⍝│ root of t (s_qrt) refuses (error[domain]).
114ˡa̲cosh ← { x →
115 y ← f̲loat x
116 t ← (y m̲in ʰh̲uge @) − 1.0
117 k ← f̲loat y ≤ ʰh̲uge @
118 lo ← l̲og1p t + (s̲qrt t) × s̲qrt t + 2.0
119 hi ← ʰl̲ogTwice y m̲ax ʰh̲uge @
120 ((k ʰp̲ick lo)_ hi)
121}
122
123⍝│ a_tanh x: the inverse hyperbolic tangent, ln((1 + x) / (1 - x)) / 2,
124⍝│ for x strictly between -1 and 1, as log1p(2a / (1 - a)) / 2 on the
125⍝│ magnitude a (no cancellation near 0). At -1 or 1 the division
126⍝│ refuses (error[division-by-zero]); beyond them l_og1p does
127⍝│ (error[domain]).
128ˡa̲tanh ← { x →
129 y ← f̲loat x
130 a ← a̲bs y
131 (ʰs̲ign y) × 0.5 × l̲og1p (2.0 × a) ÷ 1.0 − a
132}
133
134⍝━━ Units and lengths
135
136⍝│ d_egrees x: the angle x (radians) in degrees, x * 180 / pi.
137ˡd̲egrees ← { x → (f̲loat x) × 180.0 ÷ p̲i @ }
138
139⍝│ r_adians x: the angle x (degrees) in radians, x * pi / 180.
140ˡr̲adians ← { x → (f̲loat x) × (p̲i @) ÷ 180.0 }
141
142⍝│ y h_ypot x: the length of (x, y), sqrt(x^2 + y^2), without
143⍝│ overflow or underflow: both are divided by the larger magnitude m
144⍝│ before squaring (so the squares are at most 1) and the root is
145⍝│ multiplied by m again; 0 when both are 0.
146ˡh̲ypot ← { y x →
147 a ← a̲bs f̲loat x
148 b ← a̲bs f̲loat y
149 m ← a m̲ax b
150 d ← m + f̲loat m = 0.0
151 m × s̲qrt ((a ÷ d) × a ÷ d) + (b ÷ d) × b ÷ d
152}