Skip to main content

wide/
f32x16_.rs

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