#![cfg(feature = "floats")]
#![allow(
missing_docs,
reason = "these are test fixtures rather than a published surface, so their schemas stay \
undocumented"
)]
use core::mem::align_of;
use core::mem::size_of;
use std::vec::Vec as StdVec;
use pinapod::PinaPod;
use pinapod::ZcElem;
use pinapod::ZcField;
use pinapod::ZcValidate;
use pinapod::pod::PodF32;
use pinapod::pod::PodF64;
fn assert_mapping<T, Pod>()
where
T: ZcField<Pod = Pod>,
Pod: ZcElem,
{
assert_eq!(size_of::<T::Pod>(), size_of::<Pod>());
assert_eq!(align_of::<Pod>(), 1);
}
#[test]
fn float_primitives_map_to_alignment_one_pods() {
assert_mapping::<f32, PodF32>();
assert_mapping::<f64, PodF64>();
fn assert_zc_elem<T: ZcElem>() {}
assert_zc_elem::<PodF32>();
assert_zc_elem::<PodF64>();
}
#[test]
fn float_pods_roundtrip_bit_patterns_exactly() {
for value in [
0.0_f32,
-0.0,
-1.5,
core::f32::consts::PI,
f32::MIN_POSITIVE,
f32::MAX,
f32::INFINITY,
f32::NEG_INFINITY,
f32::from_bits(0x7f80_0001), f32::from_bits(0xffc0_0000), ] {
let pod = PodF32::from(value);
assert_eq!(pod.to_bits(), value.to_bits(), "bits changed for {value}");
assert_eq!(pod.get().to_bits(), value.to_bits());
assert_eq!(f32::from(pod).to_bits(), value.to_bits());
}
for value in [0.0_f64, -2.25, f64::MAX, f64::NEG_INFINITY] {
let pod = PodF64::from(value);
assert_eq!(pod.to_bits(), value.to_bits());
assert_eq!(pod.get(), value);
}
}
#[test]
fn float_storage_is_little_endian() {
let mut pod = PodF32::ZERO;
pod.set(1.5);
assert_eq!(pod.as_ref(), &1.5_f32.to_bits().to_le_bytes());
pod.set_bits(0x4049_0fdb);
assert_eq!(pod.get().to_bits(), 0x4049_0fdb);
assert_eq!(pod.to_bits(), 0x4049_0fdb);
let mut wide = PodF64::ZERO;
wide.set(3.125);
assert_eq!(wide.as_ref(), &3.125_f64.to_bits().to_le_bytes());
assert_eq!(
PodF64::new_from_array(3.125_f64.to_bits().to_le_bytes()),
wide
);
}
#[test]
fn equality_is_bitwise_not_float_valued() {
assert_eq!(PodF32::from(1.5), PodF32::from(1.5));
assert_ne!(PodF32::from(0.0), PodF32::from(-0.0));
let quiet_nan = PodF32::from(f32::NAN);
assert_eq!(quiet_nan, quiet_nan, "NaN payloads compare bitwise");
assert!(PodF32::ZERO.is_zero());
assert!(!PodF32::from(-0.0).is_zero());
assert_eq!(
PodF32::from(1.0)
.get()
.partial_cmp(&PodF32::from(2.0).get()),
Some(core::cmp::Ordering::Less)
);
}
#[test]
fn every_bit_pattern_is_a_valid_stored_value() {
for bits in [0u32, 1, 0x7f80_0001, 0xffc0_0000, u32::MAX] {
let mut pod = PodF32::ZERO;
pod.set_bits(bits);
assert!(PodF32::validate_ref(&pod).is_ok());
assert_eq!(pod.to_bits(), bits);
}
let mut wide = PodF64::ZERO;
wide.set(f64::NAN);
assert!(PodF64::validate_ref(&wide).is_ok());
}
#[allow(dead_code)]
#[derive(PinaPod)]
struct FloatAccount {
pub temperature: f32,
pub depth: f64,
pub previous_depth: Option<f64>,
pub bias: PodF32,
}
#[test]
fn fixed_schema_roundtrips_native_float_fields() {
let mut bytes = [0u8; FloatAccount::SIZE];
assert_eq!(size_of::<PodF32>(), 4);
assert_eq!(size_of::<PodF64>(), 8);
{
let account = FloatAccount::read_exact_mut(&mut bytes).unwrap();
account.temperature.set(-12.5);
account.depth.set(3.125);
account.previous_depth.set(Some(PodF64::from(2.0)));
account.bias.set(0.25);
}
assert_eq!(&bytes[..4], &(-12.5_f32).to_bits().to_le_bytes());
assert_eq!(&bytes[4..12], &3.125_f64.to_bits().to_le_bytes());
let account = FloatAccount::read_exact(&bytes).unwrap();
assert_eq!(account.temperature(), -12.5);
assert_eq!(account.depth(), 3.125);
assert_eq!(
account.previous_depth.get().map(|value| value.get()),
Some(2.0)
);
assert_eq!(account.bias.get(), 0.25);
}
#[allow(dead_code)]
#[derive(PinaPod)]
#[pinapod(compact)]
struct FloatBook {
pub mark_price: f32,
pub venues: pinapod::Vec<f32, 4>,
pub name: pinapod::String<8>,
}
#[test]
fn compact_schema_roundtrips_float_header_and_tail() {
let mut bytes = [0u8; FloatBook::MAX_SIZE];
let prices = [1.0_f32, 2.5];
let mapped = prices.map(PodF32::from);
let patch = FloatBookPatch::new()
.mark_price(1.75)
.replace_venues(&mapped)
.name("float");
let encoded_size = FloatBook::initialize(&mut bytes, &patch).unwrap();
assert_eq!(
encoded_size,
FloatBook::HEADER_SIZE + mapped.len() * size_of::<PodF32>() + "float".len(),
);
let book = FloatBook::read_prefix(&bytes[..encoded_size]).unwrap();
assert_eq!(book.mark_price.get(), 1.75);
assert_eq!(
book.venues().iter().map(PodF32::get).collect::<StdVec<_>>(),
prices,
);
assert_eq!(book.name(), "float");
}
#[test]
fn nan_payloads_survive_a_schema_round_trip() {
let mut bytes = [0u8; FloatAccount::SIZE];
{
let account = FloatAccount::read_exact_mut(&mut bytes).unwrap();
account.temperature.set_bits(0x7fc0_0001);
account.depth.set(f64::NAN);
}
let account = FloatAccount::read_exact(&bytes).unwrap();
assert_eq!(account.temperature.get().to_bits(), 0x7fc0_0001);
assert!(account.depth.get().is_nan());
}
#[allow(dead_code)]
#[derive(PinaPod)]
struct FloatCollections {
pub samples: Option<f32>,
pub history: pinapod::Vec<f64, 3>,
}
#[test]
fn floats_compose_with_bounded_collections() {
let mut bytes = [0u8; FloatCollections::SIZE];
let history = [1.0_f64, -2.5];
let mapped = history.map(PodF64::from);
{
let value = FloatCollections::read_exact_mut(&mut bytes).unwrap();
value.samples.set(Some(PodF32::from(0.5)));
value.history.try_set(mapped).unwrap();
}
let value = FloatCollections::read_exact(&bytes).unwrap();
assert_eq!(value.samples.get().map(|sample| sample.get()), Some(0.5));
assert_eq!(
value.history.iter().map(PodF64::get).collect::<StdVec<_>>(),
history,
);
}
#[test]
fn float_pods_expose_formatting_hashing_and_constants() {
let pod = PodF32::from(1.5);
assert_eq!(std::format!("{pod}"), "1.5");
assert_eq!(std::format!("{pod:?}"), "1.5");
assert_eq!(std::format!("{pod:x}"), "3fc00000");
assert_eq!(std::format!("{pod:X}"), "3FC00000");
assert_eq!(std::format!("{pod:b}"), "111111110000000000000000000000");
assert_eq!(std::format!("{}", PodF64::from(3.125)), "3.125");
let mut hasher = std::collections::hash_map::DefaultHasher::new();
std::hash::Hash::hash(&pod, &mut hasher);
let first = std::hash::Hasher::finish(&hasher);
let mut hasher = std::collections::hash_map::DefaultHasher::new();
std::hash::Hash::hash(&PodF32::from(1.5), &mut hasher);
assert_eq!(first, std::hash::Hasher::finish(&hasher));
assert_eq!(PodF32::default(), PodF32::ZERO);
assert_eq!(PodF32::MIN_POSITIVE.to_bits(), f32::MIN_POSITIVE.to_bits());
assert_eq!(PodF32::MAX.to_bits(), f32::MAX.to_bits());
assert_eq!(PodF64::MAX.to_bits(), f64::MAX.to_bits());
assert!(PodF32::from(1.0).get() < PodF32::from(2.0).get());
}
#[test]
fn float_pods_copy_and_convert_like_byte_containers() {
let pod = PodF32::from(-0.75);
let copied = pod; #[allow(clippy::clone_on_copy)]
let cloned = pod.clone();
assert_eq!(copied, cloned);
assert_eq!(f32::from(copied), -0.75);
assert_eq!(
PodF64::from(f64::from(PodF64::from(1.25))),
PodF64::from(1.25)
);
assert_eq!(<StdVec<_>>::from([pod]).len(), 1);
}
#[test]
fn float_arrays_accept_every_bit_pattern() {
let f32s = [
PodF32::from(f32::NAN),
PodF32::from(f32::INFINITY),
PodF32::from(-0.0),
PodF32::ZERO,
];
ZcValidate::validate_ref(&f32s).unwrap();
let f64s = [PodF64::from(f64::NEG_INFINITY), PodF64::from(f64::MAX)];
ZcValidate::validate_ref(&f64s).unwrap();
let signaling_nan = PodF32::new_from_array(0x7F80_0001_u32.to_le_bytes());
let all_ones = PodF32::new_from_array(0xFFFF_FFFF_u32.to_le_bytes());
let raw = [signaling_nan, all_ones];
ZcValidate::validate_ref(&raw).unwrap();
}