use crate::env::{ExcFlags, FloatEnv, RoundMode, Tininess};
use crate::types::{BitOps, FloatFormat};
const INT_BIT: u32 = 126;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum FloatClass {
Normal,
Zero,
Inf,
QNaN,
SNaN,
}
#[derive(Clone, Copy, Debug)]
pub struct FloatParts {
pub sign: bool,
pub exp: i32,
pub frac: u128,
pub cls: FloatClass,
}
impl FloatParts {
pub fn is_nan(&self) -> bool {
matches!(self.cls, FloatClass::QNaN | FloatClass::SNaN)
}
pub fn is_inf(&self) -> bool {
self.cls == FloatClass::Inf
}
pub fn default_nan<F: FloatFormat>() -> Self {
Self {
sign: false,
exp: 0,
frac: 1u128 << (INT_BIT - 1), cls: FloatClass::QNaN,
}
}
}
pub fn unpack<F: FloatFormat>(val: F) -> FloatParts {
let bits = val.to_bits().to_u128();
let total_bits = 1
+ F::EXP_BITS
+ F::FRAC_BITS
+ if F::HAS_EXPLICIT_INT { 1 } else { 0 };
let _ = total_bits;
let frac_total = if F::HAS_EXPLICIT_INT {
F::FRAC_BITS + 1
} else {
F::FRAC_BITS
};
let frac_mask = (1u128 << frac_total) - 1;
let raw_frac = bits & frac_mask;
let exp_mask = (1u128 << F::EXP_BITS) - 1;
let raw_exp = ((bits >> frac_total) & exp_mask) as u32;
let sign_shift = frac_total + F::EXP_BITS;
let sign = ((bits >> sign_shift) & 1) != 0;
let max_exp = (1u32 << F::EXP_BITS) - 1;
if raw_exp == max_exp {
let frac_for_nan = if F::HAS_EXPLICIT_INT {
raw_frac & ((1u128 << F::FRAC_BITS) - 1)
} else {
raw_frac
};
if frac_for_nan == 0 {
if F::HAS_EXPLICIT_INT && (raw_frac >> F::FRAC_BITS) & 1 == 0 {
return FloatParts {
sign,
exp: 0,
frac: 1u128 << (INT_BIT - 1),
cls: FloatClass::QNaN,
};
}
return FloatParts {
sign,
exp: 0,
frac: 0,
cls: FloatClass::Inf,
};
}
let quiet_bit = F::FRAC_BITS - 1;
let is_quiet = (raw_frac >> quiet_bit) & 1 != 0;
let payload = if F::HAS_EXPLICIT_INT {
raw_frac & ((1u128 << F::FRAC_BITS) - 1)
} else {
raw_frac
};
let frac = payload << (INT_BIT - F::FRAC_BITS);
let cls = if is_quiet {
FloatClass::QNaN
} else {
FloatClass::SNaN
};
return FloatParts {
sign,
exp: 0,
frac,
cls,
};
}
if raw_exp == 0 {
if raw_frac == 0 {
return FloatParts {
sign,
exp: 0,
frac: 0,
cls: FloatClass::Zero,
};
}
return unpack_subnormal::<F>(sign, raw_frac);
}
let exp = raw_exp as i32 - F::BIAS;
let frac = if F::HAS_EXPLICIT_INT {
raw_frac << (INT_BIT - F::FRAC_BITS)
} else {
(1u128 << INT_BIT) | (raw_frac << (INT_BIT - F::FRAC_BITS))
};
FloatParts {
sign,
exp,
frac,
cls: FloatClass::Normal,
}
}
fn unpack_subnormal<F: FloatFormat>(sign: bool, raw_frac: u128) -> FloatParts {
let exp_min = 1 - F::BIAS;
let shift_base = INT_BIT - F::FRAC_BITS;
let mut frac = raw_frac << shift_base;
let lz = frac.leading_zeros();
let shift = lz - (127 - INT_BIT);
frac <<= shift;
let exp = exp_min - shift as i32;
FloatParts {
sign,
exp,
frac,
cls: FloatClass::Normal,
}
}
pub fn round_pack<F: FloatFormat>(
parts: &mut FloatParts,
env: &mut FloatEnv,
) -> F {
match parts.cls {
FloatClass::Zero => return pack_zero::<F>(parts.sign),
FloatClass::Inf => return pack_inf::<F>(parts.sign),
FloatClass::QNaN | FloatClass::SNaN => {
return pack_nan::<F>(parts, env);
}
FloatClass::Normal => {}
}
if parts.frac == 0 {
return pack_zero::<F>(parts.sign);
}
let lz = parts.frac.leading_zeros();
let target_lz = 127 - INT_BIT; if lz > target_lz {
let shift = lz - target_lz;
parts.frac <<= shift;
parts.exp -= shift as i32;
} else if lz < target_lz {
let shift = target_lz - lz;
let sticky = if parts.frac & ((1u128 << shift) - 1) != 0 {
1u128
} else {
0
};
parts.frac = (parts.frac >> shift) | sticky;
parts.exp += shift as i32;
}
pack::<F>(parts, env)
}
pub fn pack<F: FloatFormat>(parts: &mut FloatParts, env: &mut FloatEnv) -> F {
match parts.cls {
FloatClass::Zero => return pack_zero::<F>(parts.sign),
FloatClass::Inf => return pack_inf::<F>(parts.sign),
FloatClass::QNaN | FloatClass::SNaN => {
return pack_nan::<F>(parts, env);
}
FloatClass::Normal => {}
}
if parts.frac == 0 {
return pack_zero::<F>(parts.sign);
}
let max_exp = ((1u32 << F::EXP_BITS) - 1) as i32;
let rm = env.round_mode();
let round_pos = INT_BIT - F::FRAC_BITS;
let round_mask = (1u128 << round_pos) - 1;
let half = 1u128 << (round_pos - 1);
let mut biased_exp = parts.exp + F::BIAS;
if biased_exp < 1 {
let shift = (1 - biased_exp) as u32;
if shift >= 128 {
let nonzero = parts.frac != 0;
parts.frac = 0;
if nonzero {
parts.frac = 1; }
} else {
let sticky = if parts.frac & ((1u128 << shift) - 1) != 0 {
1u128
} else {
0
};
parts.frac = (parts.frac >> shift) | sticky;
}
biased_exp = 0;
let is_tiny_before = true; let remainder = parts.frac & round_mask;
if remainder != 0 {
let is_tiny = match env.tininess() {
Tininess::BeforeRounding => is_tiny_before,
Tininess::AfterRounding => {
let rounded = apply_rounding(
parts.frac, remainder, half, round_mask, rm, parts.sign,
);
(rounded >> INT_BIT) & 1 == 0
}
};
if is_tiny {
env.raise(ExcFlags::UNDERFLOW);
}
}
}
let remainder = parts.frac & round_mask;
let inexact = remainder != 0;
parts.frac =
apply_rounding(parts.frac, remainder, half, round_mask, rm, parts.sign);
if parts.frac >> (INT_BIT + 1) != 0 {
parts.frac >>= 1;
biased_exp += 1;
}
if biased_exp >= max_exp {
env.raise(ExcFlags::OVERFLOW | ExcFlags::INEXACT);
return overflow_result::<F>(parts.sign, rm);
}
if inexact {
env.raise(ExcFlags::INEXACT);
}
let frac_field = if biased_exp == 0 {
(parts.frac >> round_pos) & ((1u128 << F::FRAC_BITS) - 1)
} else if F::HAS_EXPLICIT_INT {
(parts.frac >> (INT_BIT - F::FRAC_BITS))
& ((1u128 << (F::FRAC_BITS + 1)) - 1)
} else {
(parts.frac >> round_pos) & ((1u128 << F::FRAC_BITS) - 1)
};
let frac_total = if F::HAS_EXPLICIT_INT {
F::FRAC_BITS + 1
} else {
F::FRAC_BITS
};
let bits = ((parts.sign as u128) << (frac_total + F::EXP_BITS))
| ((biased_exp as u128) << frac_total)
| frac_field;
F::from_bits(<F::Bits as crate::types::BitOps>::from_u128(bits))
}
fn apply_rounding(
frac: u128,
remainder: u128,
half: u128,
round_mask: u128,
rm: RoundMode,
sign: bool,
) -> u128 {
let truncated = frac & !round_mask;
let lsb_set = (frac >> round_mask.count_ones()) & 1 != 0;
match rm {
RoundMode::NearEven => {
if remainder > half {
truncated.wrapping_add(round_mask + 1)
} else if remainder == half {
if lsb_set {
truncated.wrapping_add(round_mask + 1)
} else {
truncated
}
} else {
truncated
}
}
RoundMode::NearMaxMag => {
if remainder >= half {
truncated.wrapping_add(round_mask + 1)
} else {
truncated
}
}
RoundMode::ToZero => truncated,
RoundMode::Down => {
if sign && remainder != 0 {
truncated.wrapping_add(round_mask + 1)
} else {
truncated
}
}
RoundMode::Up => {
if !sign && remainder != 0 {
truncated.wrapping_add(round_mask + 1)
} else {
truncated
}
}
RoundMode::Odd => {
if remainder != 0 {
truncated | (round_mask + 1)
} else {
truncated
}
}
}
}
fn overflow_result<F: FloatFormat>(sign: bool, rm: RoundMode) -> F {
match rm {
RoundMode::NearEven | RoundMode::NearMaxMag => pack_inf::<F>(sign),
RoundMode::ToZero => pack_max_finite::<F>(sign),
RoundMode::Down => {
if sign {
pack_inf::<F>(true)
} else {
pack_max_finite::<F>(false)
}
}
RoundMode::Up => {
if sign {
pack_max_finite::<F>(true)
} else {
pack_inf::<F>(false)
}
}
RoundMode::Odd => pack_max_finite::<F>(sign),
}
}
fn pack_zero<F: FloatFormat>(sign: bool) -> F {
let frac_total = if F::HAS_EXPLICIT_INT {
F::FRAC_BITS + 1
} else {
F::FRAC_BITS
};
let bits = (sign as u128) << (frac_total + F::EXP_BITS);
F::from_bits(<F::Bits as crate::types::BitOps>::from_u128(bits))
}
fn pack_inf<F: FloatFormat>(sign: bool) -> F {
let max_exp = (1u128 << F::EXP_BITS) - 1;
let frac_total = if F::HAS_EXPLICIT_INT {
F::FRAC_BITS + 1
} else {
F::FRAC_BITS
};
let mut bits = ((sign as u128) << (frac_total + F::EXP_BITS))
| (max_exp << frac_total);
if F::HAS_EXPLICIT_INT {
bits |= 1u128 << F::FRAC_BITS;
}
F::from_bits(<F::Bits as crate::types::BitOps>::from_u128(bits))
}
fn pack_max_finite<F: FloatFormat>(sign: bool) -> F {
let max_exp = (1u128 << F::EXP_BITS) - 2;
let frac_total = if F::HAS_EXPLICIT_INT {
F::FRAC_BITS + 1
} else {
F::FRAC_BITS
};
let frac_all_ones = (1u128 << frac_total) - 1;
let bits = ((sign as u128) << (frac_total + F::EXP_BITS))
| (max_exp << frac_total)
| frac_all_ones;
F::from_bits(<F::Bits as crate::types::BitOps>::from_u128(bits))
}
fn pack_nan<F: FloatFormat>(parts: &FloatParts, env: &mut FloatEnv) -> F {
if env.default_nan() {
let dn = FloatParts::default_nan::<F>();
return encode_nan::<F>(&dn);
}
let mut p = *parts;
if p.cls == FloatClass::SNaN {
p.cls = FloatClass::QNaN;
p.frac |= 1u128 << (INT_BIT - 1);
}
encode_nan::<F>(&p)
}
fn encode_nan<F: FloatFormat>(parts: &FloatParts) -> F {
let max_exp = (1u128 << F::EXP_BITS) - 1;
let frac_total = if F::HAS_EXPLICIT_INT {
F::FRAC_BITS + 1
} else {
F::FRAC_BITS
};
let payload = (parts.frac >> (INT_BIT - F::FRAC_BITS))
& ((1u128 << F::FRAC_BITS) - 1);
let quiet_bit = 1u128 << (F::FRAC_BITS - 1);
let payload = if parts.cls == FloatClass::QNaN {
payload | quiet_bit
} else {
payload & !quiet_bit
};
let payload = if payload == 0 { quiet_bit } else { payload };
let mut bits = ((parts.sign as u128) << (frac_total + F::EXP_BITS))
| (max_exp << frac_total)
| payload;
if F::HAS_EXPLICIT_INT {
bits |= 1u128 << F::FRAC_BITS;
}
F::from_bits(<F::Bits as crate::types::BitOps>::from_u128(bits))
}
pub fn nan_propagate(
a: &FloatParts,
b: &FloatParts,
env: &mut FloatEnv,
) -> FloatParts {
if a.cls == FloatClass::SNaN || b.cls == FloatClass::SNaN {
env.raise(ExcFlags::INVALID);
}
if env.default_nan() {
return FloatParts {
sign: false,
exp: 0,
frac: 1u128 << (INT_BIT - 1),
cls: FloatClass::QNaN,
};
}
let pick = if a.cls == FloatClass::SNaN {
a
} else if b.cls == FloatClass::SNaN {
b
} else if a.cls == FloatClass::QNaN {
a
} else {
b
};
let mut result = *pick;
if result.cls == FloatClass::SNaN {
result.cls = FloatClass::QNaN;
result.frac |= 1u128 << (INT_BIT - 1);
}
result
}
pub fn nan_propagate_one(a: &FloatParts, env: &mut FloatEnv) -> FloatParts {
if a.cls == FloatClass::SNaN {
env.raise(ExcFlags::INVALID);
}
if env.default_nan() {
return FloatParts {
sign: false,
exp: 0,
frac: 1u128 << (INT_BIT - 1),
cls: FloatClass::QNaN,
};
}
let mut result = *a;
if result.cls == FloatClass::SNaN {
result.cls = FloatClass::QNaN;
result.frac |= 1u128 << (INT_BIT - 1);
}
result
}
pub fn return_nan<F: FloatFormat>(env: &mut FloatEnv) -> F {
env.raise(ExcFlags::INVALID);
let dn = FloatParts::default_nan::<F>();
encode_nan::<F>(&dn)
}