JPL Spatial
Sound spatialization and propagation library
Loading...
Searching...
No Matches
SIMDMath.h
Go to the documentation of this file.
1//
2// ██╗██████╗ ██╗ ██╗██████╗ ███████╗
3// ██║██╔══██╗ ██║ ██║██╔══██╗██╔════╝ ** JPLSpatial **
4// ██║██████╔╝ ██║ ██║██████╔╝███████╗
5// ██ ██║██╔═══╝ ██║ ██║██╔══██╗╚════██║ https://github.com/Jaytheway/JPLSpatial
6// ╚█████╔╝██║ ███████╗██║██████╔╝███████║
7// ╚════╝ ╚═╝ ╚══════╝╚═╝╚═════╝ ╚══════╝
8//
9// Copyright 2024 Jaroslav Pevno, JPLSpatial is offered under the terms of the ISC license:
10//
11// Permission to use, copy, modify, and/or distribute this software for any purpose with or
12// without fee is hereby granted, provided that the above copyright notice and this permission
13// notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
14// WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
15// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
16// CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
17// WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
18// CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
19
20#pragma once
21
22#include "JPLSpatial/Core.h"
24
25#include <numbers>
26#include <concepts>
27
28namespace JPL
29{
30 static_assert(simd::size() == 4 && "The SIMD counter math below assumes simd::size() is 4.");
31
33 [[nodiscard]] JPL_INLINE constexpr auto FloorToSIMDSize(std::unsigned_integral auto count) noexcept
34 {
35 return count & 0xFFFFFFFC;
36 }
37
39 [[nodiscard]] JPL_INLINE constexpr auto FloorToSIMDSizeDouble(std::unsigned_integral auto count) noexcept
40 {
41 return count & 0xFFFFFFF8;
42 }
43
45 [[nodiscard]] JPL_INLINE constexpr auto GetSIMDTail(std::unsigned_integral auto count) noexcept
46 {
47 return count & 3;
48 }
49
51 [[nodiscard]] JPL_INLINE constexpr auto GetNumSIMDOps(std::unsigned_integral auto count) noexcept
52 {
53 return count >> 2;
54 }
55
57 [[nodiscard]] JPL_INLINE constexpr auto GetNumSIMDOpsDouble(std::unsigned_integral auto count) noexcept
58 {
59 return count >> 3;
60 }
61
63 [[nodiscard]] JPL_INLINE constexpr auto GetSIMDTailDouble(std::unsigned_integral auto count) noexcept
64 {
65 return count & 7;
66 }
67
69 [[nodiscard]] JPL_INLINE constexpr auto RoundUpToSIMD(std::unsigned_integral auto count) noexcept
70 {
71 return (count + 3) >> 2;
72 }
73
75 [[nodiscard]] JPL_INLINE constexpr auto FloorToDiv2(std::unsigned_integral auto count) noexcept
76 {
77 return count & (~1llu);
78 }
79
81 [[nodiscard]] JPL_INLINE constexpr auto GetDiv2Tail(std::unsigned_integral auto count) noexcept
82 {
83 return count & 1;
84 }
85
86 //==========================================================================
88
89#define JPL_FL_CONSTANT(Name, Val)\
90 JPL_INLINE simd c_##Name() noexcept { return simd(Val);; }
91
92#define JPL_INT_CONSTANT(Name, Val)\
93 JPL_INLINE simd_mask c_i##Name() noexcept { return simd_mask(static_cast<uint32>(Val)); }
94
95#define JPL_MASK_CONSTANT(Name, Val)\
96 JPL_INLINE simd_mask c_##Name() noexcept { return simd_mask(static_cast<uint32>(Val)); }
97
98 namespace constant
99 {
100 // the smallest non denormalized float number
104
107
112 JPL_INT_CONSTANT(0x7f, 0x7f);
113
114 JPL_FL_CONSTANT(pi, std::numbers::pi_v<float>);
115 JPL_FL_CONSTANT(half_pi, std::numbers::pi_v<float> * 0.5f);
116 }
117
118 //==========================================================================
121 {
122 return vec.as_mask() & constant::c_sign_mask();
123 }
124
127 {
128 return abs(vec) == simd::inf();
129 }
130
133 {
134 return vec != vec;
135 }
136
138 JPL_INLINE simd trunc(const simd& vec) noexcept
139 {
141 // Return original value for any the checks that are true
142 simd vec_abs = abs(vec);
147 vec,
148 vec_abs.to_mask().to_simd() | sign_bit.as_simd());
149 }
150
151 //==========================================================================
153 namespace cephes
154 {
155 JPL_FL_CONSTANT(SQRTHF, 0.707106781186547524f);
156 JPL_FL_CONSTANT(log_p0, 7.0376836292e-2f);
157 JPL_FL_CONSTANT(log_p1, -1.1514610310e-1f);
158 JPL_FL_CONSTANT(log_p2, 1.1676998740e-1f);
159 JPL_FL_CONSTANT(log_p3, -1.2420140846e-1f);
160 JPL_FL_CONSTANT(log_p4, +1.4249322787e-1f);
161 JPL_FL_CONSTANT(log_p5, -1.6668057665e-1f);
162 JPL_FL_CONSTANT(log_p6, +2.0000714765e-1f);
163 JPL_FL_CONSTANT(log_p7, -2.4999993993e-1f);
164 JPL_FL_CONSTANT(log_p8, +3.3333331174e-1f);
165 JPL_FL_CONSTANT(log_q1, -2.12194440e-4f);
166 JPL_FL_CONSTANT(log_q2, 0.693359375f);
167 }
168
169 namespace logarithm
170 {
171 // Evaluat log polynomial
172 JPL_INLINE simd polynomial(const simd& x) noexcept
173 {
174 simd y = cephes::c_log_p0();
175 y = fma(y, x, cephes::c_log_p1());
176 y = fma(y, x, cephes::c_log_p2());
177 y = fma(y, x, cephes::c_log_p3());
178 y = fma(y, x, cephes::c_log_p4());
179 y = fma(y, x, cephes::c_log_p5());
180 y = fma(y, x, cephes::c_log_p6());
181 y = fma(y, x, cephes::c_log_p7());
182 y = fma(y, x, cephes::c_log_p8());
183 y *= x;
184 return y;
185 }
186 }
187
188 //==========================================================================
191 inline simd log(simd x) noexcept
192 {
193 simd invalid_mask = (x <= simd::zero()).as_simd();
194
195 x = max(x, constant::c_min_norm_pos().as_simd()); // cut off denormalized stuff
196
197 // part 1: x = frexpf(x, &e);
198 const simd_mask emm0 = (x.as_mask() >> 23) - constant::c_i0x7f();
199
200 // now e contains the really base-2 exponent
201 simd e = emm0.to_simd() + simd::c_1();
202
203 // keep only the fractional part
204 x &= constant::c_inv_mant_mask().as_simd();
205 x |= simd::c_0p5();
206
207 /* part2:
208 if( x < SQRTHF ) {
209 e -= 1;
210 x = x + x - 1.0;
211 } else { x = x - 1.0; }
212 */
213 const simd mask = (x < cephes::c_SQRTHF()).as_simd();
214 e -= (simd::c_1() & mask);
215 x += (x & mask) - simd::c_1();
216
217 // Evaluate polynomial
219
220 const simd x2 = x * x;
221 y = fma(e, cephes::c_log_q1(), y * x2) - x2 * simd::c_0p5();
222 x = fma(e, cephes::c_log_q2(), x + y);
223 return x | invalid_mask; // negative arg will be NAN
224 }
225
226 //==========================================================================
228 namespace constant
229 {
230 JPL_FL_CONSTANT(ln10, 2.3025850930f);
231 JPL_FL_CONSTANT(inv_ln10, 0.4342944819f);
232 JPL_FL_CONSTANT(ln2, 0.69314718056f);
233 JPL_FL_CONSTANT(inv_ln2, 1.4426950216f);
234
235 // Useful for conversion to decibels: pow(10, -energy / 20) == exp(ln10div20, -energy)
236 JPL_FL_CONSTANT(ln10div20, 0.11512925465f);
237 }
238
239 //==========================================================================
241 JPL_INLINE simd log10(const simd& vec) noexcept
242 {
243 return log(vec) * constant::c_inv_ln10();
244 }
245
247 JPL_INLINE simd log2(const simd& vec) noexcept
248 {
249 return log(vec) * constant::c_inv_ln2();
250 }
251
252 //==========================================================================
254 namespace constant
255 {
256 JPL_FL_CONSTANT(exp_hi, 88.3762626647949f);
257 JPL_FL_CONSTANT(exp_lo, -88.3762626647949f);
258 }
259 namespace cephes
260 {
261 JPL_FL_CONSTANT(LOG2EF, 1.44269504088896341f);
262 JPL_FL_CONSTANT(exp_C1, 0.693359375f);
263 JPL_FL_CONSTANT(exp_C2, -2.12194440e-4f);
264
265 JPL_FL_CONSTANT(exp_p0, 1.9875691500e-4f);
266 JPL_FL_CONSTANT(exp_p1, 1.3981999507e-3f);
267 JPL_FL_CONSTANT(exp_p2, 8.3334519073e-3f);
268 JPL_FL_CONSTANT(exp_p3, 4.1665795894e-2f);
269 JPL_FL_CONSTANT(exp_p4, 1.6666665459e-1f);
270 JPL_FL_CONSTANT(exp_p5, 5.0000001201e-1f);
271 }
272
273 namespace exponent
274 {
275 // Exponent polynomial on the small remainder (Cephes form: 1 + x + x^2 * P(x))
276 JPL_INLINE simd polynomial(const simd& x) noexcept
277 {
278 simd x2 = x * x;
279 simd y = cephes::c_exp_p0();
280 y = fma(y, x, cephes::c_exp_p1());
281 y = fma(y, x, cephes::c_exp_p2());
282 y = fma(y, x, cephes::c_exp_p3());
283 y = fma(y, x, cephes::c_exp_p4());
284 y = fma(y, x, cephes::c_exp_p5());
285 y = fma(y, x2, x + simd::c_1());
286 return y;
287 }
288
289 // Build 2^n via exponent bits
291 {
292 return ((n.to_mask() + constant::c_i0x7f()) << 23).as_simd();
293 }
294 }
295
296 //==========================================================================
298 inline simd exp(simd x) noexcept
299 {
300 x = clamp(x, constant::c_exp_lo(), constant::c_exp_hi());
301
302 // Express exp(x) as exp(g + n*log(2))
303
304 // n = floor(x * LOG2E + 0.5) (round to nearest integer)
305 simd n = floor(fma(x, cephes::c_LOG2EF(), simd::c_0p5()));
306
307 // Remainder in natural-log space using split ln2 = C1 + C2 (better precision)
308 x -= fma(n, cephes::c_exp_C1(), n * cephes::c_exp_C2());
309
310 // Evaluate polynomial
312
313 // Build 2^n
315 return y * pow2n;
316 }
317
318 //==========================================================================
320 {
321 // Range reduction: x = n + r, where r ~= [-0.5, 0.5]
322 simd n = floor(x + simd::c_0p5());
323 simd r = x - n;
324
325 // Convert the small base-2 remainder to natural-log space:
326 // (x): r_ln2 ~= r * ln(2) using split constants for precision
327 x = fma(r, cephes::c_exp_C1(), r * cephes::c_exp_C2());
328
329 // Reuse exp polynomial exactly as in exp():
331
332 // Build 2^n
334 return y * pow2n;
335 }
336
339 {
340 // m = sign(q) ? -1 : 0
341 simd_mask m = exp.ashr<31>();
342
343 m = (((m.adds(exp)).ashr<6>()).subs(m)) << 4;
344 exp = exp.subs(m << 2);
345
346 m = m.adds(127);
348
349 simd u = (m << 23).as_simd();
350 // x *= u^4
351 simd u2 = u * u;
352 arg = arg * u2 * u2;
353 u = ((exp.adds(0x7f)) << 23).as_simd();
354 return arg * u;
355 }
356
357 namespace power
358 {
359 // Flip x's sign bit where y is negative
360 JPL_INLINE simd mulsign(const simd& x, const simd& y) noexcept
361 {
362 return (x.as_mask() ^ (y.as_mask() & constant::c_sign_mask())).to_simd();
363 }
364
365 // Return 1.0f with the sign of `x`
366 JPL_INLINE simd sign(const simd& x) noexcept
367 {
368 return mulsign(1.0f, x);
369 }
370 }
371
372 inline simd pow(const simd& x, const simd& y) noexcept
373 {
374 simd x_abs = abs(x);
375
376 // Compute pow
377 simd result = exp(y * log(x_abs));
378
379 // The rest is error handling...
380
381 // Inline `trunc(y)` to reuse nubmer tests
383 simd y_abs = abs(y);
388 y,
389 y_abs.to_mask().to_simd() | y_sign_bit.as_simd());
390
391 // y is integer if y == trunc(y) OR |y| >= 2^24 (float can exactly represent all ints below 2^24)
394
395 //int yisodd = (1 & (int)y) != 0 && yisint && fabsfk(y) < (float)(INT64_C(1) << 24);
397
400 simd_mask x_is_gte_zero = x >= 0.0f;
402
403 // Sleef: turn internal NaN-overflow to +inf
404 //result = simd::select(isnan(result), simd::inf(), result);
405
406 // handle x < 0: if y is not integer -> NaN; else apply sign for odd exponents
409 result,
411 simd::nan() | signbit(x).as_simd(),
413
414 // efx path for y = +-inf
418 simd::c_0(),
420 simd::c_1(),
421 simd::inf())),
422 result);
423
424 // x is inf or 0
425 {
427 power::sign(x),
428 simd::c_1());
429
431 simd::c_0(),
432 simd::inf());
433
435 }
436
437 // NaNs
439
440 // y==0 or x==1
441 simd_mask x_factor = (x == simd::c_1()) | ((x == -simd::c_1()) & y_is_inf);
443
444 return result;
445 }
446
447 //==========================================================================
449 namespace cephes
450 {
452 JPL_FL_CONSTANT(minus_DP2, -2.4187564849853515625e-4f);
453 JPL_FL_CONSTANT(minus_DP3, -3.77489497744594108e-8f);
454 JPL_FL_CONSTANT(sincof_p0, -1.9515295891e-4f);
455 JPL_FL_CONSTANT(sincof_p1, 8.3321608736e-3f);
456 JPL_FL_CONSTANT(sincof_p2, -1.6666654611e-1f);
457 JPL_FL_CONSTANT(coscof_p0, 2.443315711809948e-5f);
458 JPL_FL_CONSTANT(coscof_p1, -1.388731625493765e-3f);
459 JPL_FL_CONSTANT(coscof_p2, 4.166664568298827e-2f);
460 JPL_FL_CONSTANT(FOPI, 1.27323954473516f); // 4 / M_PI
461 }
462
463 namespace sincos
464 {
465 // Evaluate sin/cos polynomials
467 {
468 // Compute x^2
469 simd x2 = x * x;
470
471 // Evaluate the first polynom (0 <= x <= Pi/4)
472 // Cos(x) = 1 - x^2/2! + x^4/4! - x^6/6! + x^8/8! + ... = (((x2/8!- 1/6!) * x2 + 1/4!) * x2 - 1/2!) * x2 + 1
473 outTaylorCos = fma(cephes::c_coscof_p0(), x2, cephes::c_coscof_p1());
474 outTaylorCos = fma(outTaylorCos, x2, cephes::c_coscof_p2());
476
477 // Evaluate the second polynom (Pi/4 <= x <= 0)
478 // Sin(x) = x - x^3/3! + x^5/5! - x^7/7! + ... = ((-x2/7! + 1/5!) * x2 - 1/3!) * x2 * x + x
479 outTaylorSin = fma(cephes::c_sincof_p0(), x2, cephes::c_sincof_p1());
480 outTaylorSin = fma(outTaylorSin, x2, cephes::c_sincof_p2());
482 }
483 }
484 namespace Math
485 {
486 //==========================================================================
488 inline void SinCos(simd x, simd& outSin, simd& outCos) noexcept
489 {
490 // Adopted from "Simple SSE and SSE2 (and now NEON) optimized sin, cos, log and exp"
491 // by Julien Pommier (http://gruntthepeon.free.fr/ssemath/),
492 // and adapted to C++.
493 //
494 // Some concepts referenced from JoltPhysics library
495 // by Jorrit Rouwe (https://github.com/jrouwe/JoltPhysics)
496
497 // Make argument positive and remember sign for sin only
498 // since cos is symmetric around x (highest bit of a float is the sign bit)
499 simd_mask sin_sign_bit = signbit(x); // extract the sign bit (upper one)
500 x = abs(x); // take the absolute value
501
502 // Store the integer part of y in quadrant
503 // x * cephes::c_FOPI(): scale by 4/Pi
504 simd_mask quadrant = fma(cephes::c_FOPI(), x, constant::c_i1().to_simd()).to_mask() & constant::c_iinv1();
505 simd float_quadrant = quadrant.to_simd();
506
507 // The magic pass: "Extended precision modular arithmetic"
508 // x = ((x - y * DP1) - y * DP2) - y * DP3;
509 x = fma(float_quadrant, cephes::c_minus_DP1(), x);
510 x = fma(float_quadrant, cephes::c_minus_DP2(), x);
511 x = fma(float_quadrant, cephes::c_minus_DP3(), x);
512
513 // Evaluate polynomials
516
517 // Get the polynom selection mask for the sine
518 simd_mask mask = (quadrant & constant::c_i2()) == simd_mask::zero();
519
520 // Select which one of the results is sin and which one is cos
523
524 // Update the signs
525 simd_mask bit1 = quadrant << 30;
526 simd_mask bit2 = ((quadrant << 29) & constant::c_sign_mask());
527
530
531 // Correct the signs
532 outSin = (s ^ sin_sign_bit.as_simd());
533 outCos = (c ^ sign_bit_cos.as_simd());
534 }
535 } // namespace Math
536
537 //==========================================================================
540 inline simd sin(simd x) noexcept
541 {
542 // Make argument positive and remember sign for sin only
543 // since cos is symmetric around x (highest bit of a float is the sign bit)
544 simd_mask sin_sign_bit = signbit(x); // extract the sign bit (upper one)
545 x = abs(x); // take the absolute value
546
547 // Store the integer part of y in quadrant
548 // x * cephes::c_FOPI(): scale by 4/Pi
549 simd_mask quadrant = fma(cephes::c_FOPI(), x, constant::c_i1().to_simd()).to_mask() & constant::c_iinv1();
550 simd float_quadrant = quadrant.to_simd();
551
552 // The magic pass: "Extended precision modular arithmetic"
553 // x = ((x - y * DP1) - y * DP2) - y * DP3;
554 x = fma(float_quadrant, cephes::c_minus_DP1(), x);
555 x = fma(float_quadrant, cephes::c_minus_DP2(), x);
556 x = fma(float_quadrant, cephes::c_minus_DP3(), x);
557
558 // Evaluate polynomials
561
562 // Get the polynom selection mask for the sine
563 simd_mask mask = (quadrant & constant::c_i2()) == simd_mask::zero();
564
565 // Select which one of the results is sin and which one is cos
567
568 // Update the sign
569 simd_mask bit2 = ((quadrant << 29) & constant::c_sign_mask());
571
572 // Correct the sign
573 return (s ^ sin_sign_bit.as_simd());
574 }
575
576 //==========================================================================
579 inline simd cos(simd x) noexcept
580 {
581 // Make argument positive and remember sign for sin only
582 // since cos is symmetric around x (highest bit of a float is the sign bit)
583 //simd_mask sin_sign_bit = signbit(x); // extract the sign bit (upper one)
584 x = abs(x); // take the absolute value
585
586 // Store the integer part of y in quadrant
587 // x * cephes::c_FOPI(): scale by 4/Pi
588 simd_mask quadrant = fma(cephes::c_FOPI(), x, constant::c_i1().to_simd()).to_mask() & constant::c_iinv1();
589 simd float_quadrant = quadrant.to_simd();
590
591 // The magic pass: "Extended precision modular arithmetic"
592 // x = ((x - y * DP1) - y * DP2) - y * DP3;
593 x = fma(float_quadrant, cephes::c_minus_DP1(), x);
594 x = fma(float_quadrant, cephes::c_minus_DP2(), x);
595 x = fma(float_quadrant, cephes::c_minus_DP3(), x);
596
597 // Evaluate polynomials
600
601 // Get the polynom selection mask for the sine
602 simd_mask mask = (quadrant & constant::c_i2()) == simd_mask::zero();
603
604 // Select which one of the results is sin and which one is cos
606
607 // Update the sign
608 simd_mask bit1 = quadrant << 30;
609 simd_mask bit2 = ((quadrant << 29) & constant::c_sign_mask());
610
612
613 // Correct the sign
614 return (c ^ sign_bit_cos.as_simd());
615 }
616
617 namespace constant
618 {
619 JPL_FL_CONSTANT(invPIO2, 0.63661977236758134308f);
620 }
621 namespace tangent
622 {
623 // Negated for FMA transposition
624 JPL_FL_CONSTANT(minus_DP1, -1.5703125f); // split pi/2
625 JPL_FL_CONSTANT(minus_DP2, -0.0004837512969970703125f);
626 JPL_FL_CONSTANT(minus_DP3, -7.549789948768648e-8f);
627
629 {
630 const simd x2 = x * x;
631 simd y = fma(simd(9.38540185543e-3f), x2, simd(3.11992232697e-3f));
632 y = fma(y, x2, simd(2.44301354525e-2f));
633 y = fma(y, x2, simd(5.34112807005e-2f));
634 y = fma(y, x2, simd(1.33387994085e-1f));
635 y = fma(y, x2, simd(3.33331568548e-1f));
636 y = fma(y, x2 * x, x);
637 return y;
638 }
639 }
640
643 inline simd tan(simd x) noexcept
644 {
645 // n = round(x * 2/pi)
646 // REQUIRE: round() is round-to-nearest (banker's or away-from-zero both OK here)
647 const simd n_f = round(x * constant::c_invPIO2());
648
649 // Reduce to r to [-pi/4, pi/4] with high-precision split
650 // r = ((x - y * DP1) - y * DP2) - y * DP3;
651 simd r = fma(n_f, tangent::c_minus_DP1(), x);
652 r = fma(n_f, tangent::c_minus_DP2(), r);
653 r = fma(n_f, tangent::c_minus_DP3(), r);
654
655 // Polynomial on r
657
658 // odd quadrants? tan(x) = -1 / tan(r)
659 // Convert n_f (which is an exact integer in float) to int lanes safely
660 const simd_mask n = n_f.to_mask(); // float->int32 trunc fine since n_f is already integer
661 const simd_mask is_odd = (n & simd_mask(1u)) != simd_mask(0u);
662
664 return tan;
665 }
666
667 namespace asinacos
668 {
670 {
671 simd y = +0.4197454825e-1f;
672 y = fma(y, x2, +0.2424046025e-1f);
673 y = fma(y, x2, +0.4547423869e-1f);
674 y = fma(y, x2, +0.7495029271e-1f);
675 y = fma(y, x2, +0.1666677296e+0f);
676 y = fma(y, x * x2, x);
677 return y;
678 }
679 }
680
683 inline simd asin(const simd& in)
684 {
685 const simd_mask asin_sign = signbit(in);
686 simd a = abs(in);
687
688 // asin is not defined outside the range [-1, 1] but it often happens that a value is slightly above 1 so we just clamp here
689 a = min(a, simd::c_1());
690
691 // When |x| < 0.5 we use the asin approximation
692 // When |x| >= 0.5 we use the identity asin(x) = PI / 2 - 2 * asin(sqrt((1 - x) / 2))
693 const simd_mask o = a < simd::c_0p5();
694
695 const simd x2 = simd::select(o, (a * a), ((simd::c_1() - a) * 0.5f));
696 const simd x = simd::select(o, a, Math::Sqrt(x2));
697
698 // Polynomial approximation of asin
699 const simd u = asinacos::polynomial(x, x2);
700
701 // If |x| >= 0.5 we need to apply the remainder of the identity above
702 const simd r = simd::select(o, u, (constant::c_half_pi ()- (u + u)));
703
704 // Put the sign back
705 return r ^ asin_sign.as_simd();
706 }
707
710 inline simd acos(const simd& in)
711 {
712#if 1
713 // Not super accurate, but good enough
714 return constant::c_half_pi ()- asin(in);
715#else
716 // See comments in `asin()`...
717
718 const simd_mask asin_sign = signbit(in);
719 simd a = abs(in);
720 a = min(a, simd::c_1());
721 const simd_mask o = a < simd::c_0p5;
722
723 const simd x2 = simd::select(o, (a * a), ((simd::c_1() - a) * 0.5f));
724 const simd x = simd::select(a == simd::c_1(),
725 simd::c_0,
727
728 const simd u = asinacos::polynomial(x, x2);
729 const simd y = constant::c_half_pi ()- (u ^ asin_sign.as_simd());
730
731 simd r = simd::select(o, y, (u + u));
732 r = simd::select((~o) & (in < simd::c_0), constant::c_pi ()- r, r);
733
734 return r;
735#endif
736 }
737
738 // TODO: atan
739
740 namespace arctangent2
741 {
743 {
744 // Store the coefficients
745 const simd a1(0.99997726f);
746 const simd a3(-0.33262347f);
747 const simd a5(0.19354346f);
748 const simd a7(-0.11643287f);
749 const simd a9(0.05265332f);
750 const simd a11(-0.01172120f);
751
752 // Compute the polynomial
753 simd x2 = x * x;
754 simd result;
755 result = a11;
756 result = fma(x2, result, a9);
757 result = fma(x2, result, a7);
758 result = fma(x2, result, a5);
759 result = fma(x2, result, a3);
760 result = fma(x2, result, a1);
761 result *= x;
762
763 return result;
764 }
765 }
766
767 inline simd atan2(const simd& y, const simd& x)
768 {
769 // Adopted from AVX implementaion in "Speeding up atan2f by 50x"
770 // https://mazzo.li/posts/vectorized-atan2.html
771
772 const simd_mask swap_mask = abs(y) > abs(x);
773
774 // Create the atan input by "blending" `y` and `x`, according to the mask computed
775 // above. In our case we need the number of larger magnitude to
776 // be the denominator.
777 const simd atan_input =
778 simd::select(swap_mask, x, y) / // pick the lowest between |y| and |x| for each number
779 simd::select(swap_mask, y, x); // and the highest.
780
781 // Approximate atan
783
784 // If swapped, adjust atan output. We use blending again to leave
785 // the output unchanged if we didn't swap anything.
786 //
787 // If we need to adjust it, we simply carry the sign over from the input
788 // to `pi_2` by using the `sign_mask`. This avoids a more expensive comparison,
789 // and also handles edge cases such as -0 better.
791 (constant::c_half_pi ()| signbit(atan_input).as_simd()) - result,
792 result);
793
794 // Adjust the result depending on the input quadrant.
795 //
796 // We create a mask for the sign of `x` using an arithmetic right shift:
797 // the mask will be all 0s if the sign if positive, and all 1s
798 // if the sign is negative. This avoids a further (and slower) comparison
799 // with 0.
800 const simd x_sign_mask = x.as_mask().ashr<31>().as_simd();
801
802 // Then use the mask to perform the adjustment only when the sign
803 // is positive, and use the sign bit of `y` to know whether to add
804 // `pi` or `-pi`.
805 result += ((constant::c_pi ()^ signbit(y).as_simd()) & x_sign_mask);
806
807 return result;
808 }
809
810} // namespace JPL
811
812#undef JPL_FL_CONSTANT
813#undef JPL_INT_CONSTANT
814#undef JPL_MASK_CONSTANT
#define JPL_INT_CONSTANT(Name, Val)
Definition SIMDMath.h:92
#define JPL_FL_CONSTANT(Name, Val)
Declare some SIMD constants.
Definition SIMDMath.h:89
#define JPL_MASK_CONSTANT(Name, Val)
Definition SIMDMath.h:95
JPL_INLINE constexpr T Sqrt(T x) noexcept
Definition Math.h:269
JPL_INLINE std::pair< T, T > SinCos(T value) noexcept
Definition Math.h:164
simd polynomial(simd x)
Definition SIMDMath.h:742
JPL_INLINE simd polynomial(const simd &x, const simd &x2)
Definition SIMDMath.h:669
JPL_INLINE simd polynomial(const simd &x) noexcept
Definition SIMDMath.h:276
JPL_INLINE simd build2pown(const simd &n)
Definition SIMDMath.h:290
JPL_INLINE simd polynomial(const simd &x) noexcept
Definition SIMDMath.h:172
JPL_INLINE simd mulsign(const simd &x, const simd &y) noexcept
Definition SIMDMath.h:360
JPL_INLINE simd sign(const simd &x) noexcept
Definition SIMDMath.h:366
JPL_INLINE void polynomials(const simd &x, simd &outTaylorCos, simd &outTaylorSin) noexcept
Definition SIMDMath.h:466
JPL_INLINE simd polynomial(simd x)
Definition SIMDMath.h:628
Definition AcousticMaterial.h:36
simd exp(simd x) noexcept
Exponent for 4-wide 32-bit float vector.
Definition SIMDMath.h:298
JPL_INLINE simd clamp(const simd &value, const simd &minV, const simd &maxV) noexcept
Element-wise clamp.
Definition SIMD.h:1838
simd asin(const simd &in)
Definition SIMDMath.h:683
JPL_INLINE constexpr auto GetNumSIMDOps(std::unsigned_integral auto count) noexcept
Get number of SIMD operations that can fit into the count
Definition SIMDMath.h:51
JPL_INLINE simd_mask clamps(const simd_mask &value, const simd_mask &minV, const simd_mask &maxV) noexcept
Element-wise clamp, signed.
Definition SIMD.h:2116
JPL_INLINE constexpr auto GetSIMDTailDouble(std::unsigned_integral auto count) noexcept
Get the remaining tail from count that won't fill 8-wide simd vector.
Definition SIMDMath.h:63
JPL_INLINE constexpr auto GetNumSIMDOpsDouble(std::unsigned_integral auto count) noexcept
Get number of 8-wide simd operations that can fit into the count
Definition SIMDMath.h:57
JPL_INLINE constexpr auto FloorToSIMDSize(std::unsigned_integral auto count) noexcept
Floor count to 4-wide simd vector.
Definition SIMDMath.h:33
JPL_INLINE constexpr auto GetSIMDTail(std::unsigned_integral auto count) noexcept
Get the remaining tail from count that won't fill a simd vector.
Definition SIMDMath.h:45
simd atan2(const simd &y, const simd &x)
Definition SIMDMath.h:767
simd acos(const simd &in)
Definition SIMDMath.h:710
JPL_INLINE simd log2(const simd &vec) noexcept
log2 for 4-wide 32-bit float vector
Definition SIMDMath.h:247
JPL_INLINE simd_mask isinf(const simd &vec) noexcept
Determines if the given floating-point number num is a positive or negative infinity.
Definition SIMDMath.h:126
JPL_INLINE constexpr auto GetDiv2Tail(std::unsigned_integral auto count) noexcept
Definition SIMDMath.h:81
JPL_INLINE simd trunc(const simd &vec) noexcept
Computes the nearest integer not greater in magnitude than vec.
Definition SIMDMath.h:138
JPL_INLINE constexpr auto RoundUpToSIMD(std::unsigned_integral auto count) noexcept
Round count up to the next simd width.
Definition SIMDMath.h:69
JPL_INLINE simd ldexp(simd arg, simd_mask exp) noexcept
Multiplies a floating-point value arg by the number 2 raised to the exp power.
Definition SIMDMath.h:338
JPL_INLINE simd_mask isnan(const simd &vec) noexcept
Determines if the given floating point number vec is a not-a-number(NaN) value.
Definition SIMDMath.h:132
JPL_INLINE simd_mask signbit(const simd &vec) noexcept
Equivalent to std::signbit but for 4-wide 32-bit float vector.
Definition SIMDMath.h:120
JPL_INLINE simd round(const simd &vec) noexcept
Element-wise round to nearest integer value.
Definition SIMD.h:1930
simd cos(simd x) noexcept
Definition SIMDMath.h:579
JPL_INLINE simd floor(const simd &vec) noexcept
Element-wise floor.
Definition SIMD.h:1861
JPL_INLINE simd max(const simd &a, const simd &b) noexcept
Element-wise max.
Definition SIMD.h:1799
JPL_INLINE simd fma(const simd &mul1, const simd &mul2, const simd &addV) noexcept
Element-wise fused multiply-add.
Definition SIMD.h:1843
simd log(simd x) noexcept
Definition SIMDMath.h:191
simd pow(const simd &x, const simd &y) noexcept
Definition SIMDMath.h:372
JPL_INLINE simd min(const simd &a, const simd &b) noexcept
Element-wise min.
Definition SIMD.h:1813
JPL_INLINE simd log10(const simd &vec) noexcept
log10 for 4-wide 32-bit float vector
Definition SIMDMath.h:241
JPL_INLINE constexpr auto FloorToDiv2(std::unsigned_integral auto count) noexcept
Floor count to divisible by 2.
Definition SIMDMath.h:75
JPL_INLINE simd abs(const simd &vec) noexcept
Definition SIMD.h:1827
JPL_INLINE constexpr auto FloorToSIMDSizeDouble(std::unsigned_integral auto count) noexcept
Floor count to 8-wide simd vector.
Definition SIMDMath.h:39
simd sin(simd x) noexcept
Definition SIMDMath.h:540
JPL_INLINE simd exp2(simd x) noexcept
Definition SIMDMath.h:319
simd tan(simd x) noexcept
Definition SIMDMath.h:643
Definition SIMD.h:207
JPL_INLINE simd_mask ashr() const noexcept
Definition SIMD.h:1562
JPL_INLINE simd as_simd() const noexcept
Reinterpret simd_mask as a simd (doesn't change the bits)
Definition SIMD.h:1627
static JPL_INLINE simd_mask zero() noexcept
Definition SIMD.h:1121
JPL_INLINE simd to_simd() const noexcept
Convert each component from an int to a float.
Definition SIMD.h:1610
static JPL_INLINE simd_mask replicate(int value) noexcept
Definition SIMD.h:1106
Minimal 4-wide 32-bit float vector implementation for SIMD.
Definition SIMD.h:60
static JPL_INLINE simd c_0() noexcept
Frequently used constants.
Definition SIMD.h:456
static JPL_INLINE simd c_1() noexcept
Definition SIMD.h:461
JPL_INLINE simd_mask to_mask() const noexcept
Convert each component from a float to an int.
Definition SIMD.h:987
JPL_INLINE simd_mask as_mask() const noexcept
Reinterpret simd as a simd_mask (doesn't change the bits)
Definition SIMD.h:1023
static JPL_INLINE simd c_0p5() noexcept
Definition SIMD.h:466
static JPL_INLINE simd reciprocal(const simd &vec) noexcept
Reciprocal vector (1 / value) for each of the components.
Definition SIMD.h:1058
static JPL_INLINE simd zero() noexcept
Vector with all zeros.
Definition SIMD.h:541
static JPL_INLINE simd inf() noexcept
Definition SIMD.h:557
static JPL_INLINE simd select(const simd_mask &mask, const simd &a, const simd &b) noexcept
Component-wise select, returns 'a' if mask is true, 'b' otherwise.
Definition SIMD.h:1034
static JPL_INLINE simd nan() noexcept
Vector with all NaN's.
Definition SIMD.h:552
static constexpr std::size_t size() noexcept
Get number of element of the vector.
Definition SIMD.h:97