use crate::{
bint::{intrinsics::ExpType, UInt},
decimal::{
dec::{construct::construct, math::add::add, ExtraPrecision},
signals::Signals,
Context, Decimal, Sign,
},
};
type D<const N: usize> = Decimal<N>;
const EXTRA_PRECISION_DIGITS: ExpType = ExtraPrecision::EXTRA_PRECISION_DIGITS;
#[inline(always)]
pub(crate) const fn extend_scale_to<const N: usize>(mut d: D<N>, new_scale: i16) -> D<N> {
if new_scale > d.cb.get_scale() {
rescale(&mut d, new_scale)
}
d
}
#[inline(always)]
pub(crate) const fn rescale<const N: usize>(d: &mut D<N>, new_scale: i16) {
if d.cb.is_special() {
d.cb.signaling_nan();
return;
}
let scale = d.cb.get_scale();
if new_scale > scale {
rescale_up(d, new_scale);
} else if new_scale < scale {
rescale_down(d, new_scale);
}
}
#[inline]
pub(crate) const fn quantum<const N: usize>(exp: i32, ctx: Context) -> D<N> {
construct(
UInt::ONE,
exp,
Sign::Plus,
Signals::empty(),
ctx,
ExtraPrecision::new(),
)
}
#[inline(never)]
pub(crate) const fn reduce<const N: usize>(mut d: D<N>) -> D<N> {
if d.cb.is_special() {
return d.signaling_nan();
}
if d.has_extra_precision() {
return d;
}
if d.digits.is_zero() {
d.cb.set_scale(0);
} else {
let mut digits;
let mut remainder;
while !d.digits.is_zero() {
(digits, remainder) = d.digits.div_rem_digit(10);
if remainder == 0 {
if d.cb.get_scale() > i16::MIN {
d.digits = digits;
d.cb.dec_scale(1);
} else {
return d.raise_signals(Signals::OP_SUBNORMAL);
}
} else {
break;
}
}
}
d
}
#[inline]
pub(crate) const fn quantize<const N: usize>(mut d: D<N>, other: D<N>) -> D<N> {
if d.is_infinite() && other.is_infinite() {
d
} else if d.cb.is_special() || other.cb.is_special() {
d.signaling_nan()
} else {
let scale = other.cb.get_scale();
rescale(&mut d, scale);
d.cb.quiet_signals(Signals::OP_UNDERFLOW);
if d.cb.get_scale() != scale {
d.signaling_nan()
} else {
d
}
}
}
#[inline(never)]
const fn rescale_up<const N: usize>(d: &mut D<N>, new_scale: i16) {
debug_assert!(new_scale > d.cb.get_scale());
let power = (new_scale as i32 - d.cb.get_scale() as i32) as u32;
if d.digits.is_zero() {
d.cb.set_scale(new_scale);
let mut extra_precision = d.cb.get_extra_precision();
if let Some(correction) = extra_precision.scale_up(power) {
correct(d, correction);
}
return d.cb.set_extra_precision(extra_precision);
}
let remaining_decimal_digits = d.digits.remaining_decimal_digits();
if remaining_decimal_digits == 0 {
d.cb.raise_signals(Signals::OP_CLAMPED);
} else if remaining_decimal_digits < power {
d.cb.raise_signals(Signals::OP_CLAMPED);
rescale_up_unchecked(d, remaining_decimal_digits);
} else {
rescale_up_unchecked(d, power);
}
}
#[inline(never)]
const fn rescale_down<const N: usize>(d: &mut D<N>, new_scale: i16) {
debug_assert!(new_scale < d.cb.get_scale());
d.cb.raise_signals(Signals::OP_ROUNDED);
let extra_precision = d.cb.get_extra_precision();
let power = (d.cb.get_scale() - new_scale) as u32;
let decimal_digits = d.digits.decimal_digits();
if decimal_digits <= power {
if !d.digits.is_zero() || !extra_precision.is_zero() {
d.cb.raise_signals(Signals::OP_INEXACT);
}
let extra_precision_shift = power - decimal_digits;
if extra_precision_shift >= EXTRA_PRECISION_DIGITS {
d.cb.reset_extra_precision();
} else {
let new_extra_precision =
make_extra_precision(d.digits, decimal_digits, extra_precision_shift);
d.cb.set_extra_precision(new_extra_precision);
}
d.digits = UInt::ZERO;
} else {
let divisor = UInt::power_of_ten(power);
let extra;
(d.digits, extra) = d.digits.div_rem(divisor);
if !extra.is_zero() || !extra_precision.is_zero() {
d.cb.raise_signals(Signals::OP_INEXACT);
let mut new_extra_precision = make_extra_precision(extra, power, 0);
new_extra_precision.push_back(extra_precision);
d.cb.set_extra_precision(new_extra_precision);
}
}
d.cb.dec_scale(power);
}
#[inline(always)]
const fn rescale_up_unchecked<const N: usize>(d: &mut D<N>, gap: u32) {
debug_assert!(gap <= d.digits.remaining_decimal_digits());
#[allow(unsafe_code)]
{
d.digits = unsafe { d.digits.unchecked_mul(UInt::<N>::power_of_ten(gap)) };
}
d.cb.inc_scale(gap);
let mut extra_precision = d.cb.get_extra_precision();
if let Some(correction) = extra_precision.scale_up(gap) {
correct(d, correction);
}
d.cb.set_extra_precision(extra_precision);
}
#[inline]
const fn correct<const N: usize>(d: &mut D<N>, mut correction: D<N>) {
correction.set_sign(d.sign());
correction.cb.inc_scale(d.cb.get_scale() as ExpType);
*d = add(*d, correction);
}
#[inline(always)]
const fn make_extra_precision<const N: usize>(
digits: UInt<N>,
decimal_digits: ExpType,
shift: ExpType,
) -> ExtraPrecision {
let power = decimal_digits.saturating_sub(EXTRA_PRECISION_DIGITS - shift);
let digits = if power > 0 {
let scale = UInt::power_of_ten(power);
let extra = digits.div(scale);
debug_assert!(extra.last_digit_index() == 0);
extra.digits()[0]
} else {
debug_assert!(digits.last_digit_index() == 0);
digits.digits()[0]
};
ExtraPrecision::from_digits(digits, decimal_digits - power + shift)
}