Skip to main content

buff_rs/
codec.rs

1//! BUFF codec for encoding and decoding bounded floating-point arrays.
2//!
3//! This module implements the BUFF (Decomposed Bounded Floats for Fast compression
4//! and queries) algorithm from VLDB 2021.
5//!
6//! The key insight is to store floating-point values as byte-sliced fixed-point
7//! integers, enabling:
8//! 1. Efficient compression through delta encoding and bit packing
9//! 2. Fast predicate evaluation directly on compressed data (with SIMD)
10//!
11//! ## Encoding Format (v1 - without special values)
12//!
13//! The encoded format consists of:
14//! - Base integer (8 bytes, i64)
15//! - Number of values (4 bytes, u32)
16//! - Integer bits (4 bytes, u32)
17//! - Decimal bits (4 bytes, u32)
18//! - Byte slices (column-oriented storage)
19//!
20//! ## Encoding Format (v2 - with special values)
21//!
22//! The v2 format adds a header byte and special value section:
23//! - Version/flags (1 byte): 0x02 for v2 with special values
24//! - Base integer (8 bytes, i64)
25//! - Number of regular values (4 bytes, u32)
26//! - Integer bits (4 bytes, u32)
27//! - Decimal bits (4 bytes, u32)
28//! - Number of special values (4 bytes, u32)
29//! - Special value entries: (index: u32, type: u8) where type is 1=+Inf, 2=-Inf, 3=NaN
30//! - Byte slices (column-oriented storage)
31//!
32//! Each value is converted to a fixed-point representation relative to the base,
33//! then stored in byte-sliced format where each byte position across all values
34//! is stored contiguously.
35
36use crate::bitpack::BitPack;
37use crate::error::BuffError;
38use crate::precision::{get_decimal_length, get_precision_bound, PrecisionBound};
39
40/// Format version byte for encoding without special values (legacy).
41const FORMAT_V1: u8 = 0x01;
42
43/// Format version byte for encoding with special values support.
44const FORMAT_V2: u8 = 0x02;
45
46/// Special value type: positive infinity.
47const SPECIAL_POS_INF: u8 = 1;
48
49/// Special value type: negative infinity.
50const SPECIAL_NEG_INF: u8 = 2;
51
52/// Special value type: NaN.
53const SPECIAL_NAN: u8 = 3;
54
55/// Represents a special floating-point value with its position.
56#[derive(Debug, Clone, Copy, PartialEq)]
57pub struct SpecialValue {
58    /// Index in the original array.
59    pub index: u32,
60    /// Type of special value.
61    pub kind: SpecialValueKind,
62}
63
64/// Types of special floating-point values.
65#[derive(Debug, Clone, Copy, PartialEq, Eq)]
66pub enum SpecialValueKind {
67    /// Positive infinity (+∞).
68    PositiveInfinity,
69    /// Negative infinity (-∞).
70    NegativeInfinity,
71    /// Not a Number (NaN).
72    NaN,
73}
74
75impl SpecialValueKind {
76    fn to_byte(self) -> u8 {
77        match self {
78            SpecialValueKind::PositiveInfinity => SPECIAL_POS_INF,
79            SpecialValueKind::NegativeInfinity => SPECIAL_NEG_INF,
80            SpecialValueKind::NaN => SPECIAL_NAN,
81        }
82    }
83
84    fn from_byte(b: u8) -> Option<Self> {
85        match b {
86            SPECIAL_POS_INF => Some(SpecialValueKind::PositiveInfinity),
87            SPECIAL_NEG_INF => Some(SpecialValueKind::NegativeInfinity),
88            SPECIAL_NAN => Some(SpecialValueKind::NaN),
89            _ => None,
90        }
91    }
92
93    /// Convert to f64 value.
94    pub fn to_f64(self) -> f64 {
95        match self {
96            SpecialValueKind::PositiveInfinity => f64::INFINITY,
97            SpecialValueKind::NegativeInfinity => f64::NEG_INFINITY,
98            SpecialValueKind::NaN => f64::NAN,
99        }
100    }
101}
102
103/// Classify a float value as regular or special.
104pub fn classify_float(v: f64) -> Option<SpecialValueKind> {
105    if v.is_nan() {
106        Some(SpecialValueKind::NaN)
107    } else if v == f64::INFINITY {
108        Some(SpecialValueKind::PositiveInfinity)
109    } else if v == f64::NEG_INFINITY {
110        Some(SpecialValueKind::NegativeInfinity)
111    } else {
112        None
113    }
114}
115
116/// Flip the sign bit of a byte for proper ordering.
117///
118/// This ensures that negative values sort before positive values
119/// when comparing bytes lexicographically.
120#[inline]
121pub fn flip(x: u8) -> u8 {
122    x ^ (1u8 << 7)
123}
124
125/// Compute the ceiling of x/y.
126#[inline]
127fn ceil_div(x: u32, y: u32) -> u32 {
128    (x.saturating_sub(1)) / y + 1
129}
130
131/// BUFF compressor/decompressor for bounded floating-point arrays.
132///
133/// This implements byte-sliced storage where each bit position across
134/// all values is stored contiguously, enabling efficient compression
135/// and SIMD-accelerated queries.
136#[derive(Clone, Debug)]
137pub struct BuffCodec {
138    /// Decimal scale (e.g., 1000 for 3 decimal places).
139    scale: usize,
140}
141
142impl BuffCodec {
143    /// Create a new BUFF codec with the given scale.
144    ///
145    /// # Arguments
146    /// * `scale` - The decimal scale (e.g., 1000 for 3 decimal places, 10000 for 4)
147    ///
148    /// # Example
149    /// ```
150    /// use buff_rs::BuffCodec;
151    ///
152    /// // For 3 decimal places of precision
153    /// let codec = BuffCodec::new(1000);
154    /// ```
155    pub fn new(scale: usize) -> Self {
156        BuffCodec { scale }
157    }
158
159    /// Get the precision (number of decimal places) from the scale.
160    pub fn precision(&self) -> i32 {
161        if self.scale == 0 {
162            0
163        } else {
164            (self.scale as f32).log10() as i32
165        }
166    }
167
168    /// Encode an array of f64 values using BUFF byte-sliced encoding.
169    ///
170    /// # Arguments
171    /// * `data` - Slice of f64 values to encode
172    ///
173    /// # Returns
174    /// A `Vec<u8>` containing the encoded data, or an error.
175    ///
176    /// # Example
177    /// ```
178    /// use buff_rs::BuffCodec;
179    ///
180    /// let codec = BuffCodec::new(1000);
181    /// let data = vec![1.234, 5.678, 9.012];
182    /// let encoded = codec.encode(&data).unwrap();
183    /// ```
184    pub fn encode(&self, data: &[f64]) -> Result<Vec<u8>, BuffError> {
185        if data.is_empty() {
186            return Err(BuffError::EmptyInput);
187        }
188
189        let len = data.len() as u32;
190        let prec = self.precision();
191        let prec_delta = get_precision_bound(prec);
192        let dec_len = get_decimal_length(prec);
193
194        let mut bound = PrecisionBound::new(prec_delta);
195        bound.set_length(0, dec_len);
196
197        // First pass: convert to fixed-point and find min/max
198        let mut fixed_vec = Vec::with_capacity(data.len());
199        let mut min = i64::MAX;
200        let mut max = i64::MIN;
201
202        for &val in data {
203            let fixed = bound.fetch_fixed_aligned(val);
204            if fixed < min {
205                min = fixed;
206            }
207            if fixed > max {
208                max = fixed;
209            }
210            fixed_vec.push(fixed);
211        }
212
213        let delta = max - min;
214        let base_fixed = min;
215
216        // Calculate bit width needed
217        // We need enough bits to represent values 0..=delta (inclusive)
218        // So we need ceil(log2(delta + 1)) bits
219        let cal_int_length = if delta == 0 {
220            0.0
221        } else {
222            ((delta + 1) as f64).log2().ceil()
223        };
224
225        let fixed_len = cal_int_length as u32;
226        let ilen = fixed_len.saturating_sub(dec_len as u32);
227        let dlen = dec_len as u32;
228
229        // Write header
230        let mut bitpack = BitPack::<Vec<u8>>::with_capacity(20 + data.len() * 8);
231        let ubase_fixed = base_fixed as u64;
232
233        bitpack.write(ubase_fixed as u32, 32)?;
234        bitpack.write((ubase_fixed >> 32) as u32, 32)?;
235        bitpack.write(len, 32)?;
236        bitpack.write(ilen, 32)?;
237        bitpack.write(dlen, 32)?;
238
239        // Write byte-sliced data
240        let total_bits = ilen + dlen;
241        let mut remain = total_bits;
242
243        if remain < 8 {
244            // Less than one byte per value
245            let padding = 8 - remain;
246            for &fixed in &fixed_vec {
247                let val = (fixed - base_fixed) as u64;
248                bitpack.write_byte(flip(((val << padding) & 0xFF) as u8))?;
249            }
250        } else {
251            // First byte slice
252            remain -= 8;
253            let fixed_u64: Vec<u64> = if remain > 0 {
254                fixed_vec
255                    .iter()
256                    .map(|&x| {
257                        let val = (x - base_fixed) as u64;
258                        bitpack
259                            .write_byte(flip((val >> remain) as u8))
260                            .expect("write failed");
261                        val
262                    })
263                    .collect()
264            } else {
265                fixed_vec
266                    .iter()
267                    .map(|&x| {
268                        let val = (x - base_fixed) as u64;
269                        bitpack.write_byte(flip(val as u8)).expect("write failed");
270                        val
271                    })
272                    .collect()
273            };
274
275            // Remaining full byte slices
276            while remain >= 8 {
277                remain -= 8;
278                if remain > 0 {
279                    for &d in &fixed_u64 {
280                        bitpack.write_byte(flip((d >> remain) as u8))?;
281                    }
282                } else {
283                    for &d in &fixed_u64 {
284                        bitpack.write_byte(flip(d as u8))?;
285                    }
286                }
287            }
288
289            // Remaining partial byte
290            if remain > 0 {
291                let padding = 8 - remain;
292                for &d in &fixed_u64 {
293                    bitpack.write_byte(flip(((d << padding) & 0xFF) as u8))?;
294                }
295            }
296        }
297
298        Ok(bitpack.into_vec())
299    }
300
301    /// Encode an array of f64 values, including special values (Infinity, NaN).
302    ///
303    /// This method handles special floating-point values by storing them separately
304    /// from regular values. The encoded format uses version 2 which includes a
305    /// special values section.
306    ///
307    /// # Arguments
308    /// * `data` - Slice of f64 values to encode (may contain Infinity, -Infinity, NaN)
309    ///
310    /// # Returns
311    /// A `Vec<u8>` containing the encoded data, or an error.
312    ///
313    /// # Example
314    /// ```
315    /// use buff_rs::BuffCodec;
316    ///
317    /// let codec = BuffCodec::new(1000);
318    /// let data = vec![1.234, f64::INFINITY, 5.678, f64::NAN, -f64::INFINITY];
319    /// let encoded = codec.encode_with_special(&data).unwrap();
320    /// let decoded = codec.decode(&encoded).unwrap();
321    /// assert!(decoded[1].is_infinite() && decoded[1].is_sign_positive());
322    /// assert!(decoded[3].is_nan());
323    /// ```
324    pub fn encode_with_special(&self, data: &[f64]) -> Result<Vec<u8>, BuffError> {
325        if data.is_empty() {
326            return Err(BuffError::EmptyInput);
327        }
328
329        // Separate regular values from special values
330        let mut regular_values: Vec<f64> = Vec::with_capacity(data.len());
331        let mut special_values: Vec<SpecialValue> = Vec::new();
332        let mut index_map: Vec<usize> = Vec::with_capacity(data.len()); // Maps result index to regular_values index
333
334        for (i, &val) in data.iter().enumerate() {
335            if let Some(kind) = classify_float(val) {
336                special_values.push(SpecialValue {
337                    index: i as u32,
338                    kind,
339                });
340            } else {
341                index_map.push(regular_values.len());
342                regular_values.push(val);
343            }
344        }
345
346        // If no special values, use the regular encode (more compact)
347        if special_values.is_empty() {
348            return self.encode(data);
349        }
350
351        // If all values are special
352        if regular_values.is_empty() {
353            let mut result = Vec::with_capacity(1 + 4 + 5 * special_values.len());
354            result.push(FORMAT_V2);
355
356            // Write empty regular section header
357            result.extend_from_slice(&0u64.to_le_bytes()); // base
358            result.extend_from_slice(&0u32.to_le_bytes()); // count
359            result.extend_from_slice(&0u32.to_le_bytes()); // ilen
360            result.extend_from_slice(&0u32.to_le_bytes()); // dlen
361
362            // Write special values
363            result.extend_from_slice(&(special_values.len() as u32).to_le_bytes());
364            for sv in &special_values {
365                result.extend_from_slice(&sv.index.to_le_bytes());
366                result.push(sv.kind.to_byte());
367            }
368
369            return Ok(result);
370        }
371
372        // Encode regular values
373        let prec = self.precision();
374        let prec_delta = get_precision_bound(prec);
375        let dec_len = get_decimal_length(prec);
376
377        let mut bound = PrecisionBound::new(prec_delta);
378        bound.set_length(0, dec_len);
379
380        let mut fixed_vec = Vec::with_capacity(regular_values.len());
381        let mut min = i64::MAX;
382        let mut max = i64::MIN;
383
384        for &val in &regular_values {
385            let fixed = bound.fetch_fixed_aligned(val);
386            if fixed < min {
387                min = fixed;
388            }
389            if fixed > max {
390                max = fixed;
391            }
392            fixed_vec.push(fixed);
393        }
394
395        let delta = max - min;
396        let base_fixed = min;
397
398        let cal_int_length = if delta == 0 {
399            0.0
400        } else {
401            ((delta + 1) as f64).log2().ceil()
402        };
403
404        let fixed_len = cal_int_length as u32;
405        let ilen = fixed_len.saturating_sub(dec_len as u32);
406        let dlen = dec_len as u32;
407
408        // Build result
409        let mut result =
410            Vec::with_capacity(1 + 20 + 4 + 5 * special_values.len() + regular_values.len() * 8);
411
412        // Version byte
413        result.push(FORMAT_V2);
414
415        // Regular values header
416        let ubase_fixed = base_fixed as u64;
417        result.extend_from_slice(&ubase_fixed.to_le_bytes());
418        result.extend_from_slice(&(regular_values.len() as u32).to_le_bytes());
419        result.extend_from_slice(&ilen.to_le_bytes());
420        result.extend_from_slice(&dlen.to_le_bytes());
421
422        // Special values section
423        result.extend_from_slice(&(special_values.len() as u32).to_le_bytes());
424        for sv in &special_values {
425            result.extend_from_slice(&sv.index.to_le_bytes());
426            result.push(sv.kind.to_byte());
427        }
428
429        // Write byte-sliced data for regular values
430        let total_bits = ilen + dlen;
431        let mut remain = total_bits;
432
433        if remain == 0 {
434            // All same value, no data needed
435        } else if remain < 8 {
436            let padding = 8 - remain;
437            for &fixed in &fixed_vec {
438                let val = (fixed - base_fixed) as u64;
439                result.push(flip(((val << padding) & 0xFF) as u8));
440            }
441        } else {
442            remain -= 8;
443            let fixed_u64: Vec<u64> = fixed_vec
444                .iter()
445                .map(|&x| {
446                    let val = (x - base_fixed) as u64;
447                    if remain > 0 {
448                        result.push(flip((val >> remain) as u8));
449                    } else {
450                        result.push(flip(val as u8));
451                    }
452                    val
453                })
454                .collect();
455
456            while remain >= 8 {
457                remain -= 8;
458                if remain > 0 {
459                    for &d in &fixed_u64 {
460                        result.push(flip((d >> remain) as u8));
461                    }
462                } else {
463                    for &d in &fixed_u64 {
464                        result.push(flip(d as u8));
465                    }
466                }
467            }
468
469            if remain > 0 {
470                let padding = 8 - remain;
471                for &d in &fixed_u64 {
472                    result.push(flip(((d << padding) & 0xFF) as u8));
473                }
474            }
475        }
476
477        Ok(result)
478    }
479
480    /// Check if encoded data contains special values (v2 format).
481    pub fn has_special_values(&self, bytes: &[u8]) -> bool {
482        !bytes.is_empty() && bytes[0] == FORMAT_V2
483    }
484
485    /// Decode BUFF-encoded data back to f64 values.
486    ///
487    /// This method automatically detects the format version and handles both
488    /// legacy (v1) and special values (v2) formats.
489    ///
490    /// # Arguments
491    /// * `bytes` - The encoded byte array
492    ///
493    /// # Returns
494    /// A `Vec<f64>` containing the decoded values, or an error.
495    ///
496    /// # Example
497    /// ```
498    /// use buff_rs::BuffCodec;
499    ///
500    /// let codec = BuffCodec::new(1000);
501    /// let data = vec![1.234, 5.678, 9.012];
502    /// let encoded = codec.encode(&data).unwrap();
503    /// let decoded = codec.decode(&encoded).unwrap();
504    /// ```
505    pub fn decode(&self, bytes: &[u8]) -> Result<Vec<f64>, BuffError> {
506        // Check for v2 format (with special values)
507        if !bytes.is_empty() && bytes[0] == FORMAT_V2 {
508            return self.decode_v2(bytes);
509        }
510
511        // Legacy v1 format
512        self.decode_v1(bytes)
513    }
514
515    /// Decode v1 format (legacy, no special values).
516    fn decode_v1(&self, bytes: &[u8]) -> Result<Vec<f64>, BuffError> {
517        if bytes.len() < 20 {
518            return Err(BuffError::InvalidData("header too short".into()));
519        }
520
521        let prec = self.precision();
522        let prec_delta = get_precision_bound(prec);
523
524        let mut bitpack = BitPack::<&[u8]>::new(bytes);
525        let _bound = PrecisionBound::new(prec_delta);
526
527        // Read header
528        let lower = bitpack.read(32)?;
529        let higher = bitpack.read(32)?;
530        let ubase_int = (lower as u64) | ((higher as u64) << 32);
531        let base_int = ubase_int as i64;
532
533        let len = bitpack.read(32)? as usize;
534        let ilen = bitpack.read(32)?;
535        let dlen = bitpack.read(32)?;
536
537        let dec_scl = 2.0f64.powi(dlen as i32);
538        let remain = dlen + ilen;
539        let num_chunks = ceil_div(remain, 8);
540        let padding = num_chunks * 8 - ilen - dlen;
541
542        let mut result = Vec::with_capacity(len);
543
544        match num_chunks {
545            0 => {
546                // All values are the base
547                result.resize(len, base_int as f64 / dec_scl);
548            }
549            1 => {
550                let chunk0 = bitpack.read_n_byte_unmut(0, len)?;
551                for &byte in chunk0.iter().take(len) {
552                    let val = (flip(byte) as u64) >> padding;
553                    result.push((base_int + val as i64) as f64 / dec_scl);
554                }
555            }
556            2 => {
557                let chunk0 = bitpack.read_n_byte_unmut(0, len)?;
558                let chunk1 = bitpack.read_n_byte_unmut(len, len)?;
559                for (&b0, &b1) in chunk0.iter().zip(chunk1.iter()).take(len) {
560                    let val = ((flip(b0) as u64) << (8 - padding)) | ((flip(b1) as u64) >> padding);
561                    result.push((base_int + val as i64) as f64 / dec_scl);
562                }
563            }
564            3 => {
565                let chunk0 = bitpack.read_n_byte_unmut(0, len)?;
566                let chunk1 = bitpack.read_n_byte_unmut(len, len)?;
567                let chunk2 = bitpack.read_n_byte_unmut(2 * len, len)?;
568                for ((&b0, &b1), &b2) in chunk0
569                    .iter()
570                    .zip(chunk1.iter())
571                    .zip(chunk2.iter())
572                    .take(len)
573                {
574                    let val = ((flip(b0) as u64) << (16 - padding))
575                        | ((flip(b1) as u64) << (8 - padding))
576                        | ((flip(b2) as u64) >> padding);
577                    result.push((base_int + val as i64) as f64 / dec_scl);
578                }
579            }
580            4 => {
581                let chunk0 = bitpack.read_n_byte_unmut(0, len)?;
582                let chunk1 = bitpack.read_n_byte_unmut(len, len)?;
583                let chunk2 = bitpack.read_n_byte_unmut(2 * len, len)?;
584                let chunk3 = bitpack.read_n_byte_unmut(3 * len, len)?;
585                for (((&b0, &b1), &b2), &b3) in chunk0
586                    .iter()
587                    .zip(chunk1.iter())
588                    .zip(chunk2.iter())
589                    .zip(chunk3.iter())
590                    .take(len)
591                {
592                    let val = ((flip(b0) as u64) << (24 - padding))
593                        | ((flip(b1) as u64) << (16 - padding))
594                        | ((flip(b2) as u64) << (8 - padding))
595                        | ((flip(b3) as u64) >> padding);
596                    result.push((base_int + val as i64) as f64 / dec_scl);
597                }
598            }
599            _ => {
600                return Err(BuffError::InvalidData(format!(
601                    "bit length {} (>{} chunks) not supported",
602                    remain, num_chunks
603                )));
604            }
605        }
606
607        Ok(result)
608    }
609
610    /// Decode v2 format (with special values).
611    fn decode_v2(&self, bytes: &[u8]) -> Result<Vec<f64>, BuffError> {
612        if bytes.len() < 22 {
613            // 1 (version) + 8 (base) + 4 (count) + 4 (ilen) + 4 (dlen) + 4 (special count) = 25 min
614            return Err(BuffError::InvalidData("v2 header too short".into()));
615        }
616
617        let mut pos = 1; // Skip version byte
618
619        // Read regular values header
620        let base_int = i64::from_le_bytes(bytes[pos..pos + 8].try_into().unwrap());
621        pos += 8;
622
623        let regular_count = u32::from_le_bytes(bytes[pos..pos + 4].try_into().unwrap()) as usize;
624        pos += 4;
625
626        let ilen = u32::from_le_bytes(bytes[pos..pos + 4].try_into().unwrap());
627        pos += 4;
628
629        let dlen = u32::from_le_bytes(bytes[pos..pos + 4].try_into().unwrap());
630        pos += 4;
631
632        // Read special values count
633        let special_count = u32::from_le_bytes(bytes[pos..pos + 4].try_into().unwrap()) as usize;
634        pos += 4;
635
636        // Read special values
637        let mut special_values: Vec<SpecialValue> = Vec::with_capacity(special_count);
638        for _ in 0..special_count {
639            if pos + 5 > bytes.len() {
640                return Err(BuffError::InvalidData("truncated special values".into()));
641            }
642            let index = u32::from_le_bytes(bytes[pos..pos + 4].try_into().unwrap());
643            pos += 4;
644            let kind = SpecialValueKind::from_byte(bytes[pos])
645                .ok_or_else(|| BuffError::InvalidData("invalid special value type".into()))?;
646            pos += 1;
647            special_values.push(SpecialValue { index, kind });
648        }
649
650        // Calculate total count
651        let total_count = regular_count + special_count;
652
653        // If no regular values, just return special values
654        if regular_count == 0 {
655            let mut result = vec![0.0f64; total_count];
656            for sv in &special_values {
657                result[sv.index as usize] = sv.kind.to_f64();
658            }
659            return Ok(result);
660        }
661
662        // Decode regular values from byte-sliced data
663        let dec_scl = 2.0f64.powi(dlen as i32);
664        let remain = dlen + ilen;
665        let num_chunks = ceil_div(remain, 8);
666        let padding = num_chunks * 8 - ilen - dlen;
667
668        let data_start = pos;
669        let mut regular_values: Vec<f64> = Vec::with_capacity(regular_count);
670
671        match num_chunks {
672            0 => {
673                regular_values.resize(regular_count, base_int as f64 / dec_scl);
674            }
675            1 => {
676                let chunk0 = &bytes[data_start..data_start + regular_count];
677                for &byte in chunk0.iter() {
678                    let val = (flip(byte) as u64) >> padding;
679                    regular_values.push((base_int + val as i64) as f64 / dec_scl);
680                }
681            }
682            2 => {
683                let chunk0 = &bytes[data_start..data_start + regular_count];
684                let chunk1 = &bytes[data_start + regular_count..data_start + 2 * regular_count];
685                for (&b0, &b1) in chunk0.iter().zip(chunk1.iter()) {
686                    let val = ((flip(b0) as u64) << (8 - padding)) | ((flip(b1) as u64) >> padding);
687                    regular_values.push((base_int + val as i64) as f64 / dec_scl);
688                }
689            }
690            3 => {
691                let chunk0 = &bytes[data_start..data_start + regular_count];
692                let chunk1 = &bytes[data_start + regular_count..data_start + 2 * regular_count];
693                let chunk2 = &bytes[data_start + 2 * regular_count..data_start + 3 * regular_count];
694                for ((&b0, &b1), &b2) in chunk0.iter().zip(chunk1.iter()).zip(chunk2.iter()) {
695                    let val = ((flip(b0) as u64) << (16 - padding))
696                        | ((flip(b1) as u64) << (8 - padding))
697                        | ((flip(b2) as u64) >> padding);
698                    regular_values.push((base_int + val as i64) as f64 / dec_scl);
699                }
700            }
701            4 => {
702                let chunk0 = &bytes[data_start..data_start + regular_count];
703                let chunk1 = &bytes[data_start + regular_count..data_start + 2 * regular_count];
704                let chunk2 = &bytes[data_start + 2 * regular_count..data_start + 3 * regular_count];
705                let chunk3 = &bytes[data_start + 3 * regular_count..data_start + 4 * regular_count];
706                for (((&b0, &b1), &b2), &b3) in chunk0
707                    .iter()
708                    .zip(chunk1.iter())
709                    .zip(chunk2.iter())
710                    .zip(chunk3.iter())
711                {
712                    let val = ((flip(b0) as u64) << (24 - padding))
713                        | ((flip(b1) as u64) << (16 - padding))
714                        | ((flip(b2) as u64) << (8 - padding))
715                        | ((flip(b3) as u64) >> padding);
716                    regular_values.push((base_int + val as i64) as f64 / dec_scl);
717                }
718            }
719            _ => {
720                return Err(BuffError::InvalidData(format!(
721                    "bit length {} not supported",
722                    remain
723                )));
724            }
725        }
726
727        // Merge regular and special values into final result
728        let mut result = vec![0.0f64; total_count];
729        let mut regular_idx = 0;
730
731        // Create a set of special value indices for O(1) lookup
732        let special_indices: std::collections::HashSet<u32> =
733            special_values.iter().map(|sv| sv.index).collect();
734
735        for (i, slot) in result.iter_mut().enumerate() {
736            if special_indices.contains(&(i as u32)) {
737                // Find the special value for this index
738                if let Some(sv) = special_values.iter().find(|sv| sv.index == i as u32) {
739                    *slot = sv.kind.to_f64();
740                }
741            } else {
742                *slot = regular_values[regular_idx];
743                regular_idx += 1;
744            }
745        }
746
747        Ok(result)
748    }
749
750    /// Compute the sum of all values in the encoded data.
751    ///
752    /// This operates directly on the compressed data without full decompression.
753    pub fn sum(&self, bytes: &[u8]) -> Result<f64, BuffError> {
754        if bytes.len() < 20 {
755            return Err(BuffError::InvalidData("header too short".into()));
756        }
757
758        let prec = self.precision();
759        let prec_delta = get_precision_bound(prec);
760
761        let mut bitpack = BitPack::<&[u8]>::new(bytes);
762        let _bound = PrecisionBound::new(prec_delta);
763
764        // Read header
765        let lower = bitpack.read(32)?;
766        let higher = bitpack.read(32)?;
767        let ubase_int = (lower as u64) | ((higher as u64) << 32);
768        let base_int = ubase_int as i64;
769
770        let len = bitpack.read(32)? as usize;
771        let ilen = bitpack.read(32)?;
772        let dlen = bitpack.read(32)?;
773
774        let dec_scl = 2.0f64.powi(dlen as i32);
775        let remain = dlen + ilen;
776        let num_chunks = ceil_div(remain, 8);
777        let padding = num_chunks * 8 - ilen - dlen;
778
779        let mut sum = 0.0f64;
780
781        match num_chunks {
782            0 => {
783                sum = (base_int as f64 / dec_scl) * len as f64;
784            }
785            1 => {
786                let chunk0 = bitpack.read_n_byte_unmut(0, len)?;
787                for &byte in chunk0.iter().take(len) {
788                    let val = (flip(byte) as u64) >> padding;
789                    sum += (base_int + val as i64) as f64 / dec_scl;
790                }
791            }
792            2 => {
793                let chunk0 = bitpack.read_n_byte_unmut(0, len)?;
794                let chunk1 = bitpack.read_n_byte_unmut(len, len)?;
795                for (&b0, &b1) in chunk0.iter().zip(chunk1.iter()).take(len) {
796                    let val = ((flip(b0) as u64) << (8 - padding)) | ((flip(b1) as u64) >> padding);
797                    sum += (base_int + val as i64) as f64 / dec_scl;
798                }
799            }
800            3 => {
801                let chunk0 = bitpack.read_n_byte_unmut(0, len)?;
802                let chunk1 = bitpack.read_n_byte_unmut(len, len)?;
803                let chunk2 = bitpack.read_n_byte_unmut(2 * len, len)?;
804                for ((&b0, &b1), &b2) in chunk0
805                    .iter()
806                    .zip(chunk1.iter())
807                    .zip(chunk2.iter())
808                    .take(len)
809                {
810                    let val = ((flip(b0) as u64) << (16 - padding))
811                        | ((flip(b1) as u64) << (8 - padding))
812                        | ((flip(b2) as u64) >> padding);
813                    sum += (base_int + val as i64) as f64 / dec_scl;
814                }
815            }
816            4 => {
817                let chunk0 = bitpack.read_n_byte_unmut(0, len)?;
818                let chunk1 = bitpack.read_n_byte_unmut(len, len)?;
819                let chunk2 = bitpack.read_n_byte_unmut(2 * len, len)?;
820                let chunk3 = bitpack.read_n_byte_unmut(3 * len, len)?;
821                for (((&b0, &b1), &b2), &b3) in chunk0
822                    .iter()
823                    .zip(chunk1.iter())
824                    .zip(chunk2.iter())
825                    .zip(chunk3.iter())
826                    .take(len)
827                {
828                    let val = ((flip(b0) as u64) << (24 - padding))
829                        | ((flip(b1) as u64) << (16 - padding))
830                        | ((flip(b2) as u64) << (8 - padding))
831                        | ((flip(b3) as u64) >> padding);
832                    sum += (base_int + val as i64) as f64 / dec_scl;
833                }
834            }
835            _ => {
836                return Err(BuffError::InvalidData(format!(
837                    "bit length {} not supported",
838                    remain
839                )));
840            }
841        }
842
843        Ok(sum)
844    }
845
846    /// Find the maximum value in the encoded data.
847    ///
848    /// This operates directly on the compressed data without full decompression.
849    pub fn max(&self, bytes: &[u8]) -> Result<f64, BuffError> {
850        if bytes.len() < 20 {
851            return Err(BuffError::InvalidData("header too short".into()));
852        }
853
854        let prec = self.precision();
855        let prec_delta = get_precision_bound(prec);
856
857        let mut bitpack = BitPack::<&[u8]>::new(bytes);
858        let _bound = PrecisionBound::new(prec_delta);
859
860        // Read header
861        let lower = bitpack.read(32)?;
862        let higher = bitpack.read(32)?;
863        let ubase_int = (lower as u64) | ((higher as u64) << 32);
864        let base_int = ubase_int as i64;
865
866        let len = bitpack.read(32)? as usize;
867        let ilen = bitpack.read(32)?;
868        let dlen = bitpack.read(32)?;
869
870        let dec_scl = 2.0f64.powi(dlen as i32);
871        let remain = dlen + ilen;
872        let num_chunks = ceil_div(remain, 8);
873        let padding = num_chunks * 8 - ilen - dlen;
874
875        let mut max_val = f64::MIN;
876
877        match num_chunks {
878            0 => {
879                max_val = base_int as f64 / dec_scl;
880            }
881            1 => {
882                let chunk0 = bitpack.read_n_byte_unmut(0, len)?;
883                for &byte in chunk0.iter().take(len) {
884                    let val = (flip(byte) as u64) >> padding;
885                    let f = (base_int + val as i64) as f64 / dec_scl;
886                    if f > max_val {
887                        max_val = f;
888                    }
889                }
890            }
891            2 => {
892                let chunk0 = bitpack.read_n_byte_unmut(0, len)?;
893                let chunk1 = bitpack.read_n_byte_unmut(len, len)?;
894                for (&b0, &b1) in chunk0.iter().zip(chunk1.iter()).take(len) {
895                    let val = ((flip(b0) as u64) << (8 - padding)) | ((flip(b1) as u64) >> padding);
896                    let f = (base_int + val as i64) as f64 / dec_scl;
897                    if f > max_val {
898                        max_val = f;
899                    }
900                }
901            }
902            3 => {
903                let chunk0 = bitpack.read_n_byte_unmut(0, len)?;
904                let chunk1 = bitpack.read_n_byte_unmut(len, len)?;
905                let chunk2 = bitpack.read_n_byte_unmut(2 * len, len)?;
906                for ((&b0, &b1), &b2) in chunk0
907                    .iter()
908                    .zip(chunk1.iter())
909                    .zip(chunk2.iter())
910                    .take(len)
911                {
912                    let val = ((flip(b0) as u64) << (16 - padding))
913                        | ((flip(b1) as u64) << (8 - padding))
914                        | ((flip(b2) as u64) >> padding);
915                    let f = (base_int + val as i64) as f64 / dec_scl;
916                    if f > max_val {
917                        max_val = f;
918                    }
919                }
920            }
921            4 => {
922                let chunk0 = bitpack.read_n_byte_unmut(0, len)?;
923                let chunk1 = bitpack.read_n_byte_unmut(len, len)?;
924                let chunk2 = bitpack.read_n_byte_unmut(2 * len, len)?;
925                let chunk3 = bitpack.read_n_byte_unmut(3 * len, len)?;
926                for (((&b0, &b1), &b2), &b3) in chunk0
927                    .iter()
928                    .zip(chunk1.iter())
929                    .zip(chunk2.iter())
930                    .zip(chunk3.iter())
931                    .take(len)
932                {
933                    let val = ((flip(b0) as u64) << (24 - padding))
934                        | ((flip(b1) as u64) << (16 - padding))
935                        | ((flip(b2) as u64) << (8 - padding))
936                        | ((flip(b3) as u64) >> padding);
937                    let f = (base_int + val as i64) as f64 / dec_scl;
938                    if f > max_val {
939                        max_val = f;
940                    }
941                }
942            }
943            _ => {
944                return Err(BuffError::InvalidData(format!(
945                    "bit length {} not supported",
946                    remain
947                )));
948            }
949        }
950
951        Ok(max_val)
952    }
953
954    /// Count values greater than a threshold.
955    ///
956    /// This operates directly on the compressed data using early termination
957    /// when possible.
958    pub fn count_greater_than(&self, bytes: &[u8], threshold: f64) -> Result<usize, BuffError> {
959        // For now, use decode + filter. SIMD optimization can be added later.
960        let decoded = self.decode(bytes)?;
961        Ok(decoded.iter().filter(|&&v| v > threshold).count())
962    }
963
964    /// Count values equal to a target.
965    pub fn count_equal(&self, bytes: &[u8], target: f64) -> Result<usize, BuffError> {
966        let decoded = self.decode(bytes)?;
967        Ok(decoded
968            .iter()
969            .filter(|&&v| (v - target).abs() < f64::EPSILON)
970            .count())
971    }
972
973    /// Get metadata about the encoded data without decoding.
974    pub fn metadata(&self, bytes: &[u8]) -> Result<BuffMetadata, BuffError> {
975        if bytes.len() < 20 {
976            return Err(BuffError::InvalidData("header too short".into()));
977        }
978
979        let mut bitpack = BitPack::<&[u8]>::new(bytes);
980
981        let lower = bitpack.read(32)?;
982        let higher = bitpack.read(32)?;
983        let ubase_int = (lower as u64) | ((higher as u64) << 32);
984        let base_int = ubase_int as i64;
985
986        let len = bitpack.read(32)?;
987        let ilen = bitpack.read(32)?;
988        let dlen = bitpack.read(32)?;
989
990        Ok(BuffMetadata {
991            base_value: base_int,
992            count: len,
993            integer_bits: ilen,
994            decimal_bits: dlen,
995            total_bytes: bytes.len(),
996        })
997    }
998}
999
1000/// Metadata about BUFF-encoded data.
1001#[derive(Debug, Clone, PartialEq, Eq)]
1002pub struct BuffMetadata {
1003    /// The base value (minimum fixed-point value).
1004    pub base_value: i64,
1005    /// Number of encoded values.
1006    pub count: u32,
1007    /// Number of bits for the integer part.
1008    pub integer_bits: u32,
1009    /// Number of bits for the decimal part.
1010    pub decimal_bits: u32,
1011    /// Total size of the encoded data in bytes.
1012    pub total_bytes: usize,
1013}
1014
1015impl BuffMetadata {
1016    /// Calculate the compression ratio (compressed size / original size).
1017    pub fn compression_ratio(&self) -> f64 {
1018        let original_size = self.count as usize * std::mem::size_of::<f64>();
1019        self.total_bytes as f64 / original_size as f64
1020    }
1021}
1022
1023#[cfg(test)]
1024mod tests {
1025    use super::*;
1026
1027    #[test]
1028    fn test_flip() {
1029        assert_eq!(flip(0), 128);
1030        assert_eq!(flip(128), 0);
1031        assert_eq!(flip(255), 127);
1032    }
1033
1034    #[test]
1035    fn test_encode_decode_roundtrip() {
1036        let codec = BuffCodec::new(1000);
1037        let data = vec![1.234, 5.678, 9.012, -3.456, 0.0];
1038        let encoded = codec.encode(&data).unwrap();
1039        let decoded = codec.decode(&encoded).unwrap();
1040
1041        assert_eq!(data.len(), decoded.len());
1042        for (orig, dec) in data.iter().zip(decoded.iter()) {
1043            assert!((orig - dec).abs() < 0.001, "orig={}, dec={}", orig, dec);
1044        }
1045    }
1046
1047    #[test]
1048    fn test_encode_decode_high_precision() {
1049        let codec = BuffCodec::new(100000); // 5 decimal places
1050        let data = vec![1.23456, 5.67890, 9.01234];
1051        let encoded = codec.encode(&data).unwrap();
1052        let decoded = codec.decode(&encoded).unwrap();
1053
1054        for (orig, dec) in data.iter().zip(decoded.iter()) {
1055            assert!((orig - dec).abs() < 0.00001, "orig={}, dec={}", orig, dec);
1056        }
1057    }
1058
1059    #[test]
1060    fn test_empty_input() {
1061        let codec = BuffCodec::new(1000);
1062        let result = codec.encode(&[]);
1063        assert!(matches!(result, Err(BuffError::EmptyInput)));
1064    }
1065
1066    #[test]
1067    fn test_single_value() {
1068        let codec = BuffCodec::new(1000);
1069        let data = vec![42.123];
1070        let encoded = codec.encode(&data).unwrap();
1071        let decoded = codec.decode(&encoded).unwrap();
1072
1073        assert_eq!(decoded.len(), 1);
1074        assert!((data[0] - decoded[0]).abs() < 0.001);
1075    }
1076
1077    #[test]
1078    fn test_identical_values() {
1079        let codec = BuffCodec::new(1000);
1080        let data = vec![3.14159; 100];
1081        let encoded = codec.encode(&data).unwrap();
1082        let decoded = codec.decode(&encoded).unwrap();
1083
1084        assert_eq!(decoded.len(), 100);
1085        for &val in &decoded {
1086            assert!((val - 3.14159).abs() < 0.001);
1087        }
1088    }
1089
1090    #[test]
1091    fn test_sum() {
1092        let codec = BuffCodec::new(1000);
1093        let data = vec![1.0, 2.0, 3.0, 4.0, 5.0];
1094        let encoded = codec.encode(&data).unwrap();
1095        let sum = codec.sum(&encoded).unwrap();
1096
1097        assert!((sum - 15.0).abs() < 0.01);
1098    }
1099
1100    #[test]
1101    fn test_max() {
1102        let codec = BuffCodec::new(1000);
1103        let data = vec![1.5, 9.9, 3.2, 7.1, 2.8];
1104        let encoded = codec.encode(&data).unwrap();
1105        let max = codec.max(&encoded).unwrap();
1106
1107        assert!((max - 9.9).abs() < 0.01);
1108    }
1109
1110    #[test]
1111    fn test_metadata() {
1112        let codec = BuffCodec::new(1000);
1113        let data = vec![1.0, 2.0, 3.0];
1114        let encoded = codec.encode(&data).unwrap();
1115        let metadata = codec.metadata(&encoded).unwrap();
1116
1117        assert_eq!(metadata.count, 3);
1118        assert!(metadata.compression_ratio() < 1.5); // Should be reasonably compressed
1119    }
1120
1121    #[test]
1122    fn test_negative_values() {
1123        let codec = BuffCodec::new(1000);
1124        let data = vec![-10.5, -5.25, 0.0, 5.25, 10.5];
1125        let encoded = codec.encode(&data).unwrap();
1126        let decoded = codec.decode(&encoded).unwrap();
1127
1128        for (orig, dec) in data.iter().zip(decoded.iter()) {
1129            assert!((orig - dec).abs() < 0.001, "orig={}, dec={}", orig, dec);
1130        }
1131    }
1132
1133    #[test]
1134    fn test_large_values() {
1135        let codec = BuffCodec::new(100);
1136        let data = vec![1000000.0, 2000000.0, 3000000.0];
1137        let encoded = codec.encode(&data).unwrap();
1138        let decoded = codec.decode(&encoded).unwrap();
1139
1140        for (orig, dec) in data.iter().zip(decoded.iter()) {
1141            assert!((orig - dec).abs() < 1.0, "orig={}, dec={}", orig, dec);
1142        }
1143    }
1144
1145    #[test]
1146    fn test_special_values_infinity() {
1147        let codec = BuffCodec::new(1000);
1148        let data = vec![1.0, f64::INFINITY, 2.0, f64::NEG_INFINITY, 3.0];
1149        let encoded = codec.encode_with_special(&data).unwrap();
1150
1151        assert!(codec.has_special_values(&encoded));
1152
1153        let decoded = codec.decode(&encoded).unwrap();
1154
1155        assert_eq!(decoded.len(), 5);
1156        assert!((decoded[0] - 1.0).abs() < 0.001);
1157        assert!(decoded[1].is_infinite() && decoded[1].is_sign_positive());
1158        assert!((decoded[2] - 2.0).abs() < 0.001);
1159        assert!(decoded[3].is_infinite() && decoded[3].is_sign_negative());
1160        assert!((decoded[4] - 3.0).abs() < 0.001);
1161    }
1162
1163    #[test]
1164    fn test_special_values_nan() {
1165        let codec = BuffCodec::new(1000);
1166        let data = vec![1.0, f64::NAN, 2.0];
1167        let encoded = codec.encode_with_special(&data).unwrap();
1168
1169        let decoded = codec.decode(&encoded).unwrap();
1170
1171        assert_eq!(decoded.len(), 3);
1172        assert!((decoded[0] - 1.0).abs() < 0.001);
1173        assert!(decoded[1].is_nan());
1174        assert!((decoded[2] - 2.0).abs() < 0.001);
1175    }
1176
1177    #[test]
1178    fn test_all_special_values() {
1179        let codec = BuffCodec::new(1000);
1180        let data = vec![f64::INFINITY, f64::NAN, f64::NEG_INFINITY];
1181        let encoded = codec.encode_with_special(&data).unwrap();
1182        let decoded = codec.decode(&encoded).unwrap();
1183
1184        assert_eq!(decoded.len(), 3);
1185        assert!(decoded[0].is_infinite() && decoded[0].is_sign_positive());
1186        assert!(decoded[1].is_nan());
1187        assert!(decoded[2].is_infinite() && decoded[2].is_sign_negative());
1188    }
1189
1190    #[test]
1191    fn test_no_special_values_uses_v1() {
1192        let codec = BuffCodec::new(1000);
1193        let data = vec![1.0, 2.0, 3.0];
1194
1195        // encode_with_special should use v1 when no special values
1196        let encoded = codec.encode_with_special(&data).unwrap();
1197        assert!(!codec.has_special_values(&encoded));
1198
1199        let decoded = codec.decode(&encoded).unwrap();
1200        assert_eq!(decoded.len(), 3);
1201    }
1202
1203    #[test]
1204    fn test_special_value_kind() {
1205        assert_eq!(SpecialValueKind::PositiveInfinity.to_f64(), f64::INFINITY);
1206        assert_eq!(
1207            SpecialValueKind::NegativeInfinity.to_f64(),
1208            f64::NEG_INFINITY
1209        );
1210        assert!(SpecialValueKind::NaN.to_f64().is_nan());
1211    }
1212
1213    #[test]
1214    fn test_classify_float() {
1215        assert_eq!(classify_float(1.0), None);
1216        assert_eq!(
1217            classify_float(f64::INFINITY),
1218            Some(SpecialValueKind::PositiveInfinity)
1219        );
1220        assert_eq!(
1221            classify_float(f64::NEG_INFINITY),
1222            Some(SpecialValueKind::NegativeInfinity)
1223        );
1224        assert_eq!(classify_float(f64::NAN), Some(SpecialValueKind::NaN));
1225    }
1226}