Not Enough Math
Lightweight zero-dependency C++ math library
Loading...
Searching...
No Matches
trig.hpp
Go to the documentation of this file.
1#pragma once
2
3#include "config.hpp"
4#include "utils.hpp"
5#include "power.hpp"
6#include <concepts>
7#include "intrinsics.hpp"
8
9#define NEM_FAST_TRIG
10#ifndef NEM_FAST_TRIG
11 #include <cmath>
12#endif
13
14namespace nem
15{
16 template <nem::scalar_type T>
17 struct sincos
18 {
19 T sin;
20 T cos;
21 };
22
23 static constexpr int DEFAULT_TRIG_PRECISION = 1;
24 static constexpr float DEFAULT_TRIG_EPSILON = 0.01f;
25
27 template <nem::scalar_type T>
28 constexpr T degrees(T degrees) noexcept
29 {
31 }
32
34 template <nem::scalar_type T>
35 constexpr T radians(T radians) noexcept
36 {
37 return radians;
38 }
39
40#if defined(NEM_FAST_TRIG)
41
42 template <std::floating_point T, int P = DEFAULT_TRIG_PRECISION>
43 constexpr T ft_sin_q1(T x)
44 {
45 const T x2 = x * x;
46 if constexpr (P == 1)
47 {
48 return x * (-0.162 * x2 + 1.0); // my custom approximation
49 }
50 else if constexpr (P == 2)
51 {
52 // minimax https://publik-void.github.io/sin-cos-approximations
53 return x * (0.999891821255810892885564707156941565 + x2 * (-0.165960116540878989063185380996540407 + 0.00760290334336935120704015646842617915 * x2));
54 }
55 else if constexpr (P >= 3)
56 {
57 // minimax https://publik-void.github.io/sin-cos-approximations
58 return x * (0.999996615908002773079325846913220383 + x2 * (-0.16664828381895056829366054140948866 + x2 * (0.00830632522715989396465411782615901079 - x2 * 0.00018363653976946785297280224158683484)));
59 }
60 }
61
62 template <std::floating_point T, int P = DEFAULT_TRIG_PRECISION>
63 constexpr T ft_cos_q1 (T x)
64 {
65 const T x2 = x * x;
66 if constexpr (P == 1)
67 {
68 return -0.475 * x2 + 1.0; // my custom approximation
69 }
70 else if constexpr (P == 2)
71 {
72 // minimax https://publik-void.github.io/sin-cos-approximations
73 return 0.997372645040477990699027658698347186 + x2 * (-0.490966242354240750313919970830772248 + 0.0351569652103601536791893003031729288 * x2);
74 }
75 else if constexpr (P >= 3)
76 {
77 // minimax https://publik-void.github.io/sin-cos-approximations
78 return 0.999970210689953068626323587055728078 + x2 * (-0.499782706704688809140466617726333455 + x2 * (0.0413661149638482252569383872576459943 - x2 * 0.0012412397582398600702129604944720102));
79 }
80 }
81
82 template <std::floating_point T, int P = DEFAULT_TRIG_PRECISION>
83 constexpr T sin(T x)
84 {
85 const T s = nem::sign(x);
86 const T absx = x * s;
87 const int k = static_cast<int>(absx / nem::HALF_PI<T> + (T)0.5);
88 const T r = absx - static_cast<T>(k) * nem::HALF_PI<T>;
89 switch (k % 4)
90 {
91 case 0: return s * ft_sin_q1<T, P>(r);
92 case 1: return s * ft_cos_q1<T, P>(r);
93 case 2: return s * -ft_sin_q1<T, P>(r);
94 case 3: return s * -ft_cos_q1<T, P>(r);
95 };
96 NEM_UNREACHABLE();
97 }
98
99 template <std::floating_point T, int P = DEFAULT_TRIG_PRECISION>
100 constexpr T cos(T x)
101 {
102 const T absx = nem::abs(x);
103 const int k = static_cast<int>(absx / nem::HALF_PI<T> + (T)0.5);
104 const T r = absx - static_cast<T>(k) * nem::HALF_PI<T>;
105 switch (k % 4)
106 {
107 case 0: return ft_cos_q1<T, P>(r);
108 case 1: return -ft_sin_q1<T, P>(r);
109 case 2: return -ft_cos_q1<T, P>(r);
110 case 3: return ft_sin_q1<T, P>(r);
111 };
112 NEM_UNREACHABLE();
113 }
114
119 template <std::floating_point T, int P = DEFAULT_TRIG_PRECISION>
121 {
122 const T s = nem::sign(radians);
123 const T absx = radians * s;
124 const int k = static_cast<int>(absx / nem::HALF_PI<T> + (T)0.5);
125 const T r = absx - static_cast<T>(k) * nem::HALF_PI<T>;
126
127 const T sin_q1 = ft_sin_q1<T, P>(r);
128 const T cos_q1 = ft_cos_q1<T, P>(r);
129
130 T sin_r, cos_r;
131 switch (k % 4)
132 {
133 case 0: sin_r = sin_q1; cos_r = cos_q1; break;
134 case 1: sin_r = cos_q1; cos_r = -sin_q1; break;
135 case 2: sin_r = -sin_q1; cos_r = -cos_q1; break;
136 case 3: sin_r = -cos_q1; cos_r = sin_q1; break;
137 default: NEM_UNREACHABLE();
138 }
139
140 return nem::sincos<T> { s * sin_r, cos_r };
141 }
142
143#else
144
145 template <std::floating_point T> T sin(T x)
146 {
147 return ::sinf(x);
148 }
149
150 template <std::floating_point T> T cos(T x)
151 {
152 return ::cosf(x);
153 }
154
155 template <std::floating_point T, int P = DEFAULT_TRIG_PRECISION>
157 {
159 }
160
161#endif
162
166
167 template <std::floating_point T, int P = DEFAULT_TRIG_PRECISION>
168 constexpr T tan(T x)
169 {
171 if (nem::is_zero(sc.cos))
172 {
173 return nem::error::invalid_result<T>(nem::error::Kind::DivisionByZero, "Tangent is undefined here (cosine is zero)");
174 }
175 return sc.sin / sc.cos;
176 }
177
178 template <std::floating_point T, int P = DEFAULT_TRIG_PRECISION>
179 constexpr T ctg(T x)
180 {
182 if (nem::is_zero(sc.sin))
183 {
184 return nem::error::invalid_result<T>(nem::error::Kind::DivisionByZero, "Cotangent is undefined here (sine is zero)");
185 }
186 return sc.cos / sc.sin;
187 }
188
189 template <std::floating_point T, int P = DEFAULT_TRIG_PRECISION>
190 constexpr T sec(T x)
191 {
192 const T c = nem::cos<T, P>(x);
193 if (nem::is_zero(c))
194 {
195 return nem::error::invalid_result<T>(nem::error::Kind::DivisionByZero, "Secant is undefined here (cosine is zero)");
196 }
197 return (T)1.0 / c;
198 }
199
200 template <std::floating_point T, int P = DEFAULT_TRIG_PRECISION>
201 constexpr T csc(T x)
202 {
203 const T s = nem::sin<T, P>(x);
204 if (nem::is_zero(s))
205 {
206 return nem::error::invalid_result<T>(nem::error::Kind::DivisionByZero, "Cosecant is undefined here (sine is zero)");
207 }
208 return (T)1.0 / s;
209 }
210
214
218 template <std::floating_point T, int P = DEFAULT_TRIG_PRECISION>
219 constexpr T ft_atan_q1(T x)
220 {
221 const T x2 = x * x;
222 if constexpr (P == 1)
223 {
224 // minimax (Remez) fit
225 return x * (0.97239411 - 0.19194795 * x2);
226 }
227 else if constexpr (P == 2)
228 {
229 // minimax (Remez) fit
230 return x * (0.99535796 + x2 * (-0.28869025 + 0.07933904 * x2));
231 }
232 else if constexpr (P >= 3)
233 {
234 // minimax (Remez) fit
235 return x * (0.99921385 + x2 * (-0.32117487 + x2 * (0.14626402 - 0.0389862 * x2)));
236 }
237 }
238
239 template <std::floating_point T, int P = DEFAULT_TRIG_PRECISION>
240 constexpr T atan(T x)
241 {
242 const T s = nem::sign(x);
243 const T absx = s * x;
244 if (absx <= (T)1.0)
245 {
246 return s * ft_atan_q1<T, P>(absx);
247 }
248 return s * (nem::HALF_PI<T> - ft_atan_q1<T, P>((T)1.0 / absx));
249 }
250
253 template <std::floating_point T, int P = DEFAULT_TRIG_PRECISION>
254 constexpr T atan2(T y, T x)
255 {
256 if (nem::is_zero(x))
257 {
258 if (nem::is_zero(y))
259 {
260 return nem::error::invalid_result<T>(nem::error::Kind::DivisionByZero, "atan2 is undefined at the origin");
261 }
262 return nem::sign(y) * nem::HALF_PI<T>;
263 }
264
265 const T base = nem::atan<T, P>(y / x);
266 return (x > (T)0.0) ? base : base + nem::sign(y) * nem::PI<T>;
267 }
268
272 template <std::floating_point T>
273 constexpr T acos(T x)
274 {
275 const T negate = T(x < 0);
276 const T ax = nem::abs(x);
277
278 T ret = (T)-0.0187293;
279 ret = ret * ax + (T)0.0742610;
280 ret = ret * ax - (T)0.2121144;
281 ret = ret * ax + (T)1.5707288;
282 ret = ret * nem::sqrt((T)1.0 - ax);
283 ret = ret - (T)2.0 * negate * ret;
284 return negate * nem::PI<T> + ret;
285 }
286
287 template <std::floating_point T>
288 constexpr T asin(T x)
289 {
290 return nem::HALF_PI<T> - nem::acos(x);
291 }
292
293 template <std::floating_point T, int P = DEFAULT_TRIG_PRECISION>
294 constexpr T actg(T x)
295 {
297 }
298
301 template <std::floating_point T, int P = DEFAULT_TRIG_PRECISION>
302 constexpr T actg2(T x, T y)
303 {
304 return nem::atan2<T, P>(x, y);
305 }
306}
T invalid_result(nem::error::Kind kind=Kind::RuntimeError, const char *const msg=nullptr)
Definition err.hpp:120
Definition config.hpp:16
constexpr T actg(T x)
Definition trig.hpp:294
constexpr T csc(T x)
Definition trig.hpp:201
constexpr T tan(T x)
Definition trig.hpp:168
constexpr T atan(T x)
Definition trig.hpp:240
static constexpr int DEFAULT_TRIG_PRECISION
Definition trig.hpp:23
constexpr T acos(T x)
Definition trig.hpp:273
constexpr T actg2(T x, T y)
Definition trig.hpp:302
constexpr T sec(T x)
Definition trig.hpp:190
constexpr T PI
Definition consts.hpp:10
constexpr T ft_cos_q1(T x)
Definition trig.hpp:63
T sqrt(T value)
Definition power.hpp:180
constexpr T atan2(T y, T x)
Definition trig.hpp:254
constexpr T cos(T x)
Definition trig.hpp:100
constexpr T sign(T value) noexcept
Definition utils.hpp:36
constexpr T abs(T value) noexcept
Definition utils.hpp:38
constexpr bool is_zero(T a) noexcept
Is a nearly 0? Given an Epsilon > 0, within each all numbers are considered indistinguisable from 0,...
Definition utils.hpp:48
constexpr T HALF_PI
Definition consts.hpp:11
constexpr T degrees(T degrees) noexcept
Converts degrees to radians. Used to make units explicit.
Definition trig.hpp:28
constexpr nem::sincos< T > get_sincos(T radians)
Definition trig.hpp:120
constexpr T radians(T radians) noexcept
Converts radians to radians (passes the argument unchanged). Used to make units explicit.
Definition trig.hpp:35
constexpr T asin(T x)
Definition trig.hpp:288
constexpr T sin(T x)
Definition trig.hpp:83
constexpr T ctg(T x)
Definition trig.hpp:179
constexpr T ft_sin_q1(T x)
Definition trig.hpp:43
constexpr T ft_atan_q1(T x)
Definition trig.hpp:219
static constexpr float DEFAULT_TRIG_EPSILON
Definition trig.hpp:24
constexpr T DEG_TO_RAD
Definition consts.hpp:17