use crate::coding::{checked_count, checked_decode_count, zigzag_decode, zigzag_encode};
use crate::error::{EncodingError, Result};
#[derive(Debug, Clone, Copy)]
pub struct DeltaOfDeltaEncoder;
#[derive(Debug, Clone, Copy)]
pub struct DeltaOfDeltaDecoder;
impl DeltaOfDeltaEncoder {
pub fn encode(timestamps: &[i64]) -> Result<Vec<u8>> {
if timestamps.is_empty() {
return Err(EncodingError::EmptyInput {
context: "delta-of-delta encoder",
});
}
let mut writer = BitWriter::with_capacity(8 + timestamps.len());
let count = checked_count(timestamps.len())?;
writer.write_bits(u64::from(count), 32);
writer.write_bits(timestamps[0] as u64, 64);
if timestamps.len() == 1 {
return Ok(writer.finish());
}
let first_delta = timestamps[1].wrapping_sub(timestamps[0]);
writer.write_bits(first_delta as u64, 64);
let mut prev_delta = first_delta;
for i in 2..timestamps.len() {
let delta = timestamps[i].wrapping_sub(timestamps[i - 1]);
let dod = delta.wrapping_sub(prev_delta);
encode_dod(&mut writer, dod);
prev_delta = delta;
}
Ok(writer.finish())
}
}
fn encode_dod(writer: &mut BitWriter, dod: i64) {
match dod {
0 => {
writer.write_bit(false);
}
-64..=63 => {
writer.write_bits(0b10, 2);
writer.write_bits(zigzag_encode(dod), 7);
}
-256..=255 => {
writer.write_bits(0b110, 3);
writer.write_bits(zigzag_encode(dod), 9);
}
-2048..=2047 => {
writer.write_bits(0b1110, 4);
writer.write_bits(zigzag_encode(dod), 12);
}
_ => {
writer.write_bits(0b1111, 4);
writer.write_bits(dod as u64, 64);
}
}
}
impl DeltaOfDeltaDecoder {
pub fn decode(data: &[u8]) -> Result<Vec<i64>> {
if data.len() < 4 {
return Err(EncodingError::CorruptData {
detail: "delta-of-delta data too short for header".to_string(),
});
}
let mut reader = BitReader::new(data);
let count = checked_decode_count(reader.read_bits(32)? as u32 as usize, "delta")?;
if count == 0 {
return Ok(Vec::new());
}
let mut result = Vec::with_capacity(count);
let first = reader.read_bits(64)? as i64;
result.push(first);
if count == 1 {
return Ok(result);
}
let first_delta = reader.read_bits(64)? as i64;
result.push(first.wrapping_add(first_delta));
let mut prev_delta = first_delta;
for _ in 2..count {
let dod = decode_dod(&mut reader)?;
let delta = prev_delta.wrapping_add(dod);
let value = result[result.len() - 1].wrapping_add(delta);
result.push(value);
prev_delta = delta;
}
Ok(result)
}
}
fn decode_dod(reader: &mut BitReader) -> Result<i64> {
if !reader.read_bit()? {
return Ok(0);
}
if !reader.read_bit()? {
let z = reader.read_bits(7)?;
return Ok(zigzag_decode(z));
}
if !reader.read_bit()? {
let z = reader.read_bits(9)?;
return Ok(zigzag_decode(z));
}
if !reader.read_bit()? {
let z = reader.read_bits(12)?;
return Ok(zigzag_decode(z));
}
let v = reader.read_bits(64)? as i64;
Ok(v)
}
pub(crate) struct BitWriter {
buf: Vec<u8>,
accumulator: u64,
bits_in_acc: u8,
}
impl BitWriter {
pub(crate) fn with_capacity(bytes: usize) -> Self {
Self {
buf: Vec::with_capacity(bytes),
accumulator: 0,
bits_in_acc: 0,
}
}
#[inline]
fn flush_accumulator(&mut self) {
self.buf.extend_from_slice(&self.accumulator.to_be_bytes());
self.accumulator = 0;
self.bits_in_acc = 0;
}
#[inline]
pub(crate) fn write_bit(&mut self, bit: bool) {
if bit {
self.accumulator |= 1u64 << (63 - self.bits_in_acc);
}
self.bits_in_acc += 1;
if self.bits_in_acc == 64 {
self.flush_accumulator();
}
}
#[inline]
pub(crate) fn write_bits(&mut self, value: u64, count: u8) {
debug_assert!(count <= 64);
if count == 0 {
return;
}
let masked = if count >= 64 {
value
} else {
value & ((1u64 << count) - 1)
};
let space = 64 - self.bits_in_acc;
if count <= space {
self.accumulator |= masked << (space - count);
self.bits_in_acc += count;
if self.bits_in_acc == 64 {
self.flush_accumulator();
}
} else {
self.accumulator |= masked >> (count - space);
self.flush_accumulator();
let remaining = count - space;
self.accumulator = masked << (64 - remaining);
self.bits_in_acc = remaining;
}
}
pub(crate) fn finish(mut self) -> Vec<u8> {
if self.bits_in_acc > 0 {
let bytes_needed = self.bits_in_acc.div_ceil(8) as usize;
let word_bytes = self.accumulator.to_be_bytes();
self.buf.extend_from_slice(&word_bytes[..bytes_needed]);
}
self.buf
}
}
pub(crate) struct BitReader<'a> {
data: &'a [u8],
byte_pos: usize,
bit_pos: u8, }
impl<'a> BitReader<'a> {
pub(crate) fn new(data: &'a [u8]) -> Self {
Self {
data,
byte_pos: 0,
bit_pos: 0,
}
}
#[inline]
pub(crate) fn read_bit(&mut self) -> Result<bool> {
if self.byte_pos >= self.data.len() {
return Err(EncodingError::CorruptData {
detail: "unexpected end of bit stream".to_string(),
});
}
let bit = (self.data[self.byte_pos] >> (7 - self.bit_pos)) & 1 == 1;
self.bit_pos += 1;
if self.bit_pos == 8 {
self.byte_pos += 1;
self.bit_pos = 0;
}
Ok(bit)
}
#[inline]
pub(crate) fn read_bits(&mut self, count: u8) -> Result<u64> {
debug_assert!(count <= 64);
if count == 0 {
return Ok(0);
}
let mut value: u64 = 0;
let mut remaining = count;
while remaining > 0 {
if self.byte_pos >= self.data.len() {
return Err(EncodingError::CorruptData {
detail: "unexpected end of bit stream".to_string(),
});
}
let bits_left_in_byte = 8 - self.bit_pos;
let to_read = remaining.min(bits_left_in_byte);
let shift = bits_left_in_byte - to_read;
let mask: u8 = if to_read >= 8 {
0xFF
} else {
(1u8 << to_read) - 1
};
let bits = (self.data[self.byte_pos] >> shift) & mask;
value = (value << to_read) | u64::from(bits);
remaining -= to_read;
self.bit_pos += to_read;
if self.bit_pos >= 8 {
self.byte_pos += 1;
self.bit_pos = 0;
}
}
Ok(value)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn roundtrip_monotonic_1s() {
let ts: Vec<i64> = (0..1000)
.map(|i| 1_000_000_000 + i * 1_000_000_000)
.collect();
let encoded = DeltaOfDeltaEncoder::encode(&ts).unwrap();
let decoded = DeltaOfDeltaDecoder::decode(&encoded).unwrap();
assert_eq!(ts, decoded);
assert!(encoded.len() < ts.len()); }
#[test]
fn roundtrip_single_value() {
let ts = vec![42_i64];
let encoded = DeltaOfDeltaEncoder::encode(&ts).unwrap();
let decoded = DeltaOfDeltaDecoder::decode(&encoded).unwrap();
assert_eq!(ts, decoded);
}
#[test]
fn roundtrip_two_values() {
let ts = vec![100_i64, 200];
let encoded = DeltaOfDeltaEncoder::encode(&ts).unwrap();
let decoded = DeltaOfDeltaDecoder::decode(&encoded).unwrap();
assert_eq!(ts, decoded);
}
#[test]
fn roundtrip_constant_timestamps() {
let ts = vec![42_i64; 100];
let encoded = DeltaOfDeltaEncoder::encode(&ts).unwrap();
let decoded = DeltaOfDeltaDecoder::decode(&encoded).unwrap();
assert_eq!(ts, decoded);
}
#[test]
fn roundtrip_irregular_intervals() {
let ts = vec![0, 1, 5, 10, 50, 100, 500, 1000, 2000];
let encoded = DeltaOfDeltaEncoder::encode(&ts).unwrap();
let decoded = DeltaOfDeltaDecoder::decode(&encoded).unwrap();
assert_eq!(ts, decoded);
}
#[test]
fn roundtrip_negative_timestamps() {
let ts = vec![-1000, -500, -200, -100, 0, 100, 200];
let encoded = DeltaOfDeltaEncoder::encode(&ts).unwrap();
let decoded = DeltaOfDeltaDecoder::decode(&encoded).unwrap();
assert_eq!(ts, decoded);
}
#[test]
fn roundtrip_edge_i64() {
let ts = vec![i64::MIN, i64::MIN / 2, 0, i64::MAX / 2, i64::MAX];
let encoded = DeltaOfDeltaEncoder::encode(&ts).unwrap();
let decoded = DeltaOfDeltaDecoder::decode(&encoded).unwrap();
assert_eq!(ts, decoded);
}
#[test]
fn empty_input_error() {
let result = DeltaOfDeltaEncoder::encode(&[]);
assert!(result.is_err());
}
#[test]
fn compression_ratio_regular() {
let ts: Vec<i64> = (0..10_000).map(|i| i * 1_000_000_000).collect();
let raw_size = ts.len() * 8;
let encoded = DeltaOfDeltaEncoder::encode(&ts).unwrap();
#[allow(clippy::cast_precision_loss)]
let ratio = raw_size as f64 / encoded.len() as f64;
assert!(
ratio > 10.0,
"Expected ratio > 10x, got {ratio:.1}x ({} raw, {} encoded)",
raw_size,
encoded.len()
);
}
#[test]
fn roundtrip_dod_boundary_values() {
let ts_64 = vec![0_i64, 0, 64];
let encoded = DeltaOfDeltaEncoder::encode(&ts_64).unwrap();
let decoded = DeltaOfDeltaDecoder::decode(&encoded).unwrap();
assert_eq!(ts_64, decoded, "DoD=64 boundary failed");
let ts_neg64 = vec![0_i64, 64, 0];
let encoded = DeltaOfDeltaEncoder::encode(&ts_neg64).unwrap();
let decoded = DeltaOfDeltaDecoder::decode(&encoded).unwrap();
assert_eq!(ts_neg64, decoded, "DoD=-64 boundary failed");
let ts_63 = vec![0_i64, 0, 63];
let encoded = DeltaOfDeltaEncoder::encode(&ts_63).unwrap();
let decoded = DeltaOfDeltaDecoder::decode(&encoded).unwrap();
assert_eq!(ts_63, decoded, "DoD=63 boundary failed");
let ts_256 = vec![0_i64, 0, 256];
let encoded = DeltaOfDeltaEncoder::encode(&ts_256).unwrap();
let decoded = DeltaOfDeltaDecoder::decode(&encoded).unwrap();
assert_eq!(ts_256, decoded, "DoD=256 boundary failed");
let ts_neg256 = vec![0_i64, 256, 0];
let encoded = DeltaOfDeltaEncoder::encode(&ts_neg256).unwrap();
let decoded = DeltaOfDeltaDecoder::decode(&encoded).unwrap();
assert_eq!(ts_neg256, decoded, "DoD=-256 boundary failed");
let ts_255 = vec![0_i64, 0, 255];
let encoded = DeltaOfDeltaEncoder::encode(&ts_255).unwrap();
let decoded = DeltaOfDeltaDecoder::decode(&encoded).unwrap();
assert_eq!(ts_255, decoded, "DoD=255 boundary failed");
let ts_2048 = vec![0_i64, 0, 2048];
let encoded = DeltaOfDeltaEncoder::encode(&ts_2048).unwrap();
let decoded = DeltaOfDeltaDecoder::decode(&encoded).unwrap();
assert_eq!(ts_2048, decoded, "DoD=2048 boundary failed");
let ts_neg2048 = vec![0_i64, 2048, 0];
let encoded = DeltaOfDeltaEncoder::encode(&ts_neg2048).unwrap();
let decoded = DeltaOfDeltaDecoder::decode(&encoded).unwrap();
assert_eq!(ts_neg2048, decoded, "DoD=-2048 boundary failed");
let ts_2047 = vec![0_i64, 0, 2047];
let encoded = DeltaOfDeltaEncoder::encode(&ts_2047).unwrap();
let decoded = DeltaOfDeltaDecoder::decode(&encoded).unwrap();
assert_eq!(ts_2047, decoded, "DoD=2047 boundary failed");
}
#[test]
fn roundtrip_dod_just_outside_boundaries() {
let ts = vec![0_i64, 0, 65];
let encoded = DeltaOfDeltaEncoder::encode(&ts).unwrap();
let decoded = DeltaOfDeltaDecoder::decode(&encoded).unwrap();
assert_eq!(ts, decoded);
let ts = vec![0_i64, 0, 257];
let encoded = DeltaOfDeltaEncoder::encode(&ts).unwrap();
let decoded = DeltaOfDeltaDecoder::decode(&encoded).unwrap();
assert_eq!(ts, decoded);
let ts = vec![0_i64, 0, 2049];
let encoded = DeltaOfDeltaEncoder::encode(&ts).unwrap();
let decoded = DeltaOfDeltaDecoder::decode(&encoded).unwrap();
assert_eq!(ts, decoded);
}
}
#[cfg(test)]
mod proptests {
use super::*;
use proptest::prelude::*;
proptest! {
#[test]
fn roundtrip_arbitrary_sorted(mut values in proptest::collection::vec(any::<i64>(), 1..500)) {
values.sort_unstable();
let encoded = DeltaOfDeltaEncoder::encode(&values).unwrap();
let decoded = DeltaOfDeltaDecoder::decode(&encoded).unwrap();
prop_assert_eq!(&values, &decoded);
}
#[test]
fn roundtrip_arbitrary_unsorted(values in proptest::collection::vec(any::<i64>(), 1..500)) {
let encoded = DeltaOfDeltaEncoder::encode(&values).unwrap();
let decoded = DeltaOfDeltaDecoder::decode(&encoded).unwrap();
prop_assert_eq!(&values, &decoded);
}
}
}