11#include " npsr/common.h"
2- #include " npsr/trig/lut-inl .h"
2+ #include " npsr/trig/data/small .h"
33#include " npsr/utils-inl.h"
44
5- #if defined(NPSR_TRIG_SMALL_INL_H_) == defined(HWY_TARGET_TOGGLE) // NOLINT
5+ #if defined(NPSR_TRIG_SMALL_INL_H_) == defined(HWY_TARGET_TOGGLE) // NOLINT
66#ifdef NPSR_TRIG_SMALL_INL_H_
77#undef NPSR_TRIG_SMALL_INL_H_
88#else
@@ -67,25 +67,22 @@ HWY_API V SmallPolyLow(V r, V r2) {
6767 poly = MulAdd (r2, poly, c7);
6868 poly = MulAdd (r2, poly, c5);
6969 poly = MulAdd (r2, poly, c3);
70- V r3 = Mul (r2, r);
71- poly = MulAdd (r3, poly, r);
7270 return poly;
7371}
7472
75- template <bool IS_COS , typename V> HWY_API V SmallArgLow (V x) {
73+ template <bool IS_COS , typename V>
74+ HWY_API V SmallArgLow (V x) {
7675 const DFromV<V> d;
7776 const RebindToUnsigned<decltype (d)> du;
78- using U = VFromD<decltype (du)>;
7977 using T = TFromV<V>;
8078 // Load frequently used constants as vector registers
8179 const V abs_mask = BitCast (d, Set (du, SignMask<T>() - 1 ));
8280 const V x_abs = And (abs_mask, x);
83- const V x_sign = AndNot (abs_mask, x);
84-
85- constexpr bool IsSingle = std::is_same_v<T, float >;
81+ const V x_sign = AndNot (x_abs, x);
8682
83+ constexpr bool kIsSingle = std::is_same_v<T, float >;
8784 // Transform cosine to sine using identity: cos(x) = sin(x + π/2)
88- const V half_pi = Set (d, 0x1 .921fb6p0f);
85+ const V half_pi = Set (d, kIsSingle ? 0x1 .921fb6p0f : 0x1 .921fb54442d18p0 );
8986 V x_trans = x_abs;
9087 if constexpr (IS_COS ) {
9188 x_trans = Add (x_abs, half_pi);
@@ -95,11 +92,10 @@ template <bool IS_COS, typename V> HWY_API V SmallArgLow(V x) {
9592
9693 // Compute N = round(x/π) using "magic number" technique
9794 // and stores integer part in mantissa
98- const V inv_pi = Set (d, IsSingle ? 0x1 .45f306p-2f : 0x1 .45f306dc9c883p-2 );
99- const V magic_round = Set (d, IsSingle ? 0x1 .8p23f : 0x1 .8p52);
95+ const V inv_pi = Set (d, kIsSingle ? 0x1 .45f306p-2f : 0x1 .45f306dc9c883p-2 );
96+ const V magic_round = Set (d, kIsSingle ? 0x1 .8p23f : 0x1 .8p52);
10097 V n_biased = MulAdd (x_trans, inv_pi, magic_round);
10198 V n = Sub (n_biased, magic_round);
102-
10399 // Adjust quotient for cosine (accounts for π/2 phase shift)
104100 if constexpr (IS_COS ) {
105101 // For cosine, we computed N = round((x + π/2)/π) but need N' for x:
@@ -109,20 +105,25 @@ template <bool IS_COS, typename V> HWY_API V SmallArgLow(V x) {
109105 n = Sub (n, Set (d, static_cast <T>(0.5 )));
110106 }
111107 // Use Cody-Waite method with triple-precision PI
112- const V pi_hi = Set (d, IsSingle ? 0x1 .921fb6p1f : 0x1 .921fb54442d18p+1 );
113- const V pi_med = Set (d, -0x1 .777a5cp-24f );
114- const V pi_lo = Set (d, IsSingle ? -0x1 .ee59dap -49f : 0x1 .1a62633145c06p-53 );
115- V r = NegMulAdd (n, pi_hi, x_abs); // x - N*π_hi
116- if constexpr (IsSingle) {
117- r = NegMulAdd (n, pi_med, r); // - N*π_medium
108+ const V pi_hi = Set (d, kIsSingle ? 0x1 .921fb6p1f : 0x1 .921fb54442d18p+1 );
109+ const V pi_med =
110+ Set (d, kIsSingle ? -0x1 .777a5cp-24f : 0x1 .c1cd129024e09p -106 );
111+ const V pi_lo = Set (d, kIsSingle ? -0x1 .ee59dap -49f : 0x1 .1a62633145c06p-53 );
112+ V r = NegMulAdd (n, pi_hi, x_abs);
113+ if constexpr (kIsSingle ) {
114+ r = NegMulAdd (n, pi_med, r);
118115 }
119- r = NegMulAdd (n, pi_lo, r); // - N*π_low
116+ r = NegMulAdd (n, pi_lo, r);
120117 V r2 = Mul (r, r);
121- // Extract octant sign information from quotient
122- // to determines sign flip in final result
123- r = Xor (r, BitCast (d, ShiftLeft<sizeof (T) * 8 - 1 >(BitCast (du, n_biased))));
124-
125118 V poly = SmallPolyLow<IS_COS >(r, r2);
119+ if constexpr (!kIsSingle ) {
120+ V r_mid = NegMulAdd (n, pi_med, r);
121+ V r2_corr = Mul (r2, r_mid);
122+ poly = MulAdd (r2_corr, poly, r_mid);
123+ }
124+ // Extract octant sign information from quotient and flip the sign bit
125+ poly = Xor (poly,
126+ BitCast (d, ShiftLeft<sizeof (T) * 8 - 1 >(BitCast (du, n_biased))));
126127 if constexpr (IS_COS ) {
127128 poly = IfThenElse (is_cos_near_zero, Set (d, static_cast <T>(1.0 )), poly);
128129 } else {
@@ -139,18 +140,16 @@ HWY_INLINE V SmallArg(V x) {
139140 using DU = RebindToUnsigned<D>;
140141 using DH = Half<D>;
141142 using DW = RepartitionToWide<D>;
142- using VU = Vec<DU >;
143143 using VW = Vec<DW >;
144144
145145 const D d;
146146 const DU du;
147147 const DH dh;
148148 const DW dw;
149-
150149 // Load frequently used constants as vector registers
151150 const V abs_mask = BitCast (d, Set (du, 0x7FFFFFFF ));
152151 const V x_abs = And (abs_mask, x);
153- const V x_sign = AndNot (abs_mask , x);
152+ const V x_sign = AndNot (x_abs , x);
154153
155154 // Transform cosine to sine using identity: cos(x) = sin(x + π/2)
156155 const V half_pi = Set (d, 0x1 .921fb6p0f);
@@ -236,6 +235,10 @@ HWY_INLINE V SmallArg(V x) {
236235 */
237236template <bool IS_COS , typename V, HWY_IF_F64 (TFromV<V>)>
238237HWY_INLINE V SmallArg (V x) {
238+ using trig::data::kHiCosKPi16Table ;
239+ using trig::data::kHiSinKPi16Table ;
240+ using trig::data::kPackedLowSinCosKPi16Table ;
241+
239242 using T = TFromV<V>;
240243 using D = DFromV<V>;
241244 using DU = RebindToUnsigned<D>;
@@ -245,11 +248,10 @@ HWY_INLINE V SmallArg(V x) {
245248 const DU du;
246249
247250 // Constants for range reduction
248- constexpr T kInvPi = 0x1 .45f306dc9c883p2; // 16/π for range reduction
249- constexpr T kPi16High = 0x1 .921fb54442d18p-3 ; // π/16 high precision part
250- constexpr T kPi16Low = 0x1 .1a62633p-57 ; // π/16 low precision part
251- constexpr T kPi16Tiny = 0x1 .45c06e0e68948p-89 ; // π/16 tiny precision part
252-
251+ constexpr T kInvPi = 0x1 .45f306dc9c883p2; // 16/π for range reduction
252+ constexpr T kPi16High = 0x1 .921fb54442d18p-3 ; // π/16 high precision part
253+ constexpr T kPi16Low = 0x1 .1a62633p-57 ; // π/16 low precision part
254+ constexpr T kPi16Tiny = 0x1 .45c06e0e68948p-89 ; // π/16 tiny precision part
253255 // Step 1: Range reduction - find n such that x = n*(π/16) + r, where |r| <
254256 // π/16
255257 V magic = Set (d, 0x1 .8p52);
@@ -258,21 +260,26 @@ HWY_INLINE V SmallArg(V x) {
258260
259261 // Extract integer index for table lookup (n mod 16)
260262 VU n_int = BitCast (du, n_biased);
263+ VU table_idx = And (n_int, Set (du, 0xF )); // Mask to get n mod 16
261264
262265 // Step 2: Load precomputed sine/cosine values for n mod 16
263- V sin_hi = LutX2 (kHiSinKPi16Table <double >, n_int);
264- V cos_hi = LutX2 (kHiCosKPi16Table <double >, n_int);
265- V cos_lo = LutX2 (kPackedLowSinCosKPi16Table <double >, n_int);
266+ V sin_hi = LutX2 (kHiSinKPi16Table , table_idx);
267+ V cos_hi = LutX2 (kHiCosKPi16Table , table_idx);
268+ // Note: cos_lo and sin_lo are packed together (32 bits each) to save memory.
269+ // cos_lo can be used as-is since it's in the upper bits, sin_lo needs
270+ // extraction. The precision loss is negligible for the final result.
271+ // see lut-inl.h.py for the table generation code.
272+ V cos_lo = LutX2 (kPackedLowSinCosKPi16Table , table_idx);
273+ // Extract sin_low from packed format (upper 32 bits)
274+ V sin_lo = BitCast (d, ShiftLeft<32 >(BitCast (du, cos_lo)));
266275
267276 // Step 3: Multi-precision computation of remainder r
268- V r_hi = NegMulAdd (n, Set (d, kPi16High ), x); // r = x - n*(π/16)_high
269- V r_mid = NegMulAdd (n, Set (d, kPi16Low ), r_hi); // Subtract low part
270- V r = NegMulAdd (n, Set (d, kPi16Tiny ), r_mid); // Subtract tiny part
271-
277+ V r_hi = NegMulAdd (n, Set (d, kPi16High ), x); // r = x - n*(π/16)_high
278+ V r_mid = NegMulAdd (n, Set (d, kPi16Low ), r_hi); // Subtract low part
279+ V r = NegMulAdd (n, Set (d, kPi16Tiny ), r_mid); // Subtract tiny part
272280 // Compute low precision part of r for extra accuracy
273- V delta = Sub (r, r_mid);
274- V term = NegMulAdd (Set (d, kPi16Low ), n, Sub (r_hi, delta));
275- V r_lo = MulAdd (Set (d, kPi16Tiny ), n, delta);
281+ V term = NegMulAdd (Set (d, kPi16Low ), n, Sub (r_hi, r_mid));
282+ V r_lo = MulAdd (Set (d, kPi16Tiny ), n, Sub (r, r_mid));
276283 r_lo = Sub (term, r_lo);
277284
278285 // Step 4: Polynomial approximation
@@ -295,58 +302,117 @@ HWY_INLINE V SmallArg(V x) {
295302 cos_poly = MulAdd (cos_poly, r2, Set (d, -0x1 .ffffffffffffcp -2 ));
296303
297304 // Step 5: Reconstruction using angle addition formulas
298- // sin(n*π/16 + r) = sin(n*π/16)*cos(r) + cos(n*π/16)*sin(r)
299- // cos(n*π/16 + r) = cos(n*π/16)*cos(r) - sin(n*π/16)*sin(r)
300305 //
301- // Where:
302- // sin(r) = r * (1 + sin_poly)
303- // cos(r) = 1 + r² * cos_poly
306+ // Mathematical equivalence between traditional and SVML approaches:
307+ //
308+ // Traditional angle addition:
309+ // sin(a+r) = sin(a)*cos(r) + cos(a)*sin(r)
310+ // cos(a+r) = cos(a)*cos(r) - sin(a)*sin(r)
311+ //
312+ // Where for small r (|r| < π/16):
313+ // cos(r) ≈ 1 + r²*cos_poly
314+ // sin(r) ≈ r*(1 + sin_poly) ≈ r + r*sin_poly
315+ //
316+ // SVML's efficient linear approximation:
317+ // sin(a+r) ≈ sin(a) + cos(a)*r + polynomial_corrections
318+ // cos(a+r) ≈ cos(a) - sin(a)*r + polynomial_corrections
319+ //
320+ // This is mathematically equivalent but computationally more efficient:
321+ // - Uses first-order linear terms directly: Sh + Ch*R, Ch - R*Sh
322+ // - Applies higher-order polynomial corrections separately
323+ // - Fewer multiplications and better numerical stability
324+ //
325+ // Implementation follows SVML structure:
326+ // sin(n*π/16 + r) = sin_table + cos_table*remainder (+ corrections)
327+ // cos(n*π/16 + r) = cos_table - sin_table*remainder (+ corrections)
328+ V result;
329+ if constexpr (IS_COS ) {
330+ // Cosine reconstruction: cos_table - sin_table*remainder
331+ // Equivalent to: cos(a)*cos(r) - sin(a)*sin(r) but more efficient
332+ V res_hi = NegMulAdd (r, sin_hi, cos_hi); // cos_hi - r*sin_hi
304333
305- // Apply angle addition with multi-precision arithmetic
306- // Main term: sin(n*π/16) + r*cos(n*π/16)
307- V res_hi = MulAdd (r, cos_hi, sin_hi);
334+ // This captures the precision lost in the main computation
335+ V r_sin_hi = Sub (cos_hi, res_hi); // Extract high part of multiplication
308336
309- // Compute error from r*cos_hi multiplication
310- V r_cos = Sub (res_hi , sin_hi);
311- V mul_err = MulSub (r, cos_hi, r_cos);
337+ // Handles rounding errors and adds sin_low contribution
338+ V r_sin_low = MulSub (r , sin_hi, r_sin_hi); // Compute multiplication error
339+ V sin_low_corr = MulAdd (r, sin_lo, r_sin_low); // Add sin_low term
312340
313- // Compute cos(n*π/16) - r*sin(n*π/16) for intermediate calculations
314- V cos_r_sin = NegMulAdd (r, sin_hi, cos_hi );
341+ // This is used to apply the low-precision remainder correction
342+ V sin_cos_r = MulAdd (r, cos_hi, sin_hi );
315343
316- // Extract sin_low from packed format (upper 32 bits )
317- V sin_lo = BitCast (d, ShiftLeft< 32 >( BitCast (du, cos_lo)));
318- V lo_corr = MulAdd (r_lo, cos_r_sin, sin_lo );
344+ // Main low precision correction: cos_low - r_low*(sin_table + cos_table*r )
345+ // Applies the effect of the low-precision remainder on the final result
346+ V low_corr = NegMulAdd (r_lo, sin_cos_r, cos_lo );
319347
320- // Apply polynomial corrections
321- V r_cos_hi = Mul (cos_hi, r);
322- V sin_corr = Mul (sin_hi, cos_poly); // sin(n*π/16) * (cos(r)-1)/r²
348+ // Polynomial corrections using the remainder
349+ V r_sin = Mul (r, sin_hi); // For polynomial application
323350
324- // Extract cos_low from packed format (lower 32 bits used directly)
325- V cos_corr = MulAdd (r, cos_lo, mul_err);
326- V total_corr = Add (cos_corr, lo_corr);
351+ // Apply polynomial corrections: cos_table*cos_poly - r*sin_table*sin_poly
352+ // This handles the higher-order terms from cos(r) and sin(r) expansions
353+ V poly_corr = Mul (cos_hi, cos_poly); // cos(a) * (cos(r)-1)/r²
354+ // - sin(a)*r * (sin(r)/r-1)
355+ poly_corr = NegMulAdd (r_sin, sin_poly, poly_corr);
327356
328- // Combine all terms: sin(n*π/16 + r) ≈ sin(n*π/16) + r*cos(n*π/16) +
329- // corrections
330- V result = MulAdd (r_cos_hi, sin_poly, sin_corr);
331- result = MulAdd (r2, result, total_corr);
332- result = Add (res_hi, result);
357+ // Combine all low precision corrections
358+ V total_low = Sub (low_corr, sin_low_corr);
333359
334- // Handle sign for negative zero edge case
335- if constexpr (IS_COS ) {
336- // For cosine, we need to adjust the phase by π/2
337- // This would require additional logic not shown in the original
338- // TODO: Implement cosine-specific adjustments
360+ // Final assembly: main_term + r²*polynomial_corrections + low_corrections
361+ result = MulAdd (r2, poly_corr, total_low);
362+ result = Add (res_hi, result);
363+
364+ } else {
365+ // Sine reconstruction: sin_table + cos_table*remainder
366+ // Equivalent to: sin(a)*cos(r) + cos(a)*sin(r) but more efficient
367+ V res_hi = MulAdd (r, cos_hi, sin_hi); // sin_hi + r*cos_hi
368+
369+ // This captures the precision lost in the main computation
370+ V r_cos_hi = Sub (res_hi, sin_hi); // Extract high part of multiplication
371+
372+ // Handles rounding errors and adds cos_low contribution
373+ V r_cos_low = MulSub (r, cos_hi, r_cos_hi); // Compute multiplication error
374+ V cos_low_corr = MulAdd (r, cos_lo, r_cos_low); // Add cos_low term
375+
376+ // Intermediate term for r_low correction: cos_table - sin_table*r
377+ // This is used to apply the low-precision remainder correction
378+ V cos_r_sin = NegMulAdd (r, sin_hi, cos_hi);
379+
380+ // Main low precision correction: sin_low - r_low*(cos_table - sin_table*r)
381+ // Applies the effect of the low-precision remainder on the final result
382+ V low_corr = MulAdd (r_lo, cos_r_sin, sin_lo);
383+ // Polynomial corrections using the remainder
384+ V r_cos = Mul (r, cos_hi); // For polynomial application
385+
386+ // Apply polynomial corrections: sin_table*cos_poly + r*cos_table*sin_poly
387+ // This handles the higher-order terms from cos(r) and sin(r) expansions
388+ V poly_corr = Mul (sin_hi, cos_poly); // sin(a) * (cos(r)-1)/r²
389+ poly_corr =
390+ MulAdd (r_cos, sin_poly, poly_corr); // + cos(a)*r * (sin(r)/r-1)
391+
392+ // Combine all low precision corrections
393+ V total_low = Add (low_corr, cos_low_corr);
394+ // Final assembly: main_term + r²*polynomial_corrections + low_corrections
395+ result = MulAdd (r2, poly_corr, total_low);
396+ result = Add (res_hi, result);
339397 }
340398
341- // Apply final sign correction based on quadrant
342- VU sign_bits = ShiftRight<4 >(n_int);
343- sign_bits = ShiftLeft<63 >(sign_bits);
344- result = Xor (result, BitCast (d, sign_bits));
399+ // Apply final sign correction same for both sine and cosine
400+ // Both functions change sign every π radians, corresponding to bit 4 of n_int
401+ // This unified approach works because:
402+ // - sin(x + π) = -sin(x)
403+ // - cos(x + π) = -cos(x)
404+ VU x_sign_int = ShiftLeft<63 >(BitCast (du, x));
405+ // XOR with quadrant info in n_biased
406+ VU combined = Xor (BitCast (du, n_biased), ShiftLeft<4 >(x_sign_int));
407+ // Extract final sign
408+ VU sign = ShiftRight<4 >(combined);
409+ sign = ShiftLeft<63 >(sign);
410+ result = Xor (result, BitCast (d, sign)); // Apply sign flip
345411 return result;
346412}
347413// NOLINTNEXTLINE(google-readability-namespace-comments)
348- } // namespace npsr::HWY_NAMESPACE::sincos
414+ } // namespace npsr::HWY_NAMESPACE::sincos
349415
350416HWY_AFTER_NAMESPACE ();
351417
352- #endif // NPSR_TRIG_SMALL_INL_H_
418+ #endif // NPSR_TRIG_SMALL_INL_H_
0 commit comments