#![cfg(feature = "quantile")]
use bitrep::{Mergeable, RelSketch};
struct Rng(u64);
impl Rng {
fn new(seed: u64) -> Self {
Rng(seed | 1)
}
fn next_u64(&mut self) -> u64 {
self.0 ^= self.0 << 13;
self.0 ^= self.0 >> 7;
self.0 ^= self.0 << 17;
self.0.wrapping_mul(0x2545_F491_4F6C_DD1D)
}
fn unit(&mut self) -> f64 {
(self.next_u64() >> 11) as f64 / (1u64 << 53) as f64
}
fn normal(&mut self) -> f64 {
let (u, v) = (self.unit().max(1e-15), self.unit());
(-2.0 * u.ln()).sqrt() * (std::f64::consts::TAU * v).cos()
}
}
fn web_latency(rng: &mut Rng, i: usize) -> f64 {
let mut x = (1.6 + 0.5 * rng.normal()).exp(); if rng.unit() < 0.02 {
x *= 1.0 / rng.unit().max(1e-9).powf(1.0 / 1.3);
}
if i % 10_000 < 50 {
x *= 8.0; }
x
}
fn exact_quantile(sorted: &[f64], q: f64) -> f64 {
let idx = ((q * sorted.len() as f64).ceil() as usize).clamp(1, sorted.len()) - 1;
sorted[idx]
}
#[test]
fn realistic_latency_accuracy_and_size_within_guarantee() {
const N: usize = 2_000_000;
let mut rng = Rng::new(0x1A7E_9C1E);
let data: Vec<f64> = (0..N).map(|i| web_latency(&mut rng, i)).collect();
let mut sorted = data.clone();
sorted.sort_by(f64::total_cmp);
let qs = [0.5, 0.9, 0.95, 0.99, 0.999, 0.9999];
let raw_bytes = data.len() * 8;
println!(
"\n[real-data] realistic web-latency, N = {N} ({} MB raw f64)",
raw_bytes >> 20
);
println!(
" {:>6} | {:>9} | {:>10} {:>10} {:>9}",
"alpha", "guarantee", "buckets", "bytes", "vs raw"
);
for &alpha in &[0.01, 0.001] {
let mut sketch = RelSketch::new(alpha).unwrap();
for &x in &data {
sketch.add(x);
}
let guar = sketch.guaranteed_alpha();
let bytes = sketch.to_bytes().len();
println!(
" {:>6} | {:>9.5} | {:>10} {:>10} {:>8}x",
alpha,
guar,
sketch.bucket_count(),
bytes,
raw_bytes / bytes
);
print!(" quantile rel-err:");
let mut worst = 0.0f64;
for &q in &qs {
let exact = exact_quantile(&sorted, q);
let est = sketch.quantile(q).unwrap();
let rel = (est - exact).abs() / exact.abs();
worst = worst.max(rel);
let label = if q >= 0.999 {
format!("p{:.2}", q * 100.0)
} else {
format!("p{}", (q * 100.0) as u32)
};
print!(" {label}={rel:.5}");
assert!(
rel <= guar * 1.001,
"alpha {alpha}: {label} rel err {rel} exceeds guarantee {guar}"
);
}
println!(" (worst {worst:.5})");
assert!(
bytes < raw_bytes / 100,
"sketch should be >100x smaller than raw"
);
assert_eq!(
sketch.collapse_shift(),
0,
"realistic data must not collapse"
);
}
}
const NASA_HTTP_SIZES: &str = include_str!("data/nasa_http_jul95_sizes.csv");
fn nasa_http_sizes() -> Vec<f64> {
NASA_HTTP_SIZES
.lines()
.filter(|l| !l.trim().is_empty())
.map(|l| {
l.trim()
.parse::<u64>()
.expect("dataset is one integer per line") as f64
})
.collect()
}
#[test]
fn real_nasa_http_accuracy_and_byte_identity() {
let data = nasa_http_sizes();
assert_eq!(data.len(), 6421, "the committed slice must be intact");
let mut sorted = data.clone();
sorted.sort_by(f64::total_cmp);
let n_zeros = sorted.iter().take_while(|&&x| x == 0.0).count();
println!(
"\n[real-data] NASA-HTTP Jul 1995 response sizes, N = {} (min {} B, max {} B, {} zero-byte)",
data.len(),
sorted[0],
sorted[sorted.len() - 1],
n_zeros
);
let qs = [0.5, 0.9, 0.95, 0.99, 0.999];
for &alpha in &[0.01, 0.001] {
let mut sketch = RelSketch::new(alpha).unwrap();
for &x in &data {
sketch.add(x);
}
let guar = sketch.guaranteed_alpha();
let mut worst = 0.0f64;
for &q in &qs {
let exact = exact_quantile(&sorted, q);
if exact == 0.0 {
continue; }
let est = sketch.quantile(q).unwrap();
let rel = (est - exact).abs() / exact.abs();
worst = worst.max(rel);
assert!(
rel <= guar * 1.001,
"alpha {alpha}: p{} rel err {rel} exceeds guarantee {guar}",
(q * 100.0)
);
}
println!(
" alpha {alpha:>6}: guarantee {guar:.5}, {} buckets, {} bytes, worst rel-err {worst:.5}",
sketch.bucket_count(),
sketch.to_bytes().len(),
);
assert_eq!(
sketch.collapse_shift(),
0,
"real HTTP sizes must not collapse"
);
let bytes = sketch.to_bytes();
assert_eq!(RelSketch::from_bytes(&bytes).unwrap(), sketch);
let mut rng = Rng::new(0x4EA1_DA7A ^ alpha.to_bits());
for _ in 0..8 {
let mut d = data.clone();
for i in (1..d.len()).rev() {
let j = (rng.next_u64() % (i as u64 + 1)) as usize;
d.swap(i, j);
}
let mut s = RelSketch::new(alpha).unwrap();
for &x in &d {
s.add(x);
}
assert_eq!(
s.to_bytes(),
bytes,
"reordering real data changed the bytes"
);
let k = 7usize;
let mut shards: Vec<RelSketch> =
(0..k).map(|_| RelSketch::new(alpha).unwrap()).collect();
for (i, &x) in d.iter().enumerate() {
shards[i % k].add(x);
}
let mut merged = RelSketch::new(alpha).unwrap();
for s in shards.iter().rev() {
merged.merge(s);
}
assert_eq!(
merged.to_bytes(),
bytes,
"sharding real data changed the bytes"
);
}
}
}