#[cfg(all(target_arch = "x86_64", any(test, not(feature = "std"))))]
#[inline(always)]
fn hw_sqrt_f32(x: f32) -> f32 {
use core::arch::x86_64::{_mm_cvtss_f32, _mm_set_ss, _mm_sqrt_ss};
unsafe { _mm_cvtss_f32(_mm_sqrt_ss(_mm_set_ss(x))) }
}
#[cfg(all(target_arch = "x86_64", any(test, not(feature = "std"))))]
#[inline(always)]
fn hw_sqrt_f64(x: f64) -> f64 {
use core::arch::x86_64::{_mm_cvtsd_f64, _mm_set_sd, _mm_sqrt_sd};
unsafe { _mm_cvtsd_f64(_mm_sqrt_sd(_mm_set_sd(x), _mm_set_sd(x))) }
}
#[cfg(all(target_arch = "aarch64", any(test, not(feature = "std"))))]
#[inline(always)]
fn hw_sqrt_f32(x: f32) -> f32 {
let r: f32;
unsafe {
core::arch::asm!("fsqrt {y:s}, {x:s}", x = in(vreg) x, y = out(vreg) r, options(pure, nomem, nostack));
}
r
}
#[cfg(all(target_arch = "aarch64", any(test, not(feature = "std"))))]
#[inline(always)]
fn hw_sqrt_f64(x: f64) -> f64 {
let r: f64;
unsafe {
core::arch::asm!("fsqrt {y:d}, {x:d}", x = in(vreg) x, y = out(vreg) r, options(pure, nomem, nostack));
}
r
}
#[inline(always)]
fn sqrt_f32(x: f32) -> f32 {
#[cfg(feature = "std")]
{
x.sqrt()
}
#[cfg(all(not(feature = "std"), any(target_arch = "x86_64", target_arch = "aarch64")))]
{
hw_sqrt_f32(x)
}
#[cfg(all(
not(feature = "std"),
not(any(target_arch = "x86_64", target_arch = "aarch64")),
feature = "libm"
))]
{
libm::sqrtf(x)
}
#[cfg(all(
not(feature = "std"),
not(any(target_arch = "x86_64", target_arch = "aarch64")),
not(feature = "libm")
))]
{
software_sqrt_f32(x)
}
}
#[inline(always)]
fn sqrt_f64(x: f64) -> f64 {
#[cfg(feature = "std")]
{
x.sqrt()
}
#[cfg(all(not(feature = "std"), any(target_arch = "x86_64", target_arch = "aarch64")))]
{
hw_sqrt_f64(x)
}
#[cfg(all(
not(feature = "std"),
not(any(target_arch = "x86_64", target_arch = "aarch64")),
feature = "libm"
))]
{
libm::sqrt(x)
}
#[cfg(all(
not(feature = "std"),
not(any(target_arch = "x86_64", target_arch = "aarch64")),
not(feature = "libm")
))]
{
software_sqrt_f64(x)
}
}
#[cfg(all(
not(feature = "std"),
not(any(target_arch = "x86_64", target_arch = "aarch64")),
not(feature = "libm")
))]
#[inline]
fn software_sqrt_f32(x: f32) -> f32 {
if x < 0.0 {
return f32::NAN;
}
if x == 0.0 || !x.is_finite() {
return x;
}
let mut g = x;
let mut i = 0;
while i < 20 {
g = 0.5 * (g + x / g);
i += 1;
}
g
}
#[cfg(all(
not(feature = "std"),
not(any(target_arch = "x86_64", target_arch = "aarch64")),
not(feature = "libm")
))]
#[inline]
fn software_sqrt_f64(x: f64) -> f64 {
if x < 0.0 {
return f64::NAN;
}
if x == 0.0 || !x.is_finite() {
return x;
}
let mut g = x;
let mut i = 0;
while i < 24 {
g = 0.5 * (g + x / g);
i += 1;
}
g
}
pub trait Scalar:
Copy
+ PartialEq
+ PartialOrd
+ Send
+ Sync
+ 'static
+ core::ops::Add<Output = Self>
+ core::ops::Sub<Output = Self>
+ core::ops::Mul<Output = Self>
{
const ZERO: Self;
const ONE: Self;
const TYPE_BITS: u8;
fn from_f64(v: f64) -> Self;
fn into_f64(self) -> f64;
fn to_bits32(self) -> u32;
fn from_bits32(v: u32) -> Self;
fn wadd(self, o: Self) -> Self;
fn wsub(self, o: Self) -> Self;
fn wmul(self, o: Self) -> Self;
fn min(self, o: Self) -> Self;
fn max(self, o: Self) -> Self;
fn abs(self) -> Self;
fn neg(self) -> Self;
}
pub trait FloatScalar:
Scalar + num_traits::float::FloatCore + core::ops::Div<Output = Self> + core::ops::Neg<Output = Self>
{
type Compute: FloatScalar<Compute = Self::Compute>;
fn widen(self) -> Self::Compute;
fn narrow(c: Self::Compute) -> Self;
fn sqrt(self) -> Self;
#[inline(always)]
fn fma(self, b: Self, c: Self) -> Self {
self * b + c
}
}
pub trait IntScalar: Scalar + num_traits::PrimInt {}
macro_rules! impl_float_core_scalar {
($ty:ident) => {
#[inline(always)]
fn wadd(self, o: Self) -> Self {
self + o
}
#[inline(always)]
fn wsub(self, o: Self) -> Self {
self - o
}
#[inline(always)]
fn wmul(self, o: Self) -> Self {
self * o
}
#[inline(always)]
fn min(self, o: Self) -> Self {
if self.is_nan() {
o
} else if o.is_nan() {
self
} else if o < self {
o
} else {
self
}
}
#[inline(always)]
fn max(self, o: Self) -> Self {
if self.is_nan() {
o
} else if o.is_nan() {
self
} else if o > self {
o
} else {
self
}
}
#[inline(always)]
fn abs(self) -> Self {
<$ty as num_traits::float::FloatCore>::abs(self)
}
#[inline(always)]
fn neg(self) -> Self {
-self
}
};
}
impl Scalar for f32 {
const ZERO: Self = 0.0;
const ONE: Self = 1.0;
const TYPE_BITS: u8 = 1;
#[inline(always)]
fn from_f64(v: f64) -> Self {
v as f32
}
#[inline(always)]
fn into_f64(self) -> f64 {
self as f64
}
#[inline(always)]
fn to_bits32(self) -> u32 {
self.to_bits()
}
#[inline(always)]
fn from_bits32(v: u32) -> Self {
f32::from_bits(v)
}
impl_float_core_scalar!(f32);
}
impl FloatScalar for f32 {
type Compute = f32;
#[inline(always)]
fn widen(self) -> f32 {
self
}
#[inline(always)]
fn narrow(c: f32) -> Self {
c
}
#[inline(always)]
fn sqrt(self) -> Self {
sqrt_f32(self)
}
}
impl Scalar for f64 {
const ZERO: Self = 0.0;
const ONE: Self = 1.0;
const TYPE_BITS: u8 = 2;
#[inline(always)]
fn from_f64(v: f64) -> Self {
v
}
#[inline(always)]
fn into_f64(self) -> f64 {
self
}
#[inline(always)]
fn to_bits32(self) -> u32 {
self.to_bits() as u32
}
#[inline(always)]
fn from_bits32(v: u32) -> Self {
f64::from_bits(v as u64)
}
impl_float_core_scalar!(f64);
}
impl FloatScalar for f64 {
type Compute = f64;
#[inline(always)]
fn widen(self) -> f64 {
self
}
#[inline(always)]
fn narrow(c: f64) -> Self {
c
}
#[inline(always)]
fn sqrt(self) -> Self {
sqrt_f64(self)
}
}
macro_rules! impl_int_scalar {
($ty:ident, $bits:expr, $abs:expr) => {
impl Scalar for $ty {
const ZERO: Self = 0;
const ONE: Self = 1;
const TYPE_BITS: u8 = $bits;
#[inline(always)]
fn from_f64(v: f64) -> Self {
v as $ty
}
#[inline(always)]
fn into_f64(self) -> f64 {
self as f64
}
#[inline(always)]
fn to_bits32(self) -> u32 {
self as u32
}
#[inline(always)]
fn from_bits32(v: u32) -> Self {
v as $ty
}
#[inline(always)]
fn wadd(self, o: Self) -> Self {
self.wrapping_add(o)
}
#[inline(always)]
fn wsub(self, o: Self) -> Self {
self.wrapping_sub(o)
}
#[inline(always)]
fn wmul(self, o: Self) -> Self {
self.wrapping_mul(o)
}
#[inline(always)]
fn min(self, o: Self) -> Self {
Ord::min(self, o)
}
#[inline(always)]
fn max(self, o: Self) -> Self {
Ord::max(self, o)
}
#[inline(always)]
fn abs(self) -> Self {
#[allow(clippy::redundant_closure_call)]
($abs)(self)
}
#[inline(always)]
fn neg(self) -> Self {
self.wrapping_neg()
}
}
impl IntScalar for $ty {}
};
}
impl_int_scalar!(u32, 16, |x| x);
impl_int_scalar!(i32, 32, |x: i32| x.wrapping_abs());
mod half_impls {
use super::{FloatScalar, Scalar};
use half::{bf16, f16};
macro_rules! impl_half {
($ty:ident, $bits:expr) => {
impl Scalar for $ty {
const ZERO: Self = $ty::from_f32_const(0.0);
const ONE: Self = $ty::from_f32_const(1.0);
const TYPE_BITS: u8 = $bits;
#[inline(always)]
fn from_f64(v: f64) -> Self {
$ty::from_f64(v)
}
#[inline(always)]
fn into_f64(self) -> f64 {
$ty::to_f64(self)
}
#[inline(always)]
fn to_bits32(self) -> u32 {
self.to_bits() as u32
}
#[inline(always)]
fn from_bits32(v: u32) -> Self {
$ty::from_bits(v as u16)
}
#[inline(always)]
fn wadd(self, o: Self) -> Self {
self + o
}
#[inline(always)]
fn wsub(self, o: Self) -> Self {
self - o
}
#[inline(always)]
fn wmul(self, o: Self) -> Self {
self * o
}
#[inline(always)]
fn min(self, o: Self) -> Self {
if self.is_nan() {
o
} else if o.is_nan() {
self
} else if o.to_f32() < self.to_f32() {
o
} else {
self
}
}
#[inline(always)]
fn max(self, o: Self) -> Self {
if self.is_nan() {
o
} else if o.is_nan() {
self
} else if o.to_f32() > self.to_f32() {
o
} else {
self
}
}
#[inline(always)]
fn abs(self) -> Self {
$ty::from_bits(self.to_bits() & 0x7fff)
}
#[inline(always)]
fn neg(self) -> Self {
-self
}
}
impl FloatScalar for $ty {
type Compute = f32;
#[inline(always)]
fn widen(self) -> f32 {
self.to_f32()
}
#[inline(always)]
fn narrow(c: f32) -> Self {
$ty::from_f32(c)
}
#[inline(always)]
fn sqrt(self) -> Self {
$ty::from_f32(FloatScalar::sqrt(self.to_f32()))
}
}
};
}
impl_half!(f16, 4);
impl_half!(bf16, 8);
}
#[cfg(all(test, any(target_arch = "x86_64", target_arch = "aarch64")))]
mod hw_sqrt_tests {
#[test]
fn matches_std_bit_for_bit() {
let xs32 = [
0.0f32, 1.0, 2.0, 3.0, 4.0, 9.0, 0.25, 1e-12, 1e12, 123.456, f32::MIN_POSITIVE, f32::MAX,
];
for &x in &xs32 {
assert_eq!(super::hw_sqrt_f32(x).to_bits(), x.sqrt().to_bits(), "f32 sqrt({x})");
}
let xs64 = [
0.0f64, 1.0, 2.0, 3.0, 4.0, 9.0, 0.25, 1e-300, 1e300, 123.456, f64::MIN_POSITIVE, f64::MAX,
];
for &x in &xs64 {
assert_eq!(super::hw_sqrt_f64(x).to_bits(), x.sqrt().to_bits(), "f64 sqrt({x})");
}
assert!(super::hw_sqrt_f32(-1.0).is_nan());
assert!(super::hw_sqrt_f64(-1.0).is_nan());
assert_eq!(super::hw_sqrt_f32(-0.0).to_bits(), (-0.0f32).to_bits());
assert_eq!(super::hw_sqrt_f64(-0.0).to_bits(), (-0.0f64).to_bits());
}
}