base2histogram 0.2.3

A Rust histogram library using base-2 logarithmic bucketing for fast percentile estimation
Documentation
use super::log_scale::LogScale;

/// A reference to a bucket's geometry in a log scale.
///
/// Provides access to left, right, width, and midpoint without carrying
/// histogram data (count). Created by [`LogScale::bucket_span`].
#[derive(Debug, Clone, Copy)]
pub struct BucketSpan<'a> {
    log_scale: &'a LogScale,
    index: usize,
}

impl<'a> BucketSpan<'a> {
    pub(crate) fn new(log_scale: &'a LogScale, index: usize) -> Self {
        Self { log_scale, index }
    }

    /// Bucket index.
    #[inline]
    pub fn index(&self) -> usize {
        self.index
    }

    /// Left (inclusive) boundary.
    #[inline]
    pub fn left(&self) -> u64 {
        self.log_scale.bucket_min_values[self.index]
    }

    /// Returns `true` if this is the terminal bucket that contains `u64::MAX`.
    #[inline]
    pub fn is_last(&self) -> bool {
        self.index + 1 == self.log_scale.bucket_min_values.len()
    }

    /// Upper boundary.
    ///
    /// This is exclusive for every bucket except the last one.
    /// The terminal bucket returns `u64::MAX` and includes that value.
    #[inline]
    pub fn right(&self) -> u64 {
        if !self.is_last() {
            self.log_scale.bucket_min_values[self.index + 1]
        } else {
            u64::MAX
        }
    }

    /// Number of integer values represented by this bucket.
    #[inline]
    pub fn width(&self) -> u64 {
        let span = self.right() - self.left();
        if self.is_last() { span + 1 } else { span }
    }

    /// Midpoint of the bucket's value range.
    #[inline]
    pub fn midpoint(&self) -> u64 {
        self.left() + (self.right() - self.left()) / 2
    }
}

#[cfg(test)]
mod tests {
    use crate::histogram::scale::LogScale;

    #[test]
    fn test_bucket_span() {
        let scale = LogScale::get(3);

        // [0,1): width=1, midpoint=0
        let b = scale.bucket_span(0);
        assert_eq!(
            (b.index(), b.left(), b.right(), b.width(), b.midpoint()),
            (0, 0, 1, 1, 0)
        );

        // [5,6): width=1, midpoint=5
        let b = scale.bucket_span(5);
        assert_eq!(
            (b.index(), b.left(), b.right(), b.width(), b.midpoint()),
            (5, 5, 6, 1, 5)
        );

        // [8,10): width=2, midpoint=9
        let b = scale.bucket_span(8);
        assert_eq!(
            (b.index(), b.left(), b.right(), b.width(), b.midpoint()),
            (8, 8, 10, 2, 9)
        );

        // [16,20): width=4, midpoint=18
        let b = scale.bucket_span(12);
        assert_eq!(
            (b.index(), b.left(), b.right(), b.width(), b.midpoint()),
            (12, 16, 20, 4, 18)
        );

        // [32,40): width=8, midpoint=36
        let b = scale.bucket_span(16);
        assert_eq!(
            (b.index(), b.left(), b.right(), b.width(), b.midpoint()),
            (16, 32, 40, 8, 36)
        );

        // Last bucket (251): [0b111<<61, u64::MAX]
        let b = scale.bucket_span(251);
        assert_eq!(b.index(), 251);
        assert_eq!(b.left(), 0b111 << 61);
        assert_eq!(b.right(), u64::MAX);
        assert!(b.is_last());
        assert_eq!(b.width(), u64::MAX - (0b111 << 61) + 1);
        assert_eq!(b.midpoint(), (0b111 << 61) + (u64::MAX - (0b111 << 61)) / 2);
    }
}