1use std::mem::size_of;
28
29use crate::bounds::{
30 checked_add, checked_capacity, checked_mul, checked_range, decoded_len, encode_input_len,
31 encode_u32_len, u32_to_usize,
32};
33use crate::error::CodecError;
34use crate::fastlanes;
35
36const MAX_EXPONENT: u8 = 18;
38
39const POW10: [f64; 19] = [
41 1.0,
42 10.0,
43 100.0,
44 1_000.0,
45 10_000.0,
46 100_000.0,
47 1_000_000.0,
48 10_000_000.0,
49 100_000_000.0,
50 1_000_000_000.0,
51 10_000_000_000.0,
52 100_000_000_000.0,
53 1_000_000_000_000.0,
54 10_000_000_000_000.0,
55 100_000_000_000_000.0,
56 1_000_000_000_000_000.0,
57 10_000_000_000_000_000.0,
58 100_000_000_000_000_000.0,
59 1_000_000_000_000_000_000.0,
60];
61
62const INV_POW10: [f64; 19] = [
64 1.0,
65 0.1,
66 0.01,
67 0.001,
68 0.000_1,
69 0.000_01,
70 0.000_001,
71 0.000_000_1,
72 0.000_000_01,
73 0.000_000_001,
74 0.000_000_000_1,
75 0.000_000_000_01,
76 0.000_000_000_001,
77 0.000_000_000_000_1,
78 0.000_000_000_000_01,
79 0.000_000_000_000_001,
80 0.000_000_000_000_000_1,
81 0.000_000_000_000_000_01,
82 0.000_000_000_000_000_001,
83];
84
85use crate::CODEC_SAMPLE_SIZE;
86
87pub fn encode(values: &[f64]) -> Result<Vec<u8>, CodecError> {
97 let decoded_bytes = checked_mul(values.len(), size_of::<f64>(), "ALP input bytes")?;
98 decoded_len(decoded_bytes, "ALP input")?;
99 let count = encode_input_len(values.len(), "ALP value count")?;
100
101 if values.is_empty() {
102 let mut out = Vec::with_capacity(11);
103 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 Ok(out);
109 }
110
111 let params = find_best_params(values);
113 let factor = POW10[params.encode_exp as usize];
114
115 let mut encoded_ints = Vec::with_capacity(values.len());
117 let mut exceptions: Vec<(u32, u64)> = Vec::new();
118
119 for (i, &val) in values.iter().enumerate() {
120 let encoded = try_alp_encode(val, factor, params.decode_exp, params.mode);
121 match encoded {
122 Some(int_val) => {
123 encoded_ints.push(int_val);
124 }
125 None => {
126 exceptions.push((
128 u32::try_from(i).map_err(|_| CodecError::ResourceLimit {
129 resource: "ALP exception index".into(),
130 requested: i,
131 limit: u32::MAX as usize,
132 })?,
133 val.to_bits(),
134 ));
135 encoded_ints.push(0);
136 }
137 }
138 }
139
140 let exception_count = encode_u32_len(exceptions.len(), "ALP exception count")?;
142 let packed_ints = fastlanes::encode(&encoded_ints)?;
143
144 let exceptions_size = checked_mul(exceptions.len(), 12, "ALP exceptions")?;
145 let capacity = checked_add(
146 checked_add(11, exceptions_size, "ALP output")?,
147 packed_ints.len(),
148 "ALP output",
149 )?;
150 let mut out = Vec::with_capacity(capacity);
151
152 out.extend_from_slice(&count.to_le_bytes());
154 out.push(params.encode_exp);
155 out.push(params.decode_exp);
156 out.push(match params.mode {
157 DecodeMode::MultiplyInverse => 0,
158 DecodeMode::DivideByFactor => 1,
159 });
160 out.extend_from_slice(&exception_count.to_le_bytes());
161
162 for &(idx, bits) in &exceptions {
164 out.extend_from_slice(&idx.to_le_bytes());
165 out.extend_from_slice(&bits.to_le_bytes());
166 }
167
168 out.extend_from_slice(&packed_ints);
170
171 Ok(out)
172}
173
174pub fn decode(data: &[u8]) -> Result<Vec<f64>, CodecError> {
176 const HEADER_SIZE: usize = 11; if data.len() < HEADER_SIZE {
179 return Err(CodecError::Truncated {
180 expected: HEADER_SIZE,
181 actual: data.len(),
182 });
183 }
184
185 let count = u32_to_usize(
186 u32::from_le_bytes([data[0], data[1], data[2], data[3]]),
187 "ALP value count",
188 )?;
189 decoded_len(
190 checked_mul(count, size_of::<f64>(), "ALP decoded values")?,
191 "ALP",
192 )?;
193 let encode_exp = data[4];
194 let decode_exp = data[5];
195 let mode = match data[6] {
196 0 => DecodeMode::MultiplyInverse,
197 1 => DecodeMode::DivideByFactor,
198 m => {
199 return Err(CodecError::Corrupt {
200 detail: format!("invalid ALP decode mode {m}"),
201 });
202 }
203 };
204 let exception_count = u32_to_usize(
205 u32::from_le_bytes([data[7], data[8], data[9], data[10]]),
206 "ALP exception count",
207 )?;
208
209 if count == 0 {
210 if exception_count != 0 || data.len() != HEADER_SIZE {
211 return Err(CodecError::Corrupt {
212 detail: "non-canonical empty ALP frame".into(),
213 });
214 }
215 return Ok(Vec::new());
216 }
217 if exception_count > count {
218 return Err(CodecError::Corrupt {
219 detail: "ALP exception count exceeds value count".into(),
220 });
221 }
222
223 if encode_exp > MAX_EXPONENT || decode_exp > MAX_EXPONENT {
224 return Err(CodecError::Corrupt {
225 detail: format!("invalid ALP exponents encode={encode_exp}, decode={decode_exp}"),
226 });
227 }
228
229 decoded_len(
231 checked_mul(
232 exception_count,
233 size_of::<(usize, u64)>(),
234 "ALP exception allocation",
235 )?,
236 "ALP exceptions",
237 )?;
238 let exceptions_size = checked_mul(exception_count, 12, "ALP exceptions")?;
239 let exceptions_end = checked_add(HEADER_SIZE, exceptions_size, "ALP exceptions")?;
240 checked_range(data, HEADER_SIZE, exceptions_size, "ALP exceptions")?;
241
242 let exception_capacity = checked_capacity(
243 exception_count,
244 size_of::<(usize, u64)>(),
245 "ALP exception allocation",
246 )?;
247 let mut exceptions = Vec::with_capacity(exception_capacity);
248 let mut pos = HEADER_SIZE;
249 let mut previous_index = None;
250 for _ in 0..exception_count {
251 let idx = u32_to_usize(
252 u32::from_le_bytes([data[pos], data[pos + 1], data[pos + 2], data[pos + 3]]),
253 "ALP exception index",
254 )?;
255 if idx >= count || previous_index.is_some_and(|previous| idx <= previous) {
256 return Err(CodecError::Corrupt {
257 detail: "ALP exceptions must be ordered unique in-range indices".into(),
258 });
259 }
260 previous_index = Some(idx);
261 let bits = u64::from_le_bytes([
262 data[pos + 4],
263 data[pos + 5],
264 data[pos + 6],
265 data[pos + 7],
266 data[pos + 8],
267 data[pos + 9],
268 data[pos + 10],
269 data[pos + 11],
270 ]);
271 exceptions.push((idx, bits));
272 pos += 12;
273 }
274
275 let encoded_ints =
277 fastlanes::decode(&data[exceptions_end..]).map_err(|e| CodecError::Corrupt {
278 detail: format!("ALP fastlanes decode: {e}"),
279 })?;
280
281 if encoded_ints.len() != count {
282 return Err(CodecError::Corrupt {
283 detail: format!(
284 "ALP int count mismatch: expected {count}, got {}",
285 encoded_ints.len()
286 ),
287 });
288 }
289
290 let value_capacity = checked_capacity(count, size_of::<f64>(), "ALP values")?;
292 let mut values = Vec::with_capacity(value_capacity);
293 for &int_val in &encoded_ints {
294 values.push(alp_decode_value(int_val, decode_exp, mode));
295 }
296
297 let factor = POW10[encode_exp as usize];
300 for &(idx, bits) in &exceptions {
301 let value = f64::from_bits(bits);
302 if encoded_ints[idx] != 0 || try_alp_encode(value, factor, decode_exp, mode).is_some() {
303 return Err(CodecError::Corrupt {
304 detail: "non-canonical ALP exception".into(),
305 });
306 }
307 values[idx] = value;
308 }
309
310 Ok(values)
311}
312
313pub fn alp_encodability(values: &[f64]) -> f64 {
318 if values.is_empty() {
319 return 1.0;
320 }
321
322 let sample_end = values.len().min(CODEC_SAMPLE_SIZE);
323 let sample = &values[..sample_end];
324 let params = find_best_params(sample);
325 let factor = POW10[params.encode_exp as usize];
326
327 let encodable = sample
328 .iter()
329 .filter(|&&v| try_alp_encode(v, factor, params.decode_exp, params.mode).is_some())
330 .count();
331
332 encodable as f64 / sample.len() as f64
333}
334
335#[derive(Debug, Clone, Copy, PartialEq, Eq)]
345enum DecodeMode {
346 MultiplyInverse,
348 DivideByFactor,
350}
351
352#[inline]
356fn try_alp_encode(value: f64, factor: f64, decode_exp: u8, mode: DecodeMode) -> Option<i64> {
357 if !value.is_finite() {
358 return None;
359 }
360
361 let scaled = value * factor;
362 if scaled < i64::MIN as f64 || scaled > i64::MAX as f64 {
363 return None;
364 }
365
366 let encoded = scaled.round() as i64;
367
368 let decoded = match mode {
369 DecodeMode::MultiplyInverse => encoded as f64 * INV_POW10[decode_exp as usize],
370 DecodeMode::DivideByFactor => encoded as f64 / POW10[decode_exp as usize],
371 };
372
373 if decoded.to_bits() == value.to_bits() {
374 Some(encoded)
375 } else {
376 None
377 }
378}
379
380#[inline]
382fn alp_decode_value(encoded: i64, decode_exp: u8, mode: DecodeMode) -> f64 {
383 match mode {
384 DecodeMode::MultiplyInverse => encoded as f64 * INV_POW10[decode_exp as usize],
385 DecodeMode::DivideByFactor => encoded as f64 / POW10[decode_exp as usize],
386 }
387}
388
389struct AlpParams {
396 encode_exp: u8,
397 decode_exp: u8,
398 mode: DecodeMode,
399}
400
401fn find_best_params(values: &[f64]) -> AlpParams {
403 let sample_end = values.len().min(CODEC_SAMPLE_SIZE);
404 let sample = &values[..sample_end];
405
406 let mut best = AlpParams {
407 encode_exp: 0,
408 decode_exp: 0,
409 mode: DecodeMode::MultiplyInverse,
410 };
411 let mut best_count: usize = 0;
412
413 for e in 0..=MAX_EXPONENT {
414 let factor = POW10[e as usize];
415
416 for mode in [DecodeMode::MultiplyInverse, DecodeMode::DivideByFactor] {
417 let count = sample
419 .iter()
420 .filter(|&&v| try_alp_encode(v, factor, e, mode).is_some())
421 .count();
422
423 if count > best_count {
424 best_count = count;
425 best = AlpParams {
426 encode_exp: e,
427 decode_exp: e,
428 mode,
429 };
430 }
431
432 if e > 0 {
434 let count_alt = sample
435 .iter()
436 .filter(|&&v| try_alp_encode(v, factor, e - 1, mode).is_some())
437 .count();
438 if count_alt > best_count {
439 best_count = count_alt;
440 best = AlpParams {
441 encode_exp: e,
442 decode_exp: e - 1,
443 mode,
444 };
445 }
446 }
447 if e < MAX_EXPONENT {
448 let count_alt = sample
449 .iter()
450 .filter(|&&v| try_alp_encode(v, factor, e + 1, mode).is_some())
451 .count();
452 if count_alt > best_count {
453 best_count = count_alt;
454 best = AlpParams {
455 encode_exp: e,
456 decode_exp: e + 1,
457 mode,
458 };
459 }
460 }
461 }
462 }
463
464 best
465}
466
467#[cfg(test)]
468mod tests {
469 use super::*;
470
471 fn encode(values: &[f64]) -> Vec<u8> {
472 super::encode(values).expect("test ALP encode")
473 }
474
475 #[test]
476 fn empty_roundtrip() {
477 let encoded = encode(&[]);
478 let decoded = decode(&encoded).unwrap();
479 assert!(decoded.is_empty());
480 }
481
482 #[test]
483 fn single_value() {
484 let values = vec![23.5f64];
485 let encoded = encode(&values);
486 let decoded = decode(&encoded).unwrap();
487 assert_eq!(decoded[0].to_bits(), values[0].to_bits());
488 }
489
490 #[test]
491 fn integer_values() {
492 let values: Vec<f64> = (0..1000).map(|i| i as f64).collect();
494 let encoded = encode(&values);
495 let decoded = decode(&encoded).unwrap();
496 for (a, b) in values.iter().zip(decoded.iter()) {
497 assert_eq!(a.to_bits(), b.to_bits(), "mismatch: {a} vs {b}");
498 }
499 }
500
501 #[test]
502 fn decimal_values_one_digit() {
503 let values: Vec<f64> = (0..1000).map(|i| i as f64 * 0.1).collect();
505 let encoded = encode(&values);
506 let decoded = decode(&encoded).unwrap();
507 for (i, (a, b)) in values.iter().zip(decoded.iter()).enumerate() {
508 assert_eq!(a.to_bits(), b.to_bits(), "mismatch at {i}: {a} vs {b}");
509 }
510 }
511
512 #[test]
513 fn decimal_values_two_digits() {
514 let values: Vec<f64> = (0..1000).map(|i| i as f64 * 0.01).collect();
516 let encoded = encode(&values);
517 let decoded = decode(&encoded).unwrap();
518 for (i, (a, b)) in values.iter().zip(decoded.iter()).enumerate() {
519 assert_eq!(a.to_bits(), b.to_bits(), "mismatch at {i}: {a} vs {b}");
520 }
521 }
522
523 #[test]
524 fn typical_cpu_metrics() {
525 let mut values = Vec::with_capacity(10_000);
527 let mut rng: u64 = 42;
528 for _ in 0..10_000 {
529 rng = rng.wrapping_mul(6364136223846793005).wrapping_add(1);
530 let cpu = ((rng >> 33) as f64 / (u32::MAX as f64)) * 100.0;
531 let cpu = (cpu * 10.0).round() / 10.0;
533 values.push(cpu);
534 }
535 let encoded = encode(&values);
536 let decoded = decode(&encoded).unwrap();
537 for (i, (a, b)) in values.iter().zip(decoded.iter()).enumerate() {
538 assert_eq!(a.to_bits(), b.to_bits(), "mismatch at {i}: {a} vs {b}");
539 }
540
541 let raw_size = values.len() * 8;
543 let ratio = raw_size as f64 / encoded.len() as f64;
544 assert!(
545 ratio > 3.0,
546 "ALP should compress CPU metrics >3x, got {ratio:.1}x"
547 );
548 }
549
550 #[test]
551 fn temperature_readings() {
552 let mut values = Vec::with_capacity(10_000);
554 let mut rng: u64 = 99;
555 for _ in 0..10_000 {
556 rng = rng.wrapping_mul(6364136223846793005).wrapping_add(1);
557 let temp = ((rng >> 33) as f64 / (u32::MAX as f64)) * 90.0 - 40.0;
558 let temp = (temp * 100.0).round() / 100.0;
559 values.push(temp);
560 }
561 let encoded = encode(&values);
562 let decoded = decode(&encoded).unwrap();
563 for (i, (a, b)) in values.iter().zip(decoded.iter()).enumerate() {
564 assert_eq!(a.to_bits(), b.to_bits(), "mismatch at {i}: {a} vs {b}");
565 }
566 }
567
568 #[test]
569 fn exception_handling() {
570 let mut values = vec![23.5, 99.9, 100.1, 50.0];
572 values.push(f64::NAN);
573 values.push(f64::INFINITY);
574 values.push(f64::NEG_INFINITY);
575 values.push(std::f64::consts::PI); let encoded = encode(&values);
578 let decoded = decode(&encoded).unwrap();
579
580 for (i, (a, b)) in values.iter().zip(decoded.iter()).enumerate() {
581 assert_eq!(
582 a.to_bits(),
583 b.to_bits(),
584 "mismatch at {i}: {a} ({:016x}) vs {b} ({:016x})",
585 a.to_bits(),
586 b.to_bits()
587 );
588 }
589 }
590
591 #[test]
592 fn all_exceptions() {
593 let values: Vec<f64> = (1..100).map(|i| std::f64::consts::PI * i as f64).collect();
595 let encoded = encode(&values);
596 let decoded = decode(&encoded).unwrap();
597 for (i, (a, b)) in values.iter().zip(decoded.iter()).enumerate() {
598 assert_eq!(a.to_bits(), b.to_bits(), "mismatch at {i}");
599 }
600 }
601
602 #[test]
603 fn encodability_check() {
604 let decimal_values: Vec<f64> = (0..1000).map(|i| i as f64 * 0.1).collect();
606 assert!(alp_encodability(&decimal_values) > 0.95);
607
608 let irrational_values: Vec<f64> =
612 (1..1000).map(|i| std::f64::consts::PI * i as f64).collect();
613 let irrational_enc = alp_encodability(&irrational_values);
614 assert!(
616 alp_encodability(&decimal_values) >= irrational_enc,
617 "decimal encodability should be >= irrational"
618 );
619 }
620
621 #[test]
622 fn better_than_gorilla_for_decimals() {
623 let values: Vec<f64> = (0..10_000).map(|i| i as f64 * 0.1).collect();
624 let alp_encoded = encode(&values);
625 let gorilla_encoded = crate::gorilla::encode_f64(&values);
626
627 assert!(
628 alp_encoded.len() < gorilla_encoded.len(),
629 "ALP ({} bytes) should beat Gorilla ({} bytes) on decimal data",
630 alp_encoded.len(),
631 gorilla_encoded.len()
632 );
633 }
634
635 #[test]
636 fn zero_values() {
637 let values = vec![0.0f64; 1000];
638 let encoded = encode(&values);
639 let decoded = decode(&encoded).unwrap();
640 for (a, b) in values.iter().zip(decoded.iter()) {
641 assert_eq!(a.to_bits(), b.to_bits());
642 }
643 }
644
645 #[test]
646 fn negative_zero() {
647 let values = vec![-0.0f64, 0.0, -0.0];
648 let encoded = encode(&values);
649 let decoded = decode(&encoded).unwrap();
650 for (i, (a, b)) in values.iter().zip(decoded.iter()).enumerate() {
651 assert_eq!(a.to_bits(), b.to_bits(), "mismatch at {i}");
652 }
653 }
654
655 #[test]
656 fn rejects_noncanonical_exception_layout() {
657 let mut encoded = encode(&[f64::NAN, 1.0]);
658 let first_exception = 11;
659 encoded[first_exception..first_exception + 4].copy_from_slice(&2u32.to_le_bytes());
660 assert!(matches!(decode(&encoded), Err(CodecError::Corrupt { .. })));
661 let mut empty = encode(&[]);
662 empty.push(0);
663 assert!(matches!(decode(&empty), Err(CodecError::Corrupt { .. })));
664
665 let mut bad_exponent = encode(&[1.0]);
666 bad_exponent[4] = MAX_EXPONENT + 1;
667 assert!(matches!(
668 decode(&bad_exponent),
669 Err(CodecError::Corrupt { .. })
670 ));
671
672 let mut nonzero_placeholder = encode(&[f64::NAN]);
673 let nested_min = 11 + 12 + 6 + 3;
674 nonzero_placeholder[nested_min..nested_min + 8].copy_from_slice(&1i64.to_le_bytes());
675 assert!(matches!(
676 decode(&nonzero_placeholder),
677 Err(CodecError::Corrupt { .. })
678 ));
679 }
680
681 #[test]
682 fn truncated_input_errors() {
683 assert!(decode(&[]).is_err());
684 assert!(decode(&[1, 0, 0, 0, 0, 0, 0, 0, 0]).is_err()); }
686
687 #[test]
688 fn large_dataset() {
689 let mut values = Vec::with_capacity(100_000);
690 let mut rng: u64 = 12345;
691 for _ in 0..100_000 {
692 rng = rng.wrapping_mul(6364136223846793005).wrapping_add(1);
693 let val = ((rng >> 33) as f64 / (u32::MAX as f64)) * 1000.0;
694 let val = (val * 100.0).round() / 100.0; values.push(val);
696 }
697 let encoded = encode(&values);
698 let decoded = decode(&encoded).unwrap();
699 assert_eq!(decoded.len(), values.len());
700 for (i, (a, b)) in values.iter().zip(decoded.iter()).enumerate() {
701 assert_eq!(a.to_bits(), b.to_bits(), "mismatch at {i}");
702 }
703 }
704}