Skip to main content

wide/
f64x8_.rs

1use super::*;
2
3pick! {
4  if #[cfg(target_feature="avx512f")] {
5    /// A SIMD vector with eight elements of type [`f64`].
6    ///
7    /// See the [crate level documentation] for more information about SIMD
8    /// vectors.
9    ///
10    /// [crate level documentation]: crate
11    #[derive(Default, Clone, Copy, PartialEq)]
12    #[repr(C, align(64))]
13    pub struct f64x8 { pub(crate) avx512: m512d }
14  } else {
15    /// A SIMD vector with eight elements of type [`f64`].
16    ///
17    /// See the [crate level documentation] for more information about SIMD
18    /// vectors.
19    ///
20    /// [crate level documentation]: crate
21    #[derive(Default, Clone, Copy, PartialEq)]
22    #[repr(C, align(64))]
23    pub struct f64x8 { pub(crate) a : f64x4, pub(crate) b : f64x4 }
24  }
25}
26
27macro_rules! const_f64_as_f64x8 {
28  ($i:ident, $f:expr) => {
29    #[allow(non_upper_case_globals)]
30    pub const $i: f64x8 = f64x8::new([$f; 8]);
31  };
32}
33
34impl_simd_float! {
35  unsafe {
36    T = f64,
37    N = 8,
38    Simd = f64x8,
39    IntSimd = i64x8,
40    UintT = u64,
41    UintSimd = u64x8,
42    optional_type_x86_inner { X86Inner = __m512d },
43    optional_type_arm_inner {},
44    optional_type_wasm_inner {},
45  }
46  old_powf_simd_fn_name = pow_f64x8,
47
48  #[inline]
49  fn neg(self) -> Self::Output {
50    pick! {
51      if #[cfg(target_feature="avx512f")] {
52        Self { avx512: bitxor_m512d(self.avx512, Self::splat(-0.0).avx512) }
53      } else {
54        Self {
55          a : self.a.neg(),
56          b : self.b.neg(),
57        }
58      }
59    }
60  }
61
62  #[inline]
63  fn not(self) -> Self::Output {
64    pick! {
65      if #[cfg(target_feature="avx512f")] {
66        Self { avx512: bitxor_m512d(self.avx512, set_splat_m512d(f64::from_bits(u64::MAX))) }
67      } else {
68        Self {
69          a : self.a.not(),
70          b : self.b.not(),
71        }
72      }
73    }
74  }
75
76  #[inline]
77  fn add(self, rhs: Self) -> Self::Output {
78    pick! {
79      if #[cfg(target_feature="avx512f")] {
80        Self { avx512: add_m512d(self.avx512, rhs.avx512) }
81      } else {
82        Self {
83          a : self.a.add(rhs.a),
84          b : self.b.add(rhs.b),
85        }
86      }
87    }
88  }
89
90  #[inline]
91  fn sub(self, rhs: Self) -> Self::Output {
92    pick! {
93      if #[cfg(target_feature="avx512f")] {
94        Self { avx512: sub_m512d(self.avx512, rhs.avx512) }
95      } else {
96        Self {
97          a : self.a.sub(rhs.a),
98          b : self.b.sub(rhs.b),
99        }
100      }
101    }
102  }
103
104  #[inline]
105  fn mul(self, rhs: Self) -> Self::Output {
106    pick! {
107      if #[cfg(target_feature="avx512f")] {
108        Self { avx512: mul_m512d(self.avx512, rhs.avx512) }
109      } else {
110        Self { a: self.a.mul(rhs.a), b: self.b.mul(rhs.b) }
111      }
112    }
113  }
114
115  #[inline]
116  fn div(self, rhs: Self) -> Self::Output {
117    pick! {
118      if #[cfg(target_feature="avx512f")] {
119        Self { avx512: div_m512d(self.avx512, rhs.avx512) }
120      } else {
121        Self { a: self.a.div(rhs.a), b: self.b.div(rhs.b) }
122      }
123    }
124  }
125
126  #[inline]
127  fn rem(self, rhs: Self) -> Self::Output {
128    Self::new([
129      self.to_array()[0] % rhs.to_array()[0],
130      self.to_array()[1] % rhs.to_array()[1],
131      self.to_array()[2] % rhs.to_array()[2],
132      self.to_array()[3] % rhs.to_array()[3],
133      self.to_array()[4] % rhs.to_array()[4],
134      self.to_array()[5] % rhs.to_array()[5],
135      self.to_array()[6] % rhs.to_array()[6],
136      self.to_array()[7] % rhs.to_array()[7],
137    ])
138  }
139
140  #[inline]
141  fn bitand(self, rhs: Self) -> Self::Output {
142    pick! {
143      if #[cfg(target_feature="avx512f")] {
144        Self { avx512: bitand_m512d(self.avx512, rhs.avx512) }
145      } else {
146        Self {
147          a : self.a.bitand(rhs.a),
148          b : self.b.bitand(rhs.b),
149        }
150      }
151    }
152  }
153
154  #[inline]
155  fn bitor(self, rhs: Self) -> Self::Output {
156    pick! {
157    if #[cfg(target_feature="avx512f")] {
158        Self { avx512: bitor_m512d(self.avx512, rhs.avx512) }
159      } else {
160        Self {
161          a : self.a.bitor(rhs.a),
162          b : self.b.bitor(rhs.b),
163        }
164      }
165    }
166  }
167
168  #[inline]
169  fn bitxor(self, rhs: Self) -> Self::Output {
170    pick! {
171      if #[cfg(target_feature="avx512f")] {
172        Self { avx512: bitxor_m512d(self.avx512, rhs.avx512) }
173      } else {
174        Self {
175          a : self.a.bitxor(rhs.a),
176          b : self.b.bitxor(rhs.b),
177        }
178      }
179    }
180  }
181
182  #[inline]
183  fn simd_eq(self, rhs: Self) -> Self::Output {
184    pick! {
185      if #[cfg(target_feature="avx512f")] {
186        Self { avx512: cmp_op_mask_m512d::<{cmp_op!(EqualOrdered)}>(self.avx512, rhs.avx512) }
187      } else {
188        Self {
189          a : self.a.simd_eq(rhs.a),
190          b : self.b.simd_eq(rhs.b),
191        }
192      }
193    }
194  }
195
196  #[inline]
197  fn simd_ne(self, rhs: Self) -> Self::Output {
198    pick! {
199      if #[cfg(target_feature="avx512f")] {
200        Self { avx512: cmp_op_mask_m512d::<{cmp_op!(NotEqualUnordered)}>(self.avx512, rhs.avx512) }
201      } else {
202        Self {
203          a : self.a.simd_ne(rhs.a),
204          b : self.b.simd_ne(rhs.b),
205        }
206      }
207    }
208  }
209
210  #[inline]
211  fn simd_lt(self, rhs: Self) -> Self::Output {
212    pick! {
213      if #[cfg(target_feature="avx512f")] {
214        Self { avx512: cmp_op_mask_m512d::<{cmp_op!(LessThanOrdered)}>(self.avx512, rhs.avx512) }
215      } else {
216        Self {
217          a : self.a.simd_lt(rhs.a),
218          b : self.b.simd_lt(rhs.b),
219        }
220      }
221    }
222  }
223
224  #[inline]
225  fn simd_gt(self, rhs: Self) -> Self::Output {
226    pick! {
227      if #[cfg(target_feature="avx512f")] {
228        Self { avx512: cmp_op_mask_m512d::<{cmp_op!(GreaterThanOrdered)}>(self.avx512, rhs.avx512) }
229      } else {
230        Self {
231          a : self.a.simd_gt(rhs.a),
232          b : self.b.simd_gt(rhs.b),
233        }
234      }
235    }
236  }
237
238  #[inline]
239  fn simd_le(self, rhs: Self) -> Self::Output {
240    pick! {
241      if #[cfg(target_feature="avx512f")] {
242        Self { avx512: cmp_op_mask_m512d::<{cmp_op!(LessEqualOrdered)}>(self.avx512, rhs.avx512) }
243      } else {
244        Self {
245          a : self.a.simd_le(rhs.a),
246          b : self.b.simd_le(rhs.b),
247        }
248      }
249    }
250  }
251
252  #[inline]
253  fn simd_ge(self, rhs: Self) -> Self::Output {
254    pick! {
255      if #[cfg(target_feature="avx512f")] {
256        Self { avx512: cmp_op_mask_m512d::<{cmp_op!(GreaterEqualOrdered)}>(self.avx512, rhs.avx512) }
257      } else {
258        Self {
259          a : self.a.simd_ge(rhs.a),
260          b : self.b.simd_ge(rhs.b),
261        }
262      }
263    }
264  }
265
266  #[inline]
267  pub fn reduce_add(self) -> f64 {
268    pick! {
269      if #[cfg(target_feature="avx512f")] {
270        // From https://stackoverflow.com/questions/49941645/get-sum-of-values-stored-in-m256d-with-sse-avx
271        let lo = cast_to_m256d_from_m512d(self.avx512);
272        let hi = extract_m256d_from_m512d::<1>(self.avx512);
273        let v  = add_m256d(lo, hi);                // [a0+a4, a1+a5, a2+a6, a3+a7]
274        let t  = add_horizontal_m256d(v, v);       // [s01, s23, s01, s23]
275        let lo = cast_to_m128d_from_m256d(t);      // s01
276        let hi = extract_m128d_from_m256d::<1>(t); // s23
277        let s  = add_m128d(lo, hi);                // [sum, ...]
278        get_f64_from_m128d_s(s)
279      } else {
280        self.a.reduce_add() + self.b.reduce_add()
281      }
282    }
283  }
284
285  #[inline]
286  pub fn reduce_mul(self) -> f64 {
287    pick! {
288      if #[cfg(target_feature="avx512f")] {
289        // From https://stackoverflow.com/questions/49941645/get-sum-of-values-stored-in-m256d-with-sse-avx
290        let lo = cast_to_m256d_from_m512d(self.avx512);
291        let hi = extract_m256d_from_m512d::<1>(self.avx512);
292        let v  = mul_m256d(lo, hi);
293        let lo = cast_to_m128d_from_m256d(v);
294        let hi = extract_m128d_from_m256d::<1>(v);
295        let lo = mul_m128d(lo,hi);
296        let hi64 = unpack_high_m128d(lo,lo);
297        let product = mul_m128d_s(lo,hi64);
298        get_f64_from_m128d_s(product)
299      } else {
300        self.a.reduce_mul() * self.b.reduce_mul()
301      }
302    }
303  }
304
305  #[inline]
306  pub fn bitselect(self, if_one: Self, if_zero: Self) -> Self {
307    pick! {
308      if #[cfg(target_feature="avx512f")] {
309        Self {
310          avx512: bitor_m512d(
311            bitand_m512d(if_one.avx512, self.avx512),
312            bitandnot_m512d(self.avx512, if_zero.avx512),
313          ),
314        }
315      } else {
316        Self {
317          a: self.a.bitselect(if_one.a, if_zero.a),
318          b: self.b.bitselect(if_one.b, if_zero.b),
319        }
320      }
321    }
322  }
323
324  #[inline]
325  fn select(self, if_true: Self, if_false: Self) -> Self {
326    pick! {
327      if #[cfg(target_feature="avx512f")] {
328        Self { avx512: blend_varying_m512d(if_false.avx512, if_true.avx512, movepi64_mask_m512d(self.avx512)) }
329      } else {
330        Self {
331          a : self.a.select(if_true.a, if_false.a),
332          b : self.b.select(if_true.b, if_false.b),
333        }
334      }
335    }
336  }
337
338  #[inline]
339  pub fn to_bitmask(self) -> u32 {
340    pick! {
341      if #[cfg(target_feature="avx512f")] {
342        movepi64_mask_m512d(self.avx512) as u32
343      } else {
344        (self.b.to_bitmask() << 4) | self.a.to_bitmask()
345      }
346    }
347  }
348
349  #[inline]
350  pub fn any(self) -> bool {
351    pick! {
352      if #[cfg(target_feature="avx512f")] {
353        movepi64_mask_m512d(self.avx512) != 0
354      } else {
355        self.a.any() || self.b.any()
356      }
357    }
358  }
359
360  #[inline]
361  pub fn all(self) -> bool {
362    pick! {
363      if #[cfg(target_feature="avx512f")] {
364        movepi64_mask_m512d(self.avx512) == 0b11111111
365      } else {
366        self.a.all() && self.b.all()
367      }
368    }
369  }
370
371  ///
372  /// Currently this function is never accelerated.
373  #[inline]
374  pub fn transpose(data: [f64x8; 8]) -> [f64x8; 8] {
375    // Can this be optimized?
376
377    #[inline(always)]
378    fn transpose_column(data: &[f64x8; 8], index: usize) -> f64x8 {
379      f64x8::new([
380        data[0].as_array()[index],
381        data[1].as_array()[index],
382        data[2].as_array()[index],
383        data[3].as_array()[index],
384        data[4].as_array()[index],
385        data[5].as_array()[index],
386        data[6].as_array()[index],
387        data[7].as_array()[index],
388      ])
389    }
390
391    [
392      transpose_column(&data, 0),
393      transpose_column(&data, 1),
394      transpose_column(&data, 2),
395      transpose_column(&data, 3),
396      transpose_column(&data, 4),
397      transpose_column(&data, 5),
398      transpose_column(&data, 6),
399      transpose_column(&data, 7),
400    ]
401  }
402
403  #[inline]
404  pub fn is_nan(self) -> Self {
405    pick! {
406      if #[cfg(target_feature="avx512f")] {
407        Self { avx512: cmp_op_mask_m512d::<{cmp_op!(Unordered)}>(self.avx512, self.avx512) }
408      } else {
409        Self {
410          a: self.a.is_nan(),
411          b: self.b.is_nan(),
412        }
413      }
414    }
415  }
416
417  #[inline]
418  pub fn is_inf(self) -> Self {
419    let shifted_inf = u64x8::from(0xFFE0000000000000);
420    let u: u64x8 = cast(self);
421    let shift_u = u << 1_u64;
422    let out = (shift_u).simd_eq(shifted_inf);
423    cast(out)
424  }
425
426  #[inline]
427  pub fn is_finite(self) -> Self {
428    let shifted_exp_mask = u64x8::splat(0xFFE0000000000000);
429    let u: u64x8 = cast(self);
430    let shift_u = u << 1_u64;
431    let out = !(shift_u & shifted_exp_mask).simd_eq(shifted_exp_mask);
432    cast(out)
433  }
434
435  #[inline]
436  pub fn is_sign_positive(self) -> Self {
437    const SIGN_MASK: u64x8 = u64x8::splat((-0.0_f64).to_bits());
438
439    let bits = cast::<f64x8, u64x8>(self);
440    let sign = bits & SIGN_MASK;
441    let result = sign.simd_eq(u64x8::ZERO);
442    cast::<u64x8, f64x8>(result)
443  }
444
445  #[inline]
446  pub fn is_sign_negative(self) -> Self {
447    const SIGN_MASK: u64x8 = u64x8::splat((-0.0_f64).to_bits());
448
449    let bits = cast::<f64x8, u64x8>(self);
450    let sign = bits & SIGN_MASK;
451    let result = sign.simd_eq(SIGN_MASK);
452    cast::<u64x8, f64x8>(result)
453  }
454
455  #[inline]
456  pub fn recip(self) -> Self {
457    // There does not seem to be a `recip` intrinsic for any architecture. The
458    // closest is `_mm512_rcp14_pd` which has relative error.
459    Self::ONE / self
460  }
461
462  #[inline]
463  pub fn recip_sqrt(self) -> Self {
464    // There does not seem to be a `recip_sqrt` intrinsic for any architecture.
465    // The closest is `_mm512_rsqrt14_pd` which has relative error.
466    Self::ONE / self.sqrt()
467  }
468
469  #[inline]
470  pub fn max(self, rhs: Self) -> Self {
471    pick! {
472      if #[cfg(target_feature="avx512f")] {
473        rhs.is_nan().select(self, Self { avx512: max_m512d(self.avx512, rhs.avx512) })
474      } else {
475        Self {
476          a: self.a.max(rhs.a),
477          b: self.b.max(rhs.b),
478        }
479      }
480    }
481  }
482
483  #[inline]
484  pub fn fast_max(self, rhs: Self) -> Self {
485    pick! {
486      if #[cfg(target_feature="avx512f")] {
487        Self { avx512: max_m512d(self.avx512, rhs.avx512) }
488      } else {
489        Self {
490          a : self.a.fast_max(rhs.a),
491          b : self.b.fast_max(rhs.b),
492        }
493      }
494    }
495  }
496
497  #[inline]
498  pub fn min(self, rhs: Self) -> Self {
499    pick! {
500      if #[cfg(target_feature="avx512f")] {
501        rhs.is_nan().select(self, Self { avx512: min_m512d(self.avx512, rhs.avx512) })
502      } else {
503        Self {
504          a: self.a.min(rhs.a),
505          b: self.b.min(rhs.b),
506        }
507      }
508    }
509  }
510
511  #[inline]
512  pub fn fast_min(self, rhs: Self) -> Self {
513    pick! {
514      if #[cfg(target_feature="avx512f")] {
515        Self { avx512: min_m512d(self.avx512, rhs.avx512) }
516      } else {
517        Self {
518          a : self.a.fast_min(rhs.a),
519          b : self.b.fast_min(rhs.b),
520        }
521      }
522    }
523  }
524
525  #[inline]
526  pub fn clamp(self, min: Self, max: Self) -> Self {
527    pick! {
528      if #[cfg(target_feature="avx512f")] {
529        // This works since all bits set is NaN.
530        self.fast_clamp(min, max) | min.is_nan() | max.is_nan()
531      } else {
532        // Some targets have better implementations than the above one.
533        Self {
534          a: self.a.clamp(min.a, max.a),
535          b: self.b.clamp(min.b, max.b),
536        }
537      }
538    }
539  }
540
541  #[inline]
542  pub fn fast_clamp(self, min: Self, max: Self) -> Self {
543    pick! {
544      if #[cfg(target_feature="avx512f")] {
545        // For both `min_m512d` and `max_m512d` if any input is NaN, `rhs` gets
546        // chosen. For `self` to be chosen, `self` must be the second argument.
547        Self { avx512: max_m512d(min.avx512, min_m512d(max.avx512, self.avx512)) }
548      } else {
549        Self {
550          a: self.a.fast_clamp(min.a, max.a),
551          b: self.b.fast_clamp(min.b, max.b),
552        }
553      }
554    }
555  }
556
557  #[inline]
558  pub fn abs(self) -> Self {
559    pick! {
560      if #[cfg(target_feature="avx512f")] {
561        let non_sign_bits = f64x8::from(f64::from_bits(i64::MAX as u64));
562        self & non_sign_bits
563      } else {
564        Self {
565          a: self.a.abs(),
566          b: self.b.abs(),
567        }
568      }
569    }
570  }
571
572  #[inline]
573  pub fn floor(self) -> Self {
574    pick! {
575      if #[cfg(target_feature="avx512f")] {
576        Self { avx512: round_m512d::<{round_op!(NegInf)}>(self.avx512) }
577      } else {
578        Self {
579          a : self.a.floor(),
580          b : self.b.floor(),
581        }
582      }
583    }
584  }
585
586  #[inline]
587  pub fn ceil(self) -> Self {
588    pick! {
589      if #[cfg(target_feature="avx512f")] {
590        Self { avx512: round_m512d::<{round_op!(PosInf)}>(self.avx512) }
591      } else {
592        Self {
593          a : self.a.ceil(),
594          b : self.b.ceil(),
595        }
596      }
597    }
598  }
599
600  #[inline]
601  pub fn round(self) -> Self {
602    pick! {
603      if #[cfg(target_feature="avx512f")] {
604        const_f64_as_f64x8!(HALF_NEXT_DOWN, 0.5_f64.next_down());
605        const_f64_as_f64x8!(BOUNDS_LIMIT, 4503599627370496.0);
606
607        let self_abs = self.abs();
608
609        let adjusted_self = self_abs + Self::HALF;
610        let result_abs = Self { avx512: round_m512d::<{round_op!(Zero)}>(adjusted_self.avx512) };
611        // The addition breaks for `0.5.next_down()` which incorrectly rounds to
612        // `1.0`. This resets the result back to `0.0`.
613        let result_abs = result_abs & self_abs.simd_ne(HALF_NEXT_DOWN);
614
615        // Large value, infinity and NaN need special handling.
616        let bounds_mask: Self = cast(cmp_op_mask_i64_m512i::<{cmp_int_op!(Lt)}>(
617          cast(self_abs),
618          cast(BOUNDS_LIMIT),
619        ));
620
621        // `abs` keeps the original sign.
622        bounds_mask.abs().bitselect(result_abs, self)
623      } else {
624        Self {
625          a: self.a.round(),
626          b: self.b.round(),
627        }
628      }
629    }
630  }
631
632  #[inline]
633  pub fn round_int(self) -> i64x8 {
634    pick! {
635      if #[cfg(target_feature="avx512dq")] {
636        #[cfg(target_arch = "x86")]
637        use core::arch::x86::_mm512_cvtpd_epi64;
638        #[cfg(target_arch = "x86_64")]
639        use core::arch::x86_64::_mm512_cvtpd_epi64;
640
641        // Based on: https://github.com/v8/v8/blob/210987a552a2bf2a854b0baa9588a5959ff3979d/src/codegen/shared-ia32-x64/macro-assembler-shared-ia32-x64.h#L489-L504
642        let non_nan_mask = self.simd_eq(self);
643        let non_nan = self & non_nan_mask;
644        let flip_to_max: i64x8 = cast(self.simd_ge(Self::splat(9223372036854775808.0)));
645
646        // TODO(safe_arch): Add `_mm512_cvtpd_epi64`.
647        let cast: i64x8 = cast(m512i(unsafe { _mm512_cvtpd_epi64(non_nan.avx512.0) }));
648        flip_to_max ^ cast
649      } else {
650        let [a,b]: [f64x4;2] = cast(self);
651        cast([
652          a.round_int(),
653          b.round_int(),
654        ])
655      }
656    }
657  }
658
659  #[inline]
660  pub fn fast_round_int(self) -> i64x8 {
661    pick! {
662      if #[cfg(target_feature="avx512dq")] {
663        #[cfg(target_arch = "x86")]
664        use core::arch::x86::_mm512_cvtpd_epi64;
665        #[cfg(target_arch = "x86_64")]
666        use core::arch::x86_64::_mm512_cvtpd_epi64;
667
668        // TODO(safe_arch): Add `_mm512_cvtpd_epi64`.
669        cast(m512i(unsafe { _mm512_cvtpd_epi64(self.avx512.0) }))
670      } else {
671
672        let [a,b]: [f64x4;2] = cast(self);
673        cast([
674          a.fast_round_int(),
675          b.fast_round_int(),
676        ])
677      }
678    }
679  }
680
681  #[inline]
682  pub fn round_ties_even(self) -> Self {
683    pick! {
684      if #[cfg(target_feature="avx512f")] {
685        Self { avx512: round_m512d::<{round_op!(Nearest)}>(self.avx512) }
686      } else {
687        Self {
688          a: self.a.round_ties_even(),
689          b: self.b.round_ties_even(),
690        }
691      }
692    }
693  }
694
695  #[inline]
696  pub fn trunc(self) -> Self {
697    pick! {
698      if #[cfg(target_feature="avx512f")] {
699        Self { avx512: round_m512d::<{round_op!(Zero)}>(self.avx512) }
700      } else {
701        Self {
702          a: self.a.trunc(),
703          b: self.b.trunc(),
704        }
705      }
706    }
707  }
708
709  #[inline]
710  pub fn trunc_int(self) -> i64x8 {
711    pick! {
712      if #[cfg(target_feature="avx512dq")] {
713        #[cfg(target_arch = "x86")]
714        use core::arch::x86::_mm512_cvttpd_epi64;
715        #[cfg(target_arch = "x86_64")]
716        use core::arch::x86_64::_mm512_cvttpd_epi64;
717
718        // Based on: https://github.com/v8/v8/blob/210987a552a2bf2a854b0baa9588a5959ff3979d/src/codegen/shared-ia32-x64/macro-assembler-shared-ia32-x64.h#L489-L504
719        let non_nan_mask = self.simd_eq(self);
720        let non_nan = self & non_nan_mask;
721        let flip_to_max: i64x8 = cast(self.simd_ge(Self::splat(9223372036854775808.0)));
722
723        // TODO(safe_arch): Add `_mm512_cvttpd_epi64`.
724        let cast: i64x8 = cast(m512i(unsafe { _mm512_cvttpd_epi64(non_nan.avx512.0) }));
725        flip_to_max ^ cast
726      } else {
727        let [a,b]: [f64x4;2] = cast(self);
728        cast([
729          a.trunc_int(),
730          b.trunc_int(),
731        ])
732      }
733    }
734  }
735
736  #[inline]
737  pub fn fast_trunc_int(self) -> i64x8 {
738    pick! {
739      if #[cfg(target_feature="avx512dq")] {
740        #[cfg(target_arch = "x86")]
741        use core::arch::x86::_mm512_cvttpd_epi64;
742        #[cfg(target_arch = "x86_64")]
743        use core::arch::x86_64::_mm512_cvttpd_epi64;
744
745        // TODO(safe_arch): Add `_mm512_cvttpd_epi64`.
746        cast(m512i(unsafe { _mm512_cvttpd_epi64(self.avx512.0) }))
747      } else {
748        let [a,b]: [f64x4;2] = cast(self);
749        cast([
750          a.fast_trunc_int(),
751          b.fast_trunc_int(),
752        ])
753      }
754    }
755  }
756
757  ///
758  /// # Platform-specific behavior (may change in the future)
759  ///
760  /// - On `x86`/`x86_64` with AVX-512F+FMA: Uses 512-bit `vfmadd` (single
761  ///   rounding, best accuracy)
762  /// - On `x86`/`x86_64` with AVX-512F only: Uses `(self * m) + a` (two
763  ///   roundings)
764  /// - Other platforms: Delegates to [`f64x4`] (inherits its FMA behavior)
765  #[inline]
766  pub fn mul_add(self, a: Self, b: Self) -> Self {
767    pick! {
768      if #[cfg(all(target_feature="avx512f",target_feature="fma"))] {
769        Self { avx512: fused_mul_add_m512d(self.avx512, a.avx512, b.avx512) }
770      } else if #[cfg(target_feature="avx512f")] {
771        // still want to use 512 bit ops
772        (self * a) + b
773      } else {
774        Self {
775          a : self.a.mul_add(a.a, b.a),
776          b : self.b.mul_add(a.b, b.b),
777        }
778      }
779    }
780  }
781
782  ///
783  /// # Platform-specific behavior (may change in the future)
784  ///
785  /// - On `x86`/`x86_64` with AVX-512F+FMA: Uses 512-bit `vfmsub` (single
786  ///   rounding, best accuracy)
787  /// - On `x86`/`x86_64` with AVX-512F only: Uses `(self * m) - s` (two
788  ///   roundings)
789  /// - Other platforms: Delegates to [`f64x4`] (inherits its FMA behavior)
790  #[inline]
791  pub fn mul_sub(self, a: Self, b: Self) -> Self {
792    pick! {
793      if #[cfg(all(target_feature="avx512f",target_feature="fma"))] {
794        Self { avx512: fused_mul_sub_m512d(self.avx512, a.avx512, b.avx512) }
795      } else if #[cfg(target_feature="avx512f")] {
796        // still want to use 512 bit ops
797        (self * a) - b
798      } else {
799        Self {
800          a : self.a.mul_sub(a.a, b.a),
801          b : self.b.mul_sub(a.b, b.b),
802        }
803      }
804    }
805  }
806
807  ///
808  /// # Platform-specific behavior (may change in the future)
809  ///
810  /// - On `x86`/`x86_64` with AVX-512F+FMA: Uses 512-bit `vfnmadd` (single
811  ///   rounding, best accuracy)
812  /// - On `x86`/`x86_64` with AVX-512F only: Uses `a - (self * m)` (two
813  ///   roundings)
814  /// - Other platforms: Delegates to [`f64x4`] (inherits its FMA behavior)
815  #[inline]
816  pub fn mul_neg_add(self, a: Self, b: Self) -> Self {
817    pick! {
818      if #[cfg(all(target_feature="avx512f",target_feature="fma"))] {
819        Self { avx512: fused_mul_neg_add_m512d(self.avx512, a.avx512, b.avx512) }
820      } else if #[cfg(target_feature="avx512f")] {
821        // still want to use 512 bit ops
822        b - (self * a)
823      } else {
824        Self {
825          a : self.a.mul_neg_add(a.a, b.a),
826          b : self.b.mul_neg_add(a.b, b.b),
827        }
828      }
829    }
830  }
831
832  ///
833  /// # Platform-specific behavior (may change in the future)
834  ///
835  /// - On `x86`/`x86_64` with AVX-512F+FMA: Uses 512-bit `vfnmsub` (single
836  ///   rounding, best accuracy)
837  /// - On `x86`/`x86_64` with AVX-512F only: Uses `-(self * m) - s` (two
838  ///   roundings)
839  /// - Other platforms: Delegates to [`f64x4`] (inherits its FMA behavior)
840  #[inline]
841  pub fn mul_neg_sub(self, a: Self, b: Self) -> Self {
842    pick! {
843       if #[cfg(all(target_feature="avx512f",target_feature="fma"))] {
844         Self { avx512: fused_mul_neg_sub_m512d(self.avx512, a.avx512, b.avx512) }
845        } else if #[cfg(target_feature="avx512f")] {
846          // still want to use 512 bit ops
847          -(self * a) - b
848        } else {
849         Self {
850           a : self.a.mul_neg_sub(a.a, b.a),
851           b : self.b.mul_neg_sub(a.b, b.b),
852         }
853       }
854    }
855  }
856
857  #[inline]
858  pub fn powf_simd(self, n: Self) -> Self {
859    const_f64_as_f64x8!(ln2d_hi, 0.693145751953125);
860    const_f64_as_f64x8!(ln2d_lo, 1.42860682030941723212E-6);
861    const_f64_as_f64x8!(P0log, 2.0039553499201281259648E1);
862    const_f64_as_f64x8!(P1log, 5.7112963590585538103336E1);
863    const_f64_as_f64x8!(P2log, 6.0949667980987787057556E1);
864    const_f64_as_f64x8!(P3log, 2.9911919328553073277375E1);
865    const_f64_as_f64x8!(P4log, 6.5787325942061044846969E0);
866    const_f64_as_f64x8!(P5log, 4.9854102823193375972212E-1);
867    const_f64_as_f64x8!(P6log, 4.5270000862445199635215E-5);
868    const_f64_as_f64x8!(Q0log, 6.0118660497603843919306E1);
869    const_f64_as_f64x8!(Q1log, 2.1642788614495947685003E2);
870    const_f64_as_f64x8!(Q2log, 3.0909872225312059774938E2);
871    const_f64_as_f64x8!(Q3log, 2.2176239823732856465394E2);
872    const_f64_as_f64x8!(Q4log, 8.3047565967967209469434E1);
873    const_f64_as_f64x8!(Q5log, 1.5062909083469192043167E1);
874
875    // Taylor expansion constants
876    const_f64_as_f64x8!(p2, 1.0 / 2.0); // coefficients for Taylor expansion of exp
877    const_f64_as_f64x8!(p3, 1.0 / 6.0);
878    const_f64_as_f64x8!(p4, 1.0 / 24.0);
879    const_f64_as_f64x8!(p5, 1.0 / 120.0);
880    const_f64_as_f64x8!(p6, 1.0 / 720.0);
881    const_f64_as_f64x8!(p7, 1.0 / 5040.0);
882    const_f64_as_f64x8!(p8, 1.0 / 40320.0);
883    const_f64_as_f64x8!(p9, 1.0 / 362880.0);
884    const_f64_as_f64x8!(p10, 1.0 / 3628800.0);
885    const_f64_as_f64x8!(p11, 1.0 / 39916800.0);
886    const_f64_as_f64x8!(p12, 1.0 / 479001600.0);
887    const_f64_as_f64x8!(p13, 1.0 / 6227020800.0);
888
889    let x1 = self.abs();
890    let x = x1.fraction_2();
891    let mask = x.simd_gt(f64x8::SQRT_2 * f64x8::HALF);
892    let x = (!mask).select(x + x, x);
893    let x = x - f64x8::ONE;
894    let x2 = x * x;
895    let px = polynomial_6!(x, P0log, P1log, P2log, P3log, P4log, P5log, P6log);
896    let px = px * x * x2;
897    let qx = polynomial_6n!(x, Q0log, Q1log, Q2log, Q3log, Q4log, Q5log);
898    let lg1 = px / qx;
899
900    let ef = x1.exponent();
901    let ef = mask.select(ef + f64x8::ONE, ef);
902    let e1 = (ef * n).round_ties_even();
903    let yr = ef.mul_sub(n, e1);
904
905    let lg = f64x8::HALF.mul_neg_add(x2, x) + lg1;
906    let x2err = (f64x8::HALF * x).mul_sub(x, f64x8::HALF * x2);
907    let lg_err = f64x8::HALF.mul_add(x2, lg - x) - lg1;
908
909    let e2 = (lg * n * f64x8::LOG2_E).round_ties_even();
910    let v = lg.mul_sub(n, e2 * ln2d_hi);
911    let v = e2.mul_neg_add(ln2d_lo, v);
912    let v = v - (lg_err + x2err).mul_sub(n, yr * f64x8::LN_2);
913
914    let x = v;
915    let e3 = (x * f64x8::LOG2_E).round_ties_even();
916    let x = e3.mul_neg_add(f64x8::LN_2, x);
917    let z =
918      polynomial_13!(x, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11, p12, p13)
919        + f64x8::ONE;
920    let ee = e1 + e2 + e3;
921    let ei = cast::<_, i64x8>(ee.round_int());
922    let ej = cast::<_, i64x8>(ei + (cast::<_, i64x8>(z) >> 52));
923
924    let overflow = cast::<_, f64x8>(!ej.simd_lt(i64x8::splat(0x07FF)))
925      | ee.simd_gt(f64x8::splat(3000.0));
926    let underflow = cast::<_, f64x8>(!ej.simd_gt(i64x8::splat(0x000)))
927      | ee.simd_lt(f64x8::splat(-3000.0));
928
929    // Add exponent by integer addition
930    let z = cast::<_, f64x8>(cast::<_, i64x8>(z) + (ei << 52));
931
932    // Check for overflow/underflow
933    let z = if (overflow | underflow).any() {
934      let z = underflow.select(f64x8::ZERO, z);
935      overflow.select(Self::infinity(), z)
936    } else {
937      z
938    };
939
940    // Check for self == 0
941    let x_zero = self.is_zero_or_subnormal();
942    let z = x_zero.select(
943      n.simd_lt(f64x8::ZERO).select(
944        Self::infinity(),
945        n.simd_eq(f64x8::ZERO).select(f64x8::ONE, f64x8::ZERO),
946      ),
947      z,
948    );
949
950    let x_sign = self.is_sign_negative();
951
952    let z = if x_sign.any() {
953      // Y into an integer
954      let yi = n.simd_eq(n.round_ties_even());
955      // Is y odd? If yes flip the sign of the result.
956      let y_odd = cast::<i64x8, f64x8>(n.round_int() << 63);
957
958      let z1 = yi
959        .select(z | y_odd, self.simd_eq(Self::ZERO).select(z, Self::nan_pow()));
960      x_sign.select(z1, z)
961    } else {
962      z
963    };
964
965    let x_finite = self.is_finite();
966    let y_finite = n.is_finite();
967    let e_finite = ee.is_finite();
968
969    if (x_finite & y_finite & (e_finite | x_zero)).all() {
970      return z;
971    }
972
973    (self.is_nan() | n.is_nan()).select(self + n, z)
974  }
975
976  #[inline]
977  pub fn sqrt(self) -> Self {
978    pick! {
979      if #[cfg(target_feature="avx512f")] {
980        Self { avx512: sqrt_m512d(self.avx512) }
981      } else {
982        Self {
983          a : self.a.sqrt(),
984          b : self.b.sqrt(),
985        }
986      }
987    }
988  }
989
990  /// Calculate the exponent of a packed `f64x8`
991  #[inline]
992  pub fn exp(self) -> Self {
993    const_f64_as_f64x8!(P2, 1.0 / 2.0);
994    const_f64_as_f64x8!(P3, 1.0 / 6.0);
995    const_f64_as_f64x8!(P4, 1.0 / 24.0);
996    const_f64_as_f64x8!(P5, 1.0 / 120.0);
997    const_f64_as_f64x8!(P6, 1.0 / 720.0);
998    const_f64_as_f64x8!(P7, 1.0 / 5040.0);
999    const_f64_as_f64x8!(P8, 1.0 / 40320.0);
1000    const_f64_as_f64x8!(P9, 1.0 / 362880.0);
1001    const_f64_as_f64x8!(P10, 1.0 / 3628800.0);
1002    const_f64_as_f64x8!(P11, 1.0 / 39916800.0);
1003    const_f64_as_f64x8!(P12, 1.0 / 479001600.0);
1004    const_f64_as_f64x8!(P13, 1.0 / 6227020800.0);
1005    // LN2D_HI/LO: double-double decomposition of ln(2) for exp range reduction,
1006    // following fdlibm's approach (Sun Microsystems, https://www.netlib.org/fdlibm/ e_exp.c).
1007    // Values chosen so LN2D_HI + LN2D_LO = ln(2) to full f64 precision.
1008    const_f64_as_f64x8!(LN2D_HI, 0.693145751953125);
1009    const_f64_as_f64x8!(LN2D_LO, 1.42860682030941723212E-6);
1010    let max_x = f64x8::from(709.783);
1011    let min_x = f64x8::from(-744.79);
1012    let finite = self.is_finite();
1013    // x < min_x: e^x underflows to 0 -- skip the entire pipeline
1014    let neg_underflow = self.simd_lt(min_x) & finite;
1015    if neg_underflow.all() {
1016      return Self::ZERO;
1017    }
1018    let max_r = f64x8::from(1023.0);
1019    let r = (self * Self::LOG2_E).round_ties_even();
1020    let big = r.simd_gt(max_r);
1021    let r_safe = big.select(max_r, r);
1022    let excess = r - max_r;
1023    let excess = big.select(excess, Self::ZERO);
1024    let scale = Self::vm_pow2n(excess);
1025    let x = r.mul_neg_add(LN2D_HI, self);
1026    let x = r.mul_neg_add(LN2D_LO, x);
1027    let z =
1028      polynomial_13!(x, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13);
1029    let n2 = Self::vm_pow2n(r_safe);
1030    let z = (z + Self::ONE) * scale * n2;
1031    let nan_mask = self.is_nan();
1032    let mut result = nan_mask.select(Self::nan_pow(), z);
1033    let pos_overflow = self.simd_gt(max_x) & finite;
1034    result = pos_overflow.select(Self::infinity(), result);
1035    result = neg_underflow.select(Self::ZERO, result);
1036    let pos_inf = !finite & !self.is_sign_negative() & !nan_mask;
1037    result = pos_inf.select(Self::infinity(), result);
1038    let neg_inf = !finite & self.is_sign_negative() & !nan_mask;
1039    result = neg_inf.select(Self::ZERO, result);
1040    result
1041  }
1042
1043  #[inline]
1044  pub fn exp2(self) -> Self {
1045    const_f64_as_f64x8!(P2, 1.0 / 2.0);
1046    const_f64_as_f64x8!(P3, 1.0 / 6.0);
1047    const_f64_as_f64x8!(P4, 1.0 / 24.0);
1048    const_f64_as_f64x8!(P5, 1.0 / 120.0);
1049    const_f64_as_f64x8!(P6, 1.0 / 720.0);
1050    const_f64_as_f64x8!(P7, 1.0 / 5040.0);
1051    const_f64_as_f64x8!(P8, 1.0 / 40320.0);
1052    const_f64_as_f64x8!(P9, 1.0 / 362880.0);
1053    const_f64_as_f64x8!(P10, 1.0 / 3628800.0);
1054
1055    // max_x = log2(f64::MAX) ≈ 1023.9999999999999
1056    // min_x = log2(f64::MIN_POSITIVE) - 52 ≈ -1022 - 52 = -1074
1057    let max_x = f64x8::from(1023.9999999999999);
1058    let min_x = f64x8::from(-1074.5);
1059    let finite = self.is_finite();
1060    let neg_underflow = self.simd_lt(min_x) & finite;
1061    if neg_underflow.all() {
1062      return Self::ZERO;
1063    }
1064
1065    let round = self.round_ties_even();
1066    let max_r = f64x8::from(1023.0);
1067    let big = round.simd_gt(max_r);
1068    let r_safe = big.select(max_r, round);
1069    let excess = round - max_r;
1070    let excess = big.select(excess, Self::ZERO);
1071    let scale = Self::vm_pow2n(excess);
1072
1073    let fract = (self - round) * Self::LN_2;
1074    let fract_partial_exp2 =
1075      polynomial_8!(fract, P2, P3, P4, P5, P6, P7, P8, P9, P10);
1076    let fract2 = fract * fract;
1077    let fract_exp2 = fract_partial_exp2.mul_add(fract2, fract) + Self::ONE;
1078
1079    let n2 = Self::vm_pow2n(r_safe);
1080    let result = fract_exp2 * scale * n2;
1081
1082    let nan_mask = self.is_nan();
1083    let mut result = nan_mask.select(Self::nan_pow(), result);
1084    let pos_overflow = self.simd_gt(max_x) & finite;
1085    result = pos_overflow.select(Self::infinity(), result);
1086    result = neg_underflow.select(Self::ZERO, result);
1087    let pos_inf = !finite & !self.is_sign_negative() & !nan_mask;
1088    result = pos_inf.select(Self::infinity(), result);
1089    let neg_inf = !finite & self.is_sign_negative() & !nan_mask;
1090    result = neg_inf.select(Self::ZERO, result);
1091    result
1092  }
1093
1094  #[inline]
1095  pub fn ln(self) -> Self {
1096    const_f64_as_f64x8!(HALF, 0.5);
1097    const_f64_as_f64x8!(P0, 7.70838733755885391666E0);
1098    const_f64_as_f64x8!(P1, 1.79368678507819816313E1);
1099    const_f64_as_f64x8!(P2, 1.44989225341610930846E1);
1100    const_f64_as_f64x8!(P3, 4.70579119878881725854E0);
1101    const_f64_as_f64x8!(P4, 4.97494994976747001425E-1);
1102    const_f64_as_f64x8!(P5, 1.01875663804580931796E-4);
1103
1104    const_f64_as_f64x8!(Q0, 2.31251620126765340583E1);
1105    const_f64_as_f64x8!(Q1, 7.11544750618563894466E1);
1106    const_f64_as_f64x8!(Q2, 8.29875266912776603211E1);
1107    const_f64_as_f64x8!(Q3, 4.52279145837532221105E1);
1108    const_f64_as_f64x8!(Q4, 1.12873587189167450590E1);
1109    // LN2F_HI/LO from fdlibm (Freely Distributable LIBM)
1110    // Sun Microsystems, Inc. https://www.netlib.org/fdlibm/
1111    // e_log.c: bit-exact double-double decomposition of ln(2) for f64.
1112    // Replaced the original f32-literals (0.693359375, -2.12194440e-4)
1113    // which had ~10 significant digits, causing ~630 ULP error in f64 ln.
1114    const_f64_as_f64x8!(LN2F_HI, f64::from_bits(0x3FE62E42FEE00000));
1115    const_f64_as_f64x8!(LN2F_LO, f64::from_bits(0x3DEA39EF35793C76));
1116    const_f64_as_f64x8!(VM_SQRT2, 1.414213562373095048801);
1117    const_f64_as_f64x8!(VM_SMALLEST_NORMAL, 2.2250738585072014E-308);
1118
1119    let x1 = self;
1120    let x = Self::fraction_2(x1);
1121    let e = Self::exponent(x1);
1122    let mask = x.simd_gt(VM_SQRT2 * HALF);
1123    let x = (!mask).select(x + x, x);
1124    let fe = mask.select(e + Self::ONE, e);
1125    let x = x - Self::ONE;
1126    let px = polynomial_5!(x, P0, P1, P2, P3, P4, P5);
1127    let x2 = x * x;
1128    let px = x2 * x * px;
1129    let qx = polynomial_5n!(x, Q0, Q1, Q2, Q3, Q4);
1130    let res = px / qx;
1131    let res = fe.mul_add(LN2F_LO, res);
1132    let res = res + x2.mul_neg_add(HALF, x);
1133    let res = fe.mul_add(LN2F_HI, res);
1134    let overflow = !self.is_finite();
1135    let underflow = x1.simd_lt(VM_SMALLEST_NORMAL);
1136    let mask = overflow | underflow;
1137    if !mask.any() {
1138      res
1139    } else {
1140      let is_zero = self.is_zero_or_subnormal();
1141      let res = underflow.select(Self::nan_log(), res);
1142      // Note: is_zero_or_subnormal() lumps subnormals (exponent==0) with zero.
1143      // Both get -Inf here. True subnormal inputs (~5e-324..2.225e-308) should
1144      // produce a finite negative result, but are vanishingly rare in
1145      // practice.
1146      let res = is_zero.select(-Self::infinity(), res);
1147      let res = overflow.select(self, res);
1148      // This must come *after* overflow.blend to overwrite ln(-∞) = -∞ to NaN
1149      let res = (!self.is_finite() & self.is_sign_negative())
1150        .select(Self::nan_log(), res);
1151      res
1152    }
1153  }
1154
1155  #[inline]
1156  pub fn cbrt(self) -> Self {
1157    let a = self.abs();
1158    let zero = a.simd_eq(Self::ZERO);
1159    if zero.all() {
1160      return self; // preserves -0.0
1161    }
1162    let inf = a.is_inf();
1163    let nan = self.is_nan();
1164
1165    const SUBN_SCALE: f64 = 1.8014398509481984e16;
1166    const SUBN_CBRT: f64 = 262144.0;
1167    let tiny = a.simd_lt(Self::from(f64::MIN_POSITIVE));
1168    let a = tiny.select(a * Self::from(SUBN_SCALE), a);
1169
1170    let e = Self::exponent(a) + Self::ONE;
1171    let d = Self::fraction_2(a);
1172
1173    // C0..C5 from SLEEF's minimax polynomial for 1/cbrt(d) on [0.5, 1.0)
1174    // Naoki Shibata et al., "SLEEF: A Portable Vectorized Library of C99
1175    // Mathematical Functions", https://sleef.org / https://github.com/shibatch/sleef
1176    // Licensed under the Boost Software License 1.0.
1177    const_f64_as_f64x8!(C0, 2.2307275302496609725722);
1178    const_f64_as_f64x8!(C1, -3.85841935510444988821632);
1179    const_f64_as_f64x8!(C2, 6.03990368989458747961407);
1180    const_f64_as_f64x8!(C3, -5.73353060922947843636166);
1181    const_f64_as_f64x8!(C4, 2.96155103020039511818595);
1182    const_f64_as_f64x8!(C5, -0.640245898480692909870982);
1183    let mut x = polynomial_5!(d, C0, C1, C2, C3, C4, C5);
1184
1185    // Newton for 1/cbrt: x = x - (d * x^4 - x) / 3.
1186    let x2 = x * x;
1187    let x4 = x2 * x2;
1188    x = x - d.mul_add(x4, -x) * Self::from(1.0 / 3.0);
1189
1190    // cbrt(d) = d * x^2, then polish
1191    let mut y = (d * x) * x;
1192    let yx = y * x;
1193    let t = Self::from(2.0 / 3.0);
1194    y = y - t * y * (yx - Self::ONE);
1195
1196    // Scale by 2^(e/3) = 2^k * 2^(r/3)
1197    let three = Self::from(3.0);
1198    let two = Self::from(2.0);
1199    let neg = e.simd_lt(Self::ZERO);
1200    let e_adj = neg.select(e - two, e);
1201    let k = (e_adj / three).trunc();
1202    let r = e - three * k;
1203    const_f64_as_f64x8!(CBRT2, 1.2599210498948732);
1204    const_f64_as_f64x8!(CBRT4, 1.5874010519681994);
1205    y = r.simd_eq(Self::ONE).select(y * CBRT2, y);
1206    y = r.simd_eq(two).select(y * CBRT4, y);
1207    y *= Self::vm_pow2n(k);
1208    y = tiny.select(y / Self::from(SUBN_CBRT), y);
1209
1210    let result = y.flip_signs(self);
1211    let result = nan.select(self, result);
1212    let result = zero.select(self, result);
1213    let result = inf.select(self, result);
1214    result
1215  }
1216
1217  #[inline]
1218  pub fn asin(self) -> Self {
1219    // Based on the Agner Fog "vector class library":
1220    // https://github.com/vectorclass/version2/blob/master/vectormath_trig.h
1221    const_f64_as_f64x8!(R4asin, 2.967721961301243206100E-3);
1222    const_f64_as_f64x8!(R3asin, -5.634242780008963776856E-1);
1223    const_f64_as_f64x8!(R2asin, 6.968710824104713396794E0);
1224    const_f64_as_f64x8!(R1asin, -2.556901049652824852289E1);
1225    const_f64_as_f64x8!(R0asin, 2.853665548261061424989E1);
1226
1227    const_f64_as_f64x8!(S3asin, -2.194779531642920639778E1);
1228    const_f64_as_f64x8!(S2asin, 1.470656354026814941758E2);
1229    const_f64_as_f64x8!(S1asin, -3.838770957603691357202E2);
1230    const_f64_as_f64x8!(S0asin, 3.424398657913078477438E2);
1231
1232    const_f64_as_f64x8!(P5asin, 4.253011369004428248960E-3);
1233    const_f64_as_f64x8!(P4asin, -6.019598008014123785661E-1);
1234    const_f64_as_f64x8!(P3asin, 5.444622390564711410273E0);
1235    const_f64_as_f64x8!(P2asin, -1.626247967210700244449E1);
1236    const_f64_as_f64x8!(P1asin, 1.956261983317594739197E1);
1237    const_f64_as_f64x8!(P0asin, -8.198089802484824371615E0);
1238
1239    const_f64_as_f64x8!(Q4asin, -1.474091372988853791896E1);
1240    const_f64_as_f64x8!(Q3asin, 7.049610280856842141659E1);
1241    const_f64_as_f64x8!(Q2asin, -1.471791292232726029859E2);
1242    const_f64_as_f64x8!(Q1asin, 1.395105614657485689735E2);
1243    const_f64_as_f64x8!(Q0asin, -4.918853881490881290097E1);
1244
1245    let xa = self.abs();
1246
1247    let big = xa.simd_ge(f64x8::splat(0.625));
1248
1249    let x1 = big.select(f64x8::splat(1.0) - xa, xa * xa);
1250
1251    let x2 = x1 * x1;
1252    let x3 = x2 * x1;
1253    let x4 = x2 * x2;
1254    let x5 = x4 * x1;
1255
1256    let do_big = big.any();
1257    let do_small = !big.all();
1258
1259    let mut rx = f64x8::default();
1260    let mut sx = f64x8::default();
1261    let mut px = f64x8::default();
1262    let mut qx = f64x8::default();
1263
1264    if do_big {
1265      rx = x3.mul_add(R3asin, x2 * R2asin)
1266        + x4.mul_add(R4asin, x1.mul_add(R1asin, R0asin));
1267      sx =
1268        x3.mul_add(S3asin, x4) + x2.mul_add(S2asin, x1.mul_add(S1asin, S0asin));
1269    }
1270    if do_small {
1271      px = x3.mul_add(P3asin, P0asin)
1272        + x4.mul_add(P4asin, x1 * P1asin)
1273        + x5.mul_add(P5asin, x2 * P2asin);
1274      qx = x4.mul_add(Q4asin, x5)
1275        + x3.mul_add(Q3asin, x1 * Q1asin)
1276        + x2.mul_add(Q2asin, Q0asin);
1277    };
1278
1279    let vx = big.select(rx, px);
1280    let wx = big.select(sx, qx);
1281
1282    let y1 = vx / wx * x1;
1283
1284    let mut z1 = f64x8::default();
1285    let mut z2 = f64x8::default();
1286    if do_big {
1287      let xb = (x1 + x1).sqrt();
1288      z1 = xb.mul_add(y1, xb);
1289    }
1290
1291    if do_small {
1292      z2 = xa.mul_add(y1, xa);
1293    }
1294
1295    // asin
1296    let z3 = f64x8::FRAC_PI_2 - z1;
1297    let asin = big.select(z3, z2);
1298    let asin = asin.flip_signs(self);
1299
1300    asin
1301  }
1302
1303  #[inline]
1304  pub fn acos(self) -> Self {
1305    // Based on the Agner Fog "vector class library":
1306    // https://github.com/vectorclass/version2/blob/master/vectormath_trig.h
1307    const_f64_as_f64x8!(R4asin, 2.967721961301243206100E-3);
1308    const_f64_as_f64x8!(R3asin, -5.634242780008963776856E-1);
1309    const_f64_as_f64x8!(R2asin, 6.968710824104713396794E0);
1310    const_f64_as_f64x8!(R1asin, -2.556901049652824852289E1);
1311    const_f64_as_f64x8!(R0asin, 2.853665548261061424989E1);
1312
1313    const_f64_as_f64x8!(S3asin, -2.194779531642920639778E1);
1314    const_f64_as_f64x8!(S2asin, 1.470656354026814941758E2);
1315    const_f64_as_f64x8!(S1asin, -3.838770957603691357202E2);
1316    const_f64_as_f64x8!(S0asin, 3.424398657913078477438E2);
1317
1318    const_f64_as_f64x8!(P5asin, 4.253011369004428248960E-3);
1319    const_f64_as_f64x8!(P4asin, -6.019598008014123785661E-1);
1320    const_f64_as_f64x8!(P3asin, 5.444622390564711410273E0);
1321    const_f64_as_f64x8!(P2asin, -1.626247967210700244449E1);
1322    const_f64_as_f64x8!(P1asin, 1.956261983317594739197E1);
1323    const_f64_as_f64x8!(P0asin, -8.198089802484824371615E0);
1324
1325    const_f64_as_f64x8!(Q4asin, -1.474091372988853791896E1);
1326    const_f64_as_f64x8!(Q3asin, 7.049610280856842141659E1);
1327    const_f64_as_f64x8!(Q2asin, -1.471791292232726029859E2);
1328    const_f64_as_f64x8!(Q1asin, 1.395105614657485689735E2);
1329    const_f64_as_f64x8!(Q0asin, -4.918853881490881290097E1);
1330
1331    let xa = self.abs();
1332
1333    let big = xa.simd_ge(f64x8::splat(0.625));
1334
1335    let x1 = big.select(f64x8::splat(1.0) - xa, xa * xa);
1336
1337    let x2 = x1 * x1;
1338    let x3 = x2 * x1;
1339    let x4 = x2 * x2;
1340    let x5 = x4 * x1;
1341
1342    let do_big = big.any();
1343    let do_small = !big.all();
1344
1345    let mut rx = f64x8::default();
1346    let mut sx = f64x8::default();
1347    let mut px = f64x8::default();
1348    let mut qx = f64x8::default();
1349
1350    if do_big {
1351      rx = x3.mul_add(R3asin, x2 * R2asin)
1352        + x4.mul_add(R4asin, x1.mul_add(R1asin, R0asin));
1353      sx =
1354        x3.mul_add(S3asin, x4) + x2.mul_add(S2asin, x1.mul_add(S1asin, S0asin));
1355    }
1356    if do_small {
1357      px = x3.mul_add(P3asin, P0asin)
1358        + x4.mul_add(P4asin, x1 * P1asin)
1359        + x5.mul_add(P5asin, x2 * P2asin);
1360      qx = x4.mul_add(Q4asin, x5)
1361        + x3.mul_add(Q3asin, x1 * Q1asin)
1362        + x2.mul_add(Q2asin, Q0asin);
1363    };
1364
1365    let vx = big.select(rx, px);
1366    let wx = big.select(sx, qx);
1367
1368    let y1 = vx / wx * x1;
1369
1370    let mut z1 = f64x8::default();
1371    let mut z2 = f64x8::default();
1372    if do_big {
1373      let xb = (x1 + x1).sqrt();
1374      z1 = xb.mul_add(y1, xb);
1375    }
1376
1377    if do_small {
1378      z2 = xa.mul_add(y1, xa);
1379    }
1380
1381    // acos
1382    let z3 = self.simd_lt(f64x8::ZERO).select(f64x8::PI - z1, z1);
1383    let z4 = f64x8::FRAC_PI_2 - z2.flip_signs(self);
1384    let acos = big.select(z3, z4);
1385
1386    acos
1387  }
1388
1389  #[inline]
1390  pub fn atan(self) -> Self {
1391    // Based on the Agner Fog "vector class library":
1392    // https://github.com/vectorclass/version2/blob/master/vectormath_trig.h
1393    const_f64_as_f64x8!(MORE_BITS, 6.123233995736765886130E-17);
1394    const_f64_as_f64x8!(MORE_BITS_O2, 6.123233995736765886130E-17 * 0.5);
1395    const_f64_as_f64x8!(T3PO8, core::f64::consts::SQRT_2 + 1.0);
1396
1397    const_f64_as_f64x8!(P4atan, -8.750608600031904122785E-1);
1398    const_f64_as_f64x8!(P3atan, -1.615753718733365076637E1);
1399    const_f64_as_f64x8!(P2atan, -7.500855792314704667340E1);
1400    const_f64_as_f64x8!(P1atan, -1.228866684490136173410E2);
1401    const_f64_as_f64x8!(P0atan, -6.485021904942025371773E1);
1402
1403    const_f64_as_f64x8!(Q4atan, 2.485846490142306297962E1);
1404    const_f64_as_f64x8!(Q3atan, 1.650270098316988542046E2);
1405    const_f64_as_f64x8!(Q2atan, 4.328810604912902668951E2);
1406    const_f64_as_f64x8!(Q1atan, 4.853903996359136964868E2);
1407    const_f64_as_f64x8!(Q0atan, 1.945506571482613964425E2);
1408
1409    let t = self.abs();
1410
1411    // small:  t < 0.66
1412    // medium: t <= t <= 2.4142 (1+sqrt(2))
1413    // big:    t > 2.4142
1414    let notbig = t.simd_le(T3PO8);
1415    let notsmal = t.simd_ge(Self::splat(0.66));
1416
1417    let mut s = notbig.select(Self::FRAC_PI_4, Self::FRAC_PI_2);
1418    s = notsmal & s;
1419    let mut fac = notbig.select(MORE_BITS_O2, MORE_BITS);
1420    fac = notsmal & fac;
1421
1422    // small:  z = t / 1.0;
1423    // medium: z = (t-1.0) / (t+1.0);
1424    // big:    z = -1.0 / t;
1425    let mut a = notbig & t;
1426    a = notsmal.select(a - Self::ONE, a);
1427    let mut b = notbig & Self::ONE;
1428    b = notsmal.select(b + t, b);
1429    let z = a / b;
1430
1431    let zz = z * z;
1432
1433    let px = polynomial_4!(zz, P0atan, P1atan, P2atan, P3atan, P4atan);
1434    let qx = polynomial_5n!(zz, Q0atan, Q1atan, Q2atan, Q3atan, Q4atan);
1435
1436    let mut re = (px / qx).mul_add(z * zz, z);
1437    re += s + fac;
1438
1439    // get sign bit
1440    re = (self.is_sign_negative()).select(-re, re);
1441
1442    re
1443  }
1444
1445  #[inline]
1446  pub fn atan2(self, x: Self) -> Self {
1447    // Based on the Agner Fog "vector class library":
1448    // https://github.com/vectorclass/version2/blob/master/vectormath_trig.h
1449    const_f64_as_f64x8!(MORE_BITS, 6.123233995736765886130E-17);
1450    const_f64_as_f64x8!(MORE_BITS_O2, 6.123233995736765886130E-17 * 0.5);
1451    const_f64_as_f64x8!(T3PO8, core::f64::consts::SQRT_2 + 1.0);
1452
1453    const_f64_as_f64x8!(P4atan, -8.750608600031904122785E-1);
1454    const_f64_as_f64x8!(P3atan, -1.615753718733365076637E1);
1455    const_f64_as_f64x8!(P2atan, -7.500855792314704667340E1);
1456    const_f64_as_f64x8!(P1atan, -1.228866684490136173410E2);
1457    const_f64_as_f64x8!(P0atan, -6.485021904942025371773E1);
1458
1459    const_f64_as_f64x8!(Q4atan, 2.485846490142306297962E1);
1460    const_f64_as_f64x8!(Q3atan, 1.650270098316988542046E2);
1461    const_f64_as_f64x8!(Q2atan, 4.328810604912902668951E2);
1462    const_f64_as_f64x8!(Q1atan, 4.853903996359136964868E2);
1463    const_f64_as_f64x8!(Q0atan, 1.945506571482613964425E2);
1464
1465    let y = self;
1466
1467    // move in first octant
1468    let x1 = x.abs();
1469    let y1 = y.abs();
1470    let swapxy = y1.simd_gt(x1);
1471    // swap x and y if y1 > x1
1472    let mut x2 = swapxy.select(y1, x1);
1473    let mut y2 = swapxy.select(x1, y1);
1474
1475    // check for special case: x and y are both +/- INF
1476    let both_infinite = x.is_inf() & y.is_inf();
1477    if both_infinite.any() {
1478      let minus_one = -Self::ONE;
1479      x2 = both_infinite.select(x2 & minus_one, x2);
1480      y2 = both_infinite.select(y2 & minus_one, y2);
1481    }
1482
1483    // x = y = 0 gives NAN here
1484    let t = y2 / x2;
1485
1486    // small:  t < 0.66
1487    // medium: t <= t <= 2.4142 (1+sqrt(2))
1488    // big:    t > 2.4142
1489    let notbig = t.simd_le(T3PO8);
1490    let notsmal = t.simd_ge(Self::splat(0.66));
1491
1492    let mut s = notbig.select(Self::FRAC_PI_4, Self::FRAC_PI_2);
1493    s = notsmal & s;
1494    let mut fac = notbig.select(MORE_BITS_O2, MORE_BITS);
1495    fac = notsmal & fac;
1496
1497    // small:  z = t / 1.0;
1498    // medium: z = (t-1.0) / (t+1.0);
1499    // big:    z = -1.0 / t;
1500    let mut a = notbig & t;
1501    a = notsmal.select(a - Self::ONE, a);
1502    let mut b = notbig & Self::ONE;
1503    b = notsmal.select(b + t, b);
1504    let z = a / b;
1505
1506    let zz = z * z;
1507
1508    let px = polynomial_4!(zz, P0atan, P1atan, P2atan, P3atan, P4atan);
1509    let qx = polynomial_5n!(zz, Q0atan, Q1atan, Q2atan, Q3atan, Q4atan);
1510
1511    let mut re = (px / qx).mul_add(z * zz, z);
1512    re += s + fac;
1513
1514    // move back in place
1515    re = swapxy.select(Self::FRAC_PI_2 - re, re);
1516    re = ((x | y).simd_eq(Self::ZERO)).select(Self::ZERO, re);
1517    re = (x.is_sign_negative()).select(Self::PI - re, re);
1518
1519    // get sign bit
1520    re = (y.is_sign_negative()).select(-re, re);
1521
1522    re
1523  }
1524
1525  #[inline]
1526  pub fn sin_cos(self) -> (Self, Self) {
1527    // Based on the Agner Fog "vector class library":
1528    // https://github.com/vectorclass/version2/blob/master/vectormath_trig.h
1529
1530    const_f64_as_f64x8!(P0sin, -1.66666666666666307295E-1);
1531    const_f64_as_f64x8!(P1sin, 8.33333333332211858878E-3);
1532    const_f64_as_f64x8!(P2sin, -1.98412698295895385996E-4);
1533    const_f64_as_f64x8!(P3sin, 2.75573136213857245213E-6);
1534    const_f64_as_f64x8!(P4sin, -2.50507477628578072866E-8);
1535    const_f64_as_f64x8!(P5sin, 1.58962301576546568060E-10);
1536
1537    const_f64_as_f64x8!(P0cos, 4.16666666666665929218E-2);
1538    const_f64_as_f64x8!(P1cos, -1.38888888888730564116E-3);
1539    const_f64_as_f64x8!(P2cos, 2.48015872888517045348E-5);
1540    const_f64_as_f64x8!(P3cos, -2.75573141792967388112E-7);
1541    const_f64_as_f64x8!(P4cos, 2.08757008419747316778E-9);
1542    const_f64_as_f64x8!(P5cos, -1.13585365213876817300E-11);
1543
1544    const_f64_as_f64x8!(DP1, 7.853981554508209228515625E-1 * 2.);
1545    const_f64_as_f64x8!(DP2, 7.94662735614792836714E-9 * 2.);
1546    const_f64_as_f64x8!(DP3, 3.06161699786838294307E-17 * 2.);
1547
1548    const_f64_as_f64x8!(TWO_OVER_PI, 2.0 / core::f64::consts::PI);
1549
1550    let xa = self.abs();
1551
1552    let y = (xa * TWO_OVER_PI).round_ties_even();
1553    let q = y.round_int();
1554
1555    let x = y.mul_neg_add(DP3, y.mul_neg_add(DP2, y.mul_neg_add(DP1, xa)));
1556
1557    let x2 = x * x;
1558    let mut s = polynomial_5!(x2, P0sin, P1sin, P2sin, P3sin, P4sin, P5sin);
1559    let mut c = polynomial_5!(x2, P0cos, P1cos, P2cos, P3cos, P4cos, P5cos);
1560    s = (x * x2).mul_add(s, x);
1561    c =
1562      (x2 * x2).mul_add(c, x2.mul_neg_add(f64x8::from(0.5), f64x8::from(1.0)));
1563
1564    let swap = !((q & i64x8::from(1)).simd_eq(i64x8::from(0)));
1565
1566    let mut overflow: f64x8 = cast(q.simd_gt(i64x8::from(0x80000000000000)));
1567    overflow &= xa.is_finite();
1568    s = overflow.select(f64x8::from(0.0), s);
1569    c = overflow.select(f64x8::from(1.0), c);
1570
1571    // calc sin
1572    let mut sin1 = cast::<_, f64x8>(swap).select(c, s);
1573    let sign_sin: i64x8 = (q << 62) ^ cast::<_, i64x8>(self);
1574    sin1 = sin1.flip_signs(cast(sign_sin));
1575
1576    // calc cos
1577    let mut cos1 = cast::<_, f64x8>(swap).select(s, c);
1578    let sign_cos: i64x8 = ((q + i64x8::from(1)) & i64x8::from(2)) << 62;
1579    cos1 ^= cast::<_, f64x8>(sign_cos);
1580
1581    // IEEE 754: sin/cos(±∞) = NaN, sin/cos(NaN) = NaN
1582    let finite = self.is_finite();
1583    let nan = Self::splat(f64::NAN);
1584    let sin_final = finite.select(sin1, nan);
1585    let cos_final = finite.select(cos1, nan);
1586
1587    (sin_final, cos_final)
1588  }
1589
1590  #[inline]
1591  pub fn asin_acos(self) -> (Self, Self) {
1592    // Based on the Agner Fog "vector class library":
1593    // https://github.com/vectorclass/version2/blob/master/vectormath_trig.h
1594    const_f64_as_f64x8!(R4asin, 2.967721961301243206100E-3);
1595    const_f64_as_f64x8!(R3asin, -5.634242780008963776856E-1);
1596    const_f64_as_f64x8!(R2asin, 6.968710824104713396794E0);
1597    const_f64_as_f64x8!(R1asin, -2.556901049652824852289E1);
1598    const_f64_as_f64x8!(R0asin, 2.853665548261061424989E1);
1599
1600    const_f64_as_f64x8!(S3asin, -2.194779531642920639778E1);
1601    const_f64_as_f64x8!(S2asin, 1.470656354026814941758E2);
1602    const_f64_as_f64x8!(S1asin, -3.838770957603691357202E2);
1603    const_f64_as_f64x8!(S0asin, 3.424398657913078477438E2);
1604
1605    const_f64_as_f64x8!(P5asin, 4.253011369004428248960E-3);
1606    const_f64_as_f64x8!(P4asin, -6.019598008014123785661E-1);
1607    const_f64_as_f64x8!(P3asin, 5.444622390564711410273E0);
1608    const_f64_as_f64x8!(P2asin, -1.626247967210700244449E1);
1609    const_f64_as_f64x8!(P1asin, 1.956261983317594739197E1);
1610    const_f64_as_f64x8!(P0asin, -8.198089802484824371615E0);
1611
1612    const_f64_as_f64x8!(Q4asin, -1.474091372988853791896E1);
1613    const_f64_as_f64x8!(Q3asin, 7.049610280856842141659E1);
1614    const_f64_as_f64x8!(Q2asin, -1.471791292232726029859E2);
1615    const_f64_as_f64x8!(Q1asin, 1.395105614657485689735E2);
1616    const_f64_as_f64x8!(Q0asin, -4.918853881490881290097E1);
1617
1618    let xa = self.abs();
1619
1620    let big = xa.simd_ge(f64x8::splat(0.625));
1621
1622    let x1 = big.select(f64x8::splat(1.0) - xa, xa * xa);
1623
1624    let x2 = x1 * x1;
1625    let x3 = x2 * x1;
1626    let x4 = x2 * x2;
1627    let x5 = x4 * x1;
1628
1629    let do_big = big.any();
1630    let do_small = !big.all();
1631
1632    let mut rx = f64x8::default();
1633    let mut sx = f64x8::default();
1634    let mut px = f64x8::default();
1635    let mut qx = f64x8::default();
1636
1637    if do_big {
1638      rx = x3.mul_add(R3asin, x2 * R2asin)
1639        + x4.mul_add(R4asin, x1.mul_add(R1asin, R0asin));
1640      sx =
1641        x3.mul_add(S3asin, x4) + x2.mul_add(S2asin, x1.mul_add(S1asin, S0asin));
1642    }
1643
1644    if do_small {
1645      px = x3.mul_add(P3asin, P0asin)
1646        + x4.mul_add(P4asin, x1 * P1asin)
1647        + x5.mul_add(P5asin, x2 * P2asin);
1648      qx = x4.mul_add(Q4asin, x5)
1649        + x3.mul_add(Q3asin, x1 * Q1asin)
1650        + x2.mul_add(Q2asin, Q0asin);
1651    };
1652
1653    let vx = big.select(rx, px);
1654    let wx = big.select(sx, qx);
1655
1656    let y1 = vx / wx * x1;
1657
1658    let mut z1 = f64x8::default();
1659    let mut z2 = f64x8::default();
1660    if do_big {
1661      let xb = (x1 + x1).sqrt();
1662      z1 = xb.mul_add(y1, xb);
1663    }
1664
1665    if do_small {
1666      z2 = xa.mul_add(y1, xa);
1667    }
1668
1669    // asin
1670    let z3 = f64x8::FRAC_PI_2 - z1;
1671    let asin = big.select(z3, z2);
1672    let asin = asin.flip_signs(self);
1673
1674    // acos
1675    let z3 = self.simd_lt(f64x8::ZERO).select(f64x8::PI - z1, z1);
1676    let z4 = f64x8::FRAC_PI_2 - z2.flip_signs(self);
1677    let acos = big.select(z3, z4);
1678
1679    (asin, acos)
1680  }
1681
1682  #[inline]
1683  pub fn exp_m1(self) -> Self {
1684    const_f64_as_f64x8!(P2, 1.0 / 2.0);
1685    const_f64_as_f64x8!(P3, 1.0 / 6.0);
1686    const_f64_as_f64x8!(P4, 1.0 / 24.0);
1687    const_f64_as_f64x8!(P5, 1.0 / 120.0);
1688    const_f64_as_f64x8!(P6, 1.0 / 720.0);
1689    const_f64_as_f64x8!(P7, 1.0 / 5040.0);
1690    const_f64_as_f64x8!(P8, 1.0 / 40320.0);
1691    const_f64_as_f64x8!(P9, 1.0 / 362880.0);
1692    const_f64_as_f64x8!(P10, 1.0 / 3628800.0);
1693    const_f64_as_f64x8!(P11, 1.0 / 39916800.0);
1694    const_f64_as_f64x8!(P12, 1.0 / 479001600.0);
1695    const_f64_as_f64x8!(P13, 1.0 / 6227020800.0);
1696    // LN2D_HI/LO: double-double decomposition of ln(2) for exp range reduction,
1697    // following fdlibm's approach (Sun Microsystems, https://www.netlib.org/fdlibm/ e_exp.c).
1698    const_f64_as_f64x8!(LN2D_HI, 0.693145751953125);
1699    const_f64_as_f64x8!(LN2D_LO, 1.42860682030941723212E-6);
1700    // x < -37.429: e^x < 2⁻⁵⁴, exp_m1(x) = -1.0 exactly (mantissa exhaustion)
1701    // IEEE simd_lt returns false for NaN, so NaN lanes can't reach here.
1702    // -inf is < -37.429, and exp_m1(-inf) = -1.0, also correct.
1703    if self.simd_lt(f64x8::from(-37.429)).all() {
1704      return f64x8::from(-1.0);
1705    }
1706    // max_x = ln(f64::MAX) ≈ 709.7827129, max_r = 1023 (IEEE max normal
1707    // exponent) min_x = -1074.5 ln(2) ≈ -744.79: min r for vm_pow2n to
1708    // construct subnormal
1709    let max_x = f64x8::from(709.783);
1710    let min_x = f64x8::from(-744.79);
1711    let max_r = f64x8::from(1023.0);
1712    let r = (self * Self::LOG2_E).round_ties_even();
1713    let big = r.simd_gt(max_r);
1714    let r_safe = big.select(max_r, r);
1715    let excess = r - max_r;
1716    let excess = big.select(excess, Self::ZERO);
1717    let scale = Self::vm_pow2n(excess);
1718    let x = r.mul_neg_add(LN2D_HI, self);
1719    let x = r.mul_neg_add(LN2D_LO, x);
1720    let z =
1721      polynomial_13!(x, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13);
1722    let n2 = Self::vm_pow2n(r_safe);
1723    let exp_val = (z + Self::ONE) * scale * n2;
1724    // When r == 0, z is already e^x - 1 from the Taylor poly.
1725    // Computing (z+1) - 1 would lose low bits for small x (catastrophic
1726    // cancellation at z ~ 0), so keep z directly.
1727    let r_is_zero = r.simd_eq(Self::ZERO);
1728    let z = r_is_zero.select(z, exp_val - Self::ONE);
1729    let nan_mask = self.is_nan();
1730    let finite = self.is_finite();
1731    let mut result = nan_mask.select(Self::nan_pow(), z);
1732    let pos_overflow = self.simd_gt(max_x) & finite;
1733    result = pos_overflow.select(Self::infinity(), result);
1734    let neg_underflow = self.simd_lt(min_x) & finite;
1735    result = neg_underflow.select(-Self::ONE, result);
1736    let pos_inf = !finite & !self.is_sign_negative() & !nan_mask;
1737    result = pos_inf.select(Self::infinity(), result);
1738    let neg_inf = !finite & self.is_sign_negative() & !nan_mask;
1739    result = neg_inf.select(-Self::ONE, result);
1740    let is_zero = self.simd_eq(Self::ZERO);
1741    result = is_zero.select(self, result);
1742    result
1743  }
1744
1745  #[inline]
1746  pub fn ln_1p(self) -> Self {
1747    // Based on the identity ln(1+x) = x·ln(1+x)/((1+x)-1), i.e. x·ln(u)/(u-1)
1748    // where u = 1+x. From MUSL libc (Rich Felker et al., https://musl.libc.org) src/math/log1p.c
1749    // and fdlibm (Sun Microsystems, https://www.netlib.org/fdlibm/) s_log1p.c.
1750    // When 1+x rounds to 1 exactly (subnormal x), return x directly.
1751    // When 1+x overflows (+inf), return ln(u) without correction.
1752    // Mathematically exact: compensates for the rounding loss in 1+x without
1753    // needing a series threshold.
1754    let u = self + Self::ONE;
1755    let eq = u.simd_eq(Self::ONE);
1756    let ln_u = Self::ln(u);
1757    let correction = self * (ln_u / (u - Self::ONE));
1758    let result = eq.select(self, correction);
1759    let over = u.is_inf();
1760    over.select(ln_u, result)
1761  }
1762
1763  #[inline]
1764  pub fn sinh(self) -> Self {
1765    const_f64_as_f64x8!(P0, 1.0);
1766    const_f64_as_f64x8!(P1, 1.0 / 6.0);
1767    const_f64_as_f64x8!(P2, 1.0 / 120.0);
1768    const_f64_as_f64x8!(P3, 1.0 / 5040.0);
1769    const_f64_as_f64x8!(P4, 1.0 / 362880.0);
1770    const_f64_as_f64x8!(P5, 1.0 / 39916800.0);
1771    const_f64_as_f64x8!(P6, 1.0 / 6227020800.0);
1772    let a = self.abs();
1773    // |x| < 0.5: Taylor poly; last truncation term < 1 ULP at x=0.5 for both types
1774    let small = a.simd_lt(f64x8::from(0.5));
1775    let t = a * a;
1776    let poly = a * polynomial_6!(t, P0, P1, P2, P3, P4, P5, P6);
1777    let exp_based = {
1778      let e = a.exp();
1779      (e - Self::ONE / e) * Self::HALF
1780    };
1781    let result = small.select(poly, exp_based);
1782    result.flip_signs(self)
1783  }
1784
1785  #[inline]
1786  pub fn cosh(self) -> Self {
1787    const_f64_as_f64x8!(P0, 1.0);
1788    const_f64_as_f64x8!(P1, 1.0 / 2.0);
1789    const_f64_as_f64x8!(P2, 1.0 / 24.0);
1790    const_f64_as_f64x8!(P3, 1.0 / 720.0);
1791    const_f64_as_f64x8!(P4, 1.0 / 40320.0);
1792    const_f64_as_f64x8!(P5, 1.0 / 3628800.0);
1793    const_f64_as_f64x8!(P6, 1.0 / 479001600.0);
1794    const_f64_as_f64x8!(P7, 1.0 / 87178291200.0);
1795    let a = self.abs();
1796    // |x| < 0.5: Taylor poly; last truncation term < 1 ULP at x=0.5 for both types
1797    let small = a.simd_lt(f64x8::from(0.5));
1798    let t = a * a;
1799    let poly = polynomial_7!(t, P0, P1, P2, P3, P4, P5, P6, P7);
1800    let exp_based = {
1801      let e = a.exp();
1802      (e + Self::ONE / e) * Self::HALF
1803    };
1804    small.select(poly, exp_based)
1805  }
1806
1807  #[inline]
1808  pub fn tanh(self) -> Self {
1809    // |x| < 5e-8: tanh(x) ≈ x, error x³/3 < 16·ULP(x)
1810    // bound: x² < 48·2⁻⁵² → x < 1.03e-7; 5e-8 has 2× margin
1811    // |x| > 19.062: tanh(x) = ±1 to f64 precision (e⁻²ˣ < 2⁻⁵⁴)
1812    let a = self.abs();
1813    let large = a.simd_gt(f64x8::from(19.062));
1814    if large.all() {
1815      return Self::ONE.flip_signs(self);
1816    }
1817    let small = a.simd_lt(f64x8::from(5e-8));
1818    let exp_based = {
1819      let t = (Self::from(-2.0) * a).exp_m1();
1820      let pos = -t / (t + Self::from(2.0));
1821      pos.flip_signs(self)
1822    };
1823    let result = small.select(self, exp_based);
1824    large.select(Self::ONE.flip_signs(self), result)
1825  }
1826}
1827
1828/// The following functionality exists only for [`f64x8`], or only for
1829/// particular types inconsistently.
1830impl f64x8 {
1831  #[inline]
1832  fn vm_pow2n(self) -> Self {
1833    const_f64_as_f64x8!(pow2_52, 4503599627370496.0);
1834    const_f64_as_f64x8!(bias, 1023.0);
1835    let a = self + (bias + pow2_52);
1836    let c = cast::<_, i64x8>(a) << 52;
1837    let std_result = cast::<_, f64x8>(c);
1838
1839    let min_exp = f64x8::from(-1022.0);
1840    let is_sub = self.simd_lt(min_exp);
1841    if is_sub.any() {
1842      let valid = self.simd_ge(f64x8::from(-1074.0));
1843      let shift_f = self + f64x8::from(1074.0);
1844      let mut shift_i = shift_f.trunc_int();
1845      shift_i = cast::<_, i64x8>(valid).select(shift_i, i64x8::ZERO);
1846      let mantissa = i64x8::ONE << shift_i;
1847      let sub_result = cast::<_, f64x8>(mantissa);
1848      let sub_result = valid.select(sub_result, f64x8::ZERO);
1849      is_sub.select(sub_result, std_result)
1850    } else {
1851      std_result
1852    }
1853  }
1854
1855  #[inline]
1856  fn exponent(self) -> f64x8 {
1857    const_f64_as_f64x8!(pow2_52, 4503599627370496.0);
1858    const_f64_as_f64x8!(bias, 1023.0);
1859    let a = cast::<_, u64x8>(self);
1860    let b = a >> 52;
1861    let c = b | cast::<_, u64x8>(pow2_52);
1862    let d = cast::<_, f64x8>(c);
1863    let e = d - (pow2_52 + bias);
1864    e
1865  }
1866
1867  #[inline]
1868  fn fraction_2(self) -> Self {
1869    let t1 = cast::<_, u64x8>(self);
1870    let t2 = cast::<_, u64x8>(
1871      (t1 & u64x8::from(0x000FFFFFFFFFFFFF)) | u64x8::from(0x3FE0000000000000),
1872    );
1873    cast::<_, f64x8>(t2)
1874  }
1875  #[inline]
1876  fn is_zero_or_subnormal(self) -> Self {
1877    let t = cast::<_, i64x8>(self);
1878    let t = t & i64x8::splat(0x7FF0000000000000);
1879    let mask = t.simd_eq(i64x8::splat(0));
1880    cast::<_, f64x8>(mask)
1881  }
1882  #[inline]
1883  fn infinity() -> Self {
1884    cast::<_, f64x8>(i64x8::splat(0x7FF0000000000000))
1885  }
1886  #[inline]
1887  fn nan_log() -> Self {
1888    cast::<_, f64x8>(i64x8::splat(0x7FF8000000000000 | 0x101 << 29))
1889  }
1890  #[inline]
1891  fn nan_pow() -> Self {
1892    cast::<_, f64x8>(i64x8::splat(0x7FF8000000000000 | 0x101 << 29))
1893  }
1894
1895  /// Converts each element from [`i32`] to [`f64`].
1896  #[inline]
1897  pub fn from_i32x8(v: i32x8) -> Self {
1898    pick! {
1899      if #[cfg(target_feature="avx512f")] {
1900        Self { avx512: convert_to_m512d_from_i32_m256i(v.avx2) }
1901      } else {
1902        Self::new([
1903          v.as_array()[0] as f64,
1904          v.as_array()[1] as f64,
1905          v.as_array()[2] as f64,
1906          v.as_array()[3] as f64,
1907          v.as_array()[4] as f64,
1908          v.as_array()[5] as f64,
1909          v.as_array()[6] as f64,
1910          v.as_array()[7] as f64,
1911        ])
1912      }
1913    }
1914  }
1915}
1916
1917impl From<i32x8> for f64x8 {
1918  #[inline]
1919  fn from(v: i32x8) -> Self {
1920    Self::from_i32x8(v)
1921  }
1922}