use crate::env::{FloatEnv, RoundMode};
use crate::parts::{nan_propagate, round_pack, unpack, FloatClass, FloatParts};
use crate::types::{
BFloat16, Float128, Float16, Float32, Float64, FloatFormat, FloatX80,
};
pub fn add<F: FloatFormat>(a: F, b: F, env: &mut FloatEnv) -> F {
let pa = unpack::<F>(a);
let pb = unpack::<F>(b);
add_parts::<F>(&pa, &pb, env)
}
pub fn sub<F: FloatFormat>(a: F, b: F, env: &mut FloatEnv) -> F {
let pa = unpack::<F>(a);
let mut pb = unpack::<F>(b);
pb.sign = !pb.sign;
add_parts::<F>(&pa, &pb, env)
}
fn add_parts<F: FloatFormat>(
a: &FloatParts,
b: &FloatParts,
env: &mut FloatEnv,
) -> F {
if a.is_nan() || b.is_nan() {
let r = nan_propagate(a, b, env);
let mut r = r;
return round_pack::<F>(&mut r, env);
}
if a.cls == FloatClass::Inf {
if b.cls == FloatClass::Inf {
if a.sign != b.sign {
return crate::parts::return_nan::<F>(env);
}
let mut r = *a;
return round_pack::<F>(&mut r, env);
}
let mut r = *a;
return round_pack::<F>(&mut r, env);
}
if b.cls == FloatClass::Inf {
let mut r = *b;
return round_pack::<F>(&mut r, env);
}
if a.cls == FloatClass::Zero {
if b.cls == FloatClass::Zero {
let sign = if a.sign == b.sign {
a.sign
} else {
env.round_mode() == RoundMode::Down
};
let mut r = FloatParts {
sign,
exp: 0,
frac: 0,
cls: FloatClass::Zero,
};
return round_pack::<F>(&mut r, env);
}
let mut r = *b;
return round_pack::<F>(&mut r, env);
}
if b.cls == FloatClass::Zero {
let mut r = *a;
return round_pack::<F>(&mut r, env);
}
if a.sign == b.sign {
add_normal::<F>(a, b, env)
} else {
sub_normal::<F>(a, b, env)
}
}
fn add_normal<F: FloatFormat>(
a: &FloatParts,
b: &FloatParts,
env: &mut FloatEnv,
) -> F {
let (mut big, mut small) = if a.exp >= b.exp { (*a, *b) } else { (*b, *a) };
let exp_diff = (big.exp - small.exp) as u32;
if exp_diff > 0 {
if exp_diff >= 128 {
small.frac = if small.frac != 0 { 1 } else { 0 };
} else {
let sticky = if small.frac & ((1u128 << exp_diff) - 1) != 0 {
1u128
} else {
0
};
small.frac = (small.frac >> exp_diff) | sticky;
}
}
big.frac = big.frac.wrapping_add(small.frac);
let mut result = FloatParts {
sign: big.sign,
exp: big.exp,
frac: big.frac,
cls: FloatClass::Normal,
};
round_pack::<F>(&mut result, env)
}
fn sub_normal<F: FloatFormat>(
a: &FloatParts,
b: &FloatParts,
env: &mut FloatEnv,
) -> F {
let (big, small, result_sign) = if a.exp > b.exp {
(*a, *b, a.sign)
} else if a.exp < b.exp {
(*b, *a, b.sign)
} else if a.frac > b.frac {
(*a, *b, a.sign)
} else if a.frac < b.frac {
(*b, *a, b.sign)
} else {
let sign = env.round_mode() == RoundMode::Down;
let mut r = FloatParts {
sign,
exp: 0,
frac: 0,
cls: FloatClass::Zero,
};
return round_pack::<F>(&mut r, env);
};
let exp_diff = (big.exp - small.exp) as u32;
let mut small_frac = small.frac;
if exp_diff > 0 {
if exp_diff >= 128 {
small_frac = if small_frac != 0 { 1 } else { 0 };
} else {
let sticky = if small_frac & ((1u128 << exp_diff) - 1) != 0 {
1u128
} else {
0
};
small_frac = (small_frac >> exp_diff) | sticky;
}
}
let frac = big.frac.wrapping_sub(small_frac);
if frac == 0 {
let sign = env.round_mode() == RoundMode::Down;
let mut r = FloatParts {
sign,
exp: 0,
frac: 0,
cls: FloatClass::Zero,
};
return round_pack::<F>(&mut r, env);
}
let mut result = FloatParts {
sign: result_sign,
exp: big.exp,
frac,
cls: FloatClass::Normal,
};
round_pack::<F>(&mut result, env)
}
macro_rules! impl_add_sub {
($ty:ty) => {
impl $ty {
pub fn add(self, other: Self, env: &mut FloatEnv) -> Self {
add::<Self>(self, other, env)
}
pub fn sub(self, other: Self, env: &mut FloatEnv) -> Self {
sub::<Self>(self, other, env)
}
}
};
}
impl_add_sub!(Float16);
impl_add_sub!(BFloat16);
impl_add_sub!(Float32);
impl_add_sub!(Float64);
impl_add_sub!(Float128);
impl_add_sub!(FloatX80);