chronix_encoding/
gorilla.rs1use crate::coding::{checked_count, checked_decode_count};
9use crate::delta::{BitReader, BitWriter};
10use crate::error::{EncodingError, Result};
11use crate::simd::{
12 batch_f64_to_bits, batch_leading_zeros, batch_trailing_zeros, batch_xor_adjacent,
13};
14
15#[derive(Debug, Clone, Copy)]
17pub struct GorillaEncoder;
18
19#[derive(Debug, Default)]
25pub struct GorillaEncodeScratch {
26 bits: Vec<u64>,
27 xors: Vec<u64>,
28 leading: Vec<u32>,
29 trailing: Vec<u32>,
30}
31
32impl GorillaEncodeScratch {
33 #[must_use]
35 pub fn new() -> Self {
36 Self::default()
37 }
38}
39
40#[derive(Debug, Clone, Copy)]
42pub struct GorillaDecoder;
43
44impl GorillaEncoder {
45 pub fn encode(values: &[f64]) -> Result<Vec<u8>> {
51 let mut scratch = GorillaEncodeScratch::new();
52 Self::encode_with_scratch(values, &mut scratch)
53 }
54
55 pub fn encode_with_scratch(
60 values: &[f64],
61 scratch: &mut GorillaEncodeScratch,
62 ) -> Result<Vec<u8>> {
63 if values.is_empty() {
64 return Err(EncodingError::EmptyInput {
65 context: "gorilla encoder",
66 });
67 }
68
69 let mut writer = BitWriter::with_capacity(4 + values.len() * 2);
70
71 let count = checked_count(values.len())?;
73 writer.write_bits(u64::from(count), 32);
74
75 scratch.bits.clear();
79 batch_f64_to_bits(values, &mut scratch.bits);
80
81 scratch.xors.clear();
82 batch_xor_adjacent(&scratch.bits, &mut scratch.xors);
83
84 scratch.leading.clear();
85 batch_leading_zeros(&scratch.xors, &mut scratch.leading);
86
87 scratch.trailing.clear();
88 batch_trailing_zeros(&scratch.xors, &mut scratch.trailing);
89
90 let first_bits = *scratch.bits.first().ok_or(EncodingError::CorruptData {
93 detail: "gorilla: empty bits buffer after non-empty check".to_string(),
94 })?;
95 writer.write_bits(first_bits, 64);
96
97 let mut prev_leading: u32 = u32::MAX;
98 let mut prev_trailing: u32 = 0;
99
100 for i in 0..scratch.xors.len() {
101 let xor = scratch.xors[i];
102
103 if xor == 0 {
104 writer.write_bit(false);
106 } else {
107 let leading = scratch.leading[i];
108 let trailing = scratch.trailing[i];
109
110 if prev_leading != u32::MAX && leading >= prev_leading && trailing >= prev_trailing
112 {
113 writer.write_bits(0b10, 2);
115 let meaningful = 64u32
116 .checked_sub(prev_leading)
117 .and_then(|v| v.checked_sub(prev_trailing))
118 .ok_or_else(|| EncodingError::CorruptData {
119 detail: "gorilla case-1 encode: meaningful-bits underflow".to_string(),
120 })?;
121 let shifted = xor >> prev_trailing;
122 writer.write_bits(shifted, meaningful as u8);
123 } else {
124 writer.write_bits(0b11, 2);
126 writer.write_bits(u64::from(leading.min(63)), 6);
128 let meaningful = 64u32
129 .checked_sub(leading)
130 .and_then(|v| v.checked_sub(trailing))
131 .ok_or_else(|| EncodingError::CorruptData {
132 detail: format!(
133 "gorilla case-2 encode: meaningful-bits underflow \
134 (leading={leading}, trailing={trailing})"
135 ),
136 })?;
137 let meaningful_m1 =
140 meaningful
141 .checked_sub(1)
142 .ok_or_else(|| EncodingError::CorruptData {
143 detail: "gorilla case-2 encode: zero meaningful bits \
144 for non-zero XOR"
145 .to_string(),
146 })?;
147 writer.write_bits(u64::from(meaningful_m1), 6);
148 let shifted = xor >> trailing;
149 writer.write_bits(shifted, meaningful as u8);
150
151 prev_leading = leading;
152 prev_trailing = trailing;
153 }
154 }
155 }
156
157 Ok(writer.finish())
158 }
159}
160
161impl GorillaDecoder {
162 pub fn decode(data: &[u8]) -> Result<Vec<f64>> {
168 if data.len() < 4 {
169 return Err(EncodingError::CorruptData {
170 detail: "gorilla data too short for header".to_string(),
171 });
172 }
173
174 let mut reader = BitReader::new(data);
175
176 let count = checked_decode_count(reader.read_bits(32)? as usize, "Gorilla")?;
177 if count == 0 {
178 return Ok(Vec::new());
179 }
180
181 let mut result = Vec::with_capacity(count);
182
183 let first_bits = reader.read_bits(64)?;
184 result.push(f64::from_bits(first_bits));
185
186 let mut prev_bits = first_bits;
187 let mut prev_leading: u32 = u32::MAX;
188 let mut prev_trailing: u32 = 0;
189
190 for _ in 1..count {
191 if !reader.read_bit()? {
192 result.push(f64::from_bits(prev_bits));
194 continue;
195 }
196
197 if reader.read_bit()? {
198 let leading = reader.read_bits(6)? as u32;
200 let meaningful = reader.read_bits(6)? as u32 + 1; let trailing = 64u32
202 .checked_sub(leading)
203 .and_then(|v| v.checked_sub(meaningful))
204 .ok_or_else(|| EncodingError::CorruptData {
205 detail: format!(
206 "gorilla case-2: leading ({leading}) + meaningful \
207 ({meaningful}) exceeds 64"
208 ),
209 })?;
210 let shifted = reader.read_bits(meaningful as u8)?;
211 let xor = shifted << trailing;
212 let bits = prev_bits ^ xor;
213 result.push(f64::from_bits(bits));
214 prev_bits = bits;
215
216 prev_leading = leading;
217 prev_trailing = trailing;
218 } else {
219 if prev_leading == u32::MAX {
221 return Err(EncodingError::CorruptData {
222 detail: "gorilla case-1 encountered before any case-2 window".to_string(),
223 });
224 }
225 let meaningful = 64u32
226 .checked_sub(prev_leading)
227 .and_then(|v| v.checked_sub(prev_trailing))
228 .ok_or_else(|| EncodingError::CorruptData {
229 detail: format!(
230 "gorilla case-1: invalid window \
231 (leading={prev_leading}, trailing={prev_trailing})"
232 ),
233 })?;
234 let shifted = reader.read_bits(meaningful as u8)?;
235 let xor = shifted << prev_trailing;
236 let bits = prev_bits ^ xor;
237 result.push(f64::from_bits(bits));
238 prev_bits = bits;
239 }
240 }
241
242 Ok(result)
243 }
244}
245
246#[cfg(test)]
247mod tests {
248 use super::*;
249
250 #[test]
251 fn roundtrip_constant() {
252 let values = vec![42.0_f64; 100];
253 let encoded = GorillaEncoder::encode(&values).unwrap();
254 let decoded = GorillaDecoder::decode(&encoded).unwrap();
255 for (a, b) in values.iter().zip(decoded.iter()) {
256 assert_eq!(a.to_bits(), b.to_bits());
257 }
258 }
259
260 #[test]
261 fn roundtrip_special_values() {
262 let values = vec![
263 0.0,
264 -0.0,
265 f64::NAN,
266 f64::INFINITY,
267 f64::NEG_INFINITY,
268 f64::MIN,
269 f64::MAX,
270 f64::MIN_POSITIVE,
271 f64::EPSILON,
272 ];
273 let encoded = GorillaEncoder::encode(&values).unwrap();
274 let decoded = GorillaDecoder::decode(&encoded).unwrap();
275 for (a, b) in values.iter().zip(decoded.iter()) {
276 assert_eq!(a.to_bits(), b.to_bits(), "mismatch for value {a}");
277 }
278 }
279
280 #[test]
281 fn roundtrip_sinwave() {
282 let values: Vec<f64> = (0..1000).map(|i| (i as f64 * 0.01).sin() * 100.0).collect();
283 let encoded = GorillaEncoder::encode(&values).unwrap();
284 let decoded = GorillaDecoder::decode(&encoded).unwrap();
285 for (a, b) in values.iter().zip(decoded.iter()) {
286 assert_eq!(a.to_bits(), b.to_bits());
287 }
288 }
289
290 #[test]
291 fn empty_error() {
292 assert!(GorillaEncoder::encode(&[]).is_err());
293 }
294
295 #[test]
296 fn single_value() {
297 let values = vec![99.99];
298 let encoded = GorillaEncoder::encode(&values).unwrap();
299 let decoded = GorillaDecoder::decode(&encoded).unwrap();
300 assert_eq!(values[0].to_bits(), decoded[0].to_bits());
301 }
302}
303
304#[cfg(test)]
305mod proptests {
306 use super::*;
307 use proptest::prelude::*;
308
309 proptest! {
310 #[test]
311 fn roundtrip_arbitrary(values in proptest::collection::vec(any::<f64>(), 1..500)) {
312 let encoded = GorillaEncoder::encode(&values).unwrap();
313 let decoded = GorillaDecoder::decode(&encoded).unwrap();
314 prop_assert_eq!(values.len(), decoded.len());
315 for (a, b) in values.iter().zip(decoded.iter()) {
316 prop_assert_eq!(a.to_bits(), b.to_bits());
317 }
318 }
319 }
320}