Skip to main content

wide/
f32x16_.rs

1use super::*;
2
3pick! {
4  if #[cfg(target_feature="avx512f")] {
5    /// A SIMD vector with 16 elements of type [`f32`].
6    ///
7    /// See the [crate level documentation] for more information about SIMD
8    /// vectors.
9    ///
10    /// [crate level documentation]: crate
11    #[derive(Default, Clone, Copy, PartialEq)]
12    #[repr(C, align(64))]
13    pub struct f32x16 { pub(crate) avx512: m512 }
14  } else {
15    /// A SIMD vector with 16 elements of type [`f32`].
16    ///
17    /// See the [crate level documentation] for more information about SIMD
18    /// vectors.
19    ///
20    /// [crate level documentation]: crate
21    #[derive(Default, Clone, Copy, PartialEq)]
22    #[repr(C, align(64))]
23    pub struct f32x16 { pub(crate) a : f32x8, pub(crate) b : f32x8 }
24  }
25}
26
27macro_rules! const_f32_as_f32x16 {
28  ($i:ident, $f:expr) => {
29    #[allow(non_upper_case_globals)]
30    pub const $i: f32x16 = f32x16::new([$f; 16]);
31  };
32}
33
34impl_simd_float! {
35  unsafe {
36    T = f32,
37    N = 16,
38    Simd = f32x16,
39    IntSimd = i32x16,
40    UintT = u32,
41    UintSimd = u32x16,
42    optional_type_x86_inner { X86Inner = __m512 },
43    optional_type_arm_inner {},
44    optional_type_wasm_inner {},
45  }
46  old_powf_simd_fn_name = pow_f32x16,
47
48  #[inline]
49  fn neg(self) -> Self::Output {
50    pick! {
51      if #[cfg(target_feature="avx512f")] {
52        Self { avx512: bitxor_m512(self.avx512, Self::splat(-0.0).avx512) }
53      } else {
54        Self {
55          a : self.a.neg(),
56          b : self.b.neg(),
57        }
58      }
59    }
60  }
61
62  #[inline]
63  fn not(self) -> Self::Output {
64    pick! {
65      if #[cfg(target_feature="avx512f")] {
66        Self { avx512: bitxor_m512(self.avx512, set_splat_m512(f32::from_bits(u32::MAX))) }
67      } else {
68        Self {
69          a : self.a.not(),
70          b : self.b.not(),
71        }
72      }
73    }
74  }
75
76  #[inline]
77  fn add(self, rhs: Self) -> Self::Output {
78    pick! {
79      if #[cfg(target_feature="avx512f")] {
80        Self { avx512: add_m512(self.avx512, rhs.avx512) }
81      } else {
82        Self {
83          a : self.a.add(rhs.a),
84          b : self.b.add(rhs.b),
85        }
86      }
87    }
88  }
89
90  #[inline]
91  fn sub(self, rhs: Self) -> Self::Output {
92    pick! {
93      if #[cfg(target_feature="avx512f")] {
94        Self { avx512: sub_m512(self.avx512, rhs.avx512) }
95      } else {
96        Self {
97          a : self.a.sub(rhs.a),
98          b : self.b.sub(rhs.b),
99        }
100      }
101    }
102  }
103
104  #[inline]
105  fn mul(self, rhs: Self) -> Self::Output {
106    pick! {
107      if #[cfg(target_feature="avx512f")] {
108        Self { avx512: mul_m512(self.avx512, rhs.avx512) }
109      } else {
110        Self { a: self.a.mul(rhs.a), b: self.b.mul(rhs.b) }
111      }
112    }
113  }
114
115  #[inline]
116  fn div(self, rhs: Self) -> Self::Output {
117    pick! {
118      if #[cfg(target_feature="avx512f")] {
119        Self { avx512: div_m512(self.avx512, rhs.avx512) }
120      } else {
121        Self { a: self.a.div(rhs.a), b: self.b.div(rhs.b) }
122      }
123    }
124  }
125
126  #[inline]
127  fn rem(self, rhs: Self) -> Self::Output {
128    Self::new([
129      self.to_array()[0] % rhs.to_array()[0],
130      self.to_array()[1] % rhs.to_array()[1],
131      self.to_array()[2] % rhs.to_array()[2],
132      self.to_array()[3] % rhs.to_array()[3],
133      self.to_array()[4] % rhs.to_array()[4],
134      self.to_array()[5] % rhs.to_array()[5],
135      self.to_array()[6] % rhs.to_array()[6],
136      self.to_array()[7] % rhs.to_array()[7],
137      self.to_array()[8] % rhs.to_array()[8],
138      self.to_array()[9] % rhs.to_array()[9],
139      self.to_array()[10] % rhs.to_array()[10],
140      self.to_array()[11] % rhs.to_array()[11],
141      self.to_array()[12] % rhs.to_array()[12],
142      self.to_array()[13] % rhs.to_array()[13],
143      self.to_array()[14] % rhs.to_array()[14],
144      self.to_array()[15] % rhs.to_array()[15],
145    ])
146  }
147
148  #[inline]
149  fn bitand(self, rhs: Self) -> Self::Output {
150    pick! {
151      if #[cfg(target_feature="avx512f")] {
152        Self { avx512: bitand_m512(self.avx512, rhs.avx512) }
153      } else {
154        Self {
155          a : self.a.bitand(rhs.a),
156          b : self.b.bitand(rhs.b),
157        }
158      }
159    }
160  }
161
162  #[inline]
163  fn bitor(self, rhs: Self) -> Self::Output {
164    pick! {
165    if #[cfg(target_feature="avx512f")] {
166        Self { avx512: bitor_m512(self.avx512, rhs.avx512) }
167      } else {
168        Self {
169          a : self.a.bitor(rhs.a),
170          b : self.b.bitor(rhs.b),
171        }
172      }
173    }
174  }
175
176  #[inline]
177  fn bitxor(self, rhs: Self) -> Self::Output {
178    pick! {
179      if #[cfg(target_feature="avx512f")] {
180        Self { avx512: bitxor_m512(self.avx512, rhs.avx512) }
181      } else {
182        Self {
183          a : self.a.bitxor(rhs.a),
184          b : self.b.bitxor(rhs.b),
185        }
186      }
187    }
188  }
189
190  #[inline]
191  fn simd_eq(self, rhs: Self) -> Self::Output {
192    pick! {
193      if #[cfg(target_feature="avx512f")] {
194        Self { avx512: cmp_op_mask_m512::<{cmp_op!(EqualOrdered)}>(self.avx512, rhs.avx512) }
195      } else {
196        Self {
197          a : self.a.simd_eq(rhs.a),
198          b : self.b.simd_eq(rhs.b),
199        }
200      }
201    }
202  }
203
204  #[inline]
205  fn simd_ne(self, rhs: Self) -> Self::Output {
206    pick! {
207      if #[cfg(target_feature="avx512f")] {
208        Self { avx512: cmp_op_mask_m512::<{cmp_op!(NotEqualUnordered)}>(self.avx512, rhs.avx512) }
209      } else {
210        Self {
211          a : self.a.simd_ne(rhs.a),
212          b : self.b.simd_ne(rhs.b),
213        }
214      }
215    }
216  }
217
218  #[inline]
219  fn simd_lt(self, rhs: Self) -> Self::Output {
220    pick! {
221      if #[cfg(target_feature="avx512f")] {
222        Self { avx512: cmp_op_mask_m512::<{cmp_op!(LessThanOrdered)}>(self.avx512, rhs.avx512) }
223      } else {
224        Self {
225          a : self.a.simd_lt(rhs.a),
226          b : self.b.simd_lt(rhs.b),
227        }
228      }
229    }
230  }
231
232  #[inline]
233  fn simd_gt(self, rhs: Self) -> Self::Output {
234    pick! {
235      if #[cfg(target_feature="avx512f")] {
236        Self { avx512: cmp_op_mask_m512::<{cmp_op!(GreaterThanOrdered)}>(self.avx512, rhs.avx512) }
237      } else {
238        Self {
239          a : self.a.simd_gt(rhs.a),
240          b : self.b.simd_gt(rhs.b),
241        }
242      }
243    }
244  }
245
246  #[inline]
247  fn simd_le(self, rhs: Self) -> Self::Output {
248    pick! {
249      if #[cfg(target_feature="avx512f")] {
250        Self { avx512: cmp_op_mask_m512::<{cmp_op!(LessEqualOrdered)}>(self.avx512, rhs.avx512) }
251      } else {
252        Self {
253          a : self.a.simd_le(rhs.a),
254          b : self.b.simd_le(rhs.b),
255        }
256      }
257    }
258  }
259
260  #[inline]
261  fn simd_ge(self, rhs: Self) -> Self::Output {
262    pick! {
263      if #[cfg(target_feature="avx512f")] {
264        Self { avx512: cmp_op_mask_m512::<{cmp_op!(GreaterEqualOrdered)}>(self.avx512, rhs.avx512) }
265      } else {
266        Self {
267          a : self.a.simd_ge(rhs.a),
268          b : self.b.simd_ge(rhs.b),
269        }
270      }
271    }
272  }
273
274  #[inline]
275  pub fn reduce_add(self) -> f32 {
276    pick! {
277      if #[cfg(target_feature="avx512f")]{
278        reduce_add_m512(self.avx512)
279      } else {
280        self.a.reduce_add() + self.b.reduce_add()
281      }
282    }
283  }
284
285  #[inline]
286  pub fn reduce_mul(self) -> f32 {
287    pick! {
288      if #[cfg(target_feature="avx512f")] {
289        // TODO: Add `reduce_mul_m512` to `safe_arch` then make this function
290        // safe.
291        #[cfg(target_arch = "x86")]
292        use core::arch::x86::_mm512_reduce_mul_ps;
293        #[cfg(target_arch = "x86_64")]
294        use core::arch::x86_64::_mm512_reduce_mul_ps;
295
296        unsafe { _mm512_reduce_mul_ps(self.avx512.0) }
297      } else {
298        self.a.reduce_mul() * self.b.reduce_mul()
299      }
300    }
301  }
302
303  #[inline]
304  pub fn bitselect(self, if_one: Self, if_zero: Self) -> Self {
305    pick! {
306      if #[cfg(target_feature="avx512f")] {
307        Self {
308          avx512: bitor_m512(
309            bitand_m512(if_one.avx512, self.avx512),
310            bitandnot_m512(self.avx512, if_zero.avx512),
311          ),
312        }
313      } else {
314        Self {
315          a: self.a.bitselect(if_one.a, if_zero.a),
316          b: self.b.bitselect(if_one.b, if_zero.b),
317        }
318      }
319    }
320  }
321
322  #[inline]
323  fn select(self, if_true: Self, if_false: Self) -> Self {
324    pick! {
325      if #[cfg(target_feature="avx512f")] {
326        Self { avx512: blend_varying_m512(if_false.avx512, if_true.avx512, movepi32_mask_m512(self.avx512)) }
327      } else {
328        Self {
329          a : self.a.select(if_true.a, if_false.a),
330          b : self.b.select(if_true.b, if_false.b),
331        }
332      }
333    }
334  }
335
336  #[inline]
337  pub fn to_bitmask(self) -> u32 {
338    pick! {
339      if #[cfg(target_feature="avx512f")] {
340        movepi32_mask_m512(self.avx512) as u32
341      } else {
342        (self.b.to_bitmask() << 8) | self.a.to_bitmask()
343      }
344    }
345  }
346
347  #[inline]
348  pub fn any(self) -> bool {
349    pick! {
350      if #[cfg(target_feature="avx512f")] {
351        movepi32_mask_m512(self.avx512) != 0
352      } else {
353        self.a.any() || self.b.any()
354      }
355    }
356  }
357
358  #[inline]
359  pub fn all(self) -> bool {
360    pick! {
361      if #[cfg(target_feature="avx512f")] {
362        movepi32_mask_m512(self.avx512) == !0_u16
363      } else {
364        self.a.all() && self.b.all()
365      }
366    }
367  }
368
369  ///
370  /// Currently this function is never accelerated.
371  #[inline]
372  pub fn transpose(data: [f32x16; 16]) -> [f32x16; 16] {
373    // TODO: Add `_mm512_unpackhi_ps` to `safe_arch`, looks like it is missing,
374    // then try adding an optimized `avx512f` implementation.
375
376    #[inline(always)]
377    fn transpose_column(data: &[f32x16; 16], index: usize) -> f32x16 {
378      f32x16::new([
379        data[0].as_array()[index],
380        data[1].as_array()[index],
381        data[2].as_array()[index],
382        data[3].as_array()[index],
383        data[4].as_array()[index],
384        data[5].as_array()[index],
385        data[6].as_array()[index],
386        data[7].as_array()[index],
387        data[8].as_array()[index],
388        data[9].as_array()[index],
389        data[10].as_array()[index],
390        data[11].as_array()[index],
391        data[12].as_array()[index],
392        data[13].as_array()[index],
393        data[14].as_array()[index],
394        data[15].as_array()[index],
395      ])
396    }
397
398    [
399      transpose_column(&data, 0),
400      transpose_column(&data, 1),
401      transpose_column(&data, 2),
402      transpose_column(&data, 3),
403      transpose_column(&data, 4),
404      transpose_column(&data, 5),
405      transpose_column(&data, 6),
406      transpose_column(&data, 7),
407      transpose_column(&data, 8),
408      transpose_column(&data, 9),
409      transpose_column(&data, 10),
410      transpose_column(&data, 11),
411      transpose_column(&data, 12),
412      transpose_column(&data, 13),
413      transpose_column(&data, 14),
414      transpose_column(&data, 15),
415    ]
416  }
417
418  #[inline]
419  pub fn is_nan(self) -> Self {
420    pick! {
421      if #[cfg(target_feature = "avx512f")] {
422        Self { avx512: cmp_op_mask_m512::<{cmp_op!(Unordered)}>(self.avx512, self.avx512) }
423      } else {
424        Self {
425          a: self.a.is_nan(),
426          b: self.b.is_nan(),
427        }
428      }
429    }
430  }
431
432  #[inline]
433  pub fn is_inf(self) -> Self {
434    let shifted_inf = u32x16::from(0xFF000000);
435    let u: u32x16 = cast(self);
436    let shift_u = u << 1_u64;
437    let out = (shift_u).simd_eq(shifted_inf);
438    cast(out)
439  }
440
441  #[inline]
442  pub fn is_finite(self) -> Self {
443    let shifted_exp_mask = u32x16::splat(0xFF000000);
444    let u: u32x16 = cast(self);
445    let shift_u = u << 1_u32;
446    let out = !(shift_u & shifted_exp_mask).simd_eq(shifted_exp_mask);
447    cast(out)
448  }
449
450  #[inline]
451  pub fn is_sign_positive(self) -> Self {
452    const SIGN_MASK: u32x16 = u32x16::splat((-0.0_f32).to_bits());
453
454    let bits = cast::<f32x16, u32x16>(self);
455    let sign = bits & SIGN_MASK;
456    let result = sign.simd_eq(u32x16::ZERO);
457    cast::<u32x16, f32x16>(result)
458  }
459
460  #[inline]
461  pub fn is_sign_negative(self) -> Self {
462    const SIGN_MASK: u32x16 = u32x16::splat((-0.0_f32).to_bits());
463
464    let bits = cast::<f32x16, u32x16>(self);
465    let sign = bits & SIGN_MASK;
466    let result = sign.simd_eq(SIGN_MASK);
467    cast::<u32x16, f32x16>(result)
468  }
469
470  #[inline]
471  pub fn recip(self) -> Self {
472    pick! {
473      if #[cfg(target_feature="avx512f")] {
474        // TODO: Add `_mm512_rcp14_ps` to `safe_arch`, looks like it is missing,
475        // then consider updating this implementation if the relative error is
476        // acceptable.
477        1.0 / self
478      } else {
479        Self {
480          a : self.a.recip(),
481          b : self.b.recip(),
482        }
483      }
484    }
485  }
486
487  #[inline]
488  pub fn recip_sqrt(self) -> Self {
489    pick! {
490      if #[cfg(target_feature="avx512f")] {
491        // TODO: Add `_mm512_rsqrt14_ps` to `safe_arch`, looks like it is
492        // missing, then consider updating this implementation if the relative
493        // error is acceptable.
494        self.sqrt().recip()
495      } else {
496        Self {
497          a : self.a.recip_sqrt(),
498          b : self.b.recip_sqrt(),
499        }
500      }
501    }
502  }
503
504  #[inline]
505  pub fn max(self, rhs: Self) -> Self {
506    pick! {
507      if #[cfg(target_feature="avx512f")] {
508        // max_m512 seems to do rhs < self ? self : rhs. So if there's any NaN
509        // involved, it chooses rhs, so we need to specifically check rhs for
510        // NaN.
511        rhs.is_nan().select(self, Self { avx512: max_m512(self.avx512, rhs.avx512) })
512      } else {
513        Self {
514          a: self.a.max(rhs.a),
515          b: self.b.max(rhs.b),
516        }
517      }
518    }
519  }
520
521  #[inline]
522  pub fn fast_max(self, rhs: Self) -> Self {
523    pick! {
524      if #[cfg(target_feature="avx512f")] {
525        Self { avx512: max_m512(self.avx512, rhs.avx512) }
526      } else {
527        Self {
528          a: self.a.fast_max(rhs.a),
529          b: self.b.fast_max(rhs.b),
530        }
531      }
532    }
533  }
534
535  #[inline]
536  pub fn min(self, rhs: Self) -> Self {
537    pick! {
538      if #[cfg(target_feature="avx512f")] {
539        // min_m512 seems to do rhs > self ? self : rhs. So if there's any NaN
540        // involved, it chooses rhs, so we need to specifically check rhs for
541        // NaN.
542        rhs.is_nan().select(self, Self { avx512: min_m512(self.avx512, rhs.avx512) })
543      } else {
544        Self {
545          a: self.a.min(rhs.a),
546          b: self.b.min(rhs.b),
547        }
548      }
549    }
550  }
551
552  #[inline]
553  pub fn fast_min(self, rhs: Self) -> Self {
554    pick! {
555      if #[cfg(target_feature="avx512f")] {
556        Self { avx512: min_m512(self.avx512, rhs.avx512) }
557      } else {
558        Self {
559          a: self.a.fast_min(rhs.a),
560          b: self.b.fast_min(rhs.b),
561        }
562      }
563    }
564  }
565
566  #[inline]
567  pub fn clamp(self, min: Self, max: Self) -> Self {
568    pick! {
569      if #[cfg(target_feature="avx512f")] {
570        // This works since all bits set is NaN.
571        self.fast_clamp(min, max) | min.is_nan() | max.is_nan()
572      } else {
573        // Some targets have better implementations than the above one.
574        Self {
575          a: self.a.clamp(min.a, max.a),
576          b: self.b.clamp(min.b, max.b),
577        }
578      }
579    }
580  }
581
582  #[inline]
583  pub fn fast_clamp(self, min: Self, max: Self) -> Self {
584    pick! {
585      if #[cfg(target_feature="avx512f")] {
586        // For both `min_m512` and `max_m512` if any input is NaN, `rhs` gets
587        // chosen. For `self` to be chosen, `self` must be the second argument.
588        Self { avx512: max_m512(min.avx512, min_m512(max.avx512, self.avx512)) }
589      } else {
590        Self {
591          a: self.a.fast_clamp(min.a, max.a),
592          b: self.b.fast_clamp(min.b, max.b),
593        }
594      }
595    }
596  }
597
598  #[inline]
599  pub fn abs(self) -> Self {
600    pick! {
601      if #[cfg(target_feature="avx512f")] {
602        let non_sign_bits = f32x16::from(f32::from_bits(i32::MAX as u32));
603        self & non_sign_bits
604      } else {
605        Self {
606          a : self.a.abs(),
607          b : self.b.abs(),
608        }
609      }
610    }
611  }
612
613  #[inline]
614  pub fn floor(self) -> Self {
615    pick! {
616      if #[cfg(target_feature="avx512f")] {
617        Self { avx512: round_m512::<{round_op!(NegInf)}>(self.avx512) }
618      } else {
619        Self {
620          a : self.a.floor(),
621          b : self.b.floor(),
622        }
623      }
624    }
625  }
626
627  #[inline]
628  pub fn ceil(self) -> Self {
629    pick! {
630      if #[cfg(target_feature="avx512f")] {
631        Self { avx512: round_m512::<{round_op!(PosInf)}>(self.avx512) }
632      } else {
633        Self {
634          a : self.a.ceil(),
635          b : self.b.ceil(),
636        }
637      }
638    }
639  }
640
641  #[inline]
642  pub fn round(self) -> Self {
643    pick! {
644      if #[cfg(target_feature="avx512f")] {
645        const_f32_as_f32x16!(HALF_NEXT_DOWN, 0.5_f32.next_down());
646        const_f32_as_f32x16!(BOUNDS_LIMIT, 8388608.0);
647
648        let self_abs = self.abs();
649
650        let adjusted_self = self_abs + Self::HALF;
651        let result_abs = Self { avx512: round_m512::<{round_op!(Zero)}>(adjusted_self.avx512) };
652        // The addition breaks for `0.5.next_down()` which incorrectly rounds to
653        // `1.0`. This resets the result back to `0.0`.
654        let result_abs = result_abs & self_abs.simd_ne(HALF_NEXT_DOWN);
655
656        // Large value, infinity and NaN need special handling.
657        let bounds_mask: Self = cast(cmp_op_mask_i32_m512i::<{cmp_int_op!(Lt)}>(
658          cast(self_abs),
659          cast(BOUNDS_LIMIT),
660        ));
661
662        // `abs` keeps the original sign.
663        bounds_mask.abs().bitselect(result_abs, self)
664      } else {
665        Self {
666          a: self.a.round(),
667          b: self.b.round(),
668        }
669      }
670    }
671  }
672
673  #[inline]
674  pub fn round_int(self) -> i32x16 {
675    pick! {
676      if #[cfg(target_feature="avx512f")] {
677        // Based on: https://github.com/v8/v8/blob/210987a552a2bf2a854b0baa9588a5959ff3979d/src/codegen/shared-ia32-x64/macro-assembler-shared-ia32-x64.h#L489-L504
678        let non_nan_mask = self.simd_eq(self);
679        let non_nan = self & non_nan_mask;
680        let flip_to_max: i32x16 = cast(self.simd_ge(Self::splat(2147483648.0)));
681        let cast: i32x16 = cast(convert_to_i32_m512i_from_m512(non_nan.avx512));
682        flip_to_max ^ cast
683      } else {
684        i32x16 {
685          a: self.a.round_int(),
686          b: self.b.round_int(),
687        }
688      }
689    }
690  }
691
692  #[inline]
693  pub fn fast_round_int(self) -> i32x16 {
694    pick! {
695      if #[cfg(target_feature="avx512f")] {
696        cast(convert_to_i32_m512i_from_m512(self.avx512))
697      } else {
698        i32x16 {
699          a: self.a.fast_round_int(),
700          b: self.b.fast_round_int(),
701        }
702      }
703    }
704  }
705
706  #[inline]
707  pub fn round_ties_even(self) -> Self {
708    pick! {
709      if #[cfg(target_feature="avx512f")] {
710        Self { avx512: round_m512::<{round_op!(Nearest)}>(self.avx512) }
711      } else {
712        Self {
713          a: self.a.round_ties_even(),
714          b: self.b.round_ties_even(),
715        }
716      }
717    }
718  }
719
720  #[inline]
721  pub fn trunc(self) -> Self {
722    pick! {
723      if #[cfg(target_feature="avx512f")] {
724        Self { avx512: round_m512::<{round_op!(Zero)}>(self.avx512) }
725      } else {
726        Self {
727          a: self.a.trunc(),
728          b: self.b.trunc(),
729        }
730      }
731    }
732  }
733
734  #[inline]
735  pub fn trunc_int(self) -> i32x16 {
736    pick! {
737        if #[cfg(target_feature="avx512f")] {
738        // Based on: https://github.com/v8/v8/blob/210987a552a2bf2a854b0baa9588a5959ff3979d/src/codegen/shared-ia32-x64/macro-assembler-shared-ia32-x64.h#L489-L504
739        let non_nan_mask = self.simd_eq(self);
740        let non_nan = self & non_nan_mask;
741        let flip_to_max: i32x16 = cast(self.simd_ge(Self::splat(2147483648.0)));
742        let cast: i32x16 = cast(convert_truncate_m512_i32_m512i(non_nan.avx512));
743        flip_to_max ^ cast
744      } else {
745        cast([
746          self.a.trunc_int(),
747          self.b.trunc_int(),
748        ])
749      }
750    }
751  }
752
753  #[inline]
754  pub fn fast_trunc_int(self) -> i32x16 {
755    pick! {
756      if #[cfg(all(target_feature="avx512f"))] {
757        cast(convert_truncate_m512_i32_m512i(self.avx512))
758      } else {
759        cast([
760          self.a.fast_trunc_int(),
761          self.b.fast_trunc_int(),
762        ])
763      }
764    }
765  }
766
767  ///
768  /// # Platform-specific behavior (may change in the future)
769  ///
770  /// - On `x86`/`x86_64` with AVX-512F+FMA: Uses 512-bit `vfmadd` (single
771  ///   rounding, best accuracy)
772  /// - On `x86`/`x86_64` with AVX-512F only: Uses `(self * m) + a` (two
773  ///   roundings)
774  /// - Other platforms: Delegates to [`f32x8`] (inherits its FMA behavior)
775  #[inline]
776  pub fn mul_add(self, a: Self, b: Self) -> Self {
777    pick! {
778      if #[cfg(all(target_feature="avx512f",target_feature="fma"))] {
779        Self { avx512: fused_mul_add_m512(self.avx512, a.avx512, b.avx512) }
780      } else if #[cfg(target_feature="avx512f")] {
781        // still want to use 512 bit ops
782        (self * a) + b
783      } else {
784        Self {
785          a: self.a.mul_add(a.a, b.a),
786          b: self.b.mul_add(a.b, b.b),
787        }
788      }
789    }
790  }
791
792  ///
793  /// # Platform-specific behavior (may change in the future)
794  ///
795  /// - On `x86`/`x86_64` with AVX-512F+FMA: Uses 512-bit `vfmsub` (single
796  ///   rounding, best accuracy)
797  /// - On `x86`/`x86_64` with AVX-512F only: Uses `(self * m) - s` (two
798  ///   roundings)
799  /// - Other platforms: Delegates to [`f32x8`] (inherits its FMA behavior)
800  #[inline]
801  pub fn mul_sub(self, a: Self, b: Self) -> Self {
802    pick! {
803      if #[cfg(all(target_feature="avx512f",target_feature="fma"))] {
804        Self { avx512: fused_mul_sub_m512(self.avx512, a.avx512, b.avx512) }
805      } else if #[cfg(target_feature="avx512f")] {
806        // still want to use 512 bit ops
807        (self * a) - b
808      } else {
809        Self {
810          a: self.a.mul_sub(a.a, b.a),
811          b: self.b.mul_sub(a.b, b.b),
812        }
813      }
814    }
815  }
816
817  ///
818  /// # Platform-specific behavior (may change in the future)
819  ///
820  /// - On `x86`/`x86_64` with AVX-512F+FMA: Uses 512-bit `vfnmadd` (single
821  ///   rounding, best accuracy)
822  /// - On `x86`/`x86_64` with AVX-512F only: Uses `a - (self * m)` (two
823  ///   roundings)
824  /// - Other platforms: Delegates to [`f32x8`] (inherits its FMA behavior)
825  #[inline]
826  pub fn mul_neg_add(self, a: Self, b: Self) -> Self {
827    pick! {
828      if #[cfg(all(target_feature="avx512f",target_feature="fma"))] {
829        Self { avx512: fused_mul_neg_add_m512(self.avx512, a.avx512, b.avx512) }
830      } else if #[cfg(target_feature="avx512f")] {
831        // still want to use 512 bit ops
832        b - (self * a)
833      } else {
834        Self {
835          a: self.a.mul_neg_add(a.a, b.a),
836          b: self.b.mul_neg_add(a.b, b.b),
837        }
838      }
839    }
840  }
841
842  ///
843  /// # Platform-specific behavior (may change in the future)
844  ///
845  /// - On `x86`/`x86_64` with AVX-512F+FMA: Uses 512-bit `vfnmsub` (single
846  ///   rounding, best accuracy)
847  /// - On `x86`/`x86_64` with AVX-512F only: Uses `-(self * m) - s` (two
848  ///   roundings)
849  /// - Other platforms: Delegates to [`f32x8`] (inherits its FMA behavior)
850  #[inline]
851  pub fn mul_neg_sub(self, a: Self, b: Self) -> Self {
852    pick! {
853      if #[cfg(all(target_feature="avx512f",target_feature="fma"))] {
854        Self { avx512: fused_mul_neg_sub_m512(self.avx512, a.avx512, b.avx512) }
855      } else if #[cfg(target_feature="avx512f")] {
856        // still want to use 512 bit ops
857        -(self * a) - b
858      } else {
859        Self {
860          a: self.a.mul_neg_sub(a.a, b.a),
861          b: self.b.mul_neg_sub(a.b, b.b),
862        }
863      }
864    }
865  }
866
867  #[inline]
868  pub fn powf_simd(self, n: Self) -> Self {
869    const_f32_as_f32x16!(ln2f_hi, 0.693359375);
870    const_f32_as_f32x16!(ln2f_lo, -2.12194440e-4);
871    const_f32_as_f32x16!(P0logf, 3.3333331174E-1);
872    const_f32_as_f32x16!(P1logf, -2.4999993993E-1);
873    const_f32_as_f32x16!(P2logf, 2.0000714765E-1);
874    const_f32_as_f32x16!(P3logf, -1.6668057665E-1);
875    const_f32_as_f32x16!(P4logf, 1.4249322787E-1);
876    const_f32_as_f32x16!(P5logf, -1.2420140846E-1);
877    const_f32_as_f32x16!(P6logf, 1.1676998740E-1);
878    const_f32_as_f32x16!(P7logf, -1.1514610310E-1);
879    const_f32_as_f32x16!(P8logf, 7.0376836292E-2);
880
881    const_f32_as_f32x16!(p2expf, 1.0 / 2.0); // coefficients for Taylor expansion of exp
882    const_f32_as_f32x16!(p3expf, 1.0 / 6.0);
883    const_f32_as_f32x16!(p4expf, 1.0 / 24.0);
884    const_f32_as_f32x16!(p5expf, 1.0 / 120.0);
885    const_f32_as_f32x16!(p6expf, 1.0 / 720.0);
886    const_f32_as_f32x16!(p7expf, 1.0 / 5040.0);
887
888    let x1 = self.abs();
889    let x = x1.fraction_2();
890    let mask = x.simd_gt(f32x16::SQRT_2 * f32x16::HALF);
891    let x = (!mask).select(x + x, x);
892
893    let x = x - f32x16::ONE;
894    let x2 = x * x;
895    let lg1 = polynomial_8!(
896      x, P0logf, P1logf, P2logf, P3logf, P4logf, P5logf, P6logf, P7logf, P8logf
897    );
898    let lg1 = lg1 * x2 * x;
899
900    let ef = x1.exponent();
901    let ef = mask.select(ef + f32x16::ONE, ef);
902    let e1 = (ef * n).round_ties_even();
903    let yr = ef.mul_sub(n, e1);
904
905    let lg = f32x16::HALF.mul_neg_add(x2, x) + lg1;
906    let x2_err = (f32x16::HALF * x).mul_sub(x, f32x16::HALF * x2);
907    let lg_err = f32x16::HALF.mul_add(x2, lg - x) - lg1;
908
909    let e2 = (lg * n * f32x16::LOG2_E).round_ties_even();
910    let v = lg.mul_sub(n, e2 * ln2f_hi);
911    let v = e2.mul_neg_add(ln2f_lo, v);
912    let v = v - (lg_err + x2_err).mul_sub(n, yr * f32x16::LN_2);
913
914    let x = v;
915    let e3 = (x * f32x16::LOG2_E).round_ties_even();
916    let x = e3.mul_neg_add(f32x16::LN_2, x);
917    let x2 = x * x;
918    let z = x2.mul_add(
919      polynomial_5!(x, p2expf, p3expf, p4expf, p5expf, p6expf, p7expf),
920      x + f32x16::ONE,
921    );
922
923    let ee = e1 + e2 + e3;
924    let ei = cast::<_, i32x16>(ee.round_int());
925    let ej = cast::<_, i32x16>(ei + (cast::<_, i32x16>(z) >> 23));
926
927    let overflow = cast::<_, f32x16>(ej.simd_gt(i32x16::splat(0x0FF)))
928      | (ee.simd_gt(f32x16::splat(300.0)));
929    let underflow = cast::<_, f32x16>(ej.simd_lt(i32x16::splat(0x000)))
930      | (ee.simd_lt(f32x16::splat(-300.0)));
931
932    // Add exponent by integer addition
933    let z = cast::<_, f32x16>(cast::<_, i32x16>(z) + (ei << 23));
934    // Check for overflow/underflow
935    let z = underflow.select(f32x16::ZERO, z);
936    let z = overflow.select(Self::infinity(), z);
937
938    // Check for self == 0
939    let x_zero = self.is_zero_or_subnormal();
940    let z = x_zero.select(
941      n.simd_lt(f32x16::ZERO).select(
942        Self::infinity(),
943        n.simd_eq(f32x16::ZERO).select(f32x16::ONE, f32x16::ZERO),
944      ),
945      z,
946    );
947
948    let x_sign = self.is_sign_negative();
949    let z = if x_sign.any() {
950      // Y into an integer
951      let yi = n.simd_eq(n.round_ties_even());
952
953      // Is y odd? If yes flip the sign of the result.
954      let y_odd = cast::<i32x16, f32x16>(n.round_int() << 31);
955
956      let z1 = yi
957        .select(z | y_odd, self.simd_eq(Self::ZERO).select(z, Self::nan_pow()));
958
959      x_sign.select(z1, z)
960    } else {
961      z
962    };
963
964    let x_finite = self.is_finite();
965    let y_finite = n.is_finite();
966    let e_finite = ee.is_finite();
967    if (x_finite & y_finite & (e_finite | x_zero)).all() {
968      return z;
969    }
970
971    (self.is_nan() | n.is_nan()).select(self + n, z)
972  }
973
974  #[inline]
975  pub fn sqrt(self) -> Self {
976    pick! {
977      if #[cfg(target_feature="avx512f")] {
978        Self { avx512: sqrt_m512(self.avx512) }
979      } else {
980        Self {
981          a : self.a.sqrt(),
982          b : self.b.sqrt(),
983        }
984      }
985    }
986  }
987
988  /// Calculate the exponent of a packed `f32x16`
989  #[inline]
990  pub fn exp(self) -> Self {
991    const_f32_as_f32x16!(P0, 1.0 / 2.0);
992    const_f32_as_f32x16!(P1, 1.0 / 6.0);
993    const_f32_as_f32x16!(P2, 1.0 / 24.0);
994    const_f32_as_f32x16!(P3, 1.0 / 120.0);
995    const_f32_as_f32x16!(P4, 1.0 / 720.0);
996    const_f32_as_f32x16!(P5, 1.0 / 5040.0);
997    // LN2D_HI/LO: double-double decomposition of ln(2) for exp range reduction,
998    // following the approach from fdlibm's e_exp.c (Sun Microsystems,
999    // https://www.netlib.org/fdlibm/). The f32 split uses f32-precision constants
1000    // (0.693359375, -2.12194440e-4) summing to ln(2) with single-precision
1001    // accuracy; the f64 variants use a full f64 double-double
1002    // decomposition.
1003    const_f32_as_f32x16!(LN2D_HI, 0.693359375);
1004    const_f32_as_f32x16!(LN2D_LO, -2.12194440e-4);
1005    // max_x = ln(f32::MAX) ≈ 88.7229, max_r = 127 (IEEE max normal exponent)
1006    // min_x = -149.5 ln(2) ≈ -103.63: min r for vm_pow2n subnormal
1007    let max_x = f32x16::from(88.723);
1008    let min_x = f32x16::from(-103.63);
1009    // x < min_x: e^x underflows to 0 -- skip the entire pipeline
1010    let finite = self.is_finite();
1011    let neg_underflow = self.simd_lt(min_x) & finite;
1012    if neg_underflow.all() {
1013      return Self::ZERO;
1014    }
1015    let max_r = f32x16::from(127.0);
1016    let r = (self * Self::LOG2_E).round_ties_even();
1017    let big = r.simd_gt(max_r);
1018    let r_safe = big.select(max_r, r);
1019    let excess = r - max_r;
1020    let excess = big.select(excess, Self::ZERO);
1021    let scale = Self::vm_pow2n(excess);
1022    let x = r.mul_neg_add(LN2D_HI, self);
1023    let x = r.mul_neg_add(LN2D_LO, x);
1024    let z = polynomial_5!(x, P0, P1, P2, P3, P4, P5);
1025    let x2 = x * x;
1026    let z = z.mul_add(x2, x);
1027    let n2 = Self::vm_pow2n(r_safe);
1028    let z = (z + Self::ONE) * scale * n2;
1029    let nan_mask = self.is_nan();
1030    let mut result = nan_mask.select(Self::nan_pow(), z);
1031    let pos_overflow = self.simd_gt(max_x) & finite;
1032    result = pos_overflow.select(Self::infinity(), result);
1033    result = neg_underflow.select(Self::ZERO, result);
1034    let pos_inf = !finite & !self.is_sign_negative() & !nan_mask;
1035    result = pos_inf.select(Self::infinity(), result);
1036    let neg_inf = !finite & self.is_sign_negative() & !nan_mask;
1037    result = neg_inf.select(Self::ZERO, result);
1038    result
1039  }
1040
1041  #[inline]
1042  pub fn exp2(self) -> Self {
1043    const_f32_as_f32x16!(P2, 1.0 / 2.0);
1044    const_f32_as_f32x16!(P3, 1.0 / 6.0);
1045    const_f32_as_f32x16!(P4, 1.0 / 24.0);
1046    const_f32_as_f32x16!(P5, 1.0 / 120.0);
1047    const_f32_as_f32x16!(P6, 1.0 / 720.0);
1048    const_f32_as_f32x16!(P7, 1.0 / 5040.0);
1049
1050    // max_x = log2(f32::MAX) ≈ 127.99999
1051    // min_x = log2(f32::MIN_POSITIVE) - 23 ≈ -126 - 23 = -149
1052    let max_x = f32x16::from(127.99999);
1053    let min_x = f32x16::from(-149.5);
1054    let finite = self.is_finite();
1055    let neg_underflow = self.simd_lt(min_x) & finite;
1056    if neg_underflow.all() {
1057      return Self::ZERO;
1058    }
1059
1060    let round = self.round_ties_even();
1061    let max_r = f32x16::from(127.0);
1062    let big = round.simd_gt(max_r);
1063    let r_safe = big.select(max_r, round);
1064    let excess = round - max_r;
1065    let excess = big.select(excess, Self::ZERO);
1066    let scale = Self::vm_pow2n(excess);
1067
1068    let fract = (self - round) * Self::LN_2;
1069    let fract_partial_exp2 = polynomial_5!(fract, P2, P3, P4, P5, P6, P7);
1070    let fract2 = fract * fract;
1071    let fract_exp2 = fract_partial_exp2.mul_add(fract2, fract) + Self::ONE;
1072
1073    let n2 = Self::vm_pow2n(r_safe);
1074    let result = fract_exp2 * scale * n2;
1075
1076    let nan_mask = self.is_nan();
1077    let mut result = nan_mask.select(Self::nan_pow(), result);
1078    let pos_overflow = self.simd_gt(max_x) & finite;
1079    result = pos_overflow.select(Self::infinity(), result);
1080    result = neg_underflow.select(Self::ZERO, result);
1081    let pos_inf = !finite & !self.is_sign_negative() & !nan_mask;
1082    result = pos_inf.select(Self::infinity(), result);
1083    let neg_inf = !finite & self.is_sign_negative() & !nan_mask;
1084    result = neg_inf.select(Self::ZERO, result);
1085    result
1086  }
1087
1088  #[inline]
1089  pub fn ln(self) -> Self {
1090    const_f32_as_f32x16!(HALF, 0.5);
1091    const_f32_as_f32x16!(P0, 3.3333331174E-1);
1092    const_f32_as_f32x16!(P1, -2.4999993993E-1);
1093    const_f32_as_f32x16!(P2, 2.0000714765E-1);
1094    const_f32_as_f32x16!(P3, -1.6668057665E-1);
1095    const_f32_as_f32x16!(P4, 1.4249322787E-1);
1096    const_f32_as_f32x16!(P5, -1.2420140846E-1);
1097    const_f32_as_f32x16!(P6, 1.1676998740E-1);
1098    const_f32_as_f32x16!(P7, -1.1514610310E-1);
1099    const_f32_as_f32x16!(P8, 7.0376836292E-2);
1100    const_f32_as_f32x16!(LN2F_HI, 0.693359375);
1101    const_f32_as_f32x16!(LN2F_LO, -2.12194440e-4);
1102    const_f32_as_f32x16!(VM_SMALLEST_NORMAL, 1.17549435E-38);
1103
1104    let x1 = self;
1105    let x = Self::fraction_2(x1);
1106    let e = Self::exponent(x1);
1107    let mask = x.simd_gt(Self::SQRT_2 * HALF);
1108    let x = (!mask).select(x + x, x);
1109    let fe = mask.select(e + Self::ONE, e);
1110    let x = x - Self::ONE;
1111    let res = polynomial_8!(x, P0, P1, P2, P3, P4, P5, P6, P7, P8);
1112    let x2 = x * x;
1113    let res = x2 * x * res;
1114    let res = fe.mul_add(LN2F_LO, res);
1115    let res = res + x2.mul_neg_add(HALF, x);
1116    let res = fe.mul_add(LN2F_HI, res);
1117    let overflow = !self.is_finite();
1118    let underflow = x1.simd_lt(VM_SMALLEST_NORMAL);
1119    let mask = overflow | underflow;
1120    if !mask.any() {
1121      res
1122    } else {
1123      let is_zero = self.is_zero_or_subnormal();
1124      let res = underflow.select(Self::nan_log(), res);
1125      // Note: is_zero_or_subnormal() lumps subnormals (exponent==0) with zero.
1126      // Both get -Inf here. True subnormal inputs (~1.4e-45..1.175e-38) should
1127      // produce a finite negative result, but are vanishingly rare in
1128      // practice.
1129      let res = is_zero.select(-Self::infinity(), res);
1130      let res = overflow.select(self, res);
1131      // This must come *after* overflow.blend to overwrite ln(-∞) = -∞ to NaN
1132      let res = (!self.is_finite() & self.is_sign_negative())
1133        .select(Self::nan_log(), res);
1134      res
1135    }
1136  }
1137
1138  #[inline]
1139  pub fn cbrt(self) -> Self {
1140    let a = self.abs();
1141    let zero = a.simd_eq(Self::ZERO);
1142    if zero.all() {
1143      return self; // preserves -0.0
1144    }
1145    let inf = a.is_inf();
1146    let nan = self.is_nan();
1147
1148    let tiny = a.simd_lt(Self::from(f32::MIN_POSITIVE));
1149    let a_work = tiny.select(a * Self::from(16777216.0), a);
1150
1151    let e = Self::exponent(a_work) + Self::ONE;
1152    let d = Self::fraction_2(a_work);
1153
1154    // C0..C5 from SLEEF's minimax polynomial for 1/cbrt(d) on [0.5, 1.0)
1155    // Naoki Shibata et al., "SLEEF: A Portable Vectorized Library of C99
1156    // Mathematical Functions", https://sleef.org / https://github.com/shibatch/sleef
1157    // Licensed under the Boost Software License 1.0.
1158    // These are the f32-precision coefficients; our f64 variants use the f64
1159    // set.
1160    const_f32_as_f32x16!(C0, 2.2241257);
1161    const_f32_as_f32x16!(C1, -3.8095417);
1162    const_f32_as_f32x16!(C2, 5.8982625);
1163    const_f32_as_f32x16!(C3, -5.532182);
1164    const_f32_as_f32x16!(C4, 2.8208892);
1165    const_f32_as_f32x16!(C5, -0.60156447);
1166    let mut x = polynomial_5!(d, C0, C1, C2, C3, C4, C5);
1167
1168    let x2 = x * x;
1169    let x4 = x2 * x2;
1170    x = x - d.mul_add(x4, -x) * Self::from(1.0 / 3.0);
1171    // cbrt(d) = d * x² with refinement
1172    let mut y = (d * x) * x;
1173    let yx = y * x;
1174    let t = Self::from(2.0 / 3.0);
1175    y = y - t * y * (yx - Self::ONE);
1176
1177    // Scale by 2^(e/3)
1178    let three = Self::from(3.0);
1179    let two = Self::from(2.0);
1180    let neg = e.simd_lt(Self::ZERO);
1181    let e_adj = neg.select(e - two, e);
1182    let k = (e_adj / three).trunc();
1183    let r = e - three * k;
1184    const_f32_as_f32x16!(CBRT2, 1.259921);
1185    const_f32_as_f32x16!(CBRT4, 1.587401);
1186    y = r.simd_eq(Self::ONE).select(y * CBRT2, y);
1187    y = r.simd_eq(two).select(y * CBRT4, y);
1188    y *= Self::vm_pow2n(k);
1189    y = tiny.select(y / Self::from(256.0_f32), y);
1190
1191    let result = y.flip_signs(self);
1192    let result = nan.select(self, result);
1193    let result = zero.select(self, result);
1194    let result = inf.select(self, result);
1195    result
1196  }
1197
1198  #[inline]
1199  pub fn asin(self) -> Self {
1200    // Based on the Agner Fog "vector class library":
1201    // https://github.com/vectorclass/version2/blob/master/vectormath_trig.h
1202    const_f32_as_f32x16!(P4asinf, 4.2163199048E-2);
1203    const_f32_as_f32x16!(P3asinf, 2.4181311049E-2);
1204    const_f32_as_f32x16!(P2asinf, 4.5470025998E-2);
1205    const_f32_as_f32x16!(P1asinf, 7.4953002686E-2);
1206    const_f32_as_f32x16!(P0asinf, 1.6666752422E-1);
1207
1208    let xa = self.abs();
1209    let big = xa.simd_ge(f32x16::splat(0.5));
1210
1211    let x1 = f32x16::splat(0.5) * (f32x16::ONE - xa);
1212    let x2 = xa * xa;
1213    let x3 = big.select(x1, x2);
1214
1215    let xb = x1.sqrt();
1216
1217    let x4 = big.select(xb, xa);
1218
1219    let z = polynomial_4!(x3, P0asinf, P1asinf, P2asinf, P3asinf, P4asinf);
1220    let z = z.mul_add(x3 * x4, x4);
1221
1222    let z1 = z + z;
1223
1224    // asin
1225    let z3 = f32x16::FRAC_PI_2 - z1;
1226    let asin = big.select(z3, z);
1227    let asin = asin.flip_signs(self);
1228
1229    asin
1230  }
1231
1232  #[inline]
1233  pub fn acos(self) -> Self {
1234    // Based on the Agner Fog "vector class library":
1235    // https://github.com/vectorclass/version2/blob/master/vectormath_trig.h
1236    const_f32_as_f32x16!(P4asinf, 4.2163199048E-2);
1237    const_f32_as_f32x16!(P3asinf, 2.4181311049E-2);
1238    const_f32_as_f32x16!(P2asinf, 4.5470025998E-2);
1239    const_f32_as_f32x16!(P1asinf, 7.4953002686E-2);
1240    const_f32_as_f32x16!(P0asinf, 1.6666752422E-1);
1241
1242    let xa = self.abs();
1243    let big = xa.simd_ge(f32x16::splat(0.5));
1244
1245    let x1 = f32x16::splat(0.5) * (f32x16::ONE - xa);
1246    let x2 = xa * xa;
1247    let x3 = big.select(x1, x2);
1248
1249    let xb = x1.sqrt();
1250
1251    let x4 = big.select(xb, xa);
1252
1253    let z = polynomial_4!(x3, P0asinf, P1asinf, P2asinf, P3asinf, P4asinf);
1254    let z = z.mul_add(x3 * x4, x4);
1255
1256    let z1 = z + z;
1257
1258    // acos
1259    let z3 = self.simd_lt(f32x16::ZERO).select(f32x16::PI - z1, z1);
1260    let z4 = f32x16::FRAC_PI_2 - z.flip_signs(self);
1261    let acos = big.select(z3, z4);
1262
1263    acos
1264  }
1265
1266  #[inline]
1267  pub fn atan(self) -> Self {
1268    // Based on the Agner Fog "vector class library":
1269    // https://github.com/vectorclass/version2/blob/master/vectormath_trig.h
1270    const_f32_as_f32x16!(P3atanf, 8.05374449538E-2);
1271    const_f32_as_f32x16!(P2atanf, -1.38776856032E-1);
1272    const_f32_as_f32x16!(P1atanf, 1.99777106478E-1);
1273    const_f32_as_f32x16!(P0atanf, -3.33329491539E-1);
1274
1275    let t = self.abs();
1276
1277    // small:  z = t / 1.0;
1278    // medium: z = (t-1.0) / (t+1.0);
1279    // big:    z = -1.0 / t;
1280    let notsmal = t.simd_ge(Self::SQRT_2 - Self::ONE);
1281    let notbig = t.simd_le(Self::SQRT_2 + Self::ONE);
1282
1283    let mut s = notbig.select(Self::FRAC_PI_4, Self::FRAC_PI_2);
1284    s = notsmal & s;
1285
1286    let mut a = notbig & t;
1287    a = notsmal.select(a - Self::ONE, a);
1288    let mut b = notbig & Self::ONE;
1289    b = notsmal.select(b + t, b);
1290    let z = a / b;
1291
1292    let zz = z * z;
1293
1294    // Taylor expansion
1295    let mut re = polynomial_3!(zz, P0atanf, P1atanf, P2atanf, P3atanf);
1296    re = re.mul_add(zz * z, z) + s;
1297
1298    // get sign bit
1299    re = (self.is_sign_negative()).select(-re, re);
1300
1301    re
1302  }
1303
1304  #[inline]
1305  pub fn atan2(self, x: Self) -> Self {
1306    // Based on the Agner Fog "vector class library":
1307    // https://github.com/vectorclass/version2/blob/master/vectormath_trig.h
1308    const_f32_as_f32x16!(P3atanf, 8.05374449538E-2);
1309    const_f32_as_f32x16!(P2atanf, -1.38776856032E-1);
1310    const_f32_as_f32x16!(P1atanf, 1.99777106478E-1);
1311    const_f32_as_f32x16!(P0atanf, -3.33329491539E-1);
1312
1313    let y = self;
1314
1315    // move in first octant
1316    let x1 = x.abs();
1317    let y1 = y.abs();
1318    let swapxy = y1.simd_gt(x1);
1319    // swap x and y if y1 > x1
1320    let mut x2 = swapxy.select(y1, x1);
1321    let mut y2 = swapxy.select(x1, y1);
1322
1323    // check for special case: x and y are both +/- INF
1324    let both_infinite = x.is_inf() & y.is_inf();
1325    if both_infinite.any() {
1326      let minus_one = -Self::ONE;
1327      x2 = both_infinite.select(x2 & minus_one, x2);
1328      y2 = both_infinite.select(y2 & minus_one, y2);
1329    }
1330
1331    // x = y = 0 will produce NAN. No problem, fixed below
1332    let t = y2 / x2;
1333
1334    // small:  z = t / 1.0;
1335    // medium: z = (t-1.0) / (t+1.0);
1336    let notsmal = t.simd_ge(Self::SQRT_2 - Self::ONE);
1337
1338    let a = notsmal.select(t - Self::ONE, t);
1339    let b = notsmal.select(t + Self::ONE, Self::ONE);
1340    let s = notsmal & Self::FRAC_PI_4;
1341    let z = a / b;
1342
1343    let zz = z * z;
1344
1345    // Taylor expansion
1346    let mut re = polynomial_3!(zz, P0atanf, P1atanf, P2atanf, P3atanf);
1347    re = re.mul_add(zz * z, z) + s;
1348
1349    // move back in place
1350    re = swapxy.select(Self::FRAC_PI_2 - re, re);
1351    re = ((x | y).simd_eq(Self::ZERO)).select(Self::ZERO, re);
1352    re = (x.is_sign_negative()).select(Self::PI - re, re);
1353
1354    // get sign bit
1355    re = (y.is_sign_negative()).select(-re, re);
1356
1357    re
1358  }
1359
1360  #[inline]
1361  pub fn sin_cos(self) -> (Self, Self) {
1362    // Based on the Agner Fog "vector class library":
1363    // https://github.com/vectorclass/version2/blob/master/vectormath_trig.h
1364
1365    const_f32_as_f32x16!(DP1F, 0.78515625_f32 * 2.0);
1366    const_f32_as_f32x16!(DP2F, 2.4187564849853515625E-4_f32 * 2.0);
1367    const_f32_as_f32x16!(DP3F, 3.77489497744594108E-8_f32 * 2.0);
1368
1369    const_f32_as_f32x16!(P0sinf, -1.6666654611E-1);
1370    const_f32_as_f32x16!(P1sinf, 8.3321608736E-3);
1371    const_f32_as_f32x16!(P2sinf, -1.9515295891E-4);
1372
1373    const_f32_as_f32x16!(P0cosf, 4.166664568298827E-2);
1374    const_f32_as_f32x16!(P1cosf, -1.388731625493765E-3);
1375    const_f32_as_f32x16!(P2cosf, 2.443315711809948E-5);
1376
1377    const_f32_as_f32x16!(TWO_OVER_PI, 2.0 / core::f32::consts::PI);
1378
1379    let xa = self.abs();
1380
1381    // Find quadrant
1382    let y = (xa * TWO_OVER_PI).round_ties_even();
1383    let q: i32x16 = y.round_int();
1384
1385    let x = y.mul_neg_add(DP3F, y.mul_neg_add(DP2F, y.mul_neg_add(DP1F, xa)));
1386
1387    let x2 = x * x;
1388    let mut s = polynomial_2!(x2, P0sinf, P1sinf, P2sinf) * (x * x2) + x;
1389    let mut c = polynomial_2!(x2, P0cosf, P1cosf, P2cosf) * (x2 * x2)
1390      + f32x16::from(0.5).mul_neg_add(x2, f32x16::from(1.0));
1391
1392    let swap = !(q & i32x16::from(1)).simd_eq(i32x16::from(0));
1393
1394    let mut overflow: f32x16 = cast(q.simd_gt(i32x16::from(0x2000000)));
1395    overflow &= xa.is_finite();
1396    s = overflow.select(f32x16::from(0.0), s);
1397    c = overflow.select(f32x16::from(1.0), c);
1398
1399    // calc sin
1400    let mut sin1 = cast::<_, f32x16>(swap).select(c, s);
1401    let sign_sin: i32x16 = (q << 30) ^ cast::<_, i32x16>(self);
1402    sin1 = sin1.flip_signs(cast(sign_sin));
1403
1404    // calc cos
1405    let mut cos1 = cast::<_, f32x16>(swap).select(s, c);
1406    let sign_cos: i32x16 = ((q + i32x16::from(1)) & i32x16::from(2)) << 30;
1407    cos1 ^= cast::<_, f32x16>(sign_cos);
1408
1409    // IEEE 754: sin/cos(±∞) = NaN, sin/cos(NaN) = NaN
1410    let finite = self.is_finite();
1411    let nan = Self::splat(f32::NAN);
1412    let sin_final = finite.select(sin1, nan);
1413    let cos_final = finite.select(cos1, nan);
1414
1415    (sin_final, cos_final)
1416  }
1417
1418  #[inline]
1419  pub fn asin_acos(self) -> (Self, Self) {
1420    // Based on the Agner Fog "vector class library":
1421    // https://github.com/vectorclass/version2/blob/master/vectormath_trig.h
1422    const_f32_as_f32x16!(P4asinf, 4.2163199048E-2);
1423    const_f32_as_f32x16!(P3asinf, 2.4181311049E-2);
1424    const_f32_as_f32x16!(P2asinf, 4.5470025998E-2);
1425    const_f32_as_f32x16!(P1asinf, 7.4953002686E-2);
1426    const_f32_as_f32x16!(P0asinf, 1.6666752422E-1);
1427
1428    let xa = self.abs();
1429    let big = xa.simd_ge(f32x16::splat(0.5));
1430
1431    let x1 = f32x16::splat(0.5) * (f32x16::ONE - xa);
1432    let x2 = xa * xa;
1433    let x3 = big.select(x1, x2);
1434
1435    let xb = x1.sqrt();
1436
1437    let x4 = big.select(xb, xa);
1438
1439    let z = polynomial_4!(x3, P0asinf, P1asinf, P2asinf, P3asinf, P4asinf);
1440    let z = z.mul_add(x3 * x4, x4);
1441
1442    let z1 = z + z;
1443
1444    // acos
1445    let z3 = self.simd_lt(f32x16::ZERO).select(f32x16::PI - z1, z1);
1446    let z4 = f32x16::FRAC_PI_2 - z.flip_signs(self);
1447    let acos = big.select(z3, z4);
1448
1449    // asin
1450    let z3 = f32x16::FRAC_PI_2 - z1;
1451    let asin = big.select(z3, z);
1452    let asin = asin.flip_signs(self);
1453
1454    (asin, acos)
1455  }
1456
1457  #[inline]
1458  pub fn exp_m1(self) -> Self {
1459    // x < -17.329: e^x < 2⁻²⁵, exp_m1(x) = -1.0 exactly (mantissa exhaustion)
1460    // IEEE simd_lt returns false for NaN, so NaN lanes can't reach here.
1461    // -inf is < -17.329, and exp_m1(-inf) = -1.0, also correct.
1462    if self.simd_lt(f32x16::from(-17.329)).all() {
1463      return f32x16::from(-1.0);
1464    }
1465    const_f32_as_f32x16!(P0, 1.0 / 2.0);
1466    const_f32_as_f32x16!(P1, 1.0 / 6.0);
1467    const_f32_as_f32x16!(P2, 1.0 / 24.0);
1468    const_f32_as_f32x16!(P3, 1.0 / 120.0);
1469    const_f32_as_f32x16!(P4, 1.0 / 720.0);
1470    const_f32_as_f32x16!(P5, 1.0 / 5040.0);
1471    // LN2D_HI/LO: double-double decomposition of ln(2) for exp range reduction,
1472    // following the approach from fdlibm's e_exp.c (Sun Microsystems,
1473    // https://www.netlib.org/fdlibm/). The f32 split uses f32-precision constants
1474    // (0.693359375, -2.12194440e-4) summing to ln(2) with single-precision
1475    // accuracy; the f64 variants use a full f64 double-double
1476    // decomposition.
1477    const_f32_as_f32x16!(LN2D_HI, 0.693359375);
1478    const_f32_as_f32x16!(LN2D_LO, -2.12194440e-4);
1479    // max_x = ln(f32::MAX) ≈ 88.7229, max_r = 127 (IEEE max normal exponent)
1480    // min_x = -149.5 ln(2) ≈ -103.63: min r for vm_pow2n subnormal
1481    let max_x = f32x16::from(88.723);
1482    let min_x = f32x16::from(-103.63);
1483    let max_r = f32x16::from(127.0);
1484    let r = (self * Self::LOG2_E).round_ties_even();
1485    let big = r.simd_gt(max_r);
1486    let r_safe = big.select(max_r, r);
1487    let excess = r - max_r;
1488    let excess = big.select(excess, Self::ZERO);
1489    let scale = Self::vm_pow2n(excess);
1490    let x = r.mul_neg_add(LN2D_HI, self);
1491    let x = r.mul_neg_add(LN2D_LO, x);
1492    let z = polynomial_5!(x, P0, P1, P2, P3, P4, P5);
1493    let x2 = x * x;
1494    let z = z.mul_add(x2, x);
1495    let n2 = Self::vm_pow2n(r_safe);
1496    let exp_val = (z + Self::ONE) * scale * n2;
1497    let r_is_zero = r.simd_eq(Self::ZERO);
1498    let z = r_is_zero.select(z, exp_val - Self::ONE);
1499    let nan_mask = self.is_nan();
1500    let finite = self.is_finite();
1501    let mut result = nan_mask.select(Self::nan_pow(), z);
1502    let pos_overflow = self.simd_gt(max_x) & finite;
1503    result = pos_overflow.select(Self::infinity(), result);
1504    let neg_underflow = self.simd_lt(min_x) & finite;
1505    result = neg_underflow.select(-Self::ONE, result);
1506    let pos_inf = !finite & !self.is_sign_negative() & !nan_mask;
1507    result = pos_inf.select(Self::infinity(), result);
1508    let neg_inf = !finite & self.is_sign_negative() & !nan_mask;
1509    result = neg_inf.select(-Self::ONE, result);
1510    let is_zero = self.simd_eq(Self::ZERO);
1511    result = is_zero.select(self, result);
1512    result
1513  }
1514
1515  #[inline]
1516  pub fn ln_1p(self) -> Self {
1517    // Based on the identity ln(1+x) = x·ln(1+x)/((1+x)-1), i.e. x·ln(u)/(u-1)
1518    // where u = 1+x. From MUSL libc (Rich Felker et al., https://musl.libc.org) src/math/log1pf.c
1519    // and fdlibm (Sun Microsystems, https://www.netlib.org/fdlibm/) s_log1p.c.
1520    // When 1+x rounds to 1 exactly (subnormal x), return x directly.
1521    // When 1+x overflows (+inf), return ln(u) without correction.
1522    // Mathematically exact: compensates for the rounding loss in 1+x without
1523    // needing a series threshold.
1524    let u = self + Self::ONE;
1525    let eq = u.simd_eq(Self::ONE);
1526    let ln_u = Self::ln(u);
1527    let correction = self * (ln_u / (u - Self::ONE));
1528    let result = eq.select(self, correction);
1529    let over = u.is_inf();
1530    over.select(ln_u, result)
1531  }
1532
1533  #[inline]
1534  pub fn sinh(self) -> Self {
1535    const_f32_as_f32x16!(P0, 1.0);
1536    const_f32_as_f32x16!(P1, 1.0 / 6.0);
1537    const_f32_as_f32x16!(P2, 1.0 / 120.0);
1538    const_f32_as_f32x16!(P3, 1.0 / 5040.0);
1539    let a = self.abs();
1540    // |x| < 0.5: Taylor poly; last truncation term < 1 ULP at x=0.5 for both types
1541    let small = a.simd_lt(f32x16::from(0.5));
1542    let t = a * a;
1543    let poly = a * polynomial_3!(t, P0, P1, P2, P3);
1544    let exp_based = {
1545      let e = a.exp();
1546      (e - Self::ONE / e) * Self::HALF
1547    };
1548    let result = small.select(poly, exp_based);
1549    result.flip_signs(self)
1550  }
1551
1552  #[inline]
1553  pub fn cosh(self) -> Self {
1554    const_f32_as_f32x16!(P0, 1.0);
1555    const_f32_as_f32x16!(P1, 1.0 / 2.0);
1556    const_f32_as_f32x16!(P2, 1.0 / 24.0);
1557    const_f32_as_f32x16!(P3, 1.0 / 720.0);
1558    let a = self.abs();
1559    // |x| < 0.5: Taylor poly; last truncation term < 1 ULP at x=0.5 for both types
1560    let small = a.simd_lt(f32x16::from(0.5));
1561    let t = a * a;
1562    let poly = polynomial_3!(t, P0, P1, P2, P3);
1563    let exp_based = {
1564      let e = a.exp();
1565      (e + Self::ONE / e) * Self::HALF
1566    };
1567    small.select(poly, exp_based)
1568  }
1569
1570  #[inline]
1571  pub fn tanh(self) -> Self {
1572    // |x| < 2e-4: tanh(x) ≈ x, error x³/3 < 16·ULP(x)
1573    // bound: x² < 48·2⁻²³ → x < 2.39e-3; 2e-4 has 10× margin
1574    // |x| > 9.011: tanh(x) = ±1 to f32 precision (e⁻²ˣ < 2⁻²⁴)
1575    let a = self.abs();
1576    let large = a.simd_gt(f32x16::from(9.011));
1577    if large.all() {
1578      return Self::ONE.flip_signs(self);
1579    }
1580    let small = a.simd_lt(f32x16::from(2e-4));
1581    let exp_based = {
1582      let t = (Self::from(-2.0) * a).exp_m1();
1583      let pos = -t / (t + Self::from(2.0));
1584      pos.flip_signs(self)
1585    };
1586    let result = small.select(self, exp_based);
1587    large.select(Self::ONE.flip_signs(self), result)
1588  }
1589}
1590
1591/// The following functionality exists only for [`f32x16`], or only for
1592/// particular types inconsistently.
1593impl f32x16 {
1594  #[inline]
1595  fn vm_pow2n(self) -> Self {
1596    const_f32_as_f32x16!(pow2_23, 8388608.0);
1597    const_f32_as_f32x16!(bias, 127.0);
1598    let a = self + (bias + pow2_23);
1599    let c = cast::<_, i32x16>(a) << 23;
1600    let std_result = cast::<_, f32x16>(c);
1601
1602    let min_exp = f32x16::from(-126.0);
1603    let is_sub = self.simd_lt(min_exp);
1604    if is_sub.any() {
1605      let valid = self.simd_ge(f32x16::from(-149.0));
1606      let shift_f = self + f32x16::from(149.0);
1607      let mut shift_i = shift_f.trunc_int();
1608      shift_i = cast::<_, i32x16>(valid).select(shift_i, i32x16::ZERO);
1609      let mantissa = i32x16::ONE << shift_i;
1610      let sub_result = cast::<_, f32x16>(mantissa);
1611      let sub_result = valid.select(sub_result, f32x16::ZERO);
1612      is_sub.select(sub_result, std_result)
1613    } else {
1614      std_result
1615    }
1616  }
1617
1618  #[inline]
1619  fn exponent(self) -> Self {
1620    const_f32_as_f32x16!(pow2_23, 8388608.0);
1621    const_f32_as_f32x16!(bias, 127.0);
1622    let a = cast::<_, u32x16>(self);
1623    let b = a >> 23;
1624    let c = b | cast::<_, u32x16>(pow2_23);
1625    let d = cast::<_, f32x16>(c);
1626    let e = d - (pow2_23 + bias);
1627    e
1628  }
1629
1630  #[inline]
1631  fn fraction_2(self) -> Self {
1632    let t1 = cast::<_, u32x16>(self);
1633    let t2 = cast::<_, u32x16>(
1634      (t1 & u32x16::from(0x007FFFFF)) | u32x16::from(0x3F000000),
1635    );
1636    cast::<_, f32x16>(t2)
1637  }
1638
1639  #[inline]
1640  fn is_zero_or_subnormal(self) -> Self {
1641    let t = cast::<_, i32x16>(self);
1642    let t = t & i32x16::splat(0x7F800000);
1643    let mask = t.simd_eq(i32x16::splat(0));
1644    cast::<_, f32x16>(mask)
1645  }
1646
1647  #[inline]
1648  fn infinity() -> Self {
1649    cast::<_, f32x16>(i32x16::splat(0x7F800000))
1650  }
1651
1652  #[inline]
1653  fn nan_log() -> Self {
1654    cast::<_, f32x16>(i32x16::splat(0x7FC00000 | 0x101 & 0x003FFFFF))
1655  }
1656
1657  #[inline]
1658  fn nan_pow() -> Self {
1659    cast::<_, f32x16>(i32x16::splat(0x7FC00000 | 0x101 & 0x003FFFFF))
1660  }
1661
1662  /// Converts each element from [`i32`] to [`f32`].
1663  #[inline]
1664  pub fn from_i32x16(v: i32x16) -> Self {
1665    pick! {
1666      if #[cfg(target_feature="avx512f")] {
1667        Self { avx512: convert_to_m512_from_i32_m512i(v.avx512) }
1668      } else {
1669        Self {
1670          a: f32x8::from_i32x8(v.a),
1671          b: f32x8::from_i32x8(v.b),
1672        }
1673      }
1674    }
1675  }
1676
1677  /// Returns a [mask] that checks if each element has a negative sign,
1678  /// including `-0.0`, NaNs with negative sign bit and negative infinity.
1679  ///
1680  /// Note that this function has a misleading name. If the sign bit is set, the
1681  /// result has all bits set, not just the sign bit. This function has been
1682  /// renamed to [`is_sign_negative`].
1683  ///
1684  /// [mask]: crate#masks
1685  /// [`is_sign_negative`]: Self::is_sign_negative
1686  #[inline]
1687  #[must_use]
1688  #[deprecated(since = "1.4.0", note = "renamed to `is_sign_negative`")]
1689  pub fn sign_bit(self) -> Self {
1690    self.is_sign_negative()
1691  }
1692}