#![allow(clippy::unwrap_used, clippy::expect_used)]
use chronix_core::config::FloatEncoding;
use chronix_encoding::{ColumnDecoder, ColumnEncoder, DecodedColumn};
struct Workload {
name: &'static str,
scale: u8,
mantissas: Vec<i128>,
min_ratio: f64,
max_overhead_bytes: Option<usize>,
}
impl Workload {
#[allow(clippy::cast_precision_loss)]
fn floats(&self) -> Vec<f64> {
let divisor = 10f64.powi(i32::from(self.scale));
self.mantissas.iter().map(|&m| m as f64 / divisor).collect()
}
}
fn workloads() -> Vec<Workload> {
vec![
Workload {
name: "quarter-hour register, Wh, 4dp, monotone",
scale: 4,
mantissas: (0..8192).map(|i| 10_000_000_000 + i * 2_500_000).collect(),
min_ratio: 8.0,
max_overhead_bytes: Some(2),
},
Workload {
name: "tariff, EUR/kWh, 5dp, constant",
scale: 5,
mantissas: vec![28_500; 8192],
min_ratio: 100.0,
max_overhead_bytes: Some(2),
},
Workload {
name: "connection-point power, W, 2dp, noisy",
scale: 2,
mantissas: (0..8192).map(|i| 23_000 + (i * 37) % 4_000).collect(),
min_ratio: 2.0,
max_overhead_bytes: Some(0),
},
Workload {
name: "38-digit quantity, 2dp, wide form",
scale: 2,
mantissas: (0..4096)
.map(|i| 99_999_999_999_999_999_999_999_999_999_999_000_000 + i128::from(i))
.collect(),
min_ratio: 1.5,
max_overhead_bytes: None,
},
]
}
fn ratio(raw: usize, encoded: usize) -> f64 {
#[allow(clippy::cast_precision_loss)]
{
raw as f64 / encoded as f64
}
}
#[test]
fn decimal_codec_ratios_hold() {
println!(
"\n{:<44} {:>10} {:>10} {:>10}",
"workload", "decimal", "as f64", "vs f64"
);
for w in workloads() {
let block = ColumnEncoder::encode_decimal(&w.mantissas).unwrap();
let decimal_bytes = block.to_bytes().len();
let raw = w.mantissas.len() * 8;
let float_block = ColumnEncoder::encode_f64(&w.floats(), FloatEncoding::Chimp).unwrap();
let float_bytes = float_block.to_bytes().len();
if w.max_overhead_bytes.is_some() {
println!(
"{:<44} {:>9.1}x {:>9.1}x {:>9.2}x",
w.name,
ratio(raw, decimal_bytes),
ratio(raw, float_bytes),
ratio(float_bytes, decimal_bytes),
);
} else {
println!(
"{:<44} {:>9.1}x {:>10} {:>10}",
w.name,
ratio(raw, decimal_bytes),
"n/a",
"n/a",
);
}
assert!(
ratio(raw, decimal_bytes) >= w.min_ratio,
"{}: decimal reached {:.1}x, below the documented {:.1}x",
w.name,
ratio(raw, decimal_bytes),
w.min_ratio
);
if let Some(limit) = w.max_overhead_bytes {
assert!(
decimal_bytes <= float_bytes + limit,
"{}: the exact form cost {decimal_bytes} B against the float form's \
{float_bytes} B — more than the documented {limit} bytes of block header",
w.name
);
}
match ColumnDecoder::decode(&block).unwrap() {
DecodedColumn::Decimal(back) => assert_eq!(back, w.mantissas, "{}", w.name),
other => panic!("{}: decoded as {other:?}", w.name),
}
}
println!();
}
#[test]
fn a_constant_decimal_column_costs_almost_nothing() {
let values = vec![28_500i128; 96];
let block = ColumnEncoder::encode_decimal(&values).unwrap();
assert!(
block.to_bytes().len() < 32,
"a constant column took {} bytes",
block.to_bytes().len()
);
}
#[test]
fn the_narrow_form_is_what_realistic_registers_use() {
let a_hundred_terawatt_hours_in_wh_at_4dp: i128 = 100_000_000_000_000 * 10_000;
assert!(i64::try_from(a_hundred_terawatt_hours_in_wh_at_4dp).is_ok());
let block = ColumnEncoder::encode_decimal(&[a_hundred_terawatt_hours_in_wh_at_4dp]).unwrap();
assert_eq!(block.payload[0], 0, "expected the narrow form");
}