use num_bigint::BigInt;
use num_traits::{One, ToPrimitive, Zero};
use std::cmp::Ordering;
use crate::binary::{Binary, Bounds, FiniteBounds, UXBinary, XBinary};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PrefixBounds {
pub mantissa: BigInt,
pub exponent: BigInt,
}
impl PrefixBounds {
pub fn new(mantissa: BigInt, exponent: BigInt) -> Self {
Self { mantissa, exponent }
}
pub fn to_finite_bounds(&self) -> FiniteBounds {
bounds_from_normalized(self.mantissa.clone(), self.exponent.clone())
}
pub fn midpoint(&self) -> Binary {
Binary::new(&self.mantissa * 2 + 1, self.exponent.clone() - 1)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum PrefixBisectionResult {
Narrowed(PrefixBounds),
Exact(Binary),
}
pub fn bisection_step_normalized<C>(bounds: &PrefixBounds, compare: C) -> PrefixBisectionResult
where
C: FnOnce(&Binary) -> Ordering,
{
let mid = bounds.midpoint();
match compare(&mid) {
Ordering::Less => {
PrefixBisectionResult::Narrowed(PrefixBounds {
mantissa: &bounds.mantissa * 2 + 1,
exponent: bounds.exponent.clone() - 1,
})
}
Ordering::Greater => {
PrefixBisectionResult::Narrowed(PrefixBounds {
mantissa: &bounds.mantissa * 2,
exponent: bounds.exponent.clone() - 1,
})
}
Ordering::Equal => PrefixBisectionResult::Exact(mid),
}
}
pub fn midpoint(lower: &Binary, upper: &Binary) -> Binary {
FiniteBounds::new(lower.clone(), upper.clone()).midpoint()
}
pub fn bounds_from_normalized(mantissa: BigInt, exponent: BigInt) -> FiniteBounds {
use crate::binary::UBinary;
use num_bigint::BigUint;
let lower = Binary::new(mantissa, exponent.clone());
let width = UBinary::new(BigUint::one(), exponent);
FiniteBounds::from_lower_and_width(lower, width)
}
pub fn normalize_bounds(
bounds: &FiniteBounds,
) -> Result<FiniteBounds, crate::error::ComputableError> {
use num_traits::Signed;
let lower = bounds.small();
let width_ubinary = bounds.width();
use num_bigint::BigUint;
use num_traits::One;
let width_bits = width_ubinary.mantissa().bits();
let is_already_normalized = *width_ubinary.mantissa() == BigUint::one()
&& (lower.exponent() == width_ubinary.exponent() || lower.mantissa().is_zero());
let target_exp = if is_already_normalized {
width_ubinary.exponent().clone()
} else {
width_ubinary.exponent() + BigInt::from(width_bits) + BigInt::one()
};
let shift = lower.exponent() - &target_exp;
let lower_mantissa = if shift.is_zero() {
lower.mantissa().clone()
} else if shift.is_positive() {
let shift_amount = shift
.magnitude()
.to_usize()
.ok_or(crate::error::ComputableError::InfiniteBounds)?;
lower.mantissa() << shift_amount
} else {
let shift_amount = shift
.magnitude()
.to_usize()
.ok_or(crate::error::ComputableError::InfiniteBounds)?;
lower.mantissa() >> shift_amount
};
Ok(bounds_from_normalized(lower_mantissa, target_exp))
}
const NORMALIZATION_PRECISION_THRESHOLD: usize = 64;
fn normalize_zero_crossing_bounds(
bounds: &FiniteBounds,
) -> Result<FiniteBounds, crate::error::ComputableError> {
use num_traits::Signed;
let lower = bounds.small();
let upper = &bounds.hi();
let width_ubinary = bounds.width();
let width_bits = width_ubinary.mantissa().bits();
let target_exp = width_ubinary.exponent() + BigInt::from(width_bits) + BigInt::one();
let lo_shift = lower.exponent() - &target_exp;
let lo_mantissa = if lo_shift.is_zero() {
lower.mantissa().clone()
} else if lo_shift.is_positive() {
let n = lo_shift
.magnitude()
.to_usize()
.ok_or(crate::error::ComputableError::InfiniteBounds)?;
lower.mantissa() << n
} else {
let n = lo_shift
.magnitude()
.to_usize()
.ok_or(crate::error::ComputableError::InfiniteBounds)?;
lower.mantissa() >> n
};
let hi_shift = upper.exponent() - &target_exp;
let hi_mantissa = if hi_shift.is_zero() {
upper.mantissa().clone()
} else if hi_shift.is_positive() {
let n = hi_shift
.magnitude()
.to_usize()
.ok_or(crate::error::ComputableError::InfiniteBounds)?;
upper.mantissa() << n
} else {
let n = hi_shift
.magnitude()
.to_usize()
.ok_or(crate::error::ComputableError::InfiniteBounds)?;
-((-upper.mantissa()) >> n)
};
let lo = Binary::new(lo_mantissa, target_exp.clone());
let hi = Binary::new(hi_mantissa, target_exp);
Ok(FiniteBounds::new(lo, hi))
}
pub fn normalize_finite_to_bounds(
bounds: &FiniteBounds,
) -> Result<Bounds, crate::error::ComputableError> {
use num_traits::Signed;
let lower_bits = crate::sane::bits_as_usize(bounds.small().mantissa().magnitude().bits());
let upper_bits = crate::sane::bits_as_usize(bounds.large().mantissa().magnitude().bits());
let total_precision = crate::sane_arithmetic!(lower_bits, upper_bits; lower_bits + upper_bits);
let needs_normalization =
total_precision > NORMALIZATION_PRECISION_THRESHOLD && !bounds.width().mantissa().is_zero();
if needs_normalization {
let crosses_zero =
bounds.small().mantissa().is_negative() && bounds.large().mantissa().is_positive();
let normalized = if crosses_zero {
normalize_zero_crossing_bounds(bounds)?
} else {
normalize_bounds(bounds)?
};
Ok(Bounds::from_lower_and_width(
XBinary::Finite(normalized.small().clone()),
UXBinary::Finite(normalized.width().clone()),
))
} else {
Ok(Bounds::from_lower_and_width(
XBinary::Finite(bounds.small().clone()),
UXBinary::Finite(bounds.width().clone()),
))
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::test_utils::bin;
#[test]
fn midpoint_of_integers() {
let lower = bin(2, 0);
let upper = bin(4, 0);
let mid = midpoint(&lower, &upper);
assert_eq!(mid, bin(3, 0));
}
#[test]
fn midpoint_of_fractions() {
let lower = bin(1, -1); let upper = bin(3, -1); let mid = midpoint(&lower, &upper);
assert_eq!(mid, bin(1, 0)); }
#[test]
fn bisection_step_less() {
let bounds = PrefixBounds::new(BigInt::from(0), BigInt::from(2));
let result = bisection_step_normalized(&bounds, |_mid| {
Ordering::Less
});
match result {
PrefixBisectionResult::Narrowed(new_bounds) => {
assert_eq!(new_bounds.mantissa, BigInt::from(1));
assert_eq!(new_bounds.exponent, BigInt::from(1));
let finite = new_bounds.to_finite_bounds();
assert_eq!(finite.small(), &bin(2, 0));
assert_eq!(finite.large(), bin(4, 0));
}
PrefixBisectionResult::Exact(_) => panic!("expected Narrowed"),
}
}
#[test]
fn bisection_step_greater() {
let bounds = PrefixBounds::new(BigInt::from(0), BigInt::from(2));
let result = bisection_step_normalized(&bounds, |_mid| {
Ordering::Greater
});
match result {
PrefixBisectionResult::Narrowed(new_bounds) => {
assert_eq!(new_bounds.mantissa, BigInt::from(0));
assert_eq!(new_bounds.exponent, BigInt::from(1));
let finite = new_bounds.to_finite_bounds();
assert_eq!(finite.small(), &bin(0, 0));
assert_eq!(finite.large(), bin(2, 0));
}
PrefixBisectionResult::Exact(_) => panic!("expected Narrowed"),
}
}
#[test]
fn bisection_step_equal() {
let bounds = PrefixBounds::new(BigInt::from(0), BigInt::from(2));
let result = bisection_step_normalized(&bounds, |_mid| Ordering::Equal);
match result {
PrefixBisectionResult::Exact(mid) => {
assert_eq!(mid, bin(2, 0));
}
PrefixBisectionResult::Narrowed(_) => panic!("expected Exact"),
}
}
#[test]
fn bisection_finds_sqrt_4() {
let target = bin(4, 0);
let mut bounds = PrefixBounds::new(BigInt::from(0), BigInt::from(2));
for _ in 0..50 {
match bisection_step_normalized(&bounds, |mid| mid.mul(mid).cmp(&target)) {
PrefixBisectionResult::Narrowed(new_bounds) => bounds = new_bounds,
PrefixBisectionResult::Exact(mid) => {
assert_eq!(mid, bin(2, 0));
return;
}
}
}
panic!("should have found exact match for sqrt(4)");
}
#[test]
fn bisection_narrows_sqrt_2() {
let target = bin(2, 0);
let mut bounds = PrefixBounds::new(BigInt::from(1), BigInt::from(0));
let initial_lower = bin(1, 0);
let initial_upper = bin(2, 0);
for _ in 0..10 {
match bisection_step_normalized(&bounds, |mid| mid.mul(mid).cmp(&target)) {
PrefixBisectionResult::Narrowed(new_bounds) => bounds = new_bounds,
PrefixBisectionResult::Exact(_) => {
panic!("sqrt(2) is irrational, should not find exact match");
}
}
}
let finite = bounds.to_finite_bounds();
assert!(finite.small() > &initial_lower);
assert!(finite.large() < initial_upper);
let sqrt_2_approx = bin(1414, -10); assert!(finite.small() <= &sqrt_2_approx || finite.large() >= sqrt_2_approx);
}
#[test]
fn bisection_respects_iterations() {
let mut bounds = PrefixBounds::new(BigInt::from(0), BigInt::from(10));
for _ in 0..5 {
match bisection_step_normalized(&bounds, |_mid| Ordering::Less) {
PrefixBisectionResult::Narrowed(new_bounds) => bounds = new_bounds,
PrefixBisectionResult::Exact(_) => panic!("unexpected exact"),
}
}
assert_eq!(bounds.exponent, BigInt::from(5));
let finite = bounds.to_finite_bounds();
let width = finite.large() - finite.small().clone();
assert_eq!(width, bin(32, 0));
}
#[test]
fn bounds_from_normalized_creates_correct_width() {
use num_bigint::BigUint;
let bounds = super::bounds_from_normalized(BigInt::from(3 << 9), BigInt::from(-10));
assert_eq!(bounds.small(), &bin(3, -1));
assert_eq!(bounds.width().mantissa(), &BigUint::from(1u32));
assert_eq!(bounds.width().exponent(), &BigInt::from(-10));
assert_eq!(bounds.large(), bin((3 << 9) + 1, -10));
}
#[test]
fn bounds_from_normalized_with_integer_lower() {
use num_bigint::BigUint;
let bounds = super::bounds_from_normalized(BigInt::from(5 << 8), BigInt::from(-8));
assert_eq!(bounds.small(), &bin(5, 0));
assert_eq!(bounds.width().mantissa(), &BigUint::from(1u32));
assert_eq!(bounds.width().exponent(), &BigInt::from(-8));
assert_eq!(bounds.large(), bin((5 << 8) + 1, -8));
}
#[test]
fn prefix_bounds_can_be_used_for_bisection() {
let bounds = PrefixBounds::new(BigInt::from(1 << 10), BigInt::from(-10));
let target = bin(5, -2); let result = bisection_step_normalized(&bounds, |mid| mid.cmp(&target));
match result {
PrefixBisectionResult::Narrowed(new_bounds) => {
assert_eq!(new_bounds.exponent, BigInt::from(-11));
}
PrefixBisectionResult::Exact(_) => panic!("expected Narrowed"),
}
}
#[test]
fn normalize_bounds_contains_original_simple() {
use num_bigint::BigUint;
let original = FiniteBounds::new(bin(1, 0), bin(2, 0));
let normalized = normalize_bounds(&original).expect("normalization failed");
assert!(normalized.small() <= original.small());
assert!(normalized.large() >= original.large());
assert_eq!(normalized.width().mantissa(), &BigUint::from(1u32));
}
#[test]
fn normalize_bounds_contains_original_fractional() {
use num_bigint::BigUint;
let original = FiniteBounds::new(bin(1, -2), bin(3, -2));
let normalized = normalize_bounds(&original).expect("normalization failed");
assert!(normalized.small() <= original.small());
assert!(normalized.large() >= original.large());
assert_eq!(normalized.width().mantissa(), &BigUint::from(1u32));
}
#[test]
fn normalize_bounds_contains_original_mixed_exponents() {
use num_bigint::BigUint;
let original = FiniteBounds::new(bin(5, 0), bin(11, -1));
let normalized = normalize_bounds(&original).expect("normalization failed");
assert!(normalized.small() <= original.small());
assert!(normalized.large() >= original.large());
assert_eq!(normalized.width().mantissa(), &BigUint::from(1u32));
}
#[test]
fn normalize_bounds_contains_original_large_mantissas() {
use num_bigint::BigUint;
let original = FiniteBounds::new(bin(123, -5), bin(125, -5));
let normalized = normalize_bounds(&original).expect("normalization failed");
assert!(normalized.small() <= original.small());
assert!(normalized.large() >= original.large());
assert_eq!(normalized.width().mantissa(), &BigUint::from(1u32));
}
#[test]
fn normalize_bounds_contains_original_negative() {
use num_bigint::BigUint;
let original = FiniteBounds::new(bin(-3, 0), bin(-1, 0));
let normalized = normalize_bounds(&original).expect("normalization failed");
assert!(normalized.small() <= original.small());
assert!(normalized.large() >= original.large());
assert_eq!(normalized.width().mantissa(), &BigUint::from(1u32));
}
#[test]
fn normalize_bounds_preserves_already_normalized() {
use num_bigint::BigUint;
let original = FiniteBounds::new(bin(5, -3), bin(6, -3));
let normalized = normalize_bounds(&original).expect("normalization failed");
assert_eq!(normalized.small(), original.small());
assert_eq!(normalized.large(), original.large());
assert_eq!(normalized.width().mantissa(), &BigUint::from(1u32));
assert_eq!(normalized.width().exponent(), &BigInt::from(-3));
}
#[test]
fn normalize_bounds_is_idempotent() {
let test_cases = vec![
FiniteBounds::new(bin(1, 0), bin(4, 0)), FiniteBounds::new(bin(123, -5), bin(125, -5)), FiniteBounds::new(bin(-10, 0), bin(-5, 0)), FiniteBounds::new(bin(7, -2), bin(9, -2)), ];
for original in test_cases {
let normalized_once = normalize_bounds(&original).expect("first normalization failed");
let normalized_twice =
normalize_bounds(&normalized_once).expect("second normalization failed");
assert_eq!(
normalized_once.small(),
normalized_twice.small(),
"Idempotency failed for lower bound of {:?}",
original
);
assert_eq!(
normalized_once.large(),
normalized_twice.large(),
"Idempotency failed for upper bound of {:?}",
original
);
}
}
#[test]
fn normalize_finite_to_bounds_skips_low_precision() {
let original = FiniteBounds::new(bin(3, 0), bin(5, 0)); let result = normalize_finite_to_bounds(&original).expect("should succeed");
assert_eq!(result.small(), &XBinary::Finite(original.small().clone()));
assert_eq!(result.large(), XBinary::Finite(original.hi()));
}
#[test]
fn normalize_finite_to_bounds_skips_zero_width() {
let point = bin(1, -100); let original = FiniteBounds::point(point.clone());
let result = normalize_finite_to_bounds(&original).expect("should succeed");
assert_eq!(result.small(), &XBinary::Finite(original.small().clone()));
assert_eq!(result.large(), XBinary::Finite(original.hi()));
}
#[test]
fn normalize_finite_to_bounds_normalizes_zero_crossing() {
let lo = bin(-((1i64 << 40) + 1), -40); let hi = bin((1i64 << 40) + 1, -40); let original = FiniteBounds::new(lo, hi);
let result = normalize_finite_to_bounds(&original).expect("should succeed");
let result_lo = match result.small() {
XBinary::Finite(b) => b,
_ => panic!("expected finite lower"),
};
let result_hi = match &result.large() {
XBinary::Finite(b) => b.clone(),
_ => panic!("expected finite upper"),
};
assert!(
result_lo <= original.small(),
"normalized lower {} should be <= original lower {}",
result_lo,
original.small()
);
assert!(
result_hi >= original.hi(),
"normalized upper {} should be >= original upper {}",
result_hi,
original.hi()
);
assert!(
result_lo.mantissa().magnitude().bits() < 10,
"normalized lower mantissa should be small, got {} bits",
result_lo.mantissa().magnitude().bits()
);
assert!(
result_hi.mantissa().magnitude().bits() < 10,
"normalized upper mantissa should be small, got {} bits",
result_hi.mantissa().magnitude().bits()
);
}
#[test]
fn normalize_finite_to_bounds_normalizes_high_precision() {
let lo = bin((1i64 << 39) + 1, -50); let hi = bin((1i64 << 39) + 3, -50); let original = FiniteBounds::new(lo, hi);
let result = normalize_finite_to_bounds(&original).expect("should succeed");
let result_lo = match result.small() {
XBinary::Finite(b) => b,
_ => panic!("expected finite lower"),
};
let result_hi = match &result.large() {
XBinary::Finite(b) => b.clone(),
_ => panic!("expected finite upper"),
};
assert!(
result_lo <= original.small(),
"normalized lower {} should be <= original lower {}",
result_lo,
original.small()
);
assert!(
result_hi >= original.hi(),
"normalized upper {} should be >= original upper {}",
result_hi,
original.hi()
);
}
#[test]
fn normalize_finite_to_bounds_normalizes_high_precision_negative() {
let lo = bin(-((1i64 << 39) + 3), -50);
let hi = bin(-((1i64 << 39) + 1), -50);
let original = FiniteBounds::new(lo, hi);
let result = normalize_finite_to_bounds(&original).expect("should succeed");
let result_lo = match result.small() {
XBinary::Finite(b) => b,
_ => panic!("expected finite lower"),
};
let result_hi = match &result.large() {
XBinary::Finite(b) => b.clone(),
_ => panic!("expected finite upper"),
};
assert!(
result_lo <= original.small(),
"normalized lower {} should be <= original lower {}",
result_lo,
original.small()
);
assert!(
result_hi >= original.hi(),
"normalized upper {} should be >= original upper {}",
result_hi,
original.hi()
);
}
}