use dashu::{
base::Sign,
integer::{IBig, UBig},
rational::RBig,
};
use opendp_derive::proven;
#[cfg(test)]
mod test;
use crate::{
error::Fallible,
traits::{ExactIntCast, Float, InfCast, InfSqrt},
};
#[proven(proof_path = "measurements/noise/nature/float/utilities/find_nearest_multiple_of_2k.tex")]
pub fn find_nearest_multiple_of_2k(x: RBig, k: i32) -> IBig {
let (num, den) = x_mul_2k(x, -k).into_parts();
(floor_div(num << 1, den) + 1) >> 1usize
}
#[proven(proof_path = "measurements/noise/nature/float/utilities/floor_div.tex")]
fn floor_div(a: IBig, b: UBig) -> IBig {
if Sign::Positive == a.sign() {
a / b
} else {
(a - &b + 1) / b
}
}
#[proven(proof_path = "measurements/noise/nature/float/utilities/get_min_k.tex")]
pub(crate) fn get_min_k<T: Float>() -> i32
where
i32: ExactIntCast<T::Bits>,
{
-i32::exact_int_cast(T::EXPONENT_BIAS).unwrap() - i32::exact_int_cast(T::MANTISSA_BITS).unwrap()
+ 1
}
#[proven(proof_path = "measurements/noise/nature/float/utilities/get_rounding_distance.tex")]
pub fn get_rounding_distance<T: Float, const P: usize>(
k: i32,
size: Option<usize>,
) -> Fallible<RBig>
where
i32: ExactIntCast<T::Bits>,
{
let k_min = get_min_k::<T>();
if k < k_min {
return fallible!(FailedFunction, "k ({k}) must not be smaller than {k_min}");
}
let input_gran = x_mul_2k(RBig::ONE, k_min);
let output_gran = x_mul_2k(RBig::ONE, k);
let mut distance = output_gran - input_gran;
if !distance.is_zero() {
let size = size.ok_or_else(|| {
err!(
MakeMeasurement,
"domain size must be known if discretization is not exact"
)
})?;
distance *= match P {
1 => RBig::from(size),
2 => RBig::try_from(f64::inf_cast(size)?.inf_sqrt()?)?,
_ => return fallible!(MakeMeasurement, "norm ({P}) must be one or two"),
}
}
Ok(distance)
}
#[proven(proof_path = "measurements/noise/nature/float/utilities/x_mul_2k.tex")]
pub fn x_mul_2k(x: RBig, k: i32) -> RBig {
let (mut num, mut den) = x.into_parts();
if k < 0 {
den <<= -k as usize;
} else {
num <<= k as usize;
}
RBig::from_parts(num, den)
}
pub fn integerize_scale(scale: f64, k: i32) -> Fallible<RBig> {
if k == i32::MIN {
return fallible!(MakeTransformation, "k ({k}) must not be i32::MIN");
}
let scale = RBig::try_from(scale)
.map_err(|_| err!(MakeTransformation, "scale ({scale}) must be finite"))?;
Ok(x_mul_2k(scale, -k))
}