Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions consensus/src/processes/sync/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ use itertools::Itertools;
use kaspa_consensus_core::errors::sync::{SyncManagerError, SyncManagerResult};
use kaspa_database::prelude::StoreResultExt;
use kaspa_hashes::Hash;
use kaspa_math::uint::malachite_base::num::arithmetic::traits::CeilingLogBase2;
use parking_lot::RwLock;

use crate::model::{
Expand Down Expand Up @@ -141,7 +140,7 @@ impl<
return Err(SyncManagerError::LowHashHigherThanHighHash(low, high));
}

let mut locator = Vec::with_capacity((high_index - low_index).ceiling_log_base_2() as usize);
let mut locator = Vec::with_capacity(kaspa_math::ceil_log_2(high_index - low_index) as usize);
let mut step = 1;
let mut current_index = high_index;
while current_index > low_index {
Expand Down
84 changes: 84 additions & 0 deletions math/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,16 @@ construct_uint!(Uint256, 4);
construct_uint!(Uint320, 5);
construct_uint!(Uint3072, 48);

/// Returns the ceiling of the base-2 logarithm of `x`, i.e. the smallest `k` such that `2^k >= x`.
///
/// # Panics
/// Panics if `x` is 0 (the base-2 logarithm of 0 is undefined).
#[inline]
pub const fn ceil_log_2(x: u64) -> u64 {
// power of two -> floor; not a power of two -> floor + 1
x.ilog2() as u64 + (!x.is_power_of_two()) as u64
}
Comment on lines +18 to +20

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You mentioned branchless, note that this isn't actually branchless, as ilog2 panics if x == 0
see assembly: https://godbolt.org/z/E8GasceMx

This function is basically the same as:

pub fn ceil_log_2(x: u64) -> u32 {
    assert!(x > 0);
    u64::BITS - (x - 1).leading_zeros()
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The original one also panicked, we can either panic, return Option, or make the function return 0 on a 0 input

@biryukovmaxim biryukovmaxim Jun 14, 2026

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we have only one caller of the function, which never passes zero. returning zero sounds fine to me


#[derive(thiserror::Error, Debug)]
pub enum Error {
#[error("{0:?}")]
Expand Down Expand Up @@ -187,3 +197,77 @@ mod tests {
assert_eq!(r / newr, expected);
}
}

#[cfg(test)]
mod ceil_log_2_tests {
use crate::ceil_log_2;

/// Independent reference: the smallest `k` such that `2^k >= x`. Computed in `u128` so it
/// stays correct for `x` near `u64::MAX` (where the answer is 64).
fn oracle(x: u64) -> u64 {
assert!(x != 0);
let mut k = 0u64;
while (1u128 << k) < x as u128 {
k += 1;
}
k
}

/// A spread of inputs exercising the dense low range, every power-of-2 boundary, and the top.
fn sample_inputs() -> impl Iterator<Item = u64> {
let dense = 1..=8192u64;
let boundaries = (0..64u32).flat_map(|k| {
let p = 1u64 << k;
// p-1 (clamped away from 0), p, p+1
[p.saturating_sub(1).max(1), p, p + 1]
});
dense.chain(boundaries).chain([u64::MAX])
}

#[test]
fn known_values() {
for (x, expected) in [
(1u64, 0u64),
(2, 1),
(3, 2),
(4, 2),
(5, 3),
(7, 3),
(8, 3),
(9, 4),
(1023, 10),
(1024, 10),
(1025, 11),
(1u64 << 63, 63),
((1u64 << 63) + 1, 64),
(u64::MAX, 64),
] {
assert_eq!(ceil_log_2(x), expected, "ceil_log_2({x})");
}
}

#[test]
fn correctness_matches_oracle() {
for x in sample_inputs() {
assert_eq!(ceil_log_2(x), oracle(x), "x={x}");
}
}

#[test]
fn compatibility_matches_malachite() {
// Direct equivalence with the malachite `CeilingLogBase2` this replaced. Tied to the
// malachite dependency; remove alongside it once `mod_inverse` is migrated off malachite.
// Permanent correctness coverage is provided by `correctness_matches_oracle`.
use crate::uint::malachite_base::num::arithmetic::traits::CeilingLogBase2;
for x in sample_inputs() {
assert_eq!(ceil_log_2(x), x.ceiling_log_base_2(), "x={x}");
}
}

#[test]
#[should_panic]
fn panics_on_zero() {
// Matches malachite's "Cannot take the base-2 logarithm of 0." panic.
let _ = ceil_log_2(0);
}
}