use crate::natural::InnerNatural::{Large, Small};
use crate::natural::Natural;
use crate::natural::arithmetic::add::{
limbs_slice_add_limb_in_place, limbs_slice_add_same_length_in_place_left,
};
use crate::natural::arithmetic::shl::{limbs_shl_to_out, limbs_slice_shl_in_place};
use crate::natural::arithmetic::shr::limbs_shr_to_out;
use crate::natural::arithmetic::sub::{
limbs_sub_limb_in_place, limbs_sub_same_length_in_place_left,
};
use crate::natural::logic::not::limbs_not_in_place;
use crate::platform::Limb;
use alloc::vec;
use core::cmp::Ordering;
use malachite_base::fail_on_untested_path;
use malachite_base::num::arithmetic::traits::{
CeilingLogBase2, NegAssign, PowerOf2, Sign, WrappingNegAssign,
};
use malachite_base::num::basic::integers::PrimitiveInt;
use malachite_base::num::conversion::traits::ExactFrom;
use malachite_base::num::logic::traits::LeadingZeros;
use malachite_base::rounding_modes::RoundingMode::{self, *};
use malachite_base::slices::{slice_set_zero, slice_test_zero};
const WIDTH: u64 = Limb::WIDTH;
const WIDTH_I64: i64 = WIDTH as i64;
const WIDTH_USIZE: usize = WIDTH as usize;
const WIDTH_M1: u64 = WIDTH - 1;
const WIDTH_P1: u64 = WIDTH + 1;
const EXP_MIN: i64 = i64::MIN;
pub struct FloatSumInput<'a> {
pub sign: bool,
pub exp: i64,
pub prec: u64,
pub significand: &'a Natural,
}
impl FloatSumInput<'_> {
fn limbs(&self) -> &[Limb] {
match self.significand {
Natural(Small(x)) => core::slice::from_ref(x),
Natural(Large(xs)) => xs,
}
}
}
const fn limb_mask(k: u64) -> Limb {
if k == 0 { 0 } else { Limb::MAX >> (WIDTH - k) }
}
fn safe_sub(e: i64, sh: i64) -> i64 {
assert!(e >= i64::MIN + sh);
e - sh
}
#[allow(clippy::too_many_arguments)]
pub(crate) fn sum_raw(
wp: &mut [Limb],
wq: u64,
xs: &[FloatSumInput],
mut minexp: i64,
mut maxexp: i64,
tp: &mut [Limb],
logn: u64,
prec: u64,
) -> (u64, i64, i64, i64) {
let ws = wp.len();
assert!(prec >= 1);
assert_eq!(wq, u64::exact_from(ws) * WIDTH);
assert!(wq >= logn + prec + 2);
loop {
let mut maxexp2 = EXP_MIN;
assert!(maxexp > minexp);
for x in xs {
let xe = x.exp;
let xq = x.prec;
let x_limbs = x.limbs();
let mut vs = x_limbs.len();
let mut vd = xe - i64::exact_from(u64::exact_from(vs) * WIDTH) - minexp;
let mut tr;
let vp_shifted;
let mut vp_offset = 0;
let dp_offset;
if vd < 0 {
if xe <= minexp {
if xe > maxexp2 {
maxexp2 = xe;
}
continue;
}
if xe - i64::exact_from(xq) < minexp {
maxexp2 = minexp;
}
vd.neg_assign();
let vds = usize::exact_from(vd) / WIDTH_USIZE;
vs -= vds;
assert!(vs > 0);
vp_offset += vds;
vd -= i64::exact_from(u64::exact_from(vds) * WIDTH);
assert!((0..WIDTH_I64).contains(&vd));
tr = if xe > maxexp {
vs -= usize::exact_from(xe - maxexp) / WIDTH_USIZE;
assert!(vs > 0);
(xe - maxexp) % WIDTH_I64
} else {
0
};
if vd != 0 {
assert!(vs <= tp.len());
limbs_shr_to_out(tp, &x_limbs[vp_offset..vp_offset + vs], u64::exact_from(vd));
vp_shifted = true;
vp_offset = 0;
tr += vd;
if tr >= WIDTH_I64 {
vs -= 1;
tr -= WIDTH_I64;
}
assert!(vs >= 1);
assert!((0..WIDTH_I64).contains(&tr));
if tr != 0 {
tp[vs - 1] &= limb_mask(WIDTH - u64::exact_from(tr));
tr = 0;
}
} else {
vp_shifted = false;
}
dp_offset = 0;
} else {
let vds = usize::exact_from(vd) / WIDTH_USIZE;
if vds >= ws {
continue;
}
dp_offset = vds;
vd -= i64::exact_from(u64::exact_from(vds) * WIDTH);
assert!((0..WIDTH_I64).contains(&vd));
tr = if xe > maxexp {
let skip = usize::exact_from(xe - maxexp) / WIDTH_USIZE;
if skip >= vs {
continue;
}
vs -= skip;
(xe - maxexp) % WIDTH_I64
} else {
0
};
assert!((0..WIDTH_I64).contains(&tr) && vs > 0);
if vd != 0 {
assert!(vs <= tp.len());
let carry = limbs_shl_to_out(
tp,
&x_limbs[vp_offset..vp_offset + vs],
u64::exact_from(vd),
);
tr -= vd;
if tr < 0 {
tr += WIDTH_I64;
assert!(vs < tp.len());
tp[vs] = carry;
vs += 1;
}
assert!((0..WIDTH_I64).contains(&tr));
vp_shifted = true;
vp_offset = 0;
} else {
vp_shifted = false;
}
}
let ds = ws - dp_offset;
assert!(vs > 0 && vs <= ds);
if tr != 0 {
vs -= 1;
}
let vp: &[Limb] = if vp_shifted {
tp
} else {
&x_limbs[vp_offset..]
};
let dp = &mut wp[dp_offset..];
if x.sign {
let mut carry = Limb::from(
vs > 0 && limbs_slice_add_same_length_in_place_left(&mut dp[..vs], &vp[..vs]),
);
if tr != 0 {
carry += vp[vs] & limb_mask(WIDTH - u64::exact_from(tr));
}
if ds > vs {
limbs_slice_add_limb_in_place(&mut dp[vs..], carry);
}
} else {
let mut borrow = Limb::from(
vs > 0 && limbs_sub_same_length_in_place_left(&mut dp[..vs], &vp[..vs]),
);
if tr != 0 {
borrow += vp[vs] & limb_mask(WIDTH - u64::exact_from(tr));
}
if ds > vs {
limbs_sub_limb_in_place(&mut dp[vs..], borrow);
}
}
}
let a = if wp[ws - 1] >> WIDTH_M1 != 0 {
Limb::MAX
} else {
0
};
let mut cancel = 0;
let mut wi = ws;
while wi > 0 {
let b = wp[wi - 1];
if b == a {
cancel += WIDTH;
wi -= 1;
} else {
cancel += LeadingZeros::leading_zeros(b ^ a);
break;
}
}
if wi > 0 || a != 0 {
assert!(cancel > 0);
let e = minexp + i64::exact_from(wq - cancel);
assert!(e >= minexp);
let err = maxexp2.saturating_add(i64::exact_from(logn));
if maxexp2 == EXP_MIN || (err <= e && u64::exact_from(e - err) >= prec) {
return (cancel, e, minexp, maxexp2);
}
let diffexp = if err > e { err - e } else { 0 };
assert!(u64::exact_from(diffexp) < cancel - 2);
let shiftq = cancel - 2 - u64::exact_from(diffexp);
assert!(shiftq > 0);
let shifts = usize::exact_from(shiftq) / WIDTH_USIZE;
let shiftc = shiftq % WIDTH;
wp.copy_within(0..ws - shifts, shifts);
if shiftc != 0 {
limbs_slice_shl_in_place(&mut wp[shifts..], shiftc);
}
slice_set_zero(&mut wp[..shifts]);
minexp = safe_sub(minexp, i64::exact_from(shiftq));
assert!(minexp < maxexp2);
} else if maxexp2 == EXP_MIN {
return (0, 0, minexp, maxexp2);
} else {
minexp = safe_sub(maxexp2, i64::exact_from(wq - (logn + 1)));
}
maxexp = maxexp2;
}
}
pub enum FloatSumResult {
Zero,
Regular {
sign: bool,
exp: i64,
significand: Natural,
o: Ordering,
},
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
enum Tmd {
None,
Machine,
Midpoint,
}
const fn is_like_floor(rm: RoundingMode, sign: bool) -> bool {
match rm {
Floor => true,
Down => sign,
Up => !sign,
_ => false,
}
}
const fn is_like_ceiling(rm: RoundingMode, sign: bool) -> bool {
match rm {
Ceiling => true,
Down => !sign,
Up => sign,
_ => false,
}
}
pub fn sum_float_significands(xs: &[FloatSumInput], prec: u64, rm: RoundingMode) -> FloatSumResult {
let n = xs.len();
assert!(n >= 3);
assert_ne!(rm, Exact);
let maxexp = xs.iter().map(|x| x.exp).max().unwrap();
let logn = u64::exact_from(n).ceiling_log_base_2();
assert!(logn >= 2);
let sq = prec;
let cq = logn + 1;
let ws = usize::exact_from((cq + sq + logn + 2).div_ceil(WIDTH));
let wq = u64::exact_from(ws) * WIDTH;
assert!(wq - cq - sq >= 4);
let zs = usize::exact_from((wq - sq).div_ceil(WIDTH));
let ts = usize::exact_from((wq - cq + WIDTH - 1).div_ceil(WIDTH));
let mut buf = vec![0; ts + ws];
let (tp, wp) = buf.split_at_mut(ts);
let minexp0 = safe_sub(maxexp, i64::exact_from(wq - cq));
assert!(wq >= logn + sq + 5);
let (cancel, mut e, minexp, maxexp) = sum_raw(wp, wq, xs, minexp0, maxexp, tp, logn, sq + 3);
if cancel == 0 {
return FloatSumResult::Zero;
}
let u = e - i64::exact_from(sq);
let neg = wp[ws - 1] >> WIDTH_M1 != 0;
let sign = !neg;
let lbit;
let mut rbit;
let mut inex: i8;
let tmd;
if u > minexp {
let tq = u64::exact_from(u - minexp);
let mut wi = usize::exact_from(tq) / WIDTH_USIZE;
let td = tq % WIDTH;
lbit = (wp[wi] >> td) & 1;
rbit = if td >= 1 {
(wp[wi] >> (td - 1)) & 1
} else {
assert!(wi >= 1);
wp[wi - 1] >> WIDTH_M1
};
assert!(rbit == 0 || rbit == 1);
(inex, tmd) = if maxexp == EXP_MIN {
let inex_exact = if rbit == 0 || (rm == Nearest && lbit == 0) {
let mut in_ex = if td >= 2 {
wp[wi] & limb_mask(td - 1) != 0
} else if td == 0 {
assert!(wi >= 1);
wi -= 1;
wp[wi] & limb_mask(WIDTH_M1) != 0
} else {
false
};
if !in_ex {
in_ex = !slice_test_zero(&wp[..wi]);
if !in_ex && rbit != 0 {
assert_eq!(rm, Nearest);
in_ex = true;
rbit = 0;
}
}
i8::from(in_ex)
} else {
1
};
(inex_exact, Tmd::None)
} else {
let d = u - maxexp.saturating_add(i64::exact_from(logn));
assert!(d >= 3);
let mut d = u64::exact_from(d);
let (mut limb, mut mask, nbits) = if td == 0 {
assert!(wi >= 1);
wi -= 1;
(wp[wi], limb_mask(WIDTH_M1), WIDTH)
} else if td == 1 {
let limb = if wi >= 1 {
wi -= 1;
wp[wi]
} else {
0
};
(limb, Limb::MAX, WIDTH_P1)
} else {
(wp[wi], limb_mask(td - 1), td)
};
if nbits > d {
limb >>= nbits - d;
mask >>= nbits - d;
d = 0;
} else {
d -= nbits;
}
limb &= mask;
let mut t = if limb == 0 {
if rbit == 0 {
Tmd::Machine
} else if rm == Nearest {
Tmd::Midpoint
} else {
Tmd::None
}
} else if limb == mask {
limb = Limb::MAX;
if rbit != 0 {
Tmd::Machine
} else if rm == Nearest {
Tmd::Midpoint
} else {
Tmd::None
}
} else {
Tmd::None
};
while t != Tmd::None && d != 0 {
if wi == 0 {
if limb != 0 {
t = Tmd::None;
}
break;
}
wi -= 1;
let limb2 = wp[wi];
if d < WIDTH {
let c = WIDTH - d;
assert!(c > 0 && c < WIDTH);
if (limb2 >> c) != (limb >> c) {
t = Tmd::None;
}
break;
}
if limb2 != limb {
t = Tmd::None;
}
d -= WIDTH;
}
(1, t)
};
} else {
lbit = if u == minexp { wp[0] & 1 } else { 0 };
rbit = 0;
inex = i8::from(maxexp != EXP_MIN);
tmd = if maxexp == EXP_MIN {
Tmd::None
} else {
Tmd::Machine
};
}
assert!(rbit == 0 || rbit == 1);
let corr: i8;
if tmd == Tmd::None {
corr = match rm {
Floor => 0,
Ceiling => inex,
Down => i8::from(inex != 0 && neg),
Up => i8::from(inex != 0 && !neg),
Nearest => i8::exact_from(rbit),
Exact => unreachable!(),
};
assert!(corr == 0 || corr == 1);
if inex != 0 && corr == 0 {
inex = -1;
}
} else {
assert!(maxexp > EXP_MIN);
let mut zp = vec![0; zs];
let zq = u64::exact_from(zs) * WIDTH;
let err = maxexp.saturating_add(i64::exact_from(logn));
let minexp2 = if err >= minexp {
let tq = u64::exact_from(err - minexp) + 2;
assert!(tq >= 2);
let mut wi = usize::exact_from(tq) / WIDTH_USIZE;
let td = tq % WIDTH;
let (zz, minexp2) = if td != 0 {
wi += 1;
let td = WIDTH - td;
let zz = zs - wi;
assert!(zz < zs);
limbs_shl_to_out(&mut zp[zz..], &wp[..wi], td);
(
zz,
safe_sub(minexp, i64::exact_from(u64::exact_from(zz) * WIDTH + td)),
)
} else {
fail_on_untested_path("sum_float_significands, TMD copy with td == 0");
assert!(wi > 0);
let zz = zs - wi;
assert!(zz < zs);
zp[zz..zz + wi].copy_from_slice(&wp[..wi]);
(
zz,
safe_sub(minexp, i64::exact_from(u64::exact_from(zz) * WIDTH)),
)
};
slice_set_zero(&mut zp[..zz]);
assert_eq!(minexp2, err + 2 - i64::exact_from(zq));
minexp2
} else {
let minexp2 = safe_sub(maxexp, i64::exact_from(zq - cq));
assert_eq!(minexp2, err + 1 - i64::exact_from(zq));
minexp2
};
let (cancel2, ..) = sum_raw(&mut zp, zq, xs, minexp2, maxexp, tp, logn, 1);
let sst: i8 = if cancel2 != 0 {
if zp[zs - 1] >> WIDTH_M1 == 0 { 1 } else { -1 }
} else if tmd == Tmd::Machine {
0
} else {
assert_eq!(rm, Nearest);
if lbit != 0 { 1 } else { -1 }
};
inex = if is_like_floor(rm, sign) {
if sst != 0 { -1 } else { 0 }
} else if is_like_ceiling(rm, sign) {
if sst != 0 { 1 } else { 0 }
} else {
assert_eq!(rm, Nearest);
if tmd == Tmd::Machine { -sst } else { sst }
};
corr = if tmd == Tmd::Midpoint && sst == (if rbit != 0 { -1 } else { 1 }) {
1 - i8::exact_from(rbit)
} else if is_like_floor(rm, sign) && sst == -1 {
i8::exact_from(rbit) - 1
} else if is_like_ceiling(rm, sign) && sst == 1 {
i8::exact_from(rbit) + 1
} else {
i8::exact_from(rbit)
};
}
assert!((-1..=2).contains(&corr));
let sn = usize::exact_from(sq.div_ceil(WIDTH));
let sd = u64::exact_from(sn) * WIDTH - sq;
let sh = cancel % WIDTH;
let mut sump = vec![0; sn];
assert!(sd < WIDTH);
if u > minexp {
let wi = usize::exact_from(u - minexp) / WIDTH_USIZE;
if sh != 0 {
let fi = usize::exact_from(e - minexp) / WIDTH_USIZE - (sn - 1);
assert!(fi == wi || fi == wi + 1);
limbs_shl_to_out(&mut sump, &wp[fi..fi + sn], sh);
if fi != wi {
sump[0] |= wp[wi] >> (WIDTH - sh);
}
} else {
assert_eq!(u64::exact_from(ws - (wi + sn)) * WIDTH, cancel);
sump.copy_from_slice(&wp[wi..wi + sn]);
}
} else {
let en = usize::exact_from(e - minexp + WIDTH_I64 - 1) / WIDTH_USIZE;
if sh != 0 {
limbs_shl_to_out(&mut sump[sn - en..], &wp[..en], sh);
} else if en > 0 {
sump[sn - en..].copy_from_slice(&wp[..en]);
}
slice_set_zero(&mut sump[..sn - en]);
}
const HIGH_BIT: Limb = 1 << (WIDTH - 1);
if sq == 1 {
sump[0] = HIGH_BIT;
e += i64::from(if neg { 1 - corr } else { corr });
} else if neg {
assert_eq!(sump[sn - 1] >> WIDTH_M1, 0);
if corr <= 1 {
let corr2 = Limb::exact_from(1 - i64::from(corr)).wrapping_shl(u32::exact_from(sd));
sump[0] = (!(sump[0] | limb_mask(sd))).wrapping_add(corr2);
if sump[0] < corr2 || (corr2 == 0 && corr < 0) {
let all_zero = sn == 1 || slice_test_zero(&sump[1..]);
if !all_zero {
let mut i = 1;
while sump[i] == 0 {
i += 1;
}
sump[i].wrapping_neg_assign();
limbs_not_in_place(&mut sump[i + 1..]);
}
if all_zero {
sump[sn - 1] |= HIGH_BIT;
e += 1;
}
} else if sn > 1 {
limbs_not_in_place(&mut sump[1..]);
}
} else {
let corr2 = Limb::power_of_2(sd);
let c = !(sump[0] | limb_mask(sd));
sump[0] = c.wrapping_sub(corr2);
let mut i = 1;
if c == 0 {
i += sump[1..].iter().position(|&l| l != Limb::MAX).unwrap();
sump[i] = (!sump[i]).wrapping_sub(1);
i += 1;
}
if i < sn {
limbs_not_in_place(&mut sump[i..]);
} else if sump[sn - 1] >> WIDTH_M1 == 0 {
sump[sn - 1] |= HIGH_BIT;
e -= 1;
}
}
} else {
assert!(sump[sn - 1] >> WIDTH_M1 != 0);
sump[0] &= !limb_mask(sd);
if corr > 0 {
let corr2 = Limb::exact_from(u8::exact_from(corr)) << sd;
let carry_out = if corr2 != 0 {
limbs_slice_add_limb_in_place(&mut sump, corr2)
} else {
assert!(sn > 1);
limbs_slice_add_limb_in_place(&mut sump[1..], 1)
};
assert_eq!(sump[sn - 1] >> WIDTH_M1 != 0, !carry_out);
if carry_out {
sump[sn - 1] |= HIGH_BIT;
e += 1;
}
}
if corr < 0 {
limbs_sub_limb_in_place(&mut sump, Limb::power_of_2(sd));
if sump[sn - 1] >> WIDTH_M1 == 0 {
sump[sn - 1] |= HIGH_BIT;
e -= 1;
}
}
}
assert!(sump[sn - 1] >> WIDTH_M1 != 0);
FloatSumResult::Regular {
sign,
exp: e,
significand: Natural::from_owned_limbs_asc(sump),
o: inex.sign(),
}
}