Encode f64 values using ALP-RD (front-bit dictionary + raw tail bits).
(values: &[f64])
| 37 | |
| 38 | /// Encode f64 values using ALP-RD (front-bit dictionary + raw tail bits). |
| 39 | pub fn encode(values: &[f64]) -> Result<Vec<u8>, CodecError> { |
| 40 | let count = values.len() as u32; |
| 41 | |
| 42 | if values.is_empty() { |
| 43 | let mut out = Vec::with_capacity(7); |
| 44 | out.extend_from_slice(&0u32.to_le_bytes()); |
| 45 | out.push(0); // cut |
| 46 | out.extend_from_slice(&0u16.to_le_bytes()); |
| 47 | return Ok(out); |
| 48 | } |
| 49 | |
| 50 | // Find optimal cut position. |
| 51 | let cut = find_best_cut(values); |
| 52 | let bits: Vec<u64> = values.iter().map(|v| v.to_bits()).collect(); |
| 53 | |
| 54 | // Split into front and tail. |
| 55 | let front_mask: u64 = if cut == 64 { 0 } else { u64::MAX << cut }; |
| 56 | let tail_mask: u64 = if cut == 0 { 0 } else { (1u64 << cut) - 1 }; |
| 57 | let tail_bytes_per_value = (cut as usize).div_ceil(8); |
| 58 | |
| 59 | let fronts: Vec<u64> = bits.iter().map(|&b| (b & front_mask) >> cut).collect(); |
| 60 | |
| 61 | // Build dictionary from unique front values. |
| 62 | let mut dict: Vec<u64> = fronts.clone(); |
| 63 | dict.sort_unstable(); |
| 64 | dict.dedup(); |
| 65 | |
| 66 | // Map front values to dictionary indices. |
| 67 | // Safety: `dict` is built from `fronts` via sort+dedup, so every front value |
| 68 | // is guaranteed to exist in the dictionary. binary_search cannot fail here. |
| 69 | let indices: Vec<u16> = fronts |
| 70 | .iter() |
| 71 | .map(|f| { |
| 72 | dict.binary_search(f) |
| 73 | .map(|idx| idx as u16) |
| 74 | .map_err(|_| CodecError::Corrupt { |
| 75 | detail: "ALP-RD front value missing from dictionary".into(), |
| 76 | }) |
| 77 | }) |
| 78 | .collect::<Result<_, _>>()?; |
| 79 | |
| 80 | let dict_size = dict.len() as u16; |
| 81 | let use_u8_indices = dict.len() <= 256; |
| 82 | |
| 83 | // Build output. |
| 84 | let mut out = Vec::with_capacity( |
| 85 | 7 + dict.len() * 8 |
| 86 | + values.len() * if use_u8_indices { 1 } else { 2 } |
| 87 | + values.len() * tail_bytes_per_value, |
| 88 | ); |
| 89 | |
| 90 | // Header. |
| 91 | out.extend_from_slice(&count.to_le_bytes()); |
| 92 | out.push(cut); |
| 93 | out.extend_from_slice(&dict_size.to_le_bytes()); |
| 94 | |
| 95 | // Dictionary. |
| 96 | for &entry in &dict { |