use super::error::ConversionError;
use rug::Integer;
const PROBE_BYTES: f64 = (1u64 << 20) as f64;
pub(crate) fn log2_l1_norm<'a>(coeffs: impl IntoIterator<Item = &'a Integer>) -> f64 {
let mut l1 = Integer::new();
for c in coeffs {
if *c < 0 {
l1 -= c;
} else {
l1 += c;
}
}
log2_int(&l1)
}
fn log2_int(v: &Integer) -> f64 {
if v.is_zero() {
return 0.0;
}
let bits = v.significant_bits();
if bits <= 1000 {
v.to_f64().abs().log2()
} else {
let shift = bits - 64;
let top = Integer::from(v.abs_ref()) >> shift;
top.to_f64().log2() + f64::from(shift)
}
}
pub(crate) fn power_term_bound(k: usize, n: u32, cap: f64) -> f64 {
if k <= 1 {
return 1.0_f64.min(cap);
}
let n = f64::from(n);
let mut acc = 1.0_f64;
for i in 1..k {
acc = acc * (n + i as f64) / i as f64;
if acc >= cap {
return cap;
}
}
acc
}
pub(crate) fn check_power_size(
terms: f64,
coeff_bits: f64,
per_term: u64,
) -> Result<(), ConversionError> {
if coeff_bits > crate::budget::MAX_INTEGER_BITS as f64 {
return Err(ConversionError::ExponentTooLarge);
}
let per_coeff = (coeff_bits / 64.0).ceil() * 8.0 + per_term as f64;
let bytes = terms.max(1.0) * per_coeff;
if bytes < PROBE_BYTES {
return Ok(());
}
if crate::budget::memory::physical_memory().is_none() && bytes > FALLBACK_CEILING_BYTES {
return Err(ConversionError::ExponentTooLarge);
}
crate::budget::preflight_bytes(bytes as u64).map_err(|_| ConversionError::ExponentTooLarge)
}
const FALLBACK_CEILING_BYTES: f64 = (1u64 << 38) as f64;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn l1_norm_bits() {
let cs = [Integer::from(1), Integer::from(-1), Integer::from(2)];
assert!((log2_l1_norm(&cs) - 2.0).abs() < 1e-12);
assert_eq!(log2_l1_norm(&[] as &[Integer]), 0.0);
let big = Integer::from(1) << 5000;
assert!((log2_l1_norm([&big]) - 5000.0).abs() < 1e-9);
}
#[test]
fn term_bound_is_the_multinomial_count() {
assert_eq!(power_term_bound(2, 10, f64::INFINITY), 11.0);
assert_eq!(power_term_bound(3, 2, f64::INFINITY), 6.0);
assert_eq!(power_term_bound(1, 1 << 30, f64::INFINITY), 1.0);
assert_eq!(power_term_bound(50, u32::MAX, 1e12), 1e12);
}
#[test]
fn small_powers_pass_and_absurd_ones_do_not() {
assert!(check_power_size(1000.0, 1000.0, 8).is_ok());
let n = f64::from(1u32 << 21);
assert_eq!(
check_power_size(n + 1.0, n, 8),
Err(ConversionError::ExponentTooLarge)
);
assert_eq!(
check_power_size(1.0, 2.0 * crate::budget::MAX_INTEGER_BITS as f64, 8),
Err(ConversionError::ExponentTooLarge)
);
}
#[test]
fn the_active_budget_is_honoured() {
assert!(check_power_size(4096.0, 8192.0, 0).is_ok());
let _g = crate::budget::enter_with_memory(crate::budget::Budget::default(), Some(1 << 20));
assert_eq!(
check_power_size(4096.0, 8192.0, 0),
Err(ConversionError::ExponentTooLarge)
);
}
}