#[cfg(not(any(feature = "use_libm_fma", target_arch = "aarch64", target_feature = "fma")))]
use core::ops::{Add, Mul};
use core::ops::{BitAnd, BitAndAssign, BitOr, BitOrAssign, BitXor, BitXorAssign};
#[cfg(any(feature = "use_libm_fma", target_arch = "aarch64", target_feature = "fma"))]
use num_traits::MulAdd;
use num_traits::{Float, Num};
use lightweight_simd::{Simd, f64x4, f64x8, mask8};
fn check_bitwise<T, const N: usize>(mk: &impl Fn(usize) -> T)
where
T: BitAnd<Output = T>
+ BitOr<Output = T>
+ BitXor<Output = T>
+ BitAndAssign
+ BitOrAssign
+ BitXorAssign
+ Copy
+ PartialEq
+ std::fmt::Debug,
{
let a = Simd::<T, N>::from_fn(mk);
let b = Simd::<T, N>::from_fn(|i| mk(i + 1));
assert_eq!((a & b).to_array(), core::array::from_fn(|i| mk(i) & mk(i + 1)));
assert_eq!((a | b).to_array(), core::array::from_fn(|i| mk(i) | mk(i + 1)));
assert_eq!((a ^ b).to_array(), core::array::from_fn(|i| mk(i) ^ mk(i + 1)));
let mut c = a;
c &= b;
assert_eq!(c, a & b);
c |= b;
assert_eq!(c.to_array(), core::array::from_fn(|i| (mk(i) & mk(i + 1)) | mk(i + 1)));
c ^= b;
assert_eq!(c.to_array(), core::array::from_fn(|i| ((mk(i) & mk(i + 1)) | mk(i + 1)) ^ mk(i + 1)));
}
#[test]
fn bitwise_ints_and_bool() {
check_bitwise::<i64, 8>(&|i| i as i64 * 5 + 1);
check_bitwise::<i64, 3>(&|i| i as i64 * 5 + 1);
check_bitwise::<i32, 5>(&|i| i as i32 + 12345);
check_bitwise::<bool, 8>(&|i| i % 3 == 0);
}
#[test]
fn shifts_i64() {
let v = Simd::<i64, 4>::from([1, 2, 3, 4]);
assert_eq!((v << 2).to_array(), [4, 8, 12, 16]);
assert_eq!(((v << 3) >> 1).to_array(), [4, 8, 12, 16]);
}
#[cfg(any(feature = "use_libm_fma", target_arch = "aarch64", target_feature = "fma"))]
fn check_fused<T, const N: usize>(a_mk: &impl Fn(usize) -> T, b_mk: &impl Fn(usize) -> T)
where
T: MulAdd<Output = T> + Copy + PartialEq + std::fmt::Debug,
{
let a = Simd::<T, N>::from_fn(a_mk);
let b = Simd::<T, N>::from_fn(b_mk);
let c = Simd::<T, N>::from_fn(|i| a_mk(i + 7));
assert_eq!(a.mul_add(b, c).to_array(), core::array::from_fn(|i| MulAdd::mul_add(a_mk(i), b_mk(i), a_mk(i + 7))));
let mut acc = c;
acc.fma_from(b, a);
assert_eq!(acc.to_array(), core::array::from_fn(|i| MulAdd::mul_add(b_mk(i), a_mk(i), a_mk(i + 7))));
}
#[cfg(not(any(feature = "use_libm_fma", target_arch = "aarch64", target_feature = "fma")))]
fn check_fused<T, const N: usize>(a_mk: &impl Fn(usize) -> T, b_mk: &impl Fn(usize) -> T)
where
T: Mul<Output = T> + Add<Output = T> + Copy + PartialEq + std::fmt::Debug,
{
let a = Simd::<T, N>::from_fn(a_mk);
let b = Simd::<T, N>::from_fn(b_mk);
let c = Simd::<T, N>::from_fn(|i| a_mk(i + 7));
assert_eq!(a.mul_add(b, c).to_array(), core::array::from_fn(|i| a_mk(i) * b_mk(i) + a_mk(i + 7)));
let mut acc = c;
acc.fma_from(b, a);
assert_eq!(acc.to_array(), core::array::from_fn(|i| b_mk(i) * a_mk(i) + a_mk(i + 7)));
}
#[test]
fn fused_floats() {
check_fused::<f64, 4>(&|i| i as f64 * 0.5 + 1.0, &|i| i as f64 * 0.25 - 2.0);
check_fused::<f64, 3>(&|i| i as f64 * 0.5 + 1.0, &|i| i as f64 * 0.25 - 2.0);
check_fused::<f32, 8>(&|i| i as f32 * 0.5 + 1.0, &|i| i as f32 * 0.25 - 2.0);
check_fused::<f32, 5>(&|i| i as f32 * 0.5 + 1.0, &|i| i as f32 * 0.25 - 2.0);
let v = f64x4::from([2.0; 4]);
assert_eq!(v.mul_add(f64x4::from([3.0; 4]), f64x4::from([1.0; 4])).to_array(), [7.0; 4]);
let mut acc = f64x4::from([1.0; 4]);
acc.fma_from(f64x4::from([2.0; 4]), f64x4::from([3.0; 4]));
assert_eq!(acc.to_array(), [7.0; 4]);
}
#[test]
fn mul_add_rounding_mode() {
let a = f64x8::splat(1.0 + f64::EPSILON);
let b = f64x8::splat(1.0 - 0.5 * f64::EPSILON);
let c = f64x8::splat(0.5 * f64::EPSILON);
let got = a.mul_add(b, c)[0].to_bits();
#[cfg(any(feature = "use_libm_fma", target_arch = "aarch64", target_feature = "fma"))]
assert_eq!(got, 0x3ff0000000000001); #[cfg(not(any(feature = "use_libm_fma", target_arch = "aarch64", target_feature = "fma")))]
assert_eq!(got, 0x3ff0000000000000); }
fn check_float<T, const N: usize>(mk: &impl Fn(usize) -> T)
where
T: Float + Copy + PartialEq + std::fmt::Debug,
{
let a = Simd::<T, N>::from_fn(mk);
assert_eq!(a.abs().to_array(), core::array::from_fn(|i| mk(i).abs()));
assert_eq!(a.sqrt().to_array(), core::array::from_fn(|i| mk(i).sqrt()));
assert_eq!(a.recip().to_array(), core::array::from_fn(|i| mk(i).recip()));
assert_eq!(a.floor().to_array(), core::array::from_fn(|i| mk(i).floor()));
assert_eq!(a.ceil().to_array(), core::array::from_fn(|i| mk(i).ceil()));
assert_eq!(a.round().to_array(), core::array::from_fn(|i| mk(i).round()));
assert_eq!(a.trunc().to_array(), core::array::from_fn(|i| mk(i).trunc()));
assert_eq!(a.fract().to_array(), core::array::from_fn(|i| mk(i).fract()));
let b = Simd::<T, N>::from_fn(|i| mk(i + 1));
assert_eq!(a.min(b).to_array(), core::array::from_fn(|i| mk(i).min(mk(i + 1))));
assert_eq!(a.max(b).to_array(), core::array::from_fn(|i| mk(i).max(mk(i + 1))));
assert_eq!(a.copysign(b).to_array(), core::array::from_fn(|i| mk(i).copysign(mk(i + 1))));
}
#[test]
fn float_generic() {
check_float::<f64, 8>(&|i| i as f64 * 0.5 + 0.5);
check_float::<f64, 3>(&|i| i as f64 * 0.5 + 0.5);
check_float::<f32, 5>(&|i| i as f32 * 0.5 + 0.5);
check_float::<f32, 4>(&|i| i as f32 * 0.5 + 0.5);
}
#[test]
fn float_concrete_f64() {
let v = f64x4::from([4.0, 9.0, 0.25, 2.25]);
assert_eq!(v.sqrt().to_array(), [2.0, 3.0, 0.5, 1.5]);
let t = f64x4::from([0.5, 1.5, 2.5, -0.5]);
assert_eq!(t.floor().to_array(), [0.0, 1.0, 2.0, -1.0]);
assert_eq!(t.ceil().to_array(), [1.0, 2.0, 3.0, -0.0]);
assert_eq!(t.round().to_array(), [1.0, 2.0, 3.0, -1.0]);
assert_eq!(t.trunc().to_array(), [0.0, 1.0, 2.0, -0.0]);
assert_eq!(t.fract().to_array(), [0.5, 0.5, 0.5, -0.5]);
assert_eq!(t.abs().to_array(), [0.5, 1.5, 2.5, 0.5]);
assert_eq!(t.copysign(f64x4::splat(1.0)).to_array(), [0.5, 1.5, 2.5, 0.5]);
assert_eq!(t.copysign(f64x4::splat(-1.0)).to_array(), [-0.5, -1.5, -2.5, -0.5]);
assert_eq!(f64x4::splat(2.0).recip().to_array(), [0.5; 4]);
let u = f64x4::from([1.5, 2.5, -1.5, -2.5]);
assert_eq!(u.round_ties_even().to_array(), [2.0, 2.0, -2.0, -2.0]);
assert_eq!(f64x4::splat(1.0) * 2.0, f64x4::splat(2.0));
}
fn check_compare<T, const N: usize>(mk: &impl Fn(usize) -> T)
where
T: PartialOrd + Copy + std::fmt::Debug,
{
let a = Simd::<T, N>::from_fn(mk);
let b = Simd::<T, N>::from_fn(|i| mk(i + 1));
assert_eq!(a.simd_lt(b).to_array(), core::array::from_fn(|i| a[i] < b[i]));
assert_eq!(a.simd_le(b).to_array(), core::array::from_fn(|i| a[i] <= b[i]));
assert_eq!(a.simd_gt(b).to_array(), core::array::from_fn(|i| a[i] > b[i]));
assert_eq!(a.simd_ge(b).to_array(), core::array::from_fn(|i| a[i] >= b[i]));
assert_eq!(a.simd_eq(a).to_array(), [true; N]);
assert_eq!(a.simd_ne(a).to_array(), [false; N]);
let m = a.simd_ge(b);
assert_eq!(m.select(a, b).to_array(), core::array::from_fn(|i| if a[i] >= b[i] { a[i] } else { b[i] }));
let m1 = a.simd_lt(b);
let m2 = a.simd_le(b);
assert_eq!((m1 & m2).to_array(), core::array::from_fn(|i| (a[i] < b[i]) & (a[i] <= b[i])));
assert_eq!((m1 | m2).to_array(), core::array::from_fn(|i| (a[i] < b[i]) | (a[i] <= b[i])));
assert_eq!((m1 ^ m2).to_array(), core::array::from_fn(|i| (a[i] < b[i]) ^ (a[i] <= b[i])));
assert_eq!((!m1).to_array(), core::array::from_fn(|i| a[i] >= b[i]));
}
#[test]
fn compare_and_masks() {
check_compare::<f64, 8>(&|i| i as f64 + 0.5);
check_compare::<f64, 3>(&|i| i as f64 + 0.5);
check_compare::<i64, 5>(&|i| i as i64 * 3);
check_compare::<f32, 4>(&|i| i as f32 * 1.5);
}
#[test]
fn mask_concrete() {
let m = mask8::from([true, false, true, true, false, false, true, false]);
assert!(m.any_true());
assert!(!m.all_true());
assert_eq!(f64x8::splat(1.0).mask_select(m, f64x8::splat(2.0)).reduce_sum(), 12.0);
assert!(!(m & mask8::splat(false)).any_true());
assert!((m | mask8::splat(true)).all_true());
}
fn check_reduce<T, const N: usize>(mk: &impl Fn(usize) -> T)
where
T: Num + Copy + PartialEq + std::fmt::Debug,
{
let v = Simd::<T, N>::from_fn(mk);
let mut sum = T::zero();
let mut prod = T::one();
for i in 0..N {
sum = sum + mk(i);
prod = prod * mk(i);
}
assert_eq!(v.reduce_sum(), sum);
assert_eq!(v.reduce_product(), prod);
for n in 0..=N {
let mut s = T::zero();
let mut p = T::one();
for i in 0..n {
s = s + mk(i);
p = p * mk(i);
}
assert_eq!(v.reduce_sum_n(n), s);
assert_eq!(v.reduce_product_n(n), p);
}
}
#[test]
fn reduce_floats_and_ints() {
check_reduce::<f64, 1>(&|i| i as f64 * 0.5 + 1.0);
check_reduce::<f64, 3>(&|i| i as f64 * 0.5 + 1.0);
check_reduce::<f64, 8>(&|i| i as f64 * 0.5 + 1.0);
check_reduce::<f64, 32>(&|i| i as f64 * 0.5 + 1.0);
check_reduce::<f32, 5>(&|i| i as f32 * 0.5 + 1.0);
check_reduce::<i64, 8>(&|i| i as i64 % 2 + 1);
check_reduce::<i64, 32>(&|i| i as i64 % 2 + 1);
}
#[cfg(feature = "complex")]
#[test]
fn complex_aliases() {
use num_complex::Complex;
let v: lightweight_simd::c64x8 = lightweight_simd::c64x8::splat(Complex::new(1.0, -1.0));
assert_eq!(v.reduce_sum(), Complex::new(8.0, -8.0));
}
#[cfg(feature = "half")]
#[test]
fn half_aliases() {
use half::{bf16, f16};
let v = lightweight_simd::f16x8::splat(f16::from_f32(1.0));
assert_eq!(v.reduce_sum_n(4), f16::from_f32(4.0));
let w = lightweight_simd::bf16x16::splat(bf16::from_f32(0.5));
assert_eq!(w.reduce_sum(), bf16::from_f32(8.0));
}