@@ -147,12 +147,13 @@ NPSR_INTRIN V High(V x) {
147147
148148 // Step 3: Multi-precision computation of remainder r
149149 // r = x - n*(π/16)_high
150+ // Both splits apply their parts in descending order through the same DAG.
151+ // Without native FMA the non-tail parts carry 27/27/29 bits so every
152+ // n*part product is exact for |n| <= 85445660 = round(2^24 * 16/π); each
153+ // subtraction's rounding error is then captured exactly below, and the
154+ // decayed mul+sub sequence loses nothing against the fused path.
150155 constexpr auto kPiDiv16Prec29 = data::kPiDiv16Prec29 <kNativeFMA >;
151156 V r_hi = NegMulAdd (n, Set (d, kPiDiv16Prec29 [0 ]), x);
152- if constexpr (!kNativeFMA ) {
153- // For F64, we need to handle the low precision part separately
154- r_hi = NegMulAdd (n, Set (d, kPiDiv16Prec29 [3 ]), r_hi);
155- }
156157 const V pi16_med = Set (d, kPiDiv16Prec29 [1 ]);
157158 const V pi16_lo = Set (d, kPiDiv16Prec29 [2 ]);
158159 V r_med = NegMulAdd (n, pi16_med, r_hi);
@@ -162,6 +163,15 @@ NPSR_INTRIN V High(V x) {
162163 V term = NegMulAdd (pi16_med, n, Sub (r_hi, r_med));
163164 V r_lo = MulAdd (pi16_lo, n, Sub (r, r_med));
164165 r_lo = Sub (term, r_lo);
166+ if constexpr (!kNativeFMA ) {
167+ // Fourth piece (53 bits at 2^-91). Reuse the one rounded product in both
168+ // the subtraction and its error capture so (r, r_lo) stays an exact
169+ // double-double of the reduction even when r is tiny near k*π/2.
170+ const V tail_prod = Mul (n, Set (d, kPiDiv16Prec29 [3 ]));
171+ const V r_prev = r;
172+ r = Sub (r_prev, tail_prod);
173+ r_lo = Add (r_lo, Sub (Sub (r_prev, r), tail_prod));
174+ }
165175
166176 // Step 4: Polynomial approximation
167177 V r2 = Mul (r, r);
@@ -210,13 +220,23 @@ NPSR_INTRIN V High(V x) {
210220 if constexpr (OP == Operation::kCos ) {
211221 // Cosine reconstruction: cos_table - sin_table*remainder
212222 // Equivalent to: cos(a)*cos(r) - sin(a)*sin(r) but more efficient
213- V res_hi = NegMulAdd (r, sin_hi, cos_hi); // cos_hi - r*sin_hi
214-
215- // This captures the precision lost in the main computation
216- V r_sin_hi = Sub (cos_hi, res_hi); // Extract high part of multiplication
217-
218- // Handles rounding errors and adds sin_low contribution
219- V r_sin_low = MulSub (r, sin_hi, r_sin_hi); // Compute multiplication error
223+ V res_hi, r_sin_low;
224+ if constexpr (kNativeFMA ) {
225+ res_hi = NegMulAdd (r, sin_hi, cos_hi); // cos_hi - r*sin_hi
226+
227+ // This captures the precision lost in the main computation
228+ V r_sin_hi = Sub (cos_hi, res_hi); // Extract high part of multiplication
229+
230+ // Handles rounding errors via the FMA product-error idiom
231+ r_sin_low = MulSub (r, sin_hi, r_sin_hi); // Compute multiplication error
232+ } else {
233+ // Exact-by-construction head product replaces the FMA idiom
234+ V r_sin, r_sin_rest;
235+ SplitMul (r, sin_hi, r_sin, r_sin_rest);
236+ res_hi = Sub (cos_hi, r_sin);
237+ V r_sin_hi = Sub (cos_hi, res_hi);
238+ r_sin_low = Add (Sub (r_sin, r_sin_hi), r_sin_rest);
239+ }
220240 V sin_low_corr = MulAdd (r, sin_lo, r_sin_low); // Add sin_low term
221241
222242 // This is used to apply the low-precision remainder correction
@@ -245,13 +265,23 @@ NPSR_INTRIN V High(V x) {
245265 } else {
246266 // Sine reconstruction: sin_table + cos_table*remainder
247267 // Equivalent to: sin(a)*cos(r) + cos(a)*sin(r) but more efficient
248- V res_hi = MulAdd (r, cos_hi, sin_hi); // sin_hi + r*cos_hi
249-
250- // This captures the precision lost in the main computation
251- V r_cos_hi = Sub (res_hi, sin_hi); // Extract high part of multiplication
252-
253- // Handles rounding errors and adds cos_low contribution
254- V r_cos_low = MulSub (r, cos_hi, r_cos_hi); // Compute multiplication error
268+ V res_hi, r_cos_low;
269+ if constexpr (kNativeFMA ) {
270+ res_hi = MulAdd (r, cos_hi, sin_hi); // sin_hi + r*cos_hi
271+
272+ // This captures the precision lost in the main computation
273+ V r_cos_hi = Sub (res_hi, sin_hi); // Extract high part of multiplication
274+
275+ // Handles rounding errors via the FMA product-error idiom
276+ r_cos_low = MulSub (r, cos_hi, r_cos_hi); // Compute multiplication error
277+ } else {
278+ // Exact-by-construction head product replaces the FMA idiom
279+ V r_cos, r_cos_rest;
280+ SplitMul (r, cos_hi, r_cos, r_cos_rest);
281+ res_hi = Add (sin_hi, r_cos);
282+ V r_cos_hi = Sub (res_hi, sin_hi);
283+ r_cos_low = Add (Sub (r_cos, r_cos_hi), r_cos_rest);
284+ }
255285 V cos_low_corr = MulAdd (r, cos_lo, r_cos_low); // Add cos_low term
256286
257287 // Intermediate term for r_low correction: cos_table - sin_table*r
@@ -282,6 +312,9 @@ NPSR_INTRIN V High(V x) {
282312 // This unified approach works because:
283313 // - sin(x + π) = -sin(x)
284314 // - cos(x + π) = -cos(x)
315+ // TODO(seiko2plus): x_sign_int and the Xor are dead code (always zero) and
316+ // should be removed. The sign is bit 4 of n_biased alone; sin(-0.0) is
317+ // handled in trig/inl.h under kSpecialCases.
285318 VU x_sign_int = ShiftLeft<63 >(BitCast (du, x));
286319 // XOR with quadrant info in n_biased
287320 VU combined = Xor (BitCast (du, n_biased), ShiftLeft<4 >(x_sign_int));
0 commit comments