use pamoja_core::{Error, Result};
fn write_uvarint(mut value: u64, out: &mut Vec<u8>) {
loop {
let mut byte = (value & 0x7f) as u8;
value >>= 7;
if value != 0 {
byte |= 0x80;
}
out.push(byte);
if value == 0 {
break;
}
}
}
fn read_uvarint(bytes: &[u8], pos: &mut usize) -> Result<u64> {
let mut result = 0u64;
let mut shift = 0u32;
loop {
let byte = *bytes
.get(*pos)
.ok_or_else(|| Error::Codec("truncated varint".into()))?;
*pos += 1;
result |= u64::from(byte & 0x7f) << shift;
if byte & 0x80 == 0 {
break;
}
shift += 7;
if shift >= 64 {
return Err(Error::Codec("varint is too long".into()));
}
}
Ok(result)
}
fn zigzag(value: i64) -> u64 {
((value << 1) ^ (value >> 63)) as u64
}
fn unzigzag(value: u64) -> i64 {
((value >> 1) as i64) ^ -((value & 1) as i64)
}
pub fn encode_deltas(samples: &[i64]) -> Vec<u8> {
let mut out = Vec::new();
write_uvarint(samples.len() as u64, &mut out);
let mut previous = 0i64;
for &sample in samples {
let delta = sample.wrapping_sub(previous);
write_uvarint(zigzag(delta), &mut out);
previous = sample;
}
out
}
pub fn decode_deltas(bytes: &[u8]) -> Result<Vec<i64>> {
let mut pos = 0;
let count = read_uvarint(bytes, &mut pos)?;
let mut samples = Vec::new();
let mut previous = 0i64;
for _ in 0..count {
let delta = unzigzag(read_uvarint(bytes, &mut pos)?);
let sample = previous.wrapping_add(delta);
samples.push(sample);
previous = sample;
}
Ok(samples)
}
#[derive(Clone, Copy, Debug)]
pub struct Quantizer {
scale: f32,
}
impl Quantizer {
pub fn new(scale: f32) -> Self {
Self { scale }
}
pub fn encode(&self, readings: &[f32]) -> Vec<u8> {
let samples: Vec<i64> = readings
.iter()
.map(|&reading| (reading * self.scale).round() as i64)
.collect();
encode_deltas(&samples)
}
pub fn decode(&self, bytes: &[u8]) -> Result<Vec<f32>> {
let samples = decode_deltas(bytes)?;
Ok(samples
.iter()
.map(|&sample| sample as f32 / self.scale)
.collect())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn an_integer_batch_round_trips() {
for samples in [
vec![],
vec![0],
vec![42],
vec![-5, -4, -3, -2],
vec![1000, 1001, 1003, 1002, 999],
vec![i64::MIN, 0, i64::MAX],
] {
let bytes = encode_deltas(&samples);
assert_eq!(decode_deltas(&bytes).expect("decode"), samples);
}
}
#[test]
fn a_slow_series_is_far_smaller_than_raw() {
let samples: Vec<i64> = (0..100).map(|i| 5000 + i).collect();
let bytes = encode_deltas(&samples);
assert!(bytes.len() < samples.len() * 2);
}
#[test]
fn truncated_bytes_are_a_codec_error() {
let result = decode_deltas(&[3]);
assert!(matches!(result, Err(Error::Codec(_))));
}
#[test]
fn varints_cover_the_boundaries() {
for value in [0u64, 1, 127, 128, 16_383, 16_384, u64::MAX] {
let mut out = Vec::new();
write_uvarint(value, &mut out);
let mut pos = 0;
assert_eq!(read_uvarint(&out, &mut pos).expect("read"), value);
assert_eq!(pos, out.len());
}
}
#[test]
fn varints_match_the_canonical_leb128_encodings() {
let cases: [(u64, &[u8]); 4] = [
(0, &[0x00]),
(1, &[0x01]),
(128, &[0x80, 0x01]),
(300, &[0xAC, 0x02]),
];
for (value, encoded) in cases {
let mut out = Vec::new();
write_uvarint(value, &mut out);
assert_eq!(out, encoded, "varint of {value}");
}
}
#[test]
fn zigzag_matches_the_protobuf_mapping() {
let cases: [(i64, u64); 6] = [
(0, 0),
(-1, 1),
(1, 2),
(-2, 3),
(2, 4),
(2_147_483_647, 4_294_967_294),
];
for (signed, unsigned) in cases {
assert_eq!(zigzag(signed), unsigned, "zigzag of {signed}");
assert_eq!(unzigzag(unsigned), signed, "unzigzag of {unsigned}");
}
}
#[test]
fn a_quantizer_round_trips_within_its_precision() {
let quantizer = Quantizer::new(100.0);
let readings = [4.0, 4.62, 5.13, 4.77, 3.98];
let packed = quantizer.encode(&readings);
let restored = quantizer.decode(&packed).expect("decode");
for (original, decoded) in readings.iter().zip(&restored) {
assert!((original - decoded).abs() <= 0.005 + f32::EPSILON);
}
}
}