use crate::natural::InnerNatural::{Large, Small};
use crate::natural::arithmetic::add::{limbs_add_limb_to_out, limbs_slice_add_limb_in_place};
use crate::natural::arithmetic::sub::limbs_sub_limb_to_out;
use crate::natural::{
LIMB_HIGH_BIT, LIMB_MAX_HALF, Natural, WIDTH_MINUS_1, bit_to_limb_count_floor,
limb_to_bit_count,
};
use crate::platform::Limb;
use alloc::vec::Vec;
use core::cmp::min;
use malachite_base::num::arithmetic::traits::{
IsPowerOf2, ModPowerOf2, NegModPowerOf2, Parity, PowerOf2, ShrRound, WrappingSubAssign,
};
use malachite_base::num::basic::integers::PrimitiveInt;
use malachite_base::num::conversion::traits::ExactFrom;
use malachite_base::num::logic::traits::{BitAccess, LowMask};
use malachite_base::rounding_modes::RoundingMode::{self, *};
use malachite_base::slices::slice_test_zero;
pub fn float_can_round(x: &Natural, err0: u64, prec: u64, rm: RoundingMode) -> bool {
match x {
Natural(Small(small)) => limb_float_can_round(*small, err0, prec, rm),
Natural(Large(xs)) => limbs_float_can_round(xs, err0, prec, rm),
}
}
pub(crate) fn limb_float_can_round(x: Limb, err0: u64, mut prec: u64, rm: RoundingMode) -> bool {
if rm == Nearest {
prec += 1;
}
assert!(x.get_highest_bit());
let err = min(err0, u64::power_of_2(Limb::LOG_WIDTH));
if err <= prec {
return false;
}
let mut s = Limb::WIDTH - (prec & Limb::WIDTH_MASK);
let n = bit_to_limb_count_floor(err);
let mask = Limb::low_mask(s);
let mut tmp = x & mask;
s = Limb::WIDTH - (err & Limb::WIDTH_MASK);
if n == 0 {
assert!(s < Limb::WIDTH);
tmp >>= s;
tmp != 0 && tmp != mask >> s
} else if tmp == 0 {
s != Limb::WIDTH && x >> s != 0
} else if tmp == mask {
s != Limb::WIDTH && x >> s != Limb::MAX >> s
} else {
true
}
}
pub fn limbs_float_can_round(xs: &[Limb], err0: u64, mut prec: u64, rm: RoundingMode) -> bool {
if rm == Nearest {
prec += 1;
}
let len = xs.len();
assert!(xs[len - 1].get_highest_bit());
let err = min(err0, limb_to_bit_count(len));
if err <= prec {
return false;
}
let k = bit_to_limb_count_floor(prec);
let mut s = Limb::WIDTH - (prec & Limb::WIDTH_MASK);
let n = bit_to_limb_count_floor(err) - k;
assert!(len > k);
let mut i = len - k - 1;
let mask = Limb::low_mask(s);
let mut tmp = xs[i] & mask;
i.wrapping_sub_assign(1);
if n == 0 {
s = Limb::WIDTH - (err & Limb::WIDTH_MASK);
assert!(s < Limb::WIDTH);
tmp >>= s;
tmp != 0 && tmp != mask >> s
} else if tmp == 0 {
let j = i.wrapping_add(2) - n;
if n > 1 && !slice_test_zero(&xs[j..=i]) {
return true;
}
s = Limb::WIDTH - (err & Limb::WIDTH_MASK);
s != Limb::WIDTH && xs[j - 1] >> s != 0
} else if tmp == mask {
let j = i.wrapping_add(2) - n;
if n > 1 && xs[j..=i].iter().any(|&x| x != Limb::MAX) {
return true;
}
s = Limb::WIDTH - (err & Limb::WIDTH_MASK);
s != Limb::WIDTH && xs[j - 1] >> s != Limb::MAX >> s
} else {
true
}
}
pub fn limbs_float_significand_leading_ones(xs: &[Limb]) -> Option<u64> {
let mut i = xs.len();
let mut count = 0;
while i > 0 && xs[i - 1] == Limb::MAX {
count += Limb::WIDTH;
i -= 1;
}
if i == 0 {
return Some(count);
}
let m = xs[i - 1];
let j = m.leading_ones();
if m << j != 0 {
return None;
}
count += u64::from(j);
if slice_test_zero(&xs[..i - 1]) {
Some(count)
} else {
None
}
}
pub fn float_significand_leading_ones(x: &Natural) -> Option<u64> {
match x {
Natural(Small(small)) => limbs_float_significand_leading_ones(core::slice::from_ref(small)),
Natural(Large(xs)) => limbs_float_significand_leading_ones(xs),
}
}
pub(crate) const MPFR_EVEN_INEX: i8 = 2;
pub(crate) const MPFR_ROUND_FAILED: i8 = 3;
pub(crate) const NEG_MPFR_ROUND_FAILED: i8 = -MPFR_ROUND_FAILED;
pub(crate) fn round_helper_even(
out: &mut [Limb],
out_prec: u64,
xs: &[Limb],
x_prec: u64,
rm: RoundingMode,
) -> (i8, bool) {
round_helper(out, out_prec, xs, x_prec, rm, |out, xs_hi, ulp| {
let ulp_mask = !(ulp - 1);
if xs_hi[0] & ulp == 0 {
out.copy_from_slice(xs_hi);
out[0] &= ulp_mask;
(-MPFR_EVEN_INEX, false)
} else {
let increment = limbs_add_limb_to_out(out, xs_hi, ulp);
if increment {
*out.last_mut().unwrap() = LIMB_HIGH_BIT;
}
out[0] &= ulp_mask;
(MPFR_EVEN_INEX, increment)
}
})
}
#[inline]
pub fn round_helper_raw(
out: &mut [Limb],
out_prec: u64,
xs: &[Limb],
x_prec: u64,
rm: RoundingMode,
) -> (i8, bool) {
round_helper(out, out_prec, xs, x_prec, rm, |out, xs_hi, ulp| {
let ulp_mask = !(ulp - 1);
if xs_hi[0] & ulp == 0 {
out.copy_from_slice(xs_hi);
out[0] &= ulp_mask;
(-1, false)
} else {
let increment = limbs_add_limb_to_out(out, xs_hi, ulp);
if increment {
*out.last_mut().unwrap() = LIMB_HIGH_BIT;
}
out[0] &= ulp_mask;
(1, increment)
}
})
}
#[inline]
pub fn round_helper_raw_aliased(
out_offset: usize,
out_prec: u64,
xs: &mut [Limb],
x_prec: u64,
rm: RoundingMode,
) -> (i8, bool) {
round_helper_aliased(out_offset, out_prec, xs, x_prec, rm, |out, ulp| {
let ulp_mask = !(ulp - 1);
if out[0] & ulp == 0 {
out[0] &= ulp_mask;
(-1, false)
} else {
let increment = limbs_slice_add_limb_in_place(out, ulp);
if increment {
*out.last_mut().unwrap() = LIMB_HIGH_BIT;
}
out[0] &= ulp_mask;
(1, increment)
}
})
}
fn round_helper<F: Fn(&mut [Limb], &[Limb], Limb) -> (i8, bool)>(
out: &mut [Limb],
out_prec: u64,
xs: &[Limb],
x_prec: u64,
rm: RoundingMode,
middle_handler: F,
) -> (i8, bool) {
let xs_len = xs.len();
let out_len = out.len();
if out_prec >= x_prec {
out[out_len - xs_len..].copy_from_slice(xs);
(0, false)
} else {
let shift = out_prec.neg_mod_power_of_2(Limb::LOG_WIDTH);
let i = xs_len.checked_sub(out_len).unwrap();
let mut sticky_bit;
let round_bit;
let ulp = if shift != 0 {
let mask = Limb::power_of_2(shift - 1);
let x = xs[i];
round_bit = x & mask;
sticky_bit = x & (mask - 1);
if rm == Nearest || round_bit == 0 {
let mut to = i;
let mut n = xs_len - out_len;
while n != 0 && sticky_bit == 0 {
to -= 1;
sticky_bit = xs[to];
n -= 1;
}
}
mask << 1
} else {
assert!(out_len < xs_len);
let x = xs[i - 1];
round_bit = x & LIMB_HIGH_BIT;
sticky_bit = x & LIMB_MAX_HALF;
if rm == Nearest || round_bit == 0 {
let mut to = i - 1;
let mut n = xs_len - out_len - 1;
while n != 0 && sticky_bit == 0 {
to -= 1;
sticky_bit = xs[to];
n -= 1;
}
}
1
};
let xs_hi = &xs[i..];
let ulp_mask = !(ulp - 1);
match rm {
Floor | Down | Exact => {
out.copy_from_slice(xs_hi);
out[0] &= ulp_mask;
(if sticky_bit | round_bit != 0 { -1 } else { 0 }, false)
}
Ceiling | Up => {
if sticky_bit | round_bit == 0 {
out.copy_from_slice(xs_hi);
out[0] &= ulp_mask;
(0, false)
} else {
let increment = limbs_add_limb_to_out(out, xs_hi, ulp);
if increment {
out[out_len - 1] = LIMB_HIGH_BIT;
}
out[0] &= ulp_mask;
(1, increment)
}
}
Nearest => {
if round_bit == 0 {
out.copy_from_slice(xs_hi);
out[0] &= ulp_mask;
(if (sticky_bit | round_bit) != 0 { -1 } else { 0 }, false)
} else if sticky_bit == 0 {
middle_handler(out, xs_hi, ulp)
} else {
let increment = limbs_add_limb_to_out(out, xs_hi, ulp);
if increment {
out[out_len - 1] = LIMB_HIGH_BIT;
}
out[0] &= ulp_mask;
(1, increment)
}
}
}
}
}
fn round_helper_aliased<F: Fn(&mut [Limb], Limb) -> (i8, bool)>(
out_offset: usize,
out_prec: u64,
xs: &mut [Limb],
x_prec: u64,
rm: RoundingMode,
middle_handler: F,
) -> (i8, bool) {
let xs_len = xs.len();
let out_len = xs_len - out_offset;
if out_prec >= x_prec {
(0, false)
} else {
let shift = out_prec.neg_mod_power_of_2(Limb::LOG_WIDTH);
let mut sticky_bit;
let round_bit;
let ulp = if shift != 0 {
let mask = Limb::power_of_2(shift - 1);
let x = xs[out_offset];
round_bit = x & mask;
sticky_bit = x & (mask - 1);
if rm == Nearest || round_bit == 0 {
let mut n = out_offset;
while n != 0 && sticky_bit == 0 {
n -= 1;
sticky_bit = xs[n];
}
}
mask << 1
} else {
assert_ne!(out_offset, 0);
let x = xs[out_offset - 1];
round_bit = x & LIMB_HIGH_BIT;
sticky_bit = x & LIMB_MAX_HALF;
if rm == Nearest || round_bit == 0 {
let mut n = out_offset - 1;
while n != 0 && sticky_bit == 0 {
n -= 1;
sticky_bit = xs[n];
}
}
1
};
let out = &mut xs[out_offset..];
let ulp_mask = !(ulp - 1);
match rm {
Floor | Down | Exact => {
out[0] &= ulp_mask;
(if sticky_bit | round_bit != 0 { -1 } else { 0 }, false)
}
Ceiling | Up => {
if sticky_bit | round_bit == 0 {
out[0] &= ulp_mask;
(0, false)
} else {
let increment = limbs_slice_add_limb_in_place(out, ulp);
if increment {
out[out_len - 1] = LIMB_HIGH_BIT;
}
out[0] &= ulp_mask;
(1, increment)
}
}
Nearest => {
if round_bit == 0 {
out[0] &= ulp_mask;
(if (sticky_bit | round_bit) != 0 { -1 } else { 0 }, false)
} else if sticky_bit == 0 {
middle_handler(out, ulp)
} else {
let increment = limbs_slice_add_limb_in_place(out, ulp);
if increment {
out[out_len - 1] = LIMB_HIGH_BIT;
}
out[0] &= ulp_mask;
(1, increment)
}
}
}
}
}
pub(crate) fn round_helper_2(xs: &[Limb], err0: i32, prec: u64) -> bool {
let len = xs.len();
assert!(xs.last().unwrap().get_highest_bit());
let mut err = limb_to_bit_count(len);
if err0 <= 0 {
return false;
}
let err0 = u64::from(err0.unsigned_abs());
if err0 <= prec || prec >= err {
return false;
}
err = min(err, err0);
let k = bit_to_limb_count_floor(prec);
let n = bit_to_limb_count_floor(err) - k;
assert!(len > k);
let xs = &xs[len - k - n - 1..];
let (xs_last, xs_init) = xs[..=n].split_last().unwrap();
let mut tmp = *xs_last;
let mask = Limb::MAX >> (prec & Limb::WIDTH_MASK);
tmp &= mask;
if n == 0 {
let s = Limb::WIDTH - (err & Limb::WIDTH_MASK);
assert!(s < Limb::WIDTH);
tmp >>= s;
tmp != 0 && tmp != mask >> s
} else if tmp == 0 {
let (xs_head, xs_tail) = xs_init.split_first().unwrap();
if !slice_test_zero(xs_tail) {
return true;
}
let s = Limb::WIDTH - (err & Limb::WIDTH_MASK);
s != Limb::WIDTH && *xs_head >> s != 0
} else if tmp == mask {
let (xs_head, xs_tail) = xs_init.split_first().unwrap();
if xs_tail.iter().any(|&x| x != Limb::MAX) {
return true;
}
let s = Limb::WIDTH - (err & Limb::WIDTH_MASK);
s != Limb::WIDTH && *xs_head >> s != Limb::MAX >> s
} else {
true
}
}
#[inline]
pub fn limbs_significand_slice_add_limb_in_place(xs: &mut [Limb], y: Limb) -> bool {
limbs_slice_add_limb_in_place(xs, y)
}
const fn is_like_rounding_toward_zero(rm: RoundingMode, neg: bool) -> bool {
match rm {
Down => true,
Up => false,
Floor => !neg,
Ceiling => neg,
_ => panic!(),
}
}
pub fn limbs_round_would_increment(xs: &[Limb], prec: u64, rm: RoundingMode) -> bool {
let x_len = xs.len();
if limb_to_bit_count(x_len) <= prec || rm == Down {
return false;
}
let mut nw = usize::exact_from(prec >> Limb::LOG_WIDTH);
let rw = prec & Limb::WIDTH_MASK;
let mut k = x_len - nw - 1;
let (lomask, himask) = if rw != 0 {
nw += 1;
let lomask = Limb::low_mask(Limb::WIDTH - rw);
(lomask, !lomask)
} else {
(Limb::MAX, Limb::MAX)
};
let mut sb = xs[k] & lomask;
match rm {
Nearest => {
let rbmask = Limb::power_of_2(WIDTH_MINUS_1 - rw);
if sb & rbmask == 0 {
false
} else {
sb &= !rbmask;
while sb == 0 && k > 0 {
k -= 1;
sb = xs[k];
}
if sb == 0 {
xs[x_len - nw] & (himask ^ (himask << 1)) != 0
} else {
true
}
}
}
Up => {
while sb == 0 && k > 0 {
k -= 1;
sb = xs[k];
}
sb != 0
}
_ => unreachable!(),
}
}
pub fn limbs_float_can_round_raw(
xs: &[Limb],
neg: bool,
err: i64,
rnd1: RoundingMode,
rnd2: RoundingMode,
prec: u64,
) -> bool {
assert_ne!(prec, 0);
let mut bn = xs.len();
assert!(xs[bn - 1].get_highest_bit());
let rnd1 = if rnd1 == Nearest {
Nearest
} else if is_like_rounding_toward_zero(rnd1, neg) {
Down
} else {
Up
};
let rnd2 = if rnd2 == Nearest {
Nearest
} else if is_like_rounding_toward_zero(rnd2, neg) {
Down
} else {
Up
};
let iprec = i64::exact_from(prec);
let n1 = i64::from(rnd1 == Nearest);
if err < iprec + n1 || err == iprec + n1 && (rnd1 == Up || rnd2 == Down) {
return false;
}
let err = u64::exact_from(err);
let bits = limb_to_bit_count(bn);
if prec > bits {
return if (rnd1 == rnd2 || rnd2 == Nearest) && err > prec {
!(rnd1 != Down && err == prec + 1 && limbs_is_power_of_2_significand(xs))
} else {
false
};
}
if err > bits {
return if limbs_is_power_of_2_significand(xs) {
if (rnd2 == Down || rnd2 == Up) && rnd1 != rnd2 {
false
} else if rnd1 == Down {
true
} else {
err > prec + 1
}
} else if rnd2 == Nearest {
if err == prec + 1 && xs[0].odd() {
false
} else if prec < bits {
let k1 = usize::exact_from((prec + 1).shr_round(Limb::LOG_WIDTH, Ceiling).0);
let s1 = (prec + 1).neg_mod_power_of_2(Limb::LOG_WIDTH);
if (xs[bn - k1] >> s1).odd() && !limbs_round_would_increment(xs, prec + 1, Up) {
if rnd1 == Nearest {
false
} else {
let k1 = usize::exact_from(prec.shr_round(Limb::LOG_WIDTH, Ceiling).0);
let s1 = prec.neg_mod_power_of_2(Limb::LOG_WIDTH);
(rnd1 == Down) ^ (xs[bn - k1] >> s1).even()
}
} else {
true
}
} else {
true
}
} else {
rnd1 == rnd2 || limbs_round_would_increment(xs, prec, Up)
};
}
let mut k = usize::exact_from((err - 1) >> Limb::LOG_WIDTH);
let s = err.neg_mod_power_of_2(Limb::LOG_WIDTH);
let k1 = usize::exact_from((prec - 1) >> Limb::LOG_WIDTH);
let s1 = prec.neg_mod_power_of_2(Limb::LOG_WIDTH);
k -= k1;
bn -= k1;
let prec2 = prec - limb_to_bit_count(k1);
k += 1;
let mut tmp = vec![0; bn];
if bn > k {
tmp[..bn - k].copy_from_slice(&xs[..bn - k]);
}
let cc;
let eps = Limb::power_of_2(s);
if rnd1 == Down {
cc = (xs[bn - 1] >> s1).odd() ^ limbs_round_would_increment(&xs[..bn], prec2, rnd2);
let mut cy = limbs_add_limb_to_out(&mut tmp[bn - k..bn], &xs[bn - k..bn], eps);
let mut tn = 0;
while tn < k1 && cy {
cy = xs[bn + tn] == Limb::MAX;
tn += 1;
}
if !cy && err == prec {
return false;
}
if cy {
return match rnd2 {
Down => false,
Up => err > prec && k == bn && tmp[0] == 0,
_ => !cc,
};
}
} else if rnd1 == Nearest {
let mut cy = limbs_add_limb_to_out(&mut tmp[bn - k..bn], &xs[bn - k..bn], eps);
let mut tn = 0;
while tn < k1 && cy {
cy = xs[bn + tn] == Limb::MAX;
tn += 1;
}
cc = (tmp[bn - 1] >> s1).odd() ^ limbs_round_would_increment(&tmp[..bn], prec2, rnd2);
if cy {
return match rnd2 {
Down => false,
Up => err > prec + 1 && k == bn && tmp[0] == 0,
_ => err > prec + 1,
};
}
} else {
cc = (xs[bn - 1] >> s1).odd() ^ limbs_round_would_increment(&xs[..bn], prec2, rnd2);
}
if rnd1 != Down {
let mut cy = limbs_sub_limb_to_out(&mut tmp[bn - k..bn], &xs[bn - k..bn], eps);
let mut tmp_hi = tmp[bn - 1];
let mut tn = 0;
while tn < k1 && cy {
let (diff, borrow) = xs[bn + tn].overflowing_sub(Limb::from(cy));
tmp_hi = diff;
cy = borrow;
tn += 1;
}
if tn == k1 && !tmp_hi.get_highest_bit() {
if rnd2 == Down || rnd1 == Nearest && rnd2 == Up || cc {
return false;
}
return limbs_round_would_increment(&tmp[..bn], prec2 + 1, rnd2);
}
if err == prec + u64::from(rnd1 == Nearest) {
return rnd2 == Nearest
&& (xs[bn - 1] >> s1).even()
&& limbs_round_would_increment(&xs[..bn], prec2, Down)
== limbs_round_would_increment(&xs[..bn], prec2, Up);
}
}
let cc2 = (tmp[bn - 1] >> s1).odd();
cc == (cc2 ^ limbs_round_would_increment(&tmp[..bn], prec2, rnd2))
}
fn limbs_is_power_of_2_significand(xs: &[Limb]) -> bool {
let (xs_last, xs_init) = xs.split_last().unwrap();
xs_last.is_power_of_2() && slice_test_zero(xs_init)
}
pub fn float_can_round_raw(
x: &Natural,
neg: bool,
err: i64,
rnd1: RoundingMode,
rnd2: RoundingMode,
prec: u64,
) -> bool {
match x {
Natural(Small(small)) => {
limbs_float_can_round_raw(core::slice::from_ref(small), neg, err, rnd1, rnd2, prec)
}
Natural(Large(xs)) => limbs_float_can_round_raw(xs, neg, err, rnd1, rnd2, prec),
}
}
pub fn limbs_float_round_to_integer(
up: &[Limb],
exp: u64,
prec: u64,
rnd_away: Option<bool>,
ties_away: bool,
) -> (Vec<Limb>, bool, u8, bool) {
let un = up.len();
let rn =
usize::exact_from((prec + prec.neg_mod_power_of_2(Limb::LOG_WIDTH)) >> Limb::LOG_WIDTH);
let mut sh = prec.neg_mod_power_of_2(Limb::LOG_WIDTH);
let mut uflags: u8;
let ui;
let mut idiff = 0;
if (exp - 1) >> Limb::LOG_WIDTH >= u64::exact_from(un) {
ui = un;
uflags = 0; } else {
ui = usize::exact_from((exp - 1) >> Limb::LOG_WIDTH) + 1;
let uj = un - ui; idiff = exp & Limb::WIDTH_MASK; uflags = if idiff == 0 || up[uj] << idiff == 0 {
0
} else {
2
};
if uflags == 0 && !slice_test_zero(&up[..uj]) {
uflags = 2;
}
}
let mut rp = vec![0; rn];
let mut rnd_away = rnd_away;
let rp_offset;
if ui > rn {
rp.copy_from_slice(&up[un - rn..]);
rp_offset = 0;
if rnd_away.is_none() {
rnd_away = Some(if !ties_away && !rp[0].get_bit(sh) {
let (a, b) = if sh != 0 {
(rp[0].mod_power_of_2(sh), Limb::power_of_2(sh - 1))
} else {
(up[un - rn - 1], LIMB_HIGH_BIT)
};
a > b || a == b && !slice_test_zero(&up[..un - rn - usize::from(sh == 0)])
} else if sh != 0 {
rp[0].get_bit(sh - 1)
} else {
up[un - rn - 1].get_highest_bit()
});
}
if uflags == 0
&& (sh != 0 && rp[0] << (Limb::WIDTH - sh) != 0 || !slice_test_zero(&up[..un - rn]))
{
uflags = 1;
}
} else {
let uj = un - ui;
let rj = rn - ui;
rp[rj..].copy_from_slice(&up[uj..]);
rp_offset = rj;
let ush = if idiff == 0 { 0 } else { Limb::WIDTH - idiff };
if rj == 0 && ush < sh {
if uflags == 0 && rp[rj] & (Limb::low_mask(sh) - Limb::low_mask(ush)) != 0 {
uflags = 1;
}
} else {
sh = ush;
}
if rnd_away.is_none() {
rnd_away = Some(if uj == 0 && sh == 0 {
false
} else if !ties_away && !rp[rp_offset].get_bit(sh) {
let (a, b) = if sh != 0 {
(rp[rp_offset].mod_power_of_2(sh), Limb::power_of_2(sh - 1))
} else {
(up[uj - 1], LIMB_HIGH_BIT)
};
a > b || a == b && !slice_test_zero(&up[..uj - usize::from(sh == 0)])
} else if sh != 0 {
rp[rp_offset].get_bit(sh - 1)
} else {
up[uj - 1].get_highest_bit()
});
}
}
if sh != 0 {
rp[rp_offset] &= Limb::MAX << sh;
}
if uflags == 0 {
return (rp, false, 0, false);
}
let rnd_away = rnd_away.unwrap();
let mut exp_increment = false;
if rnd_away && limbs_slice_add_limb_in_place(&mut rp[rp_offset..], Limb::power_of_2(sh)) {
exp_increment = true;
*rp.last_mut().unwrap() = LIMB_HIGH_BIT;
}
(rp, exp_increment, uflags, rnd_away)
}
pub fn with_float_significand_limbs<T, F: FnOnce(&[Limb]) -> T>(x: &Natural, f: F) -> T {
match x {
Natural(Small(small)) => f(core::slice::from_ref(small)),
Natural(Large(xs)) => f(xs),
}
}