pub const FRACTIONAL_BITS: u32 = 16;
pub const ONE: u64 = 1 << FRACTIONAL_BITS;
pub const LOG2_E: u64 = 94_548;
pub const fn from_bits(bits: u32) -> u64 {
(bits as u64) << FRACTIONAL_BITS
}
pub const fn to_bits(value: u64) -> u32 {
(value >> FRACTIONAL_BITS) as u32
}
pub const fn floor_log2(x: u64) -> u64 {
assert!(x != 0, "log2 is undefined at zero");
let integer_part = 63 - x.leading_zeros() as u64;
let mut result = integer_part << FRACTIONAL_BITS;
let mut mantissa: u128 = if integer_part >= 32 {
(x as u128) >> (integer_part - 32)
} else {
(x as u128) << (32 - integer_part)
};
let mut bit = 0;
while bit < FRACTIONAL_BITS {
mantissa = (mantissa * mantissa) >> 32;
if mantissa >= 1 << 33 {
mantissa >>= 1;
result |= 1 << (FRACTIONAL_BITS - 1 - bit);
}
bit += 1;
}
result
}
pub const fn ceil_log2(x: u64) -> u64 {
if x.is_power_of_two() {
from_bits(x.trailing_zeros())
} else {
floor_log2(x) + 1
}
}
pub const fn bits_per_query(log_blowup: u32, field_bits: u64) -> u64 {
if log_blowup == 0 || field_bits == 0 {
return 0;
}
let uncorrected = from_bits(log_blowup);
let numerator = field_bits + LOG2_E + uncorrected;
let correction = ceil_log2(numerator) - floor_log2(field_bits);
uncorrected.saturating_sub(correction)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::fri::{FriRegime, conjectured_error};
use crate::shape::InstanceShape;
#[test]
fn log2_brackets_the_true_logarithm() {
let mut x = 1u64;
while x < u64::MAX / 3 {
for candidate in [x, x + 1, x * 3 / 2] {
let truth = (candidate as f64).log2() * ONE as f64;
let floor = floor_log2(candidate) as f64;
let ceil = ceil_log2(candidate) as f64;
assert!(
floor <= truth + 1.0,
"floor_log2({candidate}) overstates log2"
);
assert!(
truth - floor < 2.0,
"floor_log2({candidate}) is more than 1 ulp low"
);
assert!(
ceil + 1.0 >= truth,
"ceil_log2({candidate}) understates log2"
);
assert!(
ceil - truth < 2.0,
"ceil_log2({candidate}) is more than 1 ulp high"
);
}
x *= 2;
}
}
#[test]
fn log2_is_exact_on_powers_of_two() {
for exponent in 0..64u32 {
assert_eq!(floor_log2(1 << exponent), from_bits(exponent));
assert_eq!(ceil_log2(1 << exponent), from_bits(exponent));
}
}
#[test]
fn bits_per_query_never_overstates_the_random_words_rate() {
for log_blowup in 1..=8u32 {
for field_bits in [from_bits(64), from_bits(96), from_bits(128), from_bits(192)] {
let rho = 2f64.powi(-(log_blowup as i32));
let eta = (core::f64::consts::LOG2_E + f64::from(log_blowup)) * rho
/ (field_bits as f64 / ONE as f64);
let truth = -(rho + eta).log2() * ONE as f64;
let actual = bits_per_query(log_blowup, field_bits) as f64;
assert!(
actual <= truth,
"bits_per_query({log_blowup}, {field_bits}) overstates the rate"
);
assert!(
truth - actual < 2.0,
"bits_per_query({log_blowup}, {field_bits}) is more than 1 ulp low"
);
}
}
}
#[test]
fn bits_per_query_is_zero_without_a_blowup() {
assert_eq!(bits_per_query(0, from_bits(128)), 0);
}
#[test]
fn bits_per_query_matches_fri_conjectured_error_per_query_rate() {
for log_blowup in 1..=8usize {
let regime = FriRegime {
log_blowup,
num_queries: 1,
log_final_poly_len: 0,
max_log_arity: 0,
commit_pow_bits: 0,
query_pow_bits: 0,
};
let shape = InstanceShape {
log_trace_length: 20,
modulus_bits: 128,
collision_resistance: 128,
num_batched_functions: 1,
};
let f64_bits = conjectured_error(®ime, &shape).bits();
let fixed_bits = bits_per_query(log_blowup as u32, from_bits(128)) as f64 / ONE as f64;
assert!(
fixed_bits <= f64_bits + 1e-9,
"log_blowup={log_blowup}: fixed overstates f64"
);
assert!(
f64_bits - fixed_bits < 2.0 / ONE as f64 + 1e-9,
"log_blowup={log_blowup}: fixed is more than 1 ulp below f64"
);
}
}
}