use bitrep::SumF64;
#[derive(Clone, Default)]
struct FloatGCounter {
entries: Vec<Option<SumF64>>, naive: Vec<f64>, }
impl FloatGCounter {
fn new(n: usize) -> Self {
FloatGCounter {
entries: vec![None; n],
naive: vec![0.0; n],
}
}
fn add(&mut self, me: usize, x: f64) {
self.entries[me].get_or_insert_with(SumF64::new).add(x);
self.naive[me] += x;
}
fn join(&mut self, other: &FloatGCounter) {
for (i, e) in other.entries.iter().enumerate() {
if let Some(theirs) = e {
let take = match &self.entries[i] {
None => true,
Some(mine) => theirs.count() > mine.count(),
};
if take {
self.entries[i] = Some(theirs.clone());
self.naive[i] = other.naive[i];
}
}
}
}
fn value_acc(&self) -> SumF64 {
let mut total = SumF64::new();
for e in self.entries.iter().flatten() {
total.merge(e);
}
total
}
fn state_bytes(&self) -> Vec<u8> {
let mut out = Vec::new();
for e in &self.entries {
match e {
Some(a) => out.extend_from_slice(&a.to_bytes()),
None => out.push(0),
}
}
out
}
}
struct Rng(u64);
impl Rng {
fn next(&mut self) -> u64 {
self.0 ^= self.0 << 13;
self.0 ^= self.0 >> 7;
self.0 ^= self.0 << 17;
self.0
}
fn f64_hostile(&mut self) -> f64 {
let s = self.next();
match s % 5 {
0 => f64::from_bits(self.next() % 4503599627370496), 1 => ((self.next() >> 11) as f64 / (1u64 << 53) as f64 - 0.5) * 1e100,
_ => {
let mant = (self.next() >> 11) as f64 / (1u64 << 53) as f64 - 0.5;
let exp = (self.next() % 80) as i32 - 40;
mant * 2f64.powi(exp)
}
}
}
}
fn main() {
const REPLICAS: usize = 8;
const SCHEDULES: usize = 300;
let mut naive_disagreements = 0usize;
for schedule in 0..SCHEDULES {
let mut rng = Rng(0xA0761D6478BD642F ^ (schedule as u64).wrapping_mul(0x9E37));
let mut reps: Vec<FloatGCounter> = (0..REPLICAS)
.map(|_| FloatGCounter::new(REPLICAS))
.collect();
let mut truth = SumF64::new();
for _ in 0..400 {
let r = (rng.next() % REPLICAS as u64) as usize;
match rng.next() % 4 {
0 | 1 => {
let x = rng.f64_hostile();
reps[r].add(r, x);
truth.add(x);
if rng.next() % 6 == 0 {
reps[r].add(r, -x);
truth.add(-x);
}
}
2 => {
let to = (rng.next() % REPLICAS as u64) as usize;
let snap = reps[r].clone();
reps[to].join(&snap);
}
_ => {
let to = (rng.next() % REPLICAS as u64) as usize;
let snap = reps[r].clone();
reps[to].join(&snap);
reps[to].join(&snap); }
}
}
for _ in 0..2 {
for i in 0..REPLICAS {
for j in 0..REPLICAS {
if i != j {
let snap = reps[j].clone();
reps[i].join(&snap);
}
}
}
}
let bytes0 = reps[0].state_bytes();
let total0 = reps[0].value_acc();
for r in &reps {
assert_eq!(r.state_bytes(), bytes0, "replica state diverged");
assert_eq!(
r.value_acc().to_bytes(),
total0.to_bytes(),
"merged total diverged"
);
}
assert_eq!(
total0.value().to_bits(),
truth.value().to_bits(),
"converged value != exactly rounded sum of all adds"
);
let fwd: f64 = reps[0].naive.iter().sum();
let rev: f64 = reps[0].naive.iter().rev().sum();
if fwd.to_bits() != rev.to_bits() {
naive_disagreements += 1;
}
}
println!(
"{SCHEDULES} chaotic gossip schedules (dupes, stale snapshots, partitions): \
all {REPLICAS} replicas byte-identical, every total == exact sum: OK"
);
println!(
"naive f64 contrast: summing the same converged entries fwd vs rev \
disagreed in {naive_disagreements}/{SCHEDULES} schedules — the exactness is load-bearing"
);
println!("\nPROBE RESULT: LANDS — float counter CRDT survives the torture.");
}