Skip to main content

wide/
f32x8_.rs

1use super::*;
2
3pick! {
4  if #[cfg(target_feature="avx")] {
5    #[derive(Default, Clone, Copy, PartialEq)]
6    #[repr(C, align(32))]
7    pub struct f32x8 { pub(crate) avx: m256 }
8  } else {
9    #[derive(Default, Clone, Copy, PartialEq)]
10    #[repr(C, align(32))]
11    pub struct f32x8 { pub(crate) a : f32x4, pub(crate) b : f32x4 }
12  }
13}
14
15macro_rules! const_f32_as_f32x8 {
16  ($i:ident, $f:expr) => {
17    #[allow(non_upper_case_globals)]
18    pub const $i: f32x8 = f32x8::new([$f; 8]);
19  };
20}
21
22impl f32x8 {
23  const_f32_as_f32x8!(ONE, 1.0);
24  const_f32_as_f32x8!(HALF, 0.5);
25  const_f32_as_f32x8!(ZERO, 0.0);
26  const_f32_as_f32x8!(EPSILON, f32::EPSILON);
27  const_f32_as_f32x8!(MIN, f32::MIN);
28  const_f32_as_f32x8!(MIN_POSITIVE, f32::MIN_POSITIVE);
29  const_f32_as_f32x8!(MAX, f32::MAX);
30  const_f32_as_f32x8!(NAN, f32::NAN);
31  const_f32_as_f32x8!(INFINITY, f32::INFINITY);
32  const_f32_as_f32x8!(NEG_INFINITY, f32::NEG_INFINITY);
33  const_f32_as_f32x8!(E, core::f32::consts::E);
34  const_f32_as_f32x8!(FRAC_1_PI, core::f32::consts::FRAC_1_PI);
35  const_f32_as_f32x8!(FRAC_2_PI, core::f32::consts::FRAC_2_PI);
36  const_f32_as_f32x8!(FRAC_2_SQRT_PI, core::f32::consts::FRAC_2_SQRT_PI);
37  const_f32_as_f32x8!(FRAC_1_SQRT_2, core::f32::consts::FRAC_1_SQRT_2);
38  const_f32_as_f32x8!(FRAC_PI_2, core::f32::consts::FRAC_PI_2);
39  const_f32_as_f32x8!(FRAC_PI_3, core::f32::consts::FRAC_PI_3);
40  const_f32_as_f32x8!(FRAC_PI_4, core::f32::consts::FRAC_PI_4);
41  const_f32_as_f32x8!(FRAC_PI_6, core::f32::consts::FRAC_PI_6);
42  const_f32_as_f32x8!(FRAC_PI_8, core::f32::consts::FRAC_PI_8);
43  const_f32_as_f32x8!(LN_2, core::f32::consts::LN_2);
44  const_f32_as_f32x8!(LN_10, core::f32::consts::LN_10);
45  const_f32_as_f32x8!(LOG2_E, core::f32::consts::LOG2_E);
46  const_f32_as_f32x8!(LOG10_E, core::f32::consts::LOG10_E);
47  const_f32_as_f32x8!(LOG10_2, core::f32::consts::LOG10_2);
48  const_f32_as_f32x8!(LOG2_10, core::f32::consts::LOG2_10);
49  const_f32_as_f32x8!(PI, core::f32::consts::PI);
50  const_f32_as_f32x8!(SQRT_2, core::f32::consts::SQRT_2);
51  const_f32_as_f32x8!(TAU, core::f32::consts::TAU);
52}
53
54unsafe impl Zeroable for f32x8 {}
55unsafe impl Pod for f32x8 {}
56
57impl AlignTo for f32x8 {
58  type Elem = f32;
59}
60
61impl Add for f32x8 {
62  type Output = Self;
63  #[inline]
64  fn add(self, rhs: Self) -> Self::Output {
65    pick! {
66      if #[cfg(target_feature="avx")] {
67        Self { avx: add_m256(self.avx, rhs.avx) }
68      } else {
69        Self {
70          a : self.a.add(rhs.a),
71          b : self.b.add(rhs.b),
72        }
73      }
74    }
75  }
76}
77
78impl Sub for f32x8 {
79  type Output = Self;
80  #[inline]
81  fn sub(self, rhs: Self) -> Self::Output {
82    pick! {
83      if #[cfg(target_feature="avx")] {
84        Self { avx: sub_m256(self.avx, rhs.avx) }
85      } else {
86        Self {
87          a : self.a.sub(rhs.a),
88          b : self.b.sub(rhs.b),
89        }
90      }
91    }
92  }
93}
94
95impl Mul for f32x8 {
96  type Output = Self;
97  #[inline]
98  fn mul(self, rhs: Self) -> Self::Output {
99    pick! {
100      if #[cfg(target_feature="avx")] {
101        Self { avx: mul_m256(self.avx, rhs.avx) }
102      } else {
103        Self {
104          a : self.a.mul(rhs.a),
105          b : self.b.mul(rhs.b),
106        }
107      }
108    }
109  }
110}
111
112impl Div for f32x8 {
113  type Output = Self;
114  #[inline]
115  fn div(self, rhs: Self) -> Self::Output {
116    pick! {
117      if #[cfg(target_feature="avx")] {
118        Self { avx: div_m256(self.avx, rhs.avx) }
119      } else {
120        Self {
121          a : self.a.div(rhs.a),
122          b : self.b.div(rhs.b),
123        }
124      }
125    }
126  }
127}
128
129impl Rem for f32x8 {
130  type Output = Self;
131  #[inline]
132  fn rem(self, rhs: Self) -> Self::Output {
133    Self::new([
134      self.to_array()[0] % rhs.to_array()[0],
135      self.to_array()[1] % rhs.to_array()[1],
136      self.to_array()[2] % rhs.to_array()[2],
137      self.to_array()[3] % rhs.to_array()[3],
138      self.to_array()[4] % rhs.to_array()[4],
139      self.to_array()[5] % rhs.to_array()[5],
140      self.to_array()[6] % rhs.to_array()[6],
141      self.to_array()[7] % rhs.to_array()[7],
142    ])
143  }
144}
145
146impl Neg for f32x8 {
147  type Output = Self;
148  #[inline]
149  fn neg(self) -> Self::Output {
150    pick! {
151      if #[cfg(target_feature="avx")] {
152        Self { avx: bitxor_m256(self.avx, Self::splat(-0.0).avx) }
153      } else {
154        Self {
155          a : self.a.neg(),
156          b : self.b.neg(),
157        }
158      }
159    }
160  }
161}
162
163impl Add<f32> for f32x8 {
164  type Output = Self;
165  #[inline]
166  fn add(self, rhs: f32) -> Self::Output {
167    self.add(Self::splat(rhs))
168  }
169}
170
171impl Sub<f32> for f32x8 {
172  type Output = Self;
173  #[inline]
174  fn sub(self, rhs: f32) -> Self::Output {
175    self.sub(Self::splat(rhs))
176  }
177}
178
179impl Mul<f32> for f32x8 {
180  type Output = Self;
181  #[inline]
182  fn mul(self, rhs: f32) -> Self::Output {
183    self.mul(Self::splat(rhs))
184  }
185}
186
187impl Div<f32> for f32x8 {
188  type Output = Self;
189  #[inline]
190  fn div(self, rhs: f32) -> Self::Output {
191    self.div(Self::splat(rhs))
192  }
193}
194
195impl Rem<f32> for f32x8 {
196  type Output = Self;
197  #[inline]
198  fn rem(self, rhs: f32) -> Self::Output {
199    self.rem(Self::splat(rhs))
200  }
201}
202
203impl Add<f32x8> for f32 {
204  type Output = f32x8;
205  #[inline]
206  fn add(self, rhs: f32x8) -> Self::Output {
207    f32x8::splat(self).add(rhs)
208  }
209}
210
211impl Sub<f32x8> for f32 {
212  type Output = f32x8;
213  #[inline]
214  fn sub(self, rhs: f32x8) -> Self::Output {
215    f32x8::splat(self).sub(rhs)
216  }
217}
218
219impl Mul<f32x8> for f32 {
220  type Output = f32x8;
221  #[inline]
222  fn mul(self, rhs: f32x8) -> Self::Output {
223    f32x8::splat(self).mul(rhs)
224  }
225}
226
227impl Div<f32x8> for f32 {
228  type Output = f32x8;
229  #[inline]
230  fn div(self, rhs: f32x8) -> Self::Output {
231    f32x8::splat(self).div(rhs)
232  }
233}
234
235impl Rem<f32x8> for f32 {
236  type Output = f32x8;
237  #[inline]
238  fn rem(self, rhs: f32x8) -> Self::Output {
239    f32x8::splat(self).rem(rhs)
240  }
241}
242
243impl BitAnd for f32x8 {
244  type Output = Self;
245  #[inline]
246  fn bitand(self, rhs: Self) -> Self::Output {
247    pick! {
248      if #[cfg(target_feature="avx")] {
249        Self { avx: bitand_m256(self.avx, rhs.avx) }
250      } else {
251        Self {
252          a : self.a.bitand(rhs.a),
253          b : self.b.bitand(rhs.b),
254        }
255      }
256    }
257  }
258}
259
260impl BitOr for f32x8 {
261  type Output = Self;
262  #[inline]
263  fn bitor(self, rhs: Self) -> Self::Output {
264    pick! {
265      if #[cfg(target_feature="avx")] {
266        Self { avx: bitor_m256(self.avx, rhs.avx) }
267      } else {
268        Self {
269          a : self.a.bitor(rhs.a),
270          b : self.b.bitor(rhs.b),
271        }
272      }
273    }
274  }
275}
276
277impl BitXor for f32x8 {
278  type Output = Self;
279  #[inline]
280  fn bitxor(self, rhs: Self) -> Self::Output {
281    pick! {
282      if #[cfg(target_feature="avx")] {
283        Self { avx: bitxor_m256(self.avx, rhs.avx) }
284      } else {
285        Self {
286          a : self.a.bitxor(rhs.a),
287          b : self.b.bitxor(rhs.b),
288        }
289      }
290    }
291  }
292}
293
294#[expect(deprecated)]
295impl CmpEq for f32x8 {
296  type Output = Self;
297  #[inline]
298  fn simd_eq(self, rhs: Self) -> Self::Output {
299    pick! {
300      if #[cfg(target_feature="avx")] {
301        Self { avx: cmp_op_mask_m256::<{cmp_op!(EqualOrdered)}>(self.avx, rhs.avx) }
302      } else {
303        Self {
304          a : self.a.simd_eq(rhs.a),
305          b : self.b.simd_eq(rhs.b),
306        }
307      }
308    }
309  }
310}
311
312#[expect(deprecated)]
313impl CmpGe for f32x8 {
314  type Output = Self;
315  #[inline]
316  fn simd_ge(self, rhs: Self) -> Self::Output {
317    pick! {
318      if #[cfg(target_feature="avx")] {
319        Self { avx: cmp_op_mask_m256::<{cmp_op!(GreaterEqualOrdered)}>(self.avx, rhs.avx) }
320      } else {
321        Self {
322          a : self.a.simd_ge(rhs.a),
323          b : self.b.simd_ge(rhs.b),
324        }
325      }
326    }
327  }
328}
329
330#[expect(deprecated)]
331impl CmpGt for f32x8 {
332  type Output = Self;
333  #[inline]
334  fn simd_gt(self, rhs: Self) -> Self::Output {
335    pick! {
336      if #[cfg(target_feature="avx")] {
337        Self { avx: cmp_op_mask_m256::<{cmp_op!(GreaterThanOrdered)}>(self.avx, rhs.avx) }
338      } else {
339        Self {
340          a : self.a.simd_gt(rhs.a),
341          b : self.b.simd_gt(rhs.b),
342        }
343      }
344    }
345  }
346}
347
348#[expect(deprecated)]
349impl CmpNe for f32x8 {
350  type Output = Self;
351  #[inline]
352  fn simd_ne(self, rhs: Self) -> Self::Output {
353    pick! {
354      if #[cfg(target_feature="avx")] {
355        Self { avx: cmp_op_mask_m256::<{cmp_op!(NotEqualUnordered)}>(self.avx, rhs.avx) }
356      } else {
357        Self {
358          a : self.a.simd_ne(rhs.a),
359          b : self.b.simd_ne(rhs.b),
360        }
361      }
362    }
363  }
364}
365
366#[expect(deprecated)]
367impl CmpLe for f32x8 {
368  type Output = Self;
369  #[inline]
370  fn simd_le(self, rhs: Self) -> Self::Output {
371    pick! {
372      if #[cfg(target_feature="avx")] {
373        Self { avx: cmp_op_mask_m256::<{cmp_op!(LessEqualOrdered)}>(self.avx, rhs.avx) }
374      } else {
375        Self {
376          a : self.a.simd_le(rhs.a),
377          b : self.b.simd_le(rhs.b),
378        }
379      }
380    }
381  }
382}
383
384#[expect(deprecated)]
385impl CmpLt for f32x8 {
386  type Output = Self;
387  #[inline]
388  fn simd_lt(self, rhs: Self) -> Self::Output {
389    pick! {
390      if #[cfg(target_feature="avx")] {
391        Self { avx: cmp_op_mask_m256::<{cmp_op!(LessThanOrdered)}>(self.avx, rhs.avx) }
392      } else {
393        Self {
394          a : self.a.simd_lt(rhs.a),
395          b : self.b.simd_lt(rhs.b),
396        }
397      }
398    }
399  }
400}
401
402impl f32x8 {
403  #[inline]
404  #[must_use]
405  pub const fn new(array: [f32; 8]) -> Self {
406    unsafe { core::mem::transmute(array) }
407  }
408
409  simd_comparison_fns!();
410
411  #[inline]
412  #[must_use]
413  pub fn blend(self, t: Self, f: Self) -> Self {
414    pick! {
415      if #[cfg(target_feature="avx")] {
416        Self { avx: blend_varying_m256(f.avx, t.avx, self.avx) }
417      } else {
418        Self {
419          a : self.a.blend(t.a, f.a),
420          b : self.b.blend(t.b, f.b),
421        }
422      }
423    }
424  }
425  #[inline]
426  #[must_use]
427  pub fn abs(self) -> Self {
428    pick! {
429      if #[cfg(target_feature="avx")] {
430        let non_sign_bits = f32x8::from(f32::from_bits(i32::MAX as u32));
431        self & non_sign_bits
432      } else {
433        Self {
434          a : self.a.abs(),
435          b : self.b.abs(),
436        }
437      }
438    }
439  }
440
441  #[inline]
442  #[must_use]
443  pub fn signum(self) -> Self {
444    let result = Self::ONE | self & -Self::ZERO;
445
446    self.is_nan().blend(self, result)
447  }
448
449  #[inline]
450  #[must_use]
451  pub fn floor(self) -> Self {
452    pick! {
453      if #[cfg(target_feature="avx")] {
454        Self { avx: floor_m256(self.avx) }
455      } else {
456        Self {
457          a : self.a.floor(),
458          b : self.b.floor(),
459        }
460      }
461    }
462  }
463  #[inline]
464  #[must_use]
465  pub fn ceil(self) -> Self {
466    pick! {
467      if #[cfg(target_feature="avx")] {
468        Self { avx: ceil_m256(self.avx) }
469      } else {
470        Self {
471          a : self.a.ceil(),
472          b : self.b.ceil(),
473        }
474      }
475    }
476  }
477
478  /// Calculates the lanewise maximum of both vectors. This is a faster
479  /// implementation than `max`, but it doesn't specify any behavior if NaNs are
480  /// involved.
481  #[inline]
482  #[must_use]
483  pub fn fast_max(self, rhs: Self) -> Self {
484    pick! {
485      if #[cfg(target_feature="avx")] {
486        Self { avx: max_m256(self.avx, rhs.avx) }
487      } else {
488        Self {
489          a : self.a.fast_max(rhs.a),
490          b : self.b.fast_max(rhs.b),
491        }
492      }
493    }
494  }
495
496  /// Calculates the lanewise maximum of both vectors. This doesn't match
497  /// IEEE-754 and instead is defined as `self < rhs ? rhs : self`.
498  #[inline]
499  #[must_use]
500  pub fn max(self, rhs: Self) -> Self {
501    pick! {
502      if #[cfg(target_feature="avx")] {
503        // max_m256 seems to do rhs < self ? self : rhs. So if there's any NaN
504        // involved, it chooses rhs, so we need to specifically check rhs for
505        // NaN.
506        rhs.is_nan().blend(self, Self { avx: max_m256(self.avx, rhs.avx) })
507      } else {
508        Self {
509          a : self.a.max(rhs.a),
510          b : self.b.max(rhs.b),
511        }
512      }
513
514    }
515  }
516
517  /// Calculates the lanewise minimum of both vectors. This is a faster
518  /// implementation than `min`, but it doesn't specify any behavior if NaNs are
519  /// involved.
520  #[inline]
521  #[must_use]
522  pub fn fast_min(self, rhs: Self) -> Self {
523    pick! {
524      if #[cfg(target_feature="avx")] {
525        Self { avx: min_m256(self.avx, rhs.avx) }
526      } else {
527        Self {
528          a : self.a.fast_min(rhs.a),
529          b : self.b.fast_min(rhs.b),
530        }
531      }
532    }
533  }
534
535  /// Calculates the lanewise minimum of both vectors. If either lane is NaN,
536  /// the other lane gets chosen. Use `fast_min` for a faster implementation
537  /// that doesn't handle NaNs.
538  #[inline]
539  #[must_use]
540  pub fn min(self, rhs: Self) -> Self {
541    pick! {
542      if #[cfg(target_feature="avx")] {
543        // min_m256 seems to do rhs > self ? self : rhs. So if there's any NaN
544        // involved, it chooses rhs, so we need to specifically check rhs for
545        // NaN.
546        rhs.is_nan().blend(self, Self { avx: min_m256(self.avx, rhs.avx) })
547      } else {
548        Self {
549          a : self.a.min(rhs.a),
550          b : self.b.min(rhs.b),
551        }
552      }
553    }
554  }
555
556  /// Restrict a value to a certain interval unless it is NaN.
557  ///
558  /// If `self` is NaN, or `min` is NaN, or `max` is NaN, the result is NaN.
559  /// If `min > max`, the result is `min`, since `fast_max(min)` dominates.
560  #[inline]
561  #[must_use]
562  pub fn clamp(self, min: Self, max: Self) -> Self {
563    let is_nan = self.is_nan() | min.is_nan() | max.is_nan();
564    let clamped = self.fast_min(max).fast_max(min);
565    is_nan.blend(Self::splat(f32::NAN), clamped)
566  }
567
568  /// Restrict a value to a certain interval unless it is NaN.
569  ///
570  /// Avoids NaN detection; same speed as the old `clamp` prior to IEEE 754-2019
571  /// compliance. Does not specify any
572  /// behavior if NaNs are involved, and if `min > max` the result is
573  /// unspecified.
574  #[inline]
575  #[must_use]
576  pub fn fast_clamp(self, min: Self, max: Self) -> Self {
577    pick! {
578      if #[cfg(target_feature="avx")] {
579        // For both `min_m256` and `max_m256` if any input is NaN, `rhs` gets
580        // chosen. For `self` to be chosen, `self` must be the second argument.
581        Self { avx: min_m256(max.avx, max_m256(min.avx, self.avx)) }
582      } else {
583        Self {
584          a: self.a.fast_clamp(min.a, max.a),
585          b: self.b.fast_clamp(min.b, max.b),
586        }
587      }
588    }
589  }
590
591  #[inline]
592  #[must_use]
593  pub fn midpoint(self, other: Self) -> Self {
594    (self + other) * 0.5
595  }
596
597  #[inline]
598  #[must_use]
599  pub fn is_nan(self) -> Self {
600    pick! {
601      if #[cfg(target_feature="avx")] {
602        Self { avx: cmp_op_mask_m256::<{cmp_op!(Unordered)}>(self.avx, self.avx) }
603      } else {
604        Self {
605          a : self.a.is_nan(),
606          b : self.b.is_nan(),
607        }
608      }
609    }
610  }
611  #[inline]
612  #[must_use]
613  pub fn is_finite(self) -> Self {
614    let shifted_exp_mask = u32x8::from(0xFF000000);
615    let u: u32x8 = cast(self);
616    let shift_u = u << 1_u64;
617    let out = !(shift_u & shifted_exp_mask).simd_eq(shifted_exp_mask);
618    cast(out)
619  }
620  #[inline]
621  #[must_use]
622  pub fn is_inf(self) -> Self {
623    let shifted_inf = u32x8::from(0xFF000000);
624    let u: u32x8 = cast(self);
625    let shift_u = u << 1_u64;
626    let out = (shift_u).simd_eq(shifted_inf);
627    cast(out)
628  }
629
630  #[inline]
631  #[must_use]
632  pub fn round(self) -> Self {
633    pick! {
634      // NOTE: Is there an SSE2 version of this? f32x4 version probably translates but I've not had time to figure it out
635      if #[cfg(target_feature="avx")] {
636        Self { avx: round_m256::<{round_op!(Nearest)}>(self.avx) }
637      } else {
638        Self {
639          a : self.a.round(),
640          b : self.b.round(),
641        }
642      }
643    }
644  }
645
646  /// Rounds each lane into an integer. This is a faster implementation than
647  /// `round_int`, but it doesn't handle out of range values or NaNs. For those
648  /// values you get implementation defined behavior.
649  #[inline]
650  #[must_use]
651  pub fn fast_round_int(self) -> i32x8 {
652    pick! {
653      if #[cfg(target_feature="avx")] {
654        cast(convert_to_i32_m256i_from_m256(self.avx))
655      } else {
656        cast([
657          self.a.fast_round_int(),
658          self.b.fast_round_int()])
659      }
660    }
661  }
662
663  /// Rounds each lane into an integer. This saturates out of range values and
664  /// turns NaNs into 0. Use `fast_round_int` for a faster implementation that
665  /// doesn't handle out of range values or NaNs.
666  #[inline]
667  #[must_use]
668  pub fn round_int(self) -> i32x8 {
669    pick! {
670      if #[cfg(target_feature="avx")] {
671        // Based on: https://github.com/v8/v8/blob/210987a552a2bf2a854b0baa9588a5959ff3979d/src/codegen/shared-ia32-x64/macro-assembler-shared-ia32-x64.h#L489-L504
672        let non_nan_mask = self.simd_eq(self);
673        let non_nan = self & non_nan_mask;
674        let flip_to_max: i32x8 = cast(self.simd_ge(Self::splat(2147483648.0)));
675        let cast: i32x8 = cast(convert_to_i32_m256i_from_m256(non_nan.avx));
676        flip_to_max ^ cast
677      } else {
678        cast([
679          self.a.round_int(),
680          self.b.round_int(),
681        ])
682      }
683    }
684  }
685
686  #[inline]
687  #[must_use]
688  pub fn trunc(self) -> Self {
689    pick! {
690      if #[cfg(target_feature="avx")] {
691        Self { avx: round_m256::<{round_op!(Zero)}>(self.avx) }
692      } else {
693        Self {
694          a : self.a.trunc(),
695          b : self.b.trunc(),
696        }
697      }
698    }
699  }
700
701  /// Truncates each lane into an integer. This is a faster implementation than
702  /// `trunc_int`, but it doesn't handle out of range values or NaNs. For those
703  /// values you get implementation defined behavior.
704  #[inline]
705  #[must_use]
706  pub fn fast_trunc_int(self) -> i32x8 {
707    pick! {
708      if #[cfg(all(target_feature="avx"))] {
709        cast(convert_truncate_to_i32_m256i_from_m256(self.avx))
710      } else {
711        cast([
712          self.a.fast_trunc_int(),
713          self.b.fast_trunc_int(),
714        ])
715      }
716    }
717  }
718
719  /// Truncates each lane into an integer. This saturates out of range values
720  /// and turns NaNs into 0. Use `fast_trunc_int` for a faster implementation
721  /// that doesn't handle out of range values or NaNs.
722  #[inline]
723  #[must_use]
724  pub fn trunc_int(self) -> i32x8 {
725    pick! {
726        if #[cfg(target_feature="avx")] {
727        // Based on: https://github.com/v8/v8/blob/210987a552a2bf2a854b0baa9588a5959ff3979d/src/codegen/shared-ia32-x64/macro-assembler-shared-ia32-x64.h#L489-L504
728        let non_nan_mask = self.simd_eq(self);
729        let non_nan = self & non_nan_mask;
730        let flip_to_max: i32x8 = cast(self.simd_ge(Self::splat(2147483648.0)));
731        let cast: i32x8 = cast(convert_truncate_to_i32_m256i_from_m256(non_nan.avx));
732        flip_to_max ^ cast
733      } else {
734        cast([
735          self.a.trunc_int(),
736          self.b.trunc_int(),
737        ])
738      }
739    }
740  }
741
742  #[inline]
743  #[must_use]
744  pub fn fract(self) -> Self {
745    self - self.trunc()
746  }
747
748  /// Performs a multiply-add operation: `self * m + a`
749  ///
750  /// When hardware FMA support is available, this computes the result with a
751  /// single rounding operation. Without FMA support, it falls back to separate
752  /// multiply and add operations with two roundings.
753  ///
754  /// # Platform-specific behavior
755  /// - On `x86`/`x86_64` with AVX+FMA: Uses `vfmadd` (single rounding, best
756  ///   accuracy)
757  /// - On `x86`/`x86_64` with AVX only: Uses `(self * m) + a` (two roundings)
758  /// - Other platforms: Delegates to [`f32x4`] (may use NEON FMA or fallback)
759  ///
760  /// # Examples
761  /// ```
762  /// # use wide::f32x8;
763  /// let a = f32x8::from([1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0]);
764  /// let b = f32x8::from([2.0; 8]);
765  /// let c = f32x8::from([10.0; 8]);
766  ///
767  /// let result = a.mul_add(b, c);
768  ///
769  /// let expected = f32x8::from([12.0, 14.0, 16.0, 18.0, 20.0, 22.0, 24.0, 26.0]);
770  /// assert_eq!(result, expected);
771  /// ```
772  #[inline]
773  #[must_use]
774  pub fn mul_add(self, m: Self, a: Self) -> Self {
775    pick! {
776      if #[cfg(all(target_feature="avx",target_feature="fma"))] {
777        Self { avx: fused_mul_add_m256(self.avx, m.avx, a.avx) }
778      } else if #[cfg(target_feature="avx")] {
779        // still want to use 256 bit ops
780        (self * m) + a
781      } else {
782        Self {
783          a : self.a.mul_add(m.a, a.a),
784          b : self.b.mul_add(m.b, a.b),
785        }
786      }
787    }
788  }
789
790  /// Performs a multiply-subtract operation: `self * m - s`
791  ///
792  /// When hardware FMA support is available, this computes the result with a
793  /// single rounding operation. Without FMA support, it falls back to separate
794  /// multiply and subtract operations with two roundings.
795  ///
796  /// # Platform-specific behavior
797  /// - On `x86`/`x86_64` with AVX+FMA: Uses `vfmsub` (single rounding, best
798  ///   accuracy)
799  /// - On `x86`/`x86_64` with AVX only: Uses `(self * m) - s` (two roundings)
800  /// - Other platforms: Delegates to [`f32x4`] (may use NEON FMA or fallback)
801  ///
802  /// # Examples
803  /// ```
804  /// # use wide::f32x8;
805  /// let a = f32x8::from([10.0; 8]);
806  /// let b = f32x8::from([2.0; 8]);
807  /// let c = f32x8::from([5.0; 8]);
808  ///
809  /// let result = a.mul_sub(b, c);
810  ///
811  /// let expected = f32x8::from([15.0; 8]);
812  /// assert_eq!(result, expected);
813  /// ```
814  #[inline]
815  #[must_use]
816  pub fn mul_sub(self, m: Self, s: Self) -> Self {
817    pick! {
818      if #[cfg(all(target_feature="avx",target_feature="fma"))] {
819        Self { avx: fused_mul_sub_m256(self.avx, m.avx, s.avx) }
820      } else if #[cfg(target_feature="avx")] {
821        // still want to use 256 bit ops
822        (self * m) - s
823      } else {
824        Self {
825          a : self.a.mul_sub(m.a, s.a),
826          b : self.b.mul_sub(m.b, s.b),
827        }
828      }
829    }
830  }
831
832  /// Performs a negative multiply-add operation: `a - (self * m)`
833  ///
834  /// When hardware FMA support is available, this computes the result with a
835  /// single rounding operation. Without FMA support, it falls back to separate
836  /// operations with two roundings.
837  ///
838  /// # Platform-specific behavior
839  /// - On `x86`/`x86_64` with AVX+FMA: Uses `vfnmadd` (single rounding, best
840  ///   accuracy)
841  /// - On `x86`/`x86_64` with AVX only: Uses `a - (self * m)` (two roundings)
842  /// - Other platforms: Delegates to [`f32x4`] (may use NEON FMA or fallback)
843  ///
844  /// # Examples
845  /// ```
846  /// # use wide::f32x8;
847  /// let a = f32x8::from([3.0; 8]);
848  /// let b = f32x8::from([2.0; 8]);
849  /// let c = f32x8::from([10.0; 8]);
850  ///
851  /// let result = a.mul_neg_add(b, c);
852  ///
853  /// let expected = f32x8::from([4.0; 8]);
854  /// assert_eq!(result, expected);
855  /// ```
856  #[inline]
857  #[must_use]
858  pub fn mul_neg_add(self, m: Self, a: Self) -> Self {
859    pick! {
860      if #[cfg(all(target_feature="avx",target_feature="fma"))] {
861        Self { avx: fused_mul_neg_add_m256(self.avx, m.avx, a.avx) }
862      } else if #[cfg(target_feature="avx")] {
863        // still want to use 256 bit ops
864        a - (self * m)
865      } else {
866        Self {
867          a : self.a.mul_neg_add(m.a, a.a),
868          b : self.b.mul_neg_add(m.b, a.b),
869        }
870      }
871    }
872  }
873
874  /// Performs a negative multiply-subtract operation: `-(self * m) - s`
875  ///
876  /// When hardware FMA support is available, this computes the result with a
877  /// single rounding operation. Without FMA support, it falls back to separate
878  /// operations with two roundings.
879  ///
880  /// # Platform-specific behavior
881  /// - On `x86`/`x86_64` with AVX+FMA: Uses `vfnmsub` (single rounding, best
882  ///   accuracy)
883  /// - On `x86`/`x86_64` with AVX only: Uses `-(self * m) - s` (two roundings)
884  /// - Other platforms: Delegates to [`f32x4`] (may use NEON FMA or fallback)
885  ///
886  /// # Examples
887  /// ```
888  /// # use wide::f32x8;
889  /// let a = f32x8::from([3.0; 8]);
890  /// let b = f32x8::from([2.0; 8]);
891  /// let c = f32x8::from([1.0; 8]);
892  ///
893  /// let result = a.mul_neg_sub(b, c);
894  ///
895  /// let expected = f32x8::from([-7.0; 8]);
896  /// assert_eq!(result, expected);
897  /// ```
898  #[inline]
899  #[must_use]
900  pub fn mul_neg_sub(self, m: Self, s: Self) -> Self {
901    pick! {
902      if #[cfg(all(target_feature="avx",target_feature="fma"))] {
903        Self { avx: fused_mul_neg_sub_m256(self.avx, m.avx, s.avx) }
904      } else if #[cfg(target_feature="avx")] {
905        // still want to use 256 bit ops
906        -(self * m) - s
907      } else {
908        Self {
909          a : self.a.mul_neg_sub(m.a, s.a),
910          b : self.b.mul_neg_sub(m.b, s.b),
911        }
912      }
913    }
914  }
915
916  #[inline]
917  #[must_use]
918  pub fn div_euclid(self, rhs: Self) -> Self {
919    let q = (self / rhs).trunc();
920    (self % rhs)
921      .simd_lt(Self::ZERO)
922      .blend(rhs.simd_gt(Self::ZERO).blend(q - Self::ONE, q + Self::ONE), q)
923  }
924
925  #[inline]
926  #[must_use]
927  pub fn rem_euclid(self, rhs: Self) -> Self {
928    let r = self % rhs;
929    r.simd_lt(Self::ZERO).blend(r + rhs.abs(), r)
930  }
931
932  #[inline]
933  #[must_use]
934  pub fn flip_signs(self, signs: Self) -> Self {
935    self ^ (signs & Self::from(-0.0))
936  }
937
938  #[inline]
939  #[must_use]
940  pub fn copysign(self, sign: Self) -> Self {
941    let magnitude_mask = Self::from(f32::from_bits(u32::MAX >> 1));
942    (self & magnitude_mask) | (sign & Self::from(-0.0))
943  }
944
945  #[inline]
946  pub fn asin_acos(self) -> (Self, Self) {
947    // Based on the Agner Fog "vector class library":
948    // https://github.com/vectorclass/version2/blob/master/vectormath_trig.h
949    const_f32_as_f32x8!(P4asinf, 4.2163199048E-2);
950    const_f32_as_f32x8!(P3asinf, 2.4181311049E-2);
951    const_f32_as_f32x8!(P2asinf, 4.5470025998E-2);
952    const_f32_as_f32x8!(P1asinf, 7.4953002686E-2);
953    const_f32_as_f32x8!(P0asinf, 1.6666752422E-1);
954
955    let xa = self.abs();
956    let big = xa.simd_ge(f32x8::splat(0.5));
957
958    let x1 = f32x8::splat(0.5) * (f32x8::ONE - xa);
959    let x2 = xa * xa;
960    let x3 = big.blend(x1, x2);
961
962    let xb = x1.sqrt();
963
964    let x4 = big.blend(xb, xa);
965
966    let z = polynomial_4!(x3, P0asinf, P1asinf, P2asinf, P3asinf, P4asinf);
967    let z = z.mul_add(x3 * x4, x4);
968
969    let z1 = z + z;
970
971    // acos
972    let z3 = self.simd_lt(f32x8::ZERO).blend(f32x8::PI - z1, z1);
973    let z4 = f32x8::FRAC_PI_2 - z.flip_signs(self);
974    let acos = big.blend(z3, z4);
975
976    // asin
977    let z3 = f32x8::FRAC_PI_2 - z1;
978    let asin = big.blend(z3, z);
979    let asin = asin.flip_signs(self);
980
981    (asin, acos)
982  }
983
984  #[inline]
985  #[must_use]
986  pub fn asin(self) -> Self {
987    // Based on the Agner Fog "vector class library":
988    // https://github.com/vectorclass/version2/blob/master/vectormath_trig.h
989    const_f32_as_f32x8!(P4asinf, 4.2163199048E-2);
990    const_f32_as_f32x8!(P3asinf, 2.4181311049E-2);
991    const_f32_as_f32x8!(P2asinf, 4.5470025998E-2);
992    const_f32_as_f32x8!(P1asinf, 7.4953002686E-2);
993    const_f32_as_f32x8!(P0asinf, 1.6666752422E-1);
994
995    let xa = self.abs();
996    let big = xa.simd_ge(f32x8::splat(0.5));
997
998    let x1 = f32x8::splat(0.5) * (f32x8::ONE - xa);
999    let x2 = xa * xa;
1000    let x3 = big.blend(x1, x2);
1001
1002    let xb = x1.sqrt();
1003
1004    let x4 = big.blend(xb, xa);
1005
1006    let z = polynomial_4!(x3, P0asinf, P1asinf, P2asinf, P3asinf, P4asinf);
1007    let z = z.mul_add(x3 * x4, x4);
1008
1009    let z1 = z + z;
1010
1011    // asin
1012    let z3 = f32x8::FRAC_PI_2 - z1;
1013    let asin = big.blend(z3, z);
1014    let asin = asin.flip_signs(self);
1015
1016    asin
1017  }
1018
1019  #[inline]
1020  #[must_use]
1021  pub fn acos(self) -> Self {
1022    // Based on the Agner Fog "vector class library":
1023    // https://github.com/vectorclass/version2/blob/master/vectormath_trig.h
1024    const_f32_as_f32x8!(P4asinf, 4.2163199048E-2);
1025    const_f32_as_f32x8!(P3asinf, 2.4181311049E-2);
1026    const_f32_as_f32x8!(P2asinf, 4.5470025998E-2);
1027    const_f32_as_f32x8!(P1asinf, 7.4953002686E-2);
1028    const_f32_as_f32x8!(P0asinf, 1.6666752422E-1);
1029
1030    let xa = self.abs();
1031    let big = xa.simd_ge(f32x8::splat(0.5));
1032
1033    let x1 = f32x8::splat(0.5) * (f32x8::ONE - xa);
1034    let x2 = xa * xa;
1035    let x3 = big.blend(x1, x2);
1036
1037    let xb = x1.sqrt();
1038
1039    let x4 = big.blend(xb, xa);
1040
1041    let z = polynomial_4!(x3, P0asinf, P1asinf, P2asinf, P3asinf, P4asinf);
1042    let z = z.mul_add(x3 * x4, x4);
1043
1044    let z1 = z + z;
1045
1046    // acos
1047    let z3 = self.simd_lt(f32x8::ZERO).blend(f32x8::PI - z1, z1);
1048    let z4 = f32x8::FRAC_PI_2 - z.flip_signs(self);
1049    let acos = big.blend(z3, z4);
1050
1051    acos
1052  }
1053
1054  #[inline]
1055  pub fn atan(self) -> Self {
1056    // Based on the Agner Fog "vector class library":
1057    // https://github.com/vectorclass/version2/blob/master/vectormath_trig.h
1058    const_f32_as_f32x8!(P3atanf, 8.05374449538E-2);
1059    const_f32_as_f32x8!(P2atanf, -1.38776856032E-1);
1060    const_f32_as_f32x8!(P1atanf, 1.99777106478E-1);
1061    const_f32_as_f32x8!(P0atanf, -3.33329491539E-1);
1062
1063    let t = self.abs();
1064
1065    // small:  z = t / 1.0;
1066    // medium: z = (t-1.0) / (t+1.0);
1067    // big:    z = -1.0 / t;
1068    let notsmal = t.simd_ge(Self::SQRT_2 - Self::ONE);
1069    let notbig = t.simd_le(Self::SQRT_2 + Self::ONE);
1070
1071    let mut s = notbig.blend(Self::FRAC_PI_4, Self::FRAC_PI_2);
1072    s = notsmal & s;
1073
1074    let mut a = notbig & t;
1075    a = notsmal.blend(a - Self::ONE, a);
1076    let mut b = notbig & Self::ONE;
1077    b = notsmal.blend(b + t, b);
1078    let z = a / b;
1079
1080    let zz = z * z;
1081
1082    // Taylor expansion
1083    let mut re = polynomial_3!(zz, P0atanf, P1atanf, P2atanf, P3atanf);
1084    re = re.mul_add(zz * z, z) + s;
1085
1086    // get sign bit
1087    re = (self.is_sign_negative()).blend(-re, re);
1088
1089    re
1090  }
1091
1092  #[inline]
1093  pub fn atan2(self, x: Self) -> Self {
1094    // Based on the Agner Fog "vector class library":
1095    // https://github.com/vectorclass/version2/blob/master/vectormath_trig.h
1096    const_f32_as_f32x8!(P3atanf, 8.05374449538E-2);
1097    const_f32_as_f32x8!(P2atanf, -1.38776856032E-1);
1098    const_f32_as_f32x8!(P1atanf, 1.99777106478E-1);
1099    const_f32_as_f32x8!(P0atanf, -3.33329491539E-1);
1100
1101    let y = self;
1102
1103    // move in first octant
1104    let x1 = x.abs();
1105    let y1 = y.abs();
1106    let swapxy = y1.simd_gt(x1);
1107    // swap x and y if y1 > x1
1108    let mut x2 = swapxy.blend(y1, x1);
1109    let mut y2 = swapxy.blend(x1, y1);
1110
1111    // check for special case: x and y are both +/- INF
1112    let both_infinite = x.is_inf() & y.is_inf();
1113    if both_infinite.any() {
1114      let minus_one = -Self::ONE;
1115      x2 = both_infinite.blend(x2 & minus_one, x2);
1116      y2 = both_infinite.blend(y2 & minus_one, y2);
1117    }
1118
1119    // x = y = 0 will produce NAN. No problem, fixed below
1120    let t = y2 / x2;
1121
1122    // small:  z = t / 1.0;
1123    // medium: z = (t-1.0) / (t+1.0);
1124    let notsmal = t.simd_ge(Self::SQRT_2 - Self::ONE);
1125
1126    let a = notsmal.blend(t - Self::ONE, t);
1127    let b = notsmal.blend(t + Self::ONE, Self::ONE);
1128    let s = notsmal & Self::FRAC_PI_4;
1129    let z = a / b;
1130
1131    let zz = z * z;
1132
1133    // Taylor expansion
1134    let mut re = polynomial_3!(zz, P0atanf, P1atanf, P2atanf, P3atanf);
1135    re = re.mul_add(zz * z, z) + s;
1136
1137    // move back in place
1138    re = swapxy.blend(Self::FRAC_PI_2 - re, re);
1139    re = ((x | y).simd_eq(Self::ZERO)).blend(Self::ZERO, re);
1140    re = (x.is_sign_negative()).blend(Self::PI - re, re);
1141
1142    // get sign bit
1143    re = (y.is_sign_negative()).blend(-re, re);
1144
1145    re
1146  }
1147
1148  #[inline]
1149  #[must_use]
1150  pub fn sin_cos(self) -> (Self, Self) {
1151    // Based on the Agner Fog "vector class library":
1152    // https://github.com/vectorclass/version2/blob/master/vectormath_trig.h
1153
1154    const_f32_as_f32x8!(DP1F, 0.78515625_f32 * 2.0);
1155    const_f32_as_f32x8!(DP2F, 2.4187564849853515625E-4_f32 * 2.0);
1156    const_f32_as_f32x8!(DP3F, 3.77489497744594108E-8_f32 * 2.0);
1157
1158    const_f32_as_f32x8!(P0sinf, -1.6666654611E-1);
1159    const_f32_as_f32x8!(P1sinf, 8.3321608736E-3);
1160    const_f32_as_f32x8!(P2sinf, -1.9515295891E-4);
1161
1162    const_f32_as_f32x8!(P0cosf, 4.166664568298827E-2);
1163    const_f32_as_f32x8!(P1cosf, -1.388731625493765E-3);
1164    const_f32_as_f32x8!(P2cosf, 2.443315711809948E-5);
1165
1166    const_f32_as_f32x8!(TWO_OVER_PI, 2.0 / core::f32::consts::PI);
1167
1168    let xa = self.abs();
1169
1170    // Find quadrant
1171    let y = (xa * TWO_OVER_PI).round();
1172    let q: i32x8 = y.round_int();
1173
1174    let x = y.mul_neg_add(DP3F, y.mul_neg_add(DP2F, y.mul_neg_add(DP1F, xa)));
1175
1176    let x2 = x * x;
1177    let mut s = polynomial_2!(x2, P0sinf, P1sinf, P2sinf) * (x * x2) + x;
1178    let mut c = polynomial_2!(x2, P0cosf, P1cosf, P2cosf) * (x2 * x2)
1179      + f32x8::from(0.5).mul_neg_add(x2, f32x8::from(1.0));
1180
1181    let swap = !(q & i32x8::from(1)).simd_eq(i32x8::from(0));
1182
1183    let mut overflow: f32x8 = cast(q.simd_gt(i32x8::from(0x2000000)));
1184    overflow &= xa.is_finite();
1185    s = overflow.blend(f32x8::from(0.0), s);
1186    c = overflow.blend(f32x8::from(1.0), c);
1187
1188    // calc sin
1189    let mut sin1 = cast::<_, f32x8>(swap).blend(c, s);
1190    let sign_sin: i32x8 = (q << 30) ^ cast::<_, i32x8>(self);
1191    sin1 = sin1.flip_signs(cast(sign_sin));
1192
1193    // calc cos
1194    let mut cos1 = cast::<_, f32x8>(swap).blend(s, c);
1195    let sign_cos: i32x8 = ((q + i32x8::from(1)) & i32x8::from(2)) << 30;
1196    cos1 ^= cast::<_, f32x8>(sign_cos);
1197
1198    // IEEE 754: sin/cos(±∞) = NaN, sin/cos(NaN) = NaN
1199    let finite = self.is_finite();
1200    let nan = Self::splat(f32::NAN);
1201    let sin_final = finite.blend(sin1, nan);
1202    let cos_final = finite.blend(cos1, nan);
1203
1204    (sin_final, cos_final)
1205  }
1206  #[inline]
1207  #[must_use]
1208  pub fn sin(self) -> Self {
1209    let (s, _) = self.sin_cos();
1210    s
1211  }
1212  #[inline]
1213  #[must_use]
1214  pub fn cos(self) -> Self {
1215    let (_, c) = self.sin_cos();
1216    c
1217  }
1218  #[inline]
1219  #[must_use]
1220  pub fn tan(self) -> Self {
1221    let (s, c) = self.sin_cos();
1222    s / c
1223  }
1224
1225  /// Calculates hyperbolic sine: `(e^self - e^(-self))/2`.
1226  #[inline]
1227  #[must_use]
1228  pub fn sinh(self) -> Self {
1229    const_f32_as_f32x8!(P0, 1.0);
1230    const_f32_as_f32x8!(P1, 1.0 / 6.0);
1231    const_f32_as_f32x8!(P2, 1.0 / 120.0);
1232    const_f32_as_f32x8!(P3, 1.0 / 5040.0);
1233    let a = self.abs();
1234    // |x| < 0.5: Taylor poly; last truncation term < 1 ULP at x=0.5 for both types
1235    let small = a.simd_lt(f32x8::from(0.5));
1236    let t = a * a;
1237    let poly = a * polynomial_3!(t, P0, P1, P2, P3);
1238    let exp_based = {
1239      let e = a.exp();
1240      (e - Self::ONE / e) * Self::HALF
1241    };
1242    let result = small.blend(poly, exp_based);
1243    result.flip_signs(self)
1244  }
1245
1246  /// Calculates hyperbolic cosine: `(e^self + e^(-self))/2`.
1247  #[inline]
1248  #[must_use]
1249  pub fn cosh(self) -> Self {
1250    const_f32_as_f32x8!(P0, 1.0);
1251    const_f32_as_f32x8!(P1, 1.0 / 2.0);
1252    const_f32_as_f32x8!(P2, 1.0 / 24.0);
1253    const_f32_as_f32x8!(P3, 1.0 / 720.0);
1254    let a = self.abs();
1255    // |x| < 0.5: Taylor poly; last truncation term < 1 ULP at x=0.5 for both types
1256    let small = a.simd_lt(f32x8::from(0.5));
1257    let t = a * a;
1258    let poly = polynomial_3!(t, P0, P1, P2, P3);
1259    let exp_based = {
1260      let e = a.exp();
1261      (e + Self::ONE / e) * Self::HALF
1262    };
1263    small.blend(poly, exp_based)
1264  }
1265
1266  /// Calculates hyperbolic tangent: `sinh(self)/cosh(self)`.
1267  #[inline]
1268  #[must_use]
1269  pub fn tanh(self) -> Self {
1270    // |x| < 2e-4: tanh(x) ≈ x, error x³/3 < 16·ULP(x)
1271    // bound: x² < 48·2⁻²³ → x < 2.39e-3; 2e-4 has 10× margin
1272    // |x| > 9.011: tanh(x) = ±1 to f32 precision (e⁻²ˣ < 2⁻²⁴)
1273    let a = self.abs();
1274    let large = a.simd_gt(f32x8::from(9.011));
1275    if large.all() {
1276      return Self::ONE.flip_signs(self);
1277    }
1278    let small = a.simd_lt(f32x8::from(2e-4));
1279    let exp_based = {
1280      let t = (Self::from(-2.0) * a).exp_m1();
1281      let pos = -t / (t + Self::from(2.0));
1282      pos.flip_signs(self)
1283    };
1284    let result = small.blend(self, exp_based);
1285    large.blend(Self::ONE.flip_signs(self), result)
1286  }
1287
1288  /// Calculates the cube root: `self^(1/3)`.
1289  #[inline]
1290  #[must_use]
1291  pub fn cbrt(self) -> Self {
1292    let a = self.abs();
1293    let zero = a.simd_eq(Self::ZERO);
1294    if zero.all() {
1295      return self; // preserves -0.0
1296    }
1297    let inf = a.is_inf();
1298    let nan = self.is_nan();
1299
1300    let tiny = a.simd_lt(Self::from(f32::MIN_POSITIVE));
1301    let a_work = tiny.blend(a * Self::from(16777216.0), a);
1302
1303    let e = Self::exponent(a_work) + Self::ONE;
1304    let d = Self::fraction_2(a_work);
1305
1306    // C0..C5 from SLEEF's minimax polynomial for 1/cbrt(d) on [0.5, 1.0)
1307    // Naoki Shibata et al., "SLEEF: A Portable Vectorized Library of C99
1308    // Mathematical Functions", https://sleef.org / https://github.com/shibatch/sleef
1309    // Licensed under the Boost Software License 1.0.
1310    // These are the f32-precision coefficients; our f64 variants use the f64
1311    // set.
1312    const_f32_as_f32x8!(C0, 2.2241257);
1313    const_f32_as_f32x8!(C1, -3.8095417);
1314    const_f32_as_f32x8!(C2, 5.8982625);
1315    const_f32_as_f32x8!(C3, -5.532182);
1316    const_f32_as_f32x8!(C4, 2.8208892);
1317    const_f32_as_f32x8!(C5, -0.60156447);
1318    let mut x = polynomial_5!(d, C0, C1, C2, C3, C4, C5);
1319
1320    let x2 = x * x;
1321    let x4 = x2 * x2;
1322    x = x - d.mul_add(x4, -x) * Self::from(1.0 / 3.0);
1323    // cbrt(d) = d * x² with refinement
1324    let mut y = (d * x) * x;
1325    let yx = y * x;
1326    let t = Self::from(2.0 / 3.0);
1327    y = y - t * y * (yx - Self::ONE);
1328
1329    // Scale by 2^(e/3)
1330    let three = Self::from(3.0);
1331    let two = Self::from(2.0);
1332    let neg = e.simd_lt(Self::ZERO);
1333    let e_adj = neg.blend(e - two, e);
1334    let k = (e_adj / three).trunc();
1335    let r = e - three * k;
1336    const_f32_as_f32x8!(CBRT2, 1.259921);
1337    const_f32_as_f32x8!(CBRT4, 1.587401);
1338    y = r.simd_eq(Self::ONE).blend(y * CBRT2, y);
1339    y = r.simd_eq(two).blend(y * CBRT4, y);
1340    y *= Self::vm_pow2n(k);
1341    y = tiny.blend(y / Self::from(256.0_f32), y);
1342
1343    let result = y.flip_signs(self);
1344    let result = nan.blend(self, result);
1345    let result = zero.blend(self, result);
1346    let result = inf.blend(self, result);
1347    result
1348  }
1349
1350  #[inline]
1351  #[must_use]
1352  pub fn to_degrees(self) -> Self {
1353    const_f32_as_f32x8!(RAD_TO_DEG_RATIO, 180.0_f32 / core::f32::consts::PI);
1354    self * RAD_TO_DEG_RATIO
1355  }
1356  #[inline]
1357  #[must_use]
1358  pub fn to_radians(self) -> Self {
1359    const_f32_as_f32x8!(DEG_TO_RAD_RATIO, core::f32::consts::PI / 180.0_f32);
1360    self * DEG_TO_RAD_RATIO
1361  }
1362  #[inline]
1363  #[must_use]
1364  pub fn recip(self) -> Self {
1365    pick! {
1366      if #[cfg(target_feature="avx")] {
1367        Self { avx: reciprocal_m256(self.avx) }
1368      } else {
1369        Self {
1370          a : self.a.recip(),
1371          b : self.b.recip(),
1372        }
1373      }
1374    }
1375  }
1376  #[inline]
1377  #[must_use]
1378  pub fn recip_sqrt(self) -> Self {
1379    pick! {
1380      if #[cfg(target_feature="avx")] {
1381        Self { avx: reciprocal_sqrt_m256(self.avx) }
1382      } else {
1383        Self {
1384          a : self.a.recip_sqrt(),
1385          b : self.b.recip_sqrt(),
1386        }
1387      }
1388    }
1389  }
1390  #[inline]
1391  #[must_use]
1392  pub fn sqrt(self) -> Self {
1393    pick! {
1394      if #[cfg(target_feature="avx")] {
1395        Self { avx: sqrt_m256(self.avx) }
1396      } else {
1397        Self {
1398          a : self.a.sqrt(),
1399          b : self.b.sqrt(),
1400        }
1401      }
1402    }
1403  }
1404  #[inline]
1405  #[must_use]
1406  #[doc(alias("movemask", "move_mask"))]
1407  pub fn to_bitmask(self) -> u32 {
1408    pick! {
1409      if #[cfg(target_feature="avx")] {
1410        move_mask_m256(self.avx) as u32
1411      } else {
1412        (self.b.to_bitmask() << 4) | self.a.to_bitmask()
1413      }
1414    }
1415  }
1416  #[inline]
1417  #[must_use]
1418  pub fn any(self) -> bool {
1419    pick! {
1420      if #[cfg(target_feature="avx")] {
1421        move_mask_m256(self.avx) != 0
1422      } else {
1423        self.a.any() || self.b.any()
1424      }
1425    }
1426  }
1427  #[inline]
1428  #[must_use]
1429  pub fn all(self) -> bool {
1430    pick! {
1431      if #[cfg(target_feature="avx")] {
1432        move_mask_m256(self.avx) == 0b11111111
1433      } else {
1434        self.a.all() && self.b.all()
1435      }
1436    }
1437  }
1438  #[inline]
1439  #[must_use]
1440  pub fn none(self) -> bool {
1441    !self.any()
1442  }
1443
1444  #[inline]
1445  fn vm_pow2n(self) -> Self {
1446    const_f32_as_f32x8!(pow2_23, 8388608.0);
1447    const_f32_as_f32x8!(bias, 127.0);
1448    let a = self + (bias + pow2_23);
1449    let c = cast::<_, i32x8>(a) << 23;
1450    let std_result = cast::<_, f32x8>(c);
1451
1452    let min_exp = f32x8::from(-126.0);
1453    let is_sub = self.simd_lt(min_exp);
1454    if is_sub.any() {
1455      let valid = self.simd_ge(f32x8::from(-149.0));
1456      let shift_f = self + f32x8::from(149.0);
1457      let mut shift_i = shift_f.trunc_int();
1458      shift_i = cast::<_, i32x8>(valid).blend(shift_i, i32x8::ZERO);
1459      let mantissa = i32x8::ONE << shift_i;
1460      let sub_result = cast::<_, f32x8>(mantissa);
1461      let sub_result = valid.blend(sub_result, f32x8::ZERO);
1462      is_sub.blend(sub_result, std_result)
1463    } else {
1464      std_result
1465    }
1466  }
1467
1468  /// Calculate the exponent of a packed `f32x8`
1469  #[inline]
1470  #[must_use]
1471  pub fn exp(self) -> Self {
1472    const_f32_as_f32x8!(P0, 1.0 / 2.0);
1473    const_f32_as_f32x8!(P1, 1.0 / 6.0);
1474    const_f32_as_f32x8!(P2, 1.0 / 24.0);
1475    const_f32_as_f32x8!(P3, 1.0 / 120.0);
1476    const_f32_as_f32x8!(P4, 1.0 / 720.0);
1477    const_f32_as_f32x8!(P5, 1.0 / 5040.0);
1478    // LN2D_HI/LO: double-double decomposition of ln(2) for exp range reduction,
1479    // following the approach from fdlibm's e_exp.c (Sun Microsystems,
1480    // https://www.netlib.org/fdlibm/). The f32 split uses f32-precision constants
1481    // (0.693359375, -2.12194440e-4) summing to ln(2) with single-precision
1482    // accuracy; the f64 variants use a full f64 double-double
1483    // decomposition.
1484    const_f32_as_f32x8!(LN2D_HI, 0.693359375);
1485    const_f32_as_f32x8!(LN2D_LO, -2.12194440e-4);
1486    // max_x = ln(f32::MAX) ≈ 88.7229, max_r = 127 (IEEE max normal exponent)
1487    // min_x = -149.5 ln(2) ≈ -103.63: min r for vm_pow2n subnormal
1488    let max_x = f32x8::from(88.723);
1489    let min_x = f32x8::from(-103.63);
1490    // x < min_x: e^x underflows to 0 -- skip the entire pipeline
1491    let finite = self.is_finite();
1492    let neg_underflow = self.simd_lt(min_x) & finite;
1493    if neg_underflow.all() {
1494      return Self::ZERO;
1495    }
1496    let max_r = f32x8::from(127.0);
1497    let r = (self * Self::LOG2_E).round();
1498    let big = r.simd_gt(max_r);
1499    let r_safe = big.blend(max_r, r);
1500    let excess = r - max_r;
1501    let excess = big.blend(excess, Self::ZERO);
1502    let scale = Self::vm_pow2n(excess);
1503    let x = r.mul_neg_add(LN2D_HI, self);
1504    let x = r.mul_neg_add(LN2D_LO, x);
1505    let z = polynomial_5!(x, P0, P1, P2, P3, P4, P5);
1506    let x2 = x * x;
1507    let z = z.mul_add(x2, x);
1508    let n2 = Self::vm_pow2n(r_safe);
1509    let z = (z + Self::ONE) * scale * n2;
1510    let nan_mask = self.is_nan();
1511    let mut result = nan_mask.blend(Self::nan_pow(), z);
1512    let pos_overflow = self.simd_gt(max_x) & finite;
1513    result = pos_overflow.blend(Self::infinity(), result);
1514    result = neg_underflow.blend(Self::ZERO, result);
1515    let pos_inf = !finite & !self.is_sign_negative() & !nan_mask;
1516    result = pos_inf.blend(Self::infinity(), result);
1517    let neg_inf = !finite & self.is_sign_negative() & !nan_mask;
1518    result = neg_inf.blend(Self::ZERO, result);
1519    result
1520  }
1521
1522  /// Calculate `e^self - 1` for each lane.
1523  /// Accurate even for very small values.
1524  #[inline]
1525  #[must_use]
1526  pub fn exp_m1(self) -> Self {
1527    // x < -17.329: e^x < 2⁻²⁵, exp_m1(x) = -1.0 exactly (mantissa exhaustion)
1528    // IEEE simd_lt returns false for NaN, so NaN lanes can't reach here.
1529    // -inf is < -17.329, and exp_m1(-inf) = -1.0, also correct.
1530    if self.simd_lt(f32x8::from(-17.329)).all() {
1531      return f32x8::from(-1.0);
1532    }
1533    const_f32_as_f32x8!(P0, 1.0 / 2.0);
1534    const_f32_as_f32x8!(P1, 1.0 / 6.0);
1535    const_f32_as_f32x8!(P2, 1.0 / 24.0);
1536    const_f32_as_f32x8!(P3, 1.0 / 120.0);
1537    const_f32_as_f32x8!(P4, 1.0 / 720.0);
1538    const_f32_as_f32x8!(P5, 1.0 / 5040.0);
1539    // LN2D_HI/LO: double-double decomposition of ln(2) for exp range reduction,
1540    // following the approach from fdlibm's e_exp.c (Sun Microsystems,
1541    // https://www.netlib.org/fdlibm/). The f32 split uses f32-precision constants
1542    // (0.693359375, -2.12194440e-4) summing to ln(2) with single-precision
1543    // accuracy; the f64 variants use a full f64 double-double
1544    // decomposition.
1545    const_f32_as_f32x8!(LN2D_HI, 0.693359375);
1546    const_f32_as_f32x8!(LN2D_LO, -2.12194440e-4);
1547    // max_x = ln(f32::MAX) ≈ 88.7229, max_r = 127 (IEEE max normal exponent)
1548    // min_x = -149.5 ln(2) ≈ -103.63: min r for vm_pow2n subnormal
1549    let max_x = f32x8::from(88.723);
1550    let min_x = f32x8::from(-103.63);
1551    let max_r = f32x8::from(127.0);
1552    let r = (self * Self::LOG2_E).round();
1553    let big = r.simd_gt(max_r);
1554    let r_safe = big.blend(max_r, r);
1555    let excess = r - max_r;
1556    let excess = big.blend(excess, Self::ZERO);
1557    let scale = Self::vm_pow2n(excess);
1558    let x = r.mul_neg_add(LN2D_HI, self);
1559    let x = r.mul_neg_add(LN2D_LO, x);
1560    let z = polynomial_5!(x, P0, P1, P2, P3, P4, P5);
1561    let x2 = x * x;
1562    let z = z.mul_add(x2, x);
1563    let n2 = Self::vm_pow2n(r_safe);
1564    let exp_val = (z + Self::ONE) * scale * n2;
1565    let r_is_zero = r.simd_eq(Self::ZERO);
1566    let z = r_is_zero.blend(z, exp_val - Self::ONE);
1567    let nan_mask = self.is_nan();
1568    let finite = self.is_finite();
1569    let mut result = nan_mask.blend(Self::nan_pow(), z);
1570    let pos_overflow = self.simd_gt(max_x) & finite;
1571    result = pos_overflow.blend(Self::infinity(), result);
1572    let neg_underflow = self.simd_lt(min_x) & finite;
1573    result = neg_underflow.blend(-Self::ONE, result);
1574    let pos_inf = !finite & !self.is_sign_negative() & !nan_mask;
1575    result = pos_inf.blend(Self::infinity(), result);
1576    let neg_inf = !finite & self.is_sign_negative() & !nan_mask;
1577    result = neg_inf.blend(-Self::ONE, result);
1578    let is_zero = self.simd_eq(Self::ZERO);
1579    result = is_zero.blend(self, result);
1580    result
1581  }
1582
1583  /// Returns `2^self`.
1584  #[inline]
1585  #[must_use]
1586  pub fn exp2(self) -> Self {
1587    const_f32_as_f32x8!(P2, 1.0 / 2.0);
1588    const_f32_as_f32x8!(P3, 1.0 / 6.0);
1589    const_f32_as_f32x8!(P4, 1.0 / 24.0);
1590    const_f32_as_f32x8!(P5, 1.0 / 120.0);
1591    const_f32_as_f32x8!(P6, 1.0 / 720.0);
1592    const_f32_as_f32x8!(P7, 1.0 / 5040.0);
1593
1594    // max_x = log2(f32::MAX) ≈ 127.99999
1595    // min_x = log2(f32::MIN_POSITIVE) - 23 ≈ -126 - 23 = -149
1596    let max_x = f32x8::from(127.99999);
1597    let min_x = f32x8::from(-149.5);
1598    let finite = self.is_finite();
1599    let neg_underflow = self.simd_lt(min_x) & finite;
1600    if neg_underflow.all() {
1601      return Self::ZERO;
1602    }
1603
1604    let round = self.round();
1605    let max_r = f32x8::from(127.0);
1606    let big = round.simd_gt(max_r);
1607    let r_safe = big.blend(max_r, round);
1608    let excess = round - max_r;
1609    let excess = big.blend(excess, Self::ZERO);
1610    let scale = Self::vm_pow2n(excess);
1611
1612    let fract = (self - round) * Self::LN_2;
1613    let fract_partial_exp2 = polynomial_5!(fract, P2, P3, P4, P5, P6, P7);
1614    let fract2 = fract * fract;
1615    let fract_exp2 = fract_partial_exp2.mul_add(fract2, fract) + Self::ONE;
1616
1617    let n2 = Self::vm_pow2n(r_safe);
1618    let result = fract_exp2 * scale * n2;
1619
1620    let nan_mask = self.is_nan();
1621    let mut result = nan_mask.blend(Self::nan_pow(), result);
1622    let pos_overflow = self.simd_gt(max_x) & finite;
1623    result = pos_overflow.blend(Self::infinity(), result);
1624    result = neg_underflow.blend(Self::ZERO, result);
1625    let pos_inf = !finite & !self.is_sign_negative() & !nan_mask;
1626    result = pos_inf.blend(Self::infinity(), result);
1627    let neg_inf = !finite & self.is_sign_negative() & !nan_mask;
1628    result = neg_inf.blend(Self::ZERO, result);
1629    result
1630  }
1631
1632  #[inline]
1633  fn exponent(self) -> f32x8 {
1634    const_f32_as_f32x8!(pow2_23, 8388608.0);
1635    const_f32_as_f32x8!(bias, 127.0);
1636    let a = cast::<_, u32x8>(self);
1637    let b = a >> 23;
1638    let c = b | cast::<_, u32x8>(pow2_23);
1639    let d = cast::<_, f32x8>(c);
1640    let e = d - (pow2_23 + bias);
1641    e
1642  }
1643
1644  #[inline]
1645  fn fraction_2(self) -> Self {
1646    let t1 = cast::<_, u32x8>(self);
1647    let t2 = cast::<_, u32x8>(
1648      (t1 & u32x8::from(0x007FFFFF)) | u32x8::from(0x3F000000),
1649    );
1650    cast::<_, f32x8>(t2)
1651  }
1652  #[inline]
1653  fn is_zero_or_subnormal(self) -> Self {
1654    let t = cast::<_, i32x8>(self);
1655    let t = t & i32x8::splat(0x7F800000);
1656    let mask = t.simd_eq(i32x8::splat(0));
1657    cast::<_, f32x8>(mask)
1658  }
1659  #[inline]
1660  fn infinity() -> Self {
1661    cast::<_, f32x8>(i32x8::splat(0x7F800000))
1662  }
1663  #[inline]
1664  fn nan_log() -> Self {
1665    cast::<_, f32x8>(i32x8::splat(0x7FC00000 | 0x101 & 0x003FFFFF))
1666  }
1667  #[inline]
1668  fn nan_pow() -> Self {
1669    cast::<_, f32x8>(i32x8::splat(0x7FC00000 | 0x101 & 0x003FFFFF))
1670  }
1671
1672  /// Returns true for each element if it has a positive sign, including `+0.0`,
1673  /// `NaN`s with positive sign bit and positive infinity.
1674  #[inline]
1675  #[must_use]
1676  pub fn is_sign_positive(self) -> Self {
1677    const SIGN_MASK: u32x8 = u32x8::splat((-0.0_f32).to_bits());
1678
1679    let bits = cast::<f32x8, u32x8>(self);
1680    let sign = bits & SIGN_MASK;
1681    let result = sign.simd_eq(u32x8::ZERO);
1682    cast::<u32x8, f32x8>(result)
1683  }
1684
1685  /// Returns true for each element if it has a negative sign, including `-0.0`,
1686  /// `NaN`s with negative sign bit and negative infinity.
1687  #[inline]
1688  #[must_use]
1689  pub fn is_sign_negative(self) -> Self {
1690    const SIGN_MASK: u32x8 = u32x8::splat((-0.0_f32).to_bits());
1691
1692    let bits = cast::<f32x8, u32x8>(self);
1693    let sign = bits & SIGN_MASK;
1694    let result = sign.simd_eq(SIGN_MASK);
1695    cast::<u32x8, f32x8>(result)
1696  }
1697
1698  /// horizontal add of all the elements of the vector
1699  #[inline]
1700  #[must_use]
1701  pub fn reduce_add(self) -> f32 {
1702    pick! {
1703      // From https://stackoverflow.com/questions/13219146/how-to-sum-m256-horizontally
1704      if #[cfg(target_feature="avx")]{
1705        let hi_quad = extract_m128_from_m256::<1>(self.avx);
1706        let lo_quad = cast_to_m128_from_m256(self.avx);
1707        let sum_quad = add_m128(lo_quad,hi_quad);
1708        let lo_dual = sum_quad;
1709        let hi_dual = move_high_low_m128(sum_quad,sum_quad);
1710        let sum_dual = add_m128(lo_dual,hi_dual);
1711        let lo = sum_dual;
1712        let hi = shuffle_abi_f32_all_m128::<0b_01>(sum_dual, sum_dual);
1713        let sum = add_m128_s(lo, hi);
1714        get_f32_from_m128_s(sum)
1715      } else {
1716        self.a.reduce_add() + self.b.reduce_add()
1717      }
1718    }
1719  }
1720
1721  /// horizontal multiplication of all the elements of the vector
1722  #[inline]
1723  #[must_use]
1724  pub fn reduce_mul(self) -> f32 {
1725    pick! {
1726      // From https://stackoverflow.com/questions/13219146/how-to-sum-m256-horizontally
1727      if #[cfg(target_feature="avx")] {
1728        let hi_quad = extract_m128_from_m256::<1>(self.avx);
1729        let lo_quad = cast_to_m128_from_m256(self.avx);
1730        let product_quad = mul_m128(lo_quad,hi_quad);
1731        let lo_dual = product_quad;
1732        let hi_dual = move_high_low_m128(product_quad, product_quad);
1733        let product_dual = mul_m128(lo_dual,hi_dual);
1734        let lo = product_dual;
1735        let hi = shuffle_abi_f32_all_m128::<0b_01>(product_dual, product_dual);
1736        let product = mul_m128_s(lo, hi);
1737        get_f32_from_m128_s(product)
1738      } else {
1739        self.a.reduce_mul() * self.b.reduce_mul()
1740      }
1741    }
1742  }
1743
1744  /// Natural log (ln(x))
1745  #[inline]
1746  #[must_use]
1747  pub fn ln(self) -> Self {
1748    const_f32_as_f32x8!(HALF, 0.5);
1749    const_f32_as_f32x8!(P0, 3.3333331174E-1);
1750    const_f32_as_f32x8!(P1, -2.4999993993E-1);
1751    const_f32_as_f32x8!(P2, 2.0000714765E-1);
1752    const_f32_as_f32x8!(P3, -1.6668057665E-1);
1753    const_f32_as_f32x8!(P4, 1.4249322787E-1);
1754    const_f32_as_f32x8!(P5, -1.2420140846E-1);
1755    const_f32_as_f32x8!(P6, 1.1676998740E-1);
1756    const_f32_as_f32x8!(P7, -1.1514610310E-1);
1757    const_f32_as_f32x8!(P8, 7.0376836292E-2);
1758    const_f32_as_f32x8!(LN2F_HI, 0.693359375);
1759    const_f32_as_f32x8!(LN2F_LO, -2.12194440e-4);
1760    const_f32_as_f32x8!(VM_SMALLEST_NORMAL, 1.17549435E-38);
1761
1762    let x1 = self;
1763    let x = Self::fraction_2(x1);
1764    let e = Self::exponent(x1);
1765    let mask = x.simd_gt(Self::SQRT_2 * HALF);
1766    let x = (!mask).blend(x + x, x);
1767    let fe = mask.blend(e + Self::ONE, e);
1768    let x = x - Self::ONE;
1769    let res = polynomial_8!(x, P0, P1, P2, P3, P4, P5, P6, P7, P8);
1770    let x2 = x * x;
1771    let res = x2 * x * res;
1772    let res = fe.mul_add(LN2F_LO, res);
1773    let res = res + x2.mul_neg_add(HALF, x);
1774    let res = fe.mul_add(LN2F_HI, res);
1775    let overflow = !self.is_finite();
1776    let underflow = x1.simd_lt(VM_SMALLEST_NORMAL);
1777    let mask = overflow | underflow;
1778    if !mask.any() {
1779      res
1780    } else {
1781      let is_zero = self.is_zero_or_subnormal();
1782      let res = underflow.blend(Self::nan_log(), res);
1783      // Note: is_zero_or_subnormal() lumps subnormals (exponent==0) with zero.
1784      // Both get -Inf here. True subnormal inputs (~1.4e-45..1.175e-38) should
1785      // produce a finite negative result, but are vanishingly rare in
1786      // practice.
1787      let res = is_zero.blend(-Self::infinity(), res);
1788      let res = overflow.blend(self, res);
1789      // This must come *after* overflow.blend to overwrite ln(-∞) = -∞ to NaN
1790      let res = (!self.is_finite() & self.is_sign_negative())
1791        .blend(Self::nan_log(), res);
1792      res
1793    }
1794  }
1795
1796  /// Calculate `ln(1 + self)` for each lane.
1797  /// Accurate even for very small values.
1798  #[inline]
1799  #[must_use]
1800  pub fn ln_1p(self) -> Self {
1801    // Based on the identity ln(1+x) = x·ln(1+x)/((1+x)-1), i.e. x·ln(u)/(u-1)
1802    // where u = 1+x. From MUSL libc (Rich Felker et al., https://musl.libc.org) src/math/log1pf.c
1803    // and fdlibm (Sun Microsystems, https://www.netlib.org/fdlibm/) s_log1p.c.
1804    // When 1+x rounds to 1 exactly (subnormal x), return x directly.
1805    // When 1+x overflows (+inf), return ln(u) without correction.
1806    // Mathematically exact: compensates for the rounding loss in 1+x without
1807    // needing a series threshold.
1808    let u = self + Self::ONE;
1809    let eq = u.simd_eq(Self::ONE);
1810    let ln_u = Self::ln(u);
1811    let correction = self * (ln_u / (u - Self::ONE));
1812    let result = eq.blend(self, correction);
1813    let over = u.is_inf();
1814    over.blend(ln_u, result)
1815  }
1816
1817  #[inline]
1818  #[must_use]
1819  pub fn log2(self) -> Self {
1820    Self::ln(self) * Self::LOG2_E
1821  }
1822  #[inline]
1823  #[must_use]
1824  pub fn log10(self) -> Self {
1825    Self::ln(self) * Self::LOG10_E
1826  }
1827
1828  #[inline]
1829  #[must_use]
1830  pub fn pow_f32x8(self, y: Self) -> Self {
1831    const_f32_as_f32x8!(ln2f_hi, 0.693359375);
1832    const_f32_as_f32x8!(ln2f_lo, -2.12194440e-4);
1833    const_f32_as_f32x8!(P0logf, 3.3333331174E-1);
1834    const_f32_as_f32x8!(P1logf, -2.4999993993E-1);
1835    const_f32_as_f32x8!(P2logf, 2.0000714765E-1);
1836    const_f32_as_f32x8!(P3logf, -1.6668057665E-1);
1837    const_f32_as_f32x8!(P4logf, 1.4249322787E-1);
1838    const_f32_as_f32x8!(P5logf, -1.2420140846E-1);
1839    const_f32_as_f32x8!(P6logf, 1.1676998740E-1);
1840    const_f32_as_f32x8!(P7logf, -1.1514610310E-1);
1841    const_f32_as_f32x8!(P8logf, 7.0376836292E-2);
1842
1843    const_f32_as_f32x8!(p2expf, 1.0 / 2.0); // coefficients for Taylor expansion of exp
1844    const_f32_as_f32x8!(p3expf, 1.0 / 6.0);
1845    const_f32_as_f32x8!(p4expf, 1.0 / 24.0);
1846    const_f32_as_f32x8!(p5expf, 1.0 / 120.0);
1847    const_f32_as_f32x8!(p6expf, 1.0 / 720.0);
1848    const_f32_as_f32x8!(p7expf, 1.0 / 5040.0);
1849
1850    let x1 = self.abs();
1851    let x = x1.fraction_2();
1852    let mask = x.simd_gt(f32x8::SQRT_2 * f32x8::HALF);
1853    let x = (!mask).blend(x + x, x);
1854
1855    let x = x - f32x8::ONE;
1856    let x2 = x * x;
1857    let lg1 = polynomial_8!(
1858      x, P0logf, P1logf, P2logf, P3logf, P4logf, P5logf, P6logf, P7logf, P8logf
1859    );
1860    let lg1 = lg1 * x2 * x;
1861
1862    let ef = x1.exponent();
1863    let ef = mask.blend(ef + f32x8::ONE, ef);
1864    let e1 = (ef * y).round();
1865    let yr = ef.mul_sub(y, e1);
1866
1867    let lg = f32x8::HALF.mul_neg_add(x2, x) + lg1;
1868    let x2_err = (f32x8::HALF * x).mul_sub(x, f32x8::HALF * x2);
1869    let lg_err = f32x8::HALF.mul_add(x2, lg - x) - lg1;
1870
1871    let e2 = (lg * y * f32x8::LOG2_E).round();
1872    let v = lg.mul_sub(y, e2 * ln2f_hi);
1873    let v = e2.mul_neg_add(ln2f_lo, v);
1874    let v = v - (lg_err + x2_err).mul_sub(y, yr * f32x8::LN_2);
1875
1876    let x = v;
1877    let e3 = (x * f32x8::LOG2_E).round();
1878    let x = e3.mul_neg_add(f32x8::LN_2, x);
1879    let x2 = x * x;
1880    let z = x2.mul_add(
1881      polynomial_5!(x, p2expf, p3expf, p4expf, p5expf, p6expf, p7expf),
1882      x + f32x8::ONE,
1883    );
1884
1885    let ee = e1 + e2 + e3;
1886    let ei = cast::<_, i32x8>(ee.round_int());
1887    let ej = cast::<_, i32x8>(ei + (cast::<_, i32x8>(z) >> 23));
1888
1889    let overflow = cast::<_, f32x8>(ej.simd_gt(i32x8::splat(0x0FF)))
1890      | (ee.simd_gt(f32x8::splat(300.0)));
1891    let underflow = cast::<_, f32x8>(ej.simd_lt(i32x8::splat(0x000)))
1892      | (ee.simd_lt(f32x8::splat(-300.0)));
1893
1894    // Add exponent by integer addition
1895    let z = cast::<_, f32x8>(cast::<_, i32x8>(z) + (ei << 23));
1896    // Check for overflow/underflow
1897    let z = underflow.blend(f32x8::ZERO, z);
1898    let z = overflow.blend(Self::infinity(), z);
1899
1900    // Check for self == 0
1901    let x_zero = self.is_zero_or_subnormal();
1902    let z = x_zero.blend(
1903      y.simd_lt(f32x8::ZERO).blend(
1904        Self::infinity(),
1905        y.simd_eq(f32x8::ZERO).blend(f32x8::ONE, f32x8::ZERO),
1906      ),
1907      z,
1908    );
1909
1910    let x_sign = self.is_sign_negative();
1911    let z = if x_sign.any() {
1912      // Y into an integer
1913      let yi = y.simd_eq(y.round());
1914
1915      // Is y odd?
1916      let y_odd = cast::<_, i32x8>(y.round_int() << 31).round_float();
1917
1918      let z1 =
1919        yi.blend(z | y_odd, self.simd_eq(Self::ZERO).blend(z, Self::nan_pow()));
1920
1921      x_sign.blend(z1, z)
1922    } else {
1923      z
1924    };
1925
1926    let x_finite = self.is_finite();
1927    let y_finite = y.is_finite();
1928    let e_finite = ee.is_finite();
1929    if (x_finite & y_finite & (e_finite | x_zero)).all() {
1930      return z;
1931    }
1932
1933    (self.is_nan() | y.is_nan()).blend(self + y, z)
1934  }
1935  #[inline]
1936  pub fn powf(self, y: f32) -> Self {
1937    Self::pow_f32x8(self, f32x8::splat(y))
1938  }
1939
1940  /// Transpose matrix of 8x8 `f32` matrix. Currently only accelerated on AVX.
1941  #[must_use]
1942  #[inline]
1943  pub fn transpose(data: [f32x8; 8]) -> [f32x8; 8] {
1944    pick! {
1945      if #[cfg(target_feature="avx")] {
1946        let a0 = unpack_lo_m256(data[0].avx, data[1].avx);
1947        let a1 = unpack_hi_m256(data[0].avx, data[1].avx);
1948        let a2 = unpack_lo_m256(data[2].avx, data[3].avx);
1949        let a3 = unpack_hi_m256(data[2].avx, data[3].avx);
1950        let a4 = unpack_lo_m256(data[4].avx, data[5].avx);
1951        let a5 = unpack_hi_m256(data[4].avx, data[5].avx);
1952        let a6 = unpack_lo_m256(data[6].avx, data[7].avx);
1953        let a7 = unpack_hi_m256(data[6].avx, data[7].avx);
1954
1955        pub const fn mm_shuffle(z: i32, y: i32, x: i32, w: i32) -> i32 {
1956          (z << 6) | (y << 4) | (x << 2) | w
1957        }
1958
1959        const SHUFF_LO : i32 = mm_shuffle(1,0,1,0);
1960        const SHUFF_HI : i32 = mm_shuffle(3,2,3,2);
1961
1962        // possible todo: intel performance manual suggests alternative with blend to avoid port 5 pressure
1963        // (since blend runs on a different port than shuffle)
1964        let b0 = shuffle_m256::<SHUFF_LO>(a0,a2);
1965        let b1 = shuffle_m256::<SHUFF_HI>(a0,a2);
1966        let b2 = shuffle_m256::<SHUFF_LO>(a1,a3);
1967        let b3 = shuffle_m256::<SHUFF_HI>(a1,a3);
1968        let b4 = shuffle_m256::<SHUFF_LO>(a4,a6);
1969        let b5 = shuffle_m256::<SHUFF_HI>(a4,a6);
1970        let b6 = shuffle_m256::<SHUFF_LO>(a5,a7);
1971        let b7 = shuffle_m256::<SHUFF_HI>(a5,a7);
1972
1973        [
1974          f32x8 { avx: permute2z_m256::<0x20>(b0, b4) },
1975          f32x8 { avx: permute2z_m256::<0x20>(b1, b5) },
1976          f32x8 { avx: permute2z_m256::<0x20>(b2, b6) },
1977          f32x8 { avx: permute2z_m256::<0x20>(b3, b7) },
1978          f32x8 { avx: permute2z_m256::<0x31>(b0, b4) },
1979          f32x8 { avx: permute2z_m256::<0x31>(b1, b5) },
1980          f32x8 { avx: permute2z_m256::<0x31>(b2, b6) },
1981          f32x8 { avx: permute2z_m256::<0x31>(b3, b7) }
1982        ]
1983      } else {
1984        // possible todo: not sure that 128bit SIMD gives us a a lot of speedup here
1985
1986        #[inline(always)]
1987        fn transpose_column(data: &[f32x8; 8], index: usize) -> f32x8 {
1988          f32x8::new([
1989            data[0].as_array()[index],
1990            data[1].as_array()[index],
1991            data[2].as_array()[index],
1992            data[3].as_array()[index],
1993            data[4].as_array()[index],
1994            data[5].as_array()[index],
1995            data[6].as_array()[index],
1996            data[7].as_array()[index],
1997          ])
1998        }
1999
2000        [
2001          transpose_column(&data, 0),
2002          transpose_column(&data, 1),
2003          transpose_column(&data, 2),
2004          transpose_column(&data, 3),
2005          transpose_column(&data, 4),
2006          transpose_column(&data, 5),
2007          transpose_column(&data, 6),
2008          transpose_column(&data, 7),
2009        ]
2010      }
2011    }
2012  }
2013
2014  #[inline]
2015  pub fn to_array(self) -> [f32; 8] {
2016    cast(self)
2017  }
2018
2019  #[inline]
2020  pub fn as_array(&self) -> &[f32; 8] {
2021    cast_ref(self)
2022  }
2023
2024  #[inline]
2025  pub fn as_mut_array(&mut self) -> &mut [f32; 8] {
2026    cast_mut(self)
2027  }
2028
2029  #[inline]
2030  pub fn from_i32x8(v: i32x8) -> Self {
2031    pick! {
2032      if #[cfg(target_feature="avx2")] {
2033        Self { avx: convert_to_m256_from_i32_m256i(v.avx2) }
2034      } else {
2035        Self::new([
2036            v.as_array()[0] as f32,
2037            v.as_array()[1] as f32,
2038            v.as_array()[2] as f32,
2039            v.as_array()[3] as f32,
2040            v.as_array()[4] as f32,
2041            v.as_array()[5] as f32,
2042            v.as_array()[6] as f32,
2043            v.as_array()[7] as f32,
2044          ])
2045      }
2046    }
2047  }
2048
2049  /// Returns true for each element if its sign bit is set.
2050  ///
2051  /// If the sign bit is set, the result has all bits set, not just the sign
2052  /// bit. This has been renamed to [`is_sign_negative`].
2053  ///
2054  /// [`is_sign_negative`]: Self::is_sign_negative
2055  #[inline]
2056  #[must_use]
2057  #[deprecated(since = "1.4.0", note = "renamed to `is_sign_negative`")]
2058  pub fn sign_bit(self) -> Self {
2059    self.is_sign_negative()
2060  }
2061}
2062
2063impl Not for f32x8 {
2064  type Output = Self;
2065  #[inline]
2066  fn not(self) -> Self {
2067    pick! {
2068      if #[cfg(target_feature="avx")] {
2069        Self { avx: self.avx.not()  }
2070      } else {
2071        Self {
2072          a : self.a.not(),
2073          b : self.b.not(),
2074        }
2075      }
2076    }
2077  }
2078}