192 lines
6.6 KiB
Rust
192 lines
6.6 KiB
Rust
use std::ops::Index;
|
|
|
|
use super::prefix_sums;
|
|
|
|
#[derive(Debug, Clone)]
|
|
pub(super) enum ReferenceMatchedValues {
|
|
Identical,
|
|
Owned(Vec<f64>),
|
|
}
|
|
|
|
impl ReferenceMatchedValues {
|
|
pub(super) fn push(&mut self, value: f64, reference: &[f64], capacity: usize) {
|
|
let previous_len = reference.len().checked_sub(1).expect("reference row is missing");
|
|
match self {
|
|
Self::Identical if value.to_bits() == reference[previous_len].to_bits() => {}
|
|
Self::Identical => {
|
|
let mut values = Vec::with_capacity(capacity);
|
|
values.extend_from_slice(&reference[..previous_len]);
|
|
values.push(value);
|
|
*self = Self::Owned(values);
|
|
}
|
|
Self::Owned(values) => {
|
|
debug_assert_eq!(values.len(), previous_len);
|
|
values.push(value);
|
|
}
|
|
}
|
|
}
|
|
|
|
pub(super) fn values<'a>(&'a self, reference: &'a [f64]) -> &'a [f64] {
|
|
match self {
|
|
Self::Identical => reference,
|
|
Self::Owned(values) => {
|
|
debug_assert_eq!(values.len(), reference.len());
|
|
values
|
|
}
|
|
}
|
|
}
|
|
|
|
pub(super) fn set(&mut self, index: usize, value: f64, reference: &[f64]) {
|
|
assert!(index < reference.len(), "series index out of bounds");
|
|
match self {
|
|
Self::Owned(values) => values[index] = value,
|
|
Self::Identical if value.to_bits() == reference[index].to_bits() => {}
|
|
Self::Identical => {
|
|
let mut values = reference.to_vec();
|
|
values[index] = value;
|
|
*self = Self::Owned(values);
|
|
}
|
|
}
|
|
}
|
|
|
|
pub(super) fn prefix(&self) -> Self {
|
|
match self {
|
|
Self::Identical => Self::Identical,
|
|
Self::Owned(values) => Self::Owned(prefix_sums(values)),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone)]
|
|
pub(super) struct RepeatedValues<T> {
|
|
repeated: T,
|
|
values: Option<Vec<T>>,
|
|
len: usize,
|
|
}
|
|
|
|
impl<T: Default + Clone + Eq> RepeatedValues<T> {
|
|
pub(super) fn new() -> Self {
|
|
Self { repeated: T::default(), values: None, len: 0 }
|
|
}
|
|
|
|
pub(super) fn push(&mut self, value: &T, capacity: usize) {
|
|
if let Some(values) = &mut self.values {
|
|
values.push(value.clone());
|
|
} else if self.len == 0 {
|
|
self.repeated = value.clone();
|
|
} else if *value != self.repeated {
|
|
let mut values = Vec::with_capacity(capacity);
|
|
values.resize(self.len, std::mem::take(&mut self.repeated));
|
|
values.push(value.clone());
|
|
self.values = Some(values);
|
|
}
|
|
self.len += 1;
|
|
}
|
|
|
|
pub(super) fn set(&mut self, index: usize, value: T) {
|
|
assert!(index < self.len, "series index out of bounds");
|
|
if let Some(values) = &mut self.values {
|
|
values[index] = value;
|
|
} else if value != self.repeated {
|
|
let mut values = vec![std::mem::take(&mut self.repeated); self.len];
|
|
values[index] = value;
|
|
self.values = Some(values);
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<T> Index<usize> for RepeatedValues<T> {
|
|
type Output = T;
|
|
|
|
fn index(&self, index: usize) -> &T {
|
|
assert!(index < self.len, "series index out of bounds");
|
|
match &self.values {
|
|
Some(values) => &values[index],
|
|
None => &self.repeated,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
fn bits(values: &[f64]) -> Vec<u64> {
|
|
values.iter().map(|value| value.to_bits()).collect()
|
|
}
|
|
|
|
#[test]
|
|
fn identical_prices_share_only_after_exact_bit_comparison() {
|
|
let reference = [10., -0., f64::from_bits(0x7ff8_0000_0000_0042), f64::INFINITY];
|
|
let mut column = ReferenceMatchedValues::Identical;
|
|
for (index, value) in reference.iter().copied().enumerate() {
|
|
column.push(value, &reference[..=index], reference.len());
|
|
}
|
|
assert!(matches!(column, ReferenceMatchedValues::Identical));
|
|
assert_eq!(column.values(&reference).as_ptr(), reference.as_ptr());
|
|
let prefix = prefix_sums(&reference);
|
|
assert_eq!(bits(column.prefix().values(&prefix)), bits(&prefix));
|
|
|
|
let original = column.clone();
|
|
column.set(1, 0., &reference);
|
|
assert!(matches!(column, ReferenceMatchedValues::Owned(_)));
|
|
assert_eq!(column.values(&reference)[1].to_bits(), 0_f64.to_bits());
|
|
assert_eq!(bits(original.values(&reference)), bits(&reference));
|
|
assert_eq!(bits(column.prefix().values(&prefix)), bits(&prefix_sums(column.values(&reference))));
|
|
}
|
|
|
|
#[test]
|
|
fn differing_prices_preserve_zero_nan_payloads_and_prior_rows() {
|
|
let reference = [10., 11., f64::from_bits(0x7ff8_0000_0000_0042), 13.];
|
|
for actual in [
|
|
[10., 0., reference[2], 13.],
|
|
[10., 11., f64::from_bits(0x7ff8_0000_0000_0043), 13.],
|
|
] {
|
|
let mut column = ReferenceMatchedValues::Identical;
|
|
for (index, value) in actual.iter().copied().enumerate() {
|
|
column.push(value, &reference[..=index], actual.len());
|
|
}
|
|
assert!(matches!(column, ReferenceMatchedValues::Owned(_)));
|
|
assert_eq!(bits(column.values(&reference)), bits(&actual));
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn repeated_values_preserve_nonzero_values_and_copy_on_change() {
|
|
let mut column = RepeatedValues::new();
|
|
for _ in 0..128 { column.push(&7_u64, 128); }
|
|
assert!(column.values.is_none());
|
|
assert_eq!(column[127], 7);
|
|
column.set(0, 7);
|
|
assert!(column.values.is_none());
|
|
let mut changed = column.clone();
|
|
changed.set(64, 9);
|
|
assert_eq!(changed[64], 9);
|
|
assert_eq!(changed[63], 7);
|
|
assert_eq!(column[64], 7);
|
|
}
|
|
|
|
#[test]
|
|
fn optional_values_keep_none_distinct_from_empty_and_repeated_text() {
|
|
for repeated in [None, Some(String::new()), Some("continuous".to_string())] {
|
|
let mut column = RepeatedValues::new();
|
|
for _ in 0..12 { column.push(&repeated, 16); }
|
|
assert!(column.values.is_none());
|
|
assert_eq!(column[0], repeated);
|
|
column.push(&Some("closing".to_string()), 16);
|
|
assert_eq!(column[11], repeated);
|
|
assert_eq!(column[12].as_deref(), Some("closing"));
|
|
column.set(5, None);
|
|
assert_eq!(column[5], None);
|
|
assert_eq!(column[4], repeated);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
#[should_panic(expected = "series index out of bounds")]
|
|
fn repeated_values_reject_out_of_range_access() {
|
|
let column = RepeatedValues::<u64>::new();
|
|
let _ = column[0];
|
|
}
|
|
}
|