core_simd/simd/num/float.rs
1use crate::simd::{
2 Mask, Select, Simd, SimdCast, SimdElement,
3 cmp::{SimdPartialEq, SimdPartialOrd},
4};
5
6/// Operations on SIMD vectors of floats.
7pub impl(self) trait SimdFloat: Copy {
8 /// Mask type used for manipulating this SIMD vector type.
9 type Mask;
10
11 /// Scalar type contained by this SIMD vector type.
12 type Scalar;
13
14 /// Bit representation of this SIMD vector type.
15 type Bits;
16
17 /// A SIMD vector with a different element type.
18 type Cast<T: SimdElement>;
19
20 /// Performs elementwise conversion of this vector's elements to another SIMD-valid type.
21 ///
22 /// This follows the semantics of Rust's `as` conversion for floats (truncating or saturating
23 /// at the limits) for each element.
24 ///
25 /// # Example
26 /// ```
27 /// # #![feature(portable_simd)]
28 /// # #[cfg(feature = "as_crate")] use core_simd::simd;
29 /// # #[cfg(not(feature = "as_crate"))] use core::simd;
30 /// # use simd::prelude::*;
31 /// let floats: Simd<f32, 4> = Simd::from_array([1.9, -4.5, f32::INFINITY, f32::NAN]);
32 /// let ints = floats.cast::<i32>();
33 /// assert_eq!(ints, Simd::from_array([1, -4, i32::MAX, 0]));
34 ///
35 /// // Formally equivalent, but `Simd::cast` can optimize better.
36 /// assert_eq!(ints, Simd::from_array(floats.to_array().map(|x| x as i32)));
37 ///
38 /// // The float conversion does not round-trip.
39 /// let floats_again = ints.cast();
40 /// assert_ne!(floats, floats_again);
41 /// assert_eq!(floats_again, Simd::from_array([1.0, -4.0, 2147483647.0, 0.0]));
42 /// ```
43 #[must_use]
44 fn cast<T: SimdCast>(self) -> Self::Cast<T>;
45
46 /// Rounds toward zero and converts to the same-width integer type, assuming that
47 /// the value is finite and fits in that type.
48 ///
49 /// # Safety
50 /// The value must:
51 ///
52 /// * Not be NaN
53 /// * Not be infinite
54 /// * Be representable in the return type, after truncating off its fractional part
55 ///
56 /// If these requirements are infeasible or costly, consider using the safe function [cast],
57 /// which saturates on conversion.
58 ///
59 /// [cast]: Simd::cast
60 unsafe fn to_int_unchecked<I: SimdCast>(self) -> Self::Cast<I>
61 where
62 Self::Scalar: core::convert::FloatToInt<I>;
63
64 /// Raw transmutation to an unsigned integer vector type with the
65 /// same size and number of elements.
66 #[must_use = "method returns a new vector and does not mutate the original value"]
67 fn to_bits(self) -> Self::Bits;
68
69 /// Raw transmutation from an unsigned integer vector type with the
70 /// same size and number of elements.
71 #[must_use = "method returns a new vector and does not mutate the original value"]
72 fn from_bits(bits: Self::Bits) -> Self;
73
74 /// Produces a vector where every element has the absolute value of the
75 /// equivalently-indexed element in `self`.
76 #[must_use = "method returns a new vector and does not mutate the original value"]
77 fn abs(self) -> Self;
78
79 /// Takes the reciprocal (inverse) of each element, `1/x`.
80 #[must_use = "method returns a new vector and does not mutate the original value"]
81 fn recip(self) -> Self;
82
83 /// Converts each element from radians to degrees.
84 #[must_use = "method returns a new vector and does not mutate the original value"]
85 fn to_degrees(self) -> Self;
86
87 /// Converts each element from degrees to radians.
88 #[must_use = "method returns a new vector and does not mutate the original value"]
89 fn to_radians(self) -> Self;
90
91 /// Returns true for each element if it has a positive sign, including
92 /// `+0.0`, `NaN`s with positive sign bit and positive infinity.
93 #[must_use = "method returns a new mask and does not mutate the original value"]
94 fn is_sign_positive(self) -> Self::Mask;
95
96 /// Returns true for each element if it has a negative sign, including
97 /// `-0.0`, `NaN`s with negative sign bit and negative infinity.
98 #[must_use = "method returns a new mask and does not mutate the original value"]
99 fn is_sign_negative(self) -> Self::Mask;
100
101 /// Returns true for each element if its value is `NaN`.
102 #[must_use = "method returns a new mask and does not mutate the original value"]
103 fn is_nan(self) -> Self::Mask;
104
105 /// Returns true for each element if its value is positive infinity or negative infinity.
106 #[must_use = "method returns a new mask and does not mutate the original value"]
107 fn is_infinite(self) -> Self::Mask;
108
109 /// Returns true for each element if its value is neither infinite nor `NaN`.
110 #[must_use = "method returns a new mask and does not mutate the original value"]
111 fn is_finite(self) -> Self::Mask;
112
113 /// Returns true for each element if its value is subnormal.
114 #[must_use = "method returns a new mask and does not mutate the original value"]
115 fn is_subnormal(self) -> Self::Mask;
116
117 /// Returns true for each element if its value is neither zero, infinite,
118 /// subnormal, nor `NaN`.
119 #[must_use = "method returns a new mask and does not mutate the original value"]
120 fn is_normal(self) -> Self::Mask;
121
122 /// Replaces each element with a number that represents its sign.
123 ///
124 /// * `1.0` if the number is positive, `+0.0`, or `INFINITY`
125 /// * `-1.0` if the number is negative, `-0.0`, or `NEG_INFINITY`
126 /// * `NAN` if the number is `NAN`
127 #[must_use = "method returns a new vector and does not mutate the original value"]
128 fn signum(self) -> Self;
129
130 /// Returns each element with the magnitude of `self` and the sign of `sign`.
131 ///
132 /// For any element containing a `NAN`, a `NAN` with the sign of `sign` is returned.
133 #[must_use = "method returns a new vector and does not mutate the original value"]
134 fn copysign(self, sign: Self) -> Self;
135
136 /// Returns the minimum of each element.
137 ///
138 /// If one of the values is `NAN`, then the other value is returned.
139 #[must_use = "method returns a new vector and does not mutate the original value"]
140 fn simd_min(self, other: Self) -> Self;
141
142 /// Returns the maximum of each element.
143 ///
144 /// If one of the values is `NAN`, then the other value is returned.
145 #[must_use = "method returns a new vector and does not mutate the original value"]
146 fn simd_max(self, other: Self) -> Self;
147
148 /// Restrict each element to a certain interval unless it is NaN.
149 ///
150 /// For each element in `self`, returns the corresponding element in `max` if the element is
151 /// greater than `max`, and the corresponding element in `min` if the element is less
152 /// than `min`. Otherwise returns the element in `self`.
153 #[must_use = "method returns a new vector and does not mutate the original value"]
154 fn simd_clamp(self, min: Self, max: Self) -> Self;
155
156 /// Returns the sum of the elements of the vector.
157 ///
158 /// # Examples
159 ///
160 /// ```
161 /// # #![feature(portable_simd)]
162 /// # #[cfg(feature = "as_crate")] use core_simd::simd;
163 /// # #[cfg(not(feature = "as_crate"))] use core::simd;
164 /// # use simd::prelude::*;
165 /// let v = f32x2::from_array([1., 2.]);
166 /// assert_eq!(v.reduce_sum(), 3.);
167 /// ```
168 fn reduce_sum(self) -> Self::Scalar;
169
170 /// Reducing multiply. Returns the product of the elements of the vector.
171 ///
172 /// # Examples
173 ///
174 /// ```
175 /// # #![feature(portable_simd)]
176 /// # #[cfg(feature = "as_crate")] use core_simd::simd;
177 /// # #[cfg(not(feature = "as_crate"))] use core::simd;
178 /// # use simd::prelude::*;
179 /// let v = f32x2::from_array([3., 4.]);
180 /// assert_eq!(v.reduce_product(), 12.);
181 /// ```
182 fn reduce_product(self) -> Self::Scalar;
183
184 /// Returns the maximum element in the vector.
185 ///
186 /// Returns values based on equality, so a vector containing both `0.` and `-0.` may
187 /// return either.
188 ///
189 /// This function will not return `NaN` unless all elements are `NaN`.
190 ///
191 /// # Examples
192 ///
193 /// ```
194 /// # #![feature(portable_simd)]
195 /// # #[cfg(feature = "as_crate")] use core_simd::simd;
196 /// # #[cfg(not(feature = "as_crate"))] use core::simd;
197 /// # use simd::prelude::*;
198 /// let v = f32x2::from_array([1., 2.]);
199 /// assert_eq!(v.reduce_max(), 2.);
200 ///
201 /// // NaN values are skipped...
202 /// let v = f32x2::from_array([1., f32::NAN]);
203 /// assert_eq!(v.reduce_max(), 1.);
204 ///
205 /// // ...unless all values are NaN
206 /// let v = f32x2::from_array([f32::NAN, f32::NAN]);
207 /// assert!(v.reduce_max().is_nan());
208 /// ```
209 fn reduce_max(self) -> Self::Scalar;
210
211 /// Returns the minimum element in the vector.
212 ///
213 /// Returns values based on equality, so a vector containing both `0.` and `-0.` may
214 /// return either.
215 ///
216 /// This function will not return `NaN` unless all elements are `NaN`.
217 ///
218 /// # Examples
219 ///
220 /// ```
221 /// # #![feature(portable_simd)]
222 /// # #[cfg(feature = "as_crate")] use core_simd::simd;
223 /// # #[cfg(not(feature = "as_crate"))] use core::simd;
224 /// # use simd::prelude::*;
225 /// let v = f32x2::from_array([3., 7.]);
226 /// assert_eq!(v.reduce_min(), 3.);
227 ///
228 /// // NaN values are skipped...
229 /// let v = f32x2::from_array([1., f32::NAN]);
230 /// assert_eq!(v.reduce_min(), 1.);
231 ///
232 /// // ...unless all values are NaN
233 /// let v = f32x2::from_array([f32::NAN, f32::NAN]);
234 /// assert!(v.reduce_min().is_nan());
235 /// ```
236 fn reduce_min(self) -> Self::Scalar;
237}
238
239macro_rules! impl_trait {
240 { $($ty:ty { bits: $bits_ty:ty, mask: $mask_ty:ty }),* } => {
241 $(
242 impl<const N: usize> SimdFloat for Simd<$ty, N>
243 {
244 type Mask = Mask<<$mask_ty as SimdElement>::Mask, N>;
245 type Scalar = $ty;
246 type Bits = Simd<$bits_ty, N>;
247 type Cast<T: SimdElement> = Simd<T, N>;
248
249 #[cfg(not(target_arch = "aarch64"))]
250 #[inline]
251 fn cast<T: SimdCast>(self) -> Self::Cast<T>
252 {
253 // Safety: supported types are guaranteed by SimdCast
254 unsafe { core::intrinsics::simd::simd_as(self) }
255 }
256
257 // workaround for https://github.com/llvm/llvm-project/issues/94694 (fixed in LLVM 20)
258 // tracked in: https://github.com/rust-lang/rust/issues/135982
259 #[cfg(target_arch = "aarch64")]
260 #[inline]
261 fn cast<T: SimdCast>(self) -> Self::Cast<T>
262 {
263 const { assert!(N <= 64) };
264 if N <= 2 || N == 4 || N == 8 || N == 16 || N == 32 || N == 64 {
265 // Safety: supported types are guaranteed by SimdCast
266 unsafe { core::intrinsics::simd::simd_as(self) }
267 } else if N < 4 {
268 let x = self.resize::<4>(Default::default()).cast();
269 x.resize::<N>(x[0])
270 } else if N < 8 {
271 let x = self.resize::<8>(Default::default()).cast();
272 x.resize::<N>(x[0])
273 } else if N < 16 {
274 let x = self.resize::<16>(Default::default()).cast();
275 x.resize::<N>(x[0])
276 } else if N < 32 {
277 let x = self.resize::<32>(Default::default()).cast();
278 x.resize::<N>(x[0])
279 } else {
280 let x = self.resize::<64>(Default::default()).cast();
281 x.resize::<N>(x[0])
282 }
283 }
284
285 #[inline]
286 #[cfg_attr(miri, track_caller)] // even without panics, this helps for Miri backtraces
287 unsafe fn to_int_unchecked<I: SimdCast>(self) -> Self::Cast<I>
288 where
289 Self::Scalar: core::convert::FloatToInt<I>,
290 {
291 // Safety: supported types are guaranteed by SimdCast, the caller is responsible for the extra invariants
292 unsafe { core::intrinsics::simd::simd_cast(self) }
293 }
294
295 #[inline]
296 fn to_bits(self) -> Simd<$bits_ty, N> {
297 assert_eq!(size_of::<Self>(), size_of::<Self::Bits>());
298 // Safety: transmuting between vector types is safe
299 unsafe { core::mem::transmute_copy(&self) }
300 }
301
302 #[inline]
303 fn from_bits(bits: Simd<$bits_ty, N>) -> Self {
304 assert_eq!(size_of::<Self>(), size_of::<Self::Bits>());
305 // Safety: transmuting between vector types is safe
306 unsafe { core::mem::transmute_copy(&bits) }
307 }
308
309 #[inline]
310 fn abs(self) -> Self {
311 // Safety: `self` is a float vector
312 unsafe { core::intrinsics::simd::simd_fabs(self) }
313 }
314
315 #[inline]
316 fn recip(self) -> Self {
317 Self::splat(1.0) / self
318 }
319
320 #[inline]
321 fn to_degrees(self) -> Self {
322 // to_degrees uses a special constant for better precision, so extract that constant
323 self * Self::splat(Self::Scalar::to_degrees(1.))
324 }
325
326 #[inline]
327 fn to_radians(self) -> Self {
328 self * Self::splat(Self::Scalar::to_radians(1.))
329 }
330
331 #[inline]
332 fn is_sign_positive(self) -> Self::Mask {
333 !self.is_sign_negative()
334 }
335
336 #[inline]
337 fn is_sign_negative(self) -> Self::Mask {
338 let sign_bits = self.to_bits() & Simd::splat((!0 >> 1) + 1);
339 sign_bits.simd_gt(Simd::splat(0))
340 }
341
342 #[inline]
343 fn is_nan(self) -> Self::Mask {
344 self.simd_ne(self)
345 }
346
347 #[inline]
348 fn is_infinite(self) -> Self::Mask {
349 self.abs().simd_eq(Self::splat(Self::Scalar::INFINITY))
350 }
351
352 #[inline]
353 fn is_finite(self) -> Self::Mask {
354 self.abs().simd_lt(Self::splat(Self::Scalar::INFINITY))
355 }
356
357 #[inline]
358 fn is_subnormal(self) -> Self::Mask {
359 // On some architectures (e.g. armv7 and some ppc) subnormals are flushed to zero,
360 // so this comparison must be done with integers.
361 let not_zero = self.abs().to_bits().simd_ne(Self::splat(0.0).to_bits());
362 not_zero & (self.to_bits() & Self::splat(Self::Scalar::INFINITY).to_bits()).simd_eq(Simd::splat(0))
363 }
364
365 #[inline]
366 fn is_normal(self) -> Self::Mask {
367 !(self.abs().simd_eq(Self::splat(0.0)) | self.is_nan() | self.is_subnormal() | self.is_infinite())
368 }
369
370 #[inline]
371 fn signum(self) -> Self {
372 self.is_nan().select(Self::splat(Self::Scalar::NAN), Self::splat(1.0).copysign(self))
373 }
374
375 #[inline]
376 fn copysign(self, sign: Self) -> Self {
377 let sign_bit = sign.to_bits() & Self::splat(-0.).to_bits();
378 let magnitude = self.to_bits() & !Self::splat(-0.).to_bits();
379 Self::from_bits(sign_bit | magnitude)
380 }
381
382 #[inline]
383 fn simd_min(self, other: Self) -> Self {
384 // Safety: `self` and `other` are float vectors
385 unsafe { core::intrinsics::simd::simd_minimum_number_nsz(self, other) }
386 }
387
388 #[inline]
389 fn simd_max(self, other: Self) -> Self {
390 // Safety: `self` and `other` are floating point vectors
391 unsafe { core::intrinsics::simd::simd_maximum_number_nsz(self, other) }
392 }
393
394 #[inline]
395 fn simd_clamp(self, min: Self, max: Self) -> Self {
396 assert!(
397 min.simd_le(max).all(),
398 "each element in `min` must be less than or equal to the corresponding element in `max`",
399 );
400 let mut x = self;
401 x = x.simd_lt(min).select(min, x);
402 x = x.simd_gt(max).select(max, x);
403 x
404 }
405
406 #[inline]
407 fn reduce_sum(self) -> Self::Scalar {
408 // LLVM sum is inaccurate on i586
409 if cfg!(all(target_arch = "x86", not(target_feature = "sse2"))) {
410 self.as_array().iter().sum()
411 } else {
412 // Safety: `self` is a float vector
413 unsafe { core::intrinsics::simd::simd_reduce_add_ordered(self, -0.) }
414 }
415 }
416
417 #[inline]
418 fn reduce_product(self) -> Self::Scalar {
419 // LLVM product is inaccurate on i586
420 if cfg!(all(target_arch = "x86", not(target_feature = "sse2"))) {
421 self.as_array().iter().product()
422 } else {
423 // Safety: `self` is a float vector
424 unsafe { core::intrinsics::simd::simd_reduce_mul_ordered(self, 1.) }
425 }
426 }
427
428 #[inline]
429 fn reduce_max(self) -> Self::Scalar {
430 // LLVM has no intrinsic we can use here
431 // (https://github.com/llvm/llvm-project/issues/185827).
432 self.as_array().iter().copied().fold(Self::Scalar::NAN, Self::Scalar::max)
433 }
434
435 #[inline]
436 fn reduce_min(self) -> Self::Scalar {
437 self.as_array().iter().copied().fold(Self::Scalar::NAN, Self::Scalar::min)
438 }
439 }
440 )*
441 }
442}
443
444impl_trait! { f16 { bits: u16, mask: i16 }, f32 { bits: u32, mask: i32 }, f64 { bits: u64, mask: i64 } }