1use crate::error::CodecError;
28use crate::fastlanes;
29
30const MAX_EXPONENT: u8 = 18;
32
33const POW10: [f64; 19] = [
35 1.0,
36 10.0,
37 100.0,
38 1_000.0,
39 10_000.0,
40 100_000.0,
41 1_000_000.0,
42 10_000_000.0,
43 100_000_000.0,
44 1_000_000_000.0,
45 10_000_000_000.0,
46 100_000_000_000.0,
47 1_000_000_000_000.0,
48 10_000_000_000_000.0,
49 100_000_000_000_000.0,
50 1_000_000_000_000_000.0,
51 10_000_000_000_000_000.0,
52 100_000_000_000_000_000.0,
53 1_000_000_000_000_000_000.0,
54];
55
56const INV_POW10: [f64; 19] = [
58 1.0,
59 0.1,
60 0.01,
61 0.001,
62 0.000_1,
63 0.000_01,
64 0.000_001,
65 0.000_000_1,
66 0.000_000_01,
67 0.000_000_001,
68 0.000_000_000_1,
69 0.000_000_000_01,
70 0.000_000_000_001,
71 0.000_000_000_000_1,
72 0.000_000_000_000_01,
73 0.000_000_000_000_001,
74 0.000_000_000_000_000_1,
75 0.000_000_000_000_000_01,
76 0.000_000_000_000_000_001,
77];
78
79use crate::CODEC_SAMPLE_SIZE;
80
81pub fn encode(values: &[f64]) -> Vec<u8> {
91 let count = values.len() as u32;
92
93 if values.is_empty() {
94 let mut out = Vec::with_capacity(11);
95 out.extend_from_slice(&0u32.to_le_bytes()); out.push(0); out.push(0); out.push(0); out.extend_from_slice(&0u32.to_le_bytes()); return out;
101 }
102
103 let params = find_best_params(values);
105 let factor = POW10[params.encode_exp as usize];
106
107 let mut encoded_ints = Vec::with_capacity(values.len());
109 let mut exceptions: Vec<(u32, u64)> = Vec::new();
110
111 for (i, &val) in values.iter().enumerate() {
112 let encoded = try_alp_encode(val, factor, params.decode_exp, params.mode);
113 match encoded {
114 Some(int_val) => {
115 encoded_ints.push(int_val);
116 }
117 None => {
118 exceptions.push((i as u32, val.to_bits()));
120 encoded_ints.push(0);
121 }
122 }
123 }
124
125 let exception_count = exceptions.len() as u32;
127 let packed_ints = fastlanes::encode(&encoded_ints);
128
129 let mut out = Vec::with_capacity(10 + exceptions.len() * 12 + packed_ints.len());
130
131 out.extend_from_slice(&count.to_le_bytes());
133 out.push(params.encode_exp);
134 out.push(params.decode_exp);
135 out.push(match params.mode {
136 DecodeMode::MultiplyInverse => 0,
137 DecodeMode::DivideByFactor => 1,
138 });
139 out.extend_from_slice(&exception_count.to_le_bytes());
140
141 for &(idx, bits) in &exceptions {
143 out.extend_from_slice(&idx.to_le_bytes());
144 out.extend_from_slice(&bits.to_le_bytes());
145 }
146
147 out.extend_from_slice(&packed_ints);
149
150 out
151}
152
153pub fn decode(data: &[u8]) -> Result<Vec<f64>, CodecError> {
155 const HEADER_SIZE: usize = 11; if data.len() < HEADER_SIZE {
158 return Err(CodecError::Truncated {
159 expected: HEADER_SIZE,
160 actual: data.len(),
161 });
162 }
163
164 let count = u32::from_le_bytes([data[0], data[1], data[2], data[3]]) as usize;
165 let _encode_exp = data[4];
166 let decode_exp = data[5];
167 let mode = match data[6] {
168 0 => DecodeMode::MultiplyInverse,
169 1 => DecodeMode::DivideByFactor,
170 m => {
171 return Err(CodecError::Corrupt {
172 detail: format!("invalid ALP decode mode {m}"),
173 });
174 }
175 };
176 let exception_count = u32::from_le_bytes([data[7], data[8], data[9], data[10]]) as usize;
177
178 if count == 0 {
179 return Ok(Vec::new());
180 }
181
182 if decode_exp > MAX_EXPONENT {
183 return Err(CodecError::Corrupt {
184 detail: format!("invalid ALP decode_exp {decode_exp}"),
185 });
186 }
187
188 let exceptions_size = exception_count * 12;
190 let exceptions_end = HEADER_SIZE + exceptions_size;
191 if data.len() < exceptions_end {
192 return Err(CodecError::Truncated {
193 expected: exceptions_end,
194 actual: data.len(),
195 });
196 }
197
198 let mut exceptions = Vec::with_capacity(exception_count);
199 let mut pos = HEADER_SIZE;
200 for _ in 0..exception_count {
201 let idx =
202 u32::from_le_bytes([data[pos], data[pos + 1], data[pos + 2], data[pos + 3]]) as usize;
203 let bits = u64::from_le_bytes([
204 data[pos + 4],
205 data[pos + 5],
206 data[pos + 6],
207 data[pos + 7],
208 data[pos + 8],
209 data[pos + 9],
210 data[pos + 10],
211 data[pos + 11],
212 ]);
213 exceptions.push((idx, bits));
214 pos += 12;
215 }
216
217 let encoded_ints =
219 fastlanes::decode(&data[exceptions_end..]).map_err(|e| CodecError::Corrupt {
220 detail: format!("ALP fastlanes decode: {e}"),
221 })?;
222
223 if encoded_ints.len() != count {
224 return Err(CodecError::Corrupt {
225 detail: format!(
226 "ALP int count mismatch: expected {count}, got {}",
227 encoded_ints.len()
228 ),
229 });
230 }
231
232 let mut values = Vec::with_capacity(count);
234 for &int_val in &encoded_ints {
235 values.push(alp_decode_value(int_val, decode_exp, mode));
236 }
237
238 for &(idx, bits) in &exceptions {
240 if idx < values.len() {
241 values[idx] = f64::from_bits(bits);
242 }
243 }
244
245 Ok(values)
246}
247
248pub fn alp_encodability(values: &[f64]) -> f64 {
253 if values.is_empty() {
254 return 1.0;
255 }
256
257 let sample_end = values.len().min(CODEC_SAMPLE_SIZE);
258 let sample = &values[..sample_end];
259 let params = find_best_params(sample);
260 let factor = POW10[params.encode_exp as usize];
261
262 let encodable = sample
263 .iter()
264 .filter(|&&v| try_alp_encode(v, factor, params.decode_exp, params.mode).is_some())
265 .count();
266
267 encodable as f64 / sample.len() as f64
268}
269
270#[derive(Debug, Clone, Copy, PartialEq, Eq)]
280enum DecodeMode {
281 MultiplyInverse,
283 DivideByFactor,
285}
286
287#[inline]
291fn try_alp_encode(value: f64, factor: f64, decode_exp: u8, mode: DecodeMode) -> Option<i64> {
292 if !value.is_finite() {
293 return None;
294 }
295
296 let scaled = value * factor;
297 if scaled < i64::MIN as f64 || scaled > i64::MAX as f64 {
298 return None;
299 }
300
301 let encoded = scaled.round() as i64;
302
303 let decoded = match mode {
304 DecodeMode::MultiplyInverse => encoded as f64 * INV_POW10[decode_exp as usize],
305 DecodeMode::DivideByFactor => encoded as f64 / POW10[decode_exp as usize],
306 };
307
308 if decoded.to_bits() == value.to_bits() {
309 Some(encoded)
310 } else {
311 None
312 }
313}
314
315#[inline]
317fn alp_decode_value(encoded: i64, decode_exp: u8, mode: DecodeMode) -> f64 {
318 match mode {
319 DecodeMode::MultiplyInverse => encoded as f64 * INV_POW10[decode_exp as usize],
320 DecodeMode::DivideByFactor => encoded as f64 / POW10[decode_exp as usize],
321 }
322}
323
324struct AlpParams {
331 encode_exp: u8,
332 decode_exp: u8,
333 mode: DecodeMode,
334}
335
336fn find_best_params(values: &[f64]) -> AlpParams {
338 let sample_end = values.len().min(CODEC_SAMPLE_SIZE);
339 let sample = &values[..sample_end];
340
341 let mut best = AlpParams {
342 encode_exp: 0,
343 decode_exp: 0,
344 mode: DecodeMode::MultiplyInverse,
345 };
346 let mut best_count: usize = 0;
347
348 for e in 0..=MAX_EXPONENT {
349 let factor = POW10[e as usize];
350
351 for mode in [DecodeMode::MultiplyInverse, DecodeMode::DivideByFactor] {
352 let count = sample
354 .iter()
355 .filter(|&&v| try_alp_encode(v, factor, e, mode).is_some())
356 .count();
357
358 if count > best_count {
359 best_count = count;
360 best = AlpParams {
361 encode_exp: e,
362 decode_exp: e,
363 mode,
364 };
365 }
366
367 if e > 0 {
369 let count_alt = sample
370 .iter()
371 .filter(|&&v| try_alp_encode(v, factor, e - 1, mode).is_some())
372 .count();
373 if count_alt > best_count {
374 best_count = count_alt;
375 best = AlpParams {
376 encode_exp: e,
377 decode_exp: e - 1,
378 mode,
379 };
380 }
381 }
382 if e < MAX_EXPONENT {
383 let count_alt = sample
384 .iter()
385 .filter(|&&v| try_alp_encode(v, factor, e + 1, mode).is_some())
386 .count();
387 if count_alt > best_count {
388 best_count = count_alt;
389 best = AlpParams {
390 encode_exp: e,
391 decode_exp: e + 1,
392 mode,
393 };
394 }
395 }
396 }
397 }
398
399 best
400}
401
402#[cfg(test)]
403mod tests {
404 use super::*;
405
406 #[test]
407 fn empty_roundtrip() {
408 let encoded = encode(&[]);
409 let decoded = decode(&encoded).unwrap();
410 assert!(decoded.is_empty());
411 }
412
413 #[test]
414 fn single_value() {
415 let values = vec![23.5f64];
416 let encoded = encode(&values);
417 let decoded = decode(&encoded).unwrap();
418 assert_eq!(decoded[0].to_bits(), values[0].to_bits());
419 }
420
421 #[test]
422 fn integer_values() {
423 let values: Vec<f64> = (0..1000).map(|i| i as f64).collect();
425 let encoded = encode(&values);
426 let decoded = decode(&encoded).unwrap();
427 for (a, b) in values.iter().zip(decoded.iter()) {
428 assert_eq!(a.to_bits(), b.to_bits(), "mismatch: {a} vs {b}");
429 }
430 }
431
432 #[test]
433 fn decimal_values_one_digit() {
434 let values: Vec<f64> = (0..1000).map(|i| i as f64 * 0.1).collect();
436 let encoded = encode(&values);
437 let decoded = decode(&encoded).unwrap();
438 for (i, (a, b)) in values.iter().zip(decoded.iter()).enumerate() {
439 assert_eq!(a.to_bits(), b.to_bits(), "mismatch at {i}: {a} vs {b}");
440 }
441 }
442
443 #[test]
444 fn decimal_values_two_digits() {
445 let values: Vec<f64> = (0..1000).map(|i| i as f64 * 0.01).collect();
447 let encoded = encode(&values);
448 let decoded = decode(&encoded).unwrap();
449 for (i, (a, b)) in values.iter().zip(decoded.iter()).enumerate() {
450 assert_eq!(a.to_bits(), b.to_bits(), "mismatch at {i}: {a} vs {b}");
451 }
452 }
453
454 #[test]
455 fn typical_cpu_metrics() {
456 let mut values = Vec::with_capacity(10_000);
458 let mut rng: u64 = 42;
459 for _ in 0..10_000 {
460 rng = rng.wrapping_mul(6364136223846793005).wrapping_add(1);
461 let cpu = ((rng >> 33) as f64 / (u32::MAX as f64)) * 100.0;
462 let cpu = (cpu * 10.0).round() / 10.0;
464 values.push(cpu);
465 }
466 let encoded = encode(&values);
467 let decoded = decode(&encoded).unwrap();
468 for (i, (a, b)) in values.iter().zip(decoded.iter()).enumerate() {
469 assert_eq!(a.to_bits(), b.to_bits(), "mismatch at {i}: {a} vs {b}");
470 }
471
472 let raw_size = values.len() * 8;
474 let ratio = raw_size as f64 / encoded.len() as f64;
475 assert!(
476 ratio > 3.0,
477 "ALP should compress CPU metrics >3x, got {ratio:.1}x"
478 );
479 }
480
481 #[test]
482 fn temperature_readings() {
483 let mut values = Vec::with_capacity(10_000);
485 let mut rng: u64 = 99;
486 for _ in 0..10_000 {
487 rng = rng.wrapping_mul(6364136223846793005).wrapping_add(1);
488 let temp = ((rng >> 33) as f64 / (u32::MAX as f64)) * 90.0 - 40.0;
489 let temp = (temp * 100.0).round() / 100.0;
490 values.push(temp);
491 }
492 let encoded = encode(&values);
493 let decoded = decode(&encoded).unwrap();
494 for (i, (a, b)) in values.iter().zip(decoded.iter()).enumerate() {
495 assert_eq!(a.to_bits(), b.to_bits(), "mismatch at {i}: {a} vs {b}");
496 }
497 }
498
499 #[test]
500 fn exception_handling() {
501 let mut values = vec![23.5, 99.9, 100.1, 50.0];
503 values.push(f64::NAN);
504 values.push(f64::INFINITY);
505 values.push(f64::NEG_INFINITY);
506 values.push(std::f64::consts::PI); let encoded = encode(&values);
509 let decoded = decode(&encoded).unwrap();
510
511 for (i, (a, b)) in values.iter().zip(decoded.iter()).enumerate() {
512 assert_eq!(
513 a.to_bits(),
514 b.to_bits(),
515 "mismatch at {i}: {a} ({:016x}) vs {b} ({:016x})",
516 a.to_bits(),
517 b.to_bits()
518 );
519 }
520 }
521
522 #[test]
523 fn all_exceptions() {
524 let values: Vec<f64> = (1..100).map(|i| std::f64::consts::PI * i as f64).collect();
526 let encoded = encode(&values);
527 let decoded = decode(&encoded).unwrap();
528 for (i, (a, b)) in values.iter().zip(decoded.iter()).enumerate() {
529 assert_eq!(a.to_bits(), b.to_bits(), "mismatch at {i}");
530 }
531 }
532
533 #[test]
534 fn encodability_check() {
535 let decimal_values: Vec<f64> = (0..1000).map(|i| i as f64 * 0.1).collect();
537 assert!(alp_encodability(&decimal_values) > 0.95);
538
539 let irrational_values: Vec<f64> =
543 (1..1000).map(|i| std::f64::consts::PI * i as f64).collect();
544 let irrational_enc = alp_encodability(&irrational_values);
545 assert!(
547 alp_encodability(&decimal_values) >= irrational_enc,
548 "decimal encodability should be >= irrational"
549 );
550 }
551
552 #[test]
553 fn better_than_gorilla_for_decimals() {
554 let values: Vec<f64> = (0..10_000).map(|i| i as f64 * 0.1).collect();
555 let alp_encoded = encode(&values);
556 let gorilla_encoded = crate::gorilla::encode_f64(&values);
557
558 assert!(
559 alp_encoded.len() < gorilla_encoded.len(),
560 "ALP ({} bytes) should beat Gorilla ({} bytes) on decimal data",
561 alp_encoded.len(),
562 gorilla_encoded.len()
563 );
564 }
565
566 #[test]
567 fn zero_values() {
568 let values = vec![0.0f64; 1000];
569 let encoded = encode(&values);
570 let decoded = decode(&encoded).unwrap();
571 for (a, b) in values.iter().zip(decoded.iter()) {
572 assert_eq!(a.to_bits(), b.to_bits());
573 }
574 }
575
576 #[test]
577 fn negative_zero() {
578 let values = vec![-0.0f64, 0.0, -0.0];
579 let encoded = encode(&values);
580 let decoded = decode(&encoded).unwrap();
581 for (i, (a, b)) in values.iter().zip(decoded.iter()).enumerate() {
582 assert_eq!(a.to_bits(), b.to_bits(), "mismatch at {i}");
583 }
584 }
585
586 #[test]
587 fn truncated_input_errors() {
588 assert!(decode(&[]).is_err());
589 assert!(decode(&[1, 0, 0, 0, 0, 0, 0, 0, 0]).is_err()); }
591
592 #[test]
593 fn large_dataset() {
594 let mut values = Vec::with_capacity(100_000);
595 let mut rng: u64 = 12345;
596 for _ in 0..100_000 {
597 rng = rng.wrapping_mul(6364136223846793005).wrapping_add(1);
598 let val = ((rng >> 33) as f64 / (u32::MAX as f64)) * 1000.0;
599 let val = (val * 100.0).round() / 100.0; values.push(val);
601 }
602 let encoded = encode(&values);
603 let decoded = decode(&encoded).unwrap();
604 assert_eq!(decoded.len(), values.len());
605 for (i, (a, b)) in values.iter().zip(decoded.iter()).enumerate() {
606 assert_eq!(a.to_bits(), b.to_bits(), "mismatch at {i}");
607 }
608 }
609}