use core::{
cmp::{max, min},
ops::{Div, DivAssign},
};
use crate::{
abs_bits, abs_bits_sticky, exp_bits, f256, norm_bit, norm_signif,
BigUInt, BinEncAnySpecial, DivRem, HiLo, EMIN, EXP_BIAS, EXP_BITS,
EXP_MAX, FRACTION_BITS, HI_ABS_MASK, HI_EXP_MASK, HI_FRACTION_BIAS,
HI_FRACTION_BITS, HI_FRACTION_MASK, HI_SIGN_MASK, INF_HI, MAX_HI,
SIGNIFICAND_BITS, U256, U512,
};
#[inline]
fn div_signifs(x: &U256, y: &U256) -> (U256, u32) {
debug_assert_eq!(x.hi.leading_zeros(), EXP_BITS);
debug_assert_eq!(y.hi.leading_zeros(), EXP_BITS);
let mut t = U512::from_hi_lo(U256::ZERO, *x);
t <<= SIGNIFICAND_BITS + (x < y) as u32;
let (mut q, r) = t.div_rem(*y);
let c = ((q.lo.lo.0 & 1) as u32) << 1 | (!r.is_zero() as u32);
q >>= 1;
debug_assert!(q.hi.is_zero());
(q.lo, c)
}
#[allow(clippy::cast_possible_truncation)]
#[allow(clippy::cast_possible_wrap)]
#[allow(clippy::cast_sign_loss)]
#[inline]
pub(crate) fn div(x: f256, y: f256) -> f256 {
let sign_bits_hi_z = (x.bits.hi.0 ^ y.bits.hi.0) & HI_SIGN_MASK;
let mut abs_bits_x = abs_bits(&x);
let mut abs_bits_y = abs_bits(&y);
let abs_bits_sticky_x = abs_bits_sticky(&abs_bits_x);
let abs_bits_sticky_y = abs_bits_sticky(&abs_bits_y);
if (abs_bits_sticky_x, abs_bits_sticky_y).any_special() {
let max_abs_bits_sticky = max(abs_bits_sticky_x, abs_bits_sticky_y);
if max_abs_bits_sticky > HI_EXP_MASK
|| abs_bits_sticky_x == abs_bits_sticky_y
{
return f256::NAN;
}
if abs_bits_sticky_x < abs_bits_sticky_y {
return f256 {
bits: U256::new(sign_bits_hi_z, 0),
};
}
return f256 {
bits: U256::new(sign_bits_hi_z | INF_HI, 0),
};
}
let mut exp_bits_x = exp_bits(&abs_bits_x) as i32;
let norm_bit_x = norm_bit(&abs_bits_x) as i32;
let (mut norm_signif_x, norm_shift_x) = norm_signif(&abs_bits_x);
let mut exp_bits_y = exp_bits(&abs_bits_y) as i32;
let norm_bit_y = norm_bit(&abs_bits_y) as i32;
let (mut norm_signif_y, norm_shift_y) = norm_signif(&abs_bits_y);
let mut exp_bits_z_minus_1 = (exp_bits_x - norm_bit_x)
- (exp_bits_y - norm_bit_y)
- (norm_shift_x as i32 - norm_shift_y as i32)
+ (-EMIN - 1)
+ (norm_signif_x >= norm_signif_y) as i32;
if exp_bits_z_minus_1 >= EXP_MAX as i32 - 1 {
return f256 {
bits: U256::new(sign_bits_hi_z | INF_HI, 0),
};
}
let (mut signif_z, mut rnd_bits) =
div_signifs(&norm_signif_x, &norm_signif_y);
if exp_bits_z_minus_1 < 0 {
let shift = exp_bits_z_minus_1.unsigned_abs();
if shift > SIGNIFICAND_BITS + 1 {
return f256 {
bits: U256::new(sign_bits_hi_z, 0),
};
}
if shift > 0 {
match shift {
1 => {
rnd_bits = (((signif_z.lo.0 & 1) as u32) << 1)
| (rnd_bits != 0) as u32;
}
2 => {
rnd_bits =
((signif_z.lo.0 & 3) as u32) | (rnd_bits != 0) as u32;
}
3..=127 => {
let rem = signif_z.rem_pow2(shift).lo.0;
rnd_bits = (rem >> (shift - 2)) as u32
| (rem > (1_u128 << (shift - 1))) as u32
| (rnd_bits != 0) as u32;
}
_ => {
let rem = signif_z.rem_pow2(shift);
rnd_bits = (rem >> (shift - 2)).lo.0 as u32
| (rem > (U256::ONE << (shift - 1))) as u32
| (rnd_bits != 0) as u32;
}
}
signif_z >>= shift;
}
exp_bits_z_minus_1 = 0;
}
let mut bits_z = U256::new(
signif_z.hi.0 + ((exp_bits_z_minus_1 as u128) << HI_FRACTION_BITS),
signif_z.lo.0,
);
bits_z.hi.0 |= sign_bits_hi_z;
if rnd_bits > 0b10 || (rnd_bits == 0b10 && bits_z.lo.is_odd()) {
bits_z.incr();
}
f256 { bits: bits_z }
}
impl Div for f256 {
type Output = Self;
fn div(self, rhs: Self) -> Self::Output {
div(self, rhs)
}
}
forward_ref_binop!(impl Div, div);
forward_op_assign!(impl DivAssign, div_assign, Div, div);