use f8::f8;
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
use std::mem::{align_of, size_of};
use std::panic::{AssertUnwindSafe, catch_unwind};
fn reference_encode(value: f32) -> u8 {
(f64::from(value) * 255.0).round_ties_even() as u8
}
const SPECIAL_CASES: &[(u32, u8)] = &[
(0x0000_0000, 0), (0x8000_0000, 0),
(0x0000_0001, 0), (0x003f_ffff, 0),
(0x0040_0000, 0),
(0x007f_ffff, 0),
(0x0080_0000, 0),
(0x0080_0001, 0),
(0x8000_0001, 0),
(0x803f_ffff, 0),
(0x8040_0000, 0),
(0x807f_ffff, 0),
(0x8080_0000, 0),
(0x8080_0001, 0),
(0x3aff_ffff, 0), (0x3b00_0000, 0),
(0x3b00_0001, 0),
(0x3eff_ffff, 127), (0x3f00_0000, 128),
(0x3f00_0001, 128),
(0x3f7f_ffff, 255),
(0x3f80_0000, 255),
(0x3f80_0001, 255),
(0x4000_0000, 255),
(0x7f7f_ffff, 255),
(0x7f80_0000, 255), (0xbf00_0000, 0),
(0xbf80_0000, 0),
(0xff7f_ffff, 0),
(0xff80_0000, 0),
(0x7f80_0001, 0), (0x7fa0_0000, 0),
(0x7fbf_ffff, 0),
(0xff80_0001, 0),
(0xffa0_0000, 0),
(0xffbf_ffff, 0),
(0x7fc0_0000, 0), (0x7fc0_0001, 0),
(0x7fff_ffff, 0),
(0xffc0_0000, 0),
(0xffc0_0001, 0),
(0xffff_ffff, 0),
];
fn threshold_inputs() -> Vec<f32> {
let mut inputs = Vec::with_capacity(255 * 3);
for lower in 0..255 {
let nearest = ((f64::from(lower) + 0.5) / 255.0) as f32;
let bits = nearest.to_bits();
inputs.extend([bits - 1, bits, bits + 1].map(f32::from_bits));
}
inputs
}
fn assert_encodings(inputs: &[f32]) {
let mut bulk = vec![f8::from_bits(0xa5); inputs.len()];
f8::from_f32_slice(inputs, &mut bulk);
for (&input, actual) in inputs.iter().zip(bulk) {
let expected = reference_encode(input);
let bits = input.to_bits();
assert_eq!(
f8::from_f32(input).to_bits(),
expected,
"scalar {bits:#010x}"
);
assert_eq!(
f8::from(input).to_bits(),
expected,
"From<f32> {bits:#010x}"
);
assert_eq!(actual.to_bits(), expected, "bulk {bits:#010x}");
}
}
#[test]
fn all_bytes_round_trip_and_decode_bit_exactly() {
for bits in 0..=u8::MAX {
let value = f8::from_bits(bits);
let expected = f32::from(bits) / 255.0;
assert_eq!(value.to_bits(), bits);
assert_eq!(value.to_f32().to_bits(), expected.to_bits(), "byte {bits}");
assert_eq!(
f32::from(value).to_bits(),
expected.to_bits(),
"byte {bits}"
);
assert_eq!(f8::from_f32(expected).to_bits(), bits);
assert_eq!(f8::from_f32(value.to_f32()), value);
assert_eq!(f8::from(bits), value);
assert_eq!(u8::from(value), bits);
assert_eq!(f8::from(f32::from(value)), value);
}
}
#[test]
fn conversions_and_constants_work_in_const_evaluation() {
const CONVERSIONS: [(u8, u32, u8); 256] = {
let mut results = [(0, 0, 0); 256];
let mut index = 0;
while index < results.len() {
let value = f8::from_bits(index as u8);
let decoded = value.to_f32();
results[index] = (
value.to_bits(),
decoded.to_bits(),
f8::from_f32(decoded).to_bits(),
);
index += 1;
}
results
};
const SPECIAL_ENCODINGS: [u8; SPECIAL_CASES.len()] = {
let mut results = [0; SPECIAL_CASES.len()];
let mut index = 0;
while index < results.len() {
results[index] = f8::from_f32(f32::from_bits(SPECIAL_CASES[index].0)).to_bits();
index += 1;
}
results
};
const EXTREMA: [u8; 4] = [
f8::ZERO.to_bits(),
f8::ONE.to_bits(),
f8::MIN.to_bits(),
f8::MAX.to_bits(),
];
const EXTREMA_FLOATS: [u32; 4] = [
f8::ZERO.to_f32().to_bits(),
f8::ONE.to_f32().to_bits(),
f8::MIN.to_f32().to_bits(),
f8::MAX.to_f32().to_bits(),
];
assert_eq!(EXTREMA, [0, 255, 0, 255]);
assert_eq!(EXTREMA_FLOATS, [0, 0x3f80_0000, 0, 0x3f80_0000]);
for (index, &actual) in CONVERSIONS.iter().enumerate() {
let bits = index as u8;
assert_eq!(actual, (bits, (f32::from(bits) / 255.0).to_bits(), bits));
}
for (&actual, &(bits, expected)) in SPECIAL_ENCODINGS.iter().zip(SPECIAL_CASES) {
assert_eq!(actual, expected, "const input {bits:#010x}");
}
}
#[test]
fn every_bin_threshold_and_its_neighbors_use_exact_nearest_even() {
let inputs = threshold_inputs();
let mut exact_ties = 0;
let mut false_ties_rounding_down = 0;
let mut false_ties_rounding_up = 0;
for (lower, neighbors) in inputs.chunks_exact(3).enumerate() {
let midpoint = lower as f64 + 0.5;
assert!(f64::from(neighbors[0]) * 255.0 < midpoint);
assert!(f64::from(neighbors[2]) * 255.0 > midpoint);
assert_eq!(reference_encode(neighbors[0]), lower as u8);
assert_eq!(reference_encode(neighbors[2]), (lower + 1) as u8);
for &input in neighbors {
let exact_product = f64::from(input) * 255.0;
let expected = reference_encode(input);
if exact_product == midpoint {
exact_ties += 1;
assert_eq!(input.to_bits(), 0.5_f32.to_bits());
assert_eq!(expected, (lower + (lower & 1)) as u8);
}
let rounded_product = input * 255.0_f32;
if f64::from(rounded_product) == midpoint && exact_product != midpoint {
let incorrectly_rounded = rounded_product.round_ties_even() as u8;
false_ties_rounding_down += usize::from(incorrectly_rounded < expected);
false_ties_rounding_up += usize::from(incorrectly_rounded > expected);
}
}
}
assert_eq!(exact_ties, 1);
assert!(false_ties_rounding_down > 0);
assert!(false_ties_rounding_up > 0);
assert_encodings(&inputs);
}
#[test]
fn special_values_clamp_and_nan_payloads_map_to_zero() {
let inputs: Vec<_> = SPECIAL_CASES
.iter()
.map(|&(bits, expected)| {
let input = f32::from_bits(bits);
assert_eq!(reference_encode(input), expected, "reference {bits:#010x}");
assert_eq!(f8::from_f32(input).to_bits(), expected, "{bits:#010x}");
input
})
.collect();
assert_encodings(&inputs);
}
#[test]
fn every_sign_and_exponent_class_matches_reference() {
let mut inputs = Vec::new();
for sign in [0, 0x8000_0000] {
for exponent in 0..=255 {
for fraction in [
0,
1,
0x003f_ffff,
0x0040_0000,
0x0040_0001,
0x007f_fffe,
0x007f_ffff,
] {
inputs.push(f32::from_bits(sign | (exponent << 23) | fraction));
}
}
}
assert_encodings(&inputs);
}
#[test]
fn deterministic_random_bitpatterns_match_reference() {
let count = if cfg!(miri) { 512 } else { 100_000 };
let mut inputs = Vec::with_capacity(count);
let mut state = 0x6d2b_79f5_u32;
for _ in 0..count {
state ^= state << 13;
state ^= state >> 17;
state ^= state << 5;
inputs.push(f32::from_bits(state));
}
assert_encodings(&inputs);
}
#[test]
fn bulk_lengths_alignments_and_guards_match_reference() {
#[repr(align(32))]
struct Aligned<T>(T);
const CAPACITY: usize = if cfg!(miri) { 65 + 64 } else { 1025 + 64 };
const BYTE_SENTINEL: f8 = f8::from_bits(0xa5);
const FLOAT_SENTINEL: f32 = f32::from_bits(0xffc1_2345);
let lengths: &[usize] = if cfg!(miri) {
&[0, 1, 31, 32, 33, 63, 64, 65]
} else {
&[
0, 1, 2, 3, 7, 8, 15, 16, 17, 31, 32, 33, 47, 63, 64, 65, 95, 96, 97, 127, 128, 129,
255, 256, 257, 1023, 1024, 1025,
]
};
let mut inputs = threshold_inputs();
inputs.extend(SPECIAL_CASES.iter().map(|&(bits, _)| f32::from_bits(bits)));
let source_offsets = if cfg!(miri) { 2 } else { 8 };
let destination_offsets = if cfg!(miri) { 2 } else { 32 };
for &len in lengths {
for source_offset in 0..source_offsets {
for destination_offset in 0..destination_offsets {
let source_start = 32 + source_offset;
let destination_start = 32 + destination_offset;
let source_end = source_start + len;
let destination_end = destination_start + len;
let corpus_start = len + source_offset * 131 + destination_offset * 17;
let mut floats = Aligned([FLOAT_SENTINEL; CAPACITY]);
let mut bytes = Aligned([BYTE_SENTINEL; CAPACITY]);
let mut encoded = Aligned([BYTE_SENTINEL; CAPACITY]);
let mut decoded = Aligned([FLOAT_SENTINEL; CAPACITY]);
for index in 0..len {
floats.0[source_start + index] = inputs[(corpus_start + index) % inputs.len()];
bytes.0[destination_start + index] =
f8::from_bits((index * 73 + source_offset * 19 + destination_offset) as u8);
}
let original_floats = floats.0.map(f32::to_bits);
let original_bytes = bytes.0;
f8::from_f32_slice(
&floats.0[source_start..source_end],
&mut encoded.0[destination_start..destination_end],
);
f8::to_f32_slice(
&bytes.0[destination_start..destination_end],
&mut decoded.0[source_start..source_end],
);
for index in 0..len {
let expected =
reference_encode(f32::from_bits(original_floats[source_start + index]));
assert_eq!(
encoded.0[destination_start + index].to_bits(),
expected,
"encode len={len}, src={source_offset}, dst={destination_offset}, index={index}",
);
let bits = original_bytes[destination_start + index].to_bits();
assert_eq!(
decoded.0[source_start + index].to_bits(),
(f32::from(bits) / 255.0).to_bits(),
"decode byte={bits}, len={len}, src={destination_offset}, dst={source_offset}, index={index}",
);
}
assert!(
encoded.0[..destination_start]
.iter()
.all(|&value| value == BYTE_SENTINEL)
);
assert!(
encoded.0[destination_end..]
.iter()
.all(|&value| value == BYTE_SENTINEL)
);
assert!(
decoded.0[..source_start]
.iter()
.chain(&decoded.0[source_end..])
.all(|value| value.to_bits() == FLOAT_SENTINEL.to_bits())
);
assert_eq!(floats.0.map(f32::to_bits), original_floats);
assert_eq!(bytes.0, original_bytes);
}
}
}
}
#[test]
fn mismatched_bulk_lengths_panic_before_any_write() {
const FLOAT_SENTINEL_BITS: u32 = 0xffc1_2345;
for (source_len, destination_len) in [
(0, 1),
(1, 0),
(1, 2),
(2, 1),
(0, 33),
(33, 0),
(31, 32),
(32, 31),
(32, 33),
(33, 32),
(64, 65),
(65, 64),
(96, 97),
(97, 96),
] {
let source = vec![0.5; source_len];
let mut destination = vec![f8::from_bits(0x35); destination_len + 2];
let before = destination.clone();
let result = catch_unwind(AssertUnwindSafe(|| {
f8::from_f32_slice(&source, &mut destination[1..1 + destination_len]);
}));
assert!(
result.is_err(),
"encode lengths {}, {}",
source_len,
destination_len
);
assert_eq!(destination, before, "encode wrote before panicking");
let source = vec![f8::from_bits(128); source_len];
let mut destination = vec![f32::from_bits(FLOAT_SENTINEL_BITS); destination_len + 2];
let result = catch_unwind(AssertUnwindSafe(|| {
f8::to_f32_slice(&source, &mut destination[1..1 + destination_len]);
}));
assert!(
result.is_err(),
"decode lengths {}, {}",
source_len,
destination_len
);
assert!(
destination
.iter()
.all(|value| value.to_bits() == FLOAT_SENTINEL_BITS),
"decode wrote before panicking"
);
}
}
#[test]
fn byte_views_preserve_layout_pointers_and_mutations() {
assert_eq!(size_of::<f8>(), 1);
assert_eq!(align_of::<f8>(), 1);
assert_eq!(size_of::<[f8; 256]>(), size_of::<[u8; 256]>());
let mut values = [f8::from_bits(0xa5); 258];
for (index, value) in values[1..257].iter_mut().enumerate() {
*value = f8::from_bits(index as u8);
}
{
let slice = &values[1..257];
let bytes = f8::as_bytes(slice);
assert_eq!(bytes.as_ptr(), slice.as_ptr().cast::<u8>());
assert_eq!(bytes.len(), slice.len());
assert!(bytes.iter().copied().eq(0..=u8::MAX));
let round_trip = f8::from_bytes(bytes);
assert_eq!(round_trip.as_ptr(), slice.as_ptr());
assert_eq!(round_trip, slice);
}
{
let slice = &mut values[1..257];
let pointer = slice.as_mut_ptr().cast::<u8>();
let bytes = f8::as_bytes_mut(slice);
assert_eq!(bytes.as_mut_ptr(), pointer);
assert_eq!(bytes.len(), 256);
for (index, byte) in bytes.iter_mut().enumerate() {
*byte = 255 - index as u8;
}
let round_trip = f8::from_bytes_mut(bytes);
assert_eq!(round_trip.as_mut_ptr().cast::<u8>(), pointer);
assert_eq!(round_trip.len(), 256);
}
for (index, value) in values[1..257].iter().enumerate() {
assert_eq!(value.to_bits(), 255 - index as u8);
}
assert_eq!(values[0].to_bits(), 0xa5);
assert_eq!(values[257].to_bits(), 0xa5);
let mut bytes = [0xa5; 258];
for (index, byte) in bytes[1..257].iter_mut().enumerate() {
*byte = index as u8;
}
{
let slice = &bytes[1..257];
let values = f8::from_bytes(slice);
assert_eq!(values.as_ptr().cast::<u8>(), slice.as_ptr());
assert_eq!(values.len(), slice.len());
assert!(values.iter().map(|value| value.to_bits()).eq(0..=u8::MAX));
assert_eq!(f8::as_bytes(values).as_ptr(), slice.as_ptr());
assert_eq!(f8::as_bytes(values), slice);
}
{
let slice = &mut bytes[1..257];
let pointer = slice.as_mut_ptr();
let values = f8::from_bytes_mut(slice);
assert_eq!(values.as_mut_ptr().cast::<u8>(), pointer);
assert_eq!(values.len(), 256);
for (index, value) in values.iter_mut().enumerate() {
*value = f8::from_bits(255 - index as u8);
}
let round_trip = f8::as_bytes_mut(values);
assert_eq!(round_trip.as_mut_ptr(), pointer);
assert_eq!(round_trip.len(), 256);
}
assert!(bytes[1..257].iter().copied().eq((0..=u8::MAX).rev()));
assert_eq!(bytes[0], 0xa5);
assert_eq!(bytes[257], 0xa5);
}
#[test]
fn empty_byte_views_preserve_pointers_and_storage() {
for len in [0, 3] {
let mut values = vec![f8::from_bits(0xa5); len];
let mut bytes = vec![0xa5; len];
for offset in 0..=len {
let slice = &values[offset..offset];
let view = f8::as_bytes(slice);
assert!(view.is_empty());
assert_eq!(view.as_ptr(), slice.as_ptr().cast::<u8>());
let slice = &mut values[offset..offset];
let pointer = slice.as_mut_ptr().cast::<u8>();
let view = f8::as_bytes_mut(slice);
assert!(view.is_empty());
assert_eq!(view.as_mut_ptr(), pointer);
let slice = &bytes[offset..offset];
let view = f8::from_bytes(slice);
assert!(view.is_empty());
assert_eq!(view.as_ptr().cast::<u8>(), slice.as_ptr());
let slice = &mut bytes[offset..offset];
let pointer = slice.as_mut_ptr();
let view = f8::from_bytes_mut(slice);
assert!(view.is_empty());
assert_eq!(view.as_mut_ptr().cast::<u8>(), pointer);
}
assert_eq!(values, vec![f8::from_bits(0xa5); len]);
assert_eq!(bytes, vec![0xa5; len]);
}
}
#[test]
fn default_equality_ordering_and_hash_follow_raw_bytes() {
assert_eq!(f8::default(), f8::ZERO);
assert_eq!(f8::default().to_bits(), 0);
assert_eq!(f8::MIN, f8::ZERO);
assert_eq!(f8::MAX, f8::ONE);
let mut values: Vec<_> = (0..=u8::MAX).rev().map(f8::from_bits).collect();
values.sort();
assert!(values.iter().map(|value| value.to_bits()).eq(0..=u8::MAX));
for bits in 0..=u8::MAX {
let value = f8::from_bits(bits);
for other_bits in [0, bits, bits.wrapping_add(1), 255 - bits, 255] {
let other = f8::from_bits(other_bits);
assert_eq!(value == other, bits == other_bits);
assert_eq!(value.cmp(&other), bits.cmp(&other_bits));
assert_eq!(value.partial_cmp(&other), Some(bits.cmp(&other_bits)));
}
let mut raw_hash = DefaultHasher::new();
let mut value_hash = DefaultHasher::new();
bits.hash(&mut raw_hash);
value.hash(&mut value_hash);
assert_eq!(value_hash.finish(), raw_hash.finish(), "byte {bits}");
}
}
#[test]
fn display_formats_the_normalized_f32_and_forwards_flags() {
assert_eq!(f8::ZERO.to_string(), "0");
assert_eq!(f8::ONE.to_string(), "1");
for bits in 0..=u8::MAX {
let value = f8::from_bits(bits);
let decoded = f32::from(bits) / 255.0;
assert_eq!(format!("{value}"), format!("{decoded}"));
assert_eq!(format!("{value:.3}"), format!("{decoded:.3}"));
assert_eq!(format!("{value:+08.3}"), format!("{decoded:+08.3}"));
assert_eq!(format!("{value:*^14.6}"), format!("{decoded:*^14.6}"));
}
}
#[cfg(feature = "serde")]
#[test]
fn serde_is_a_newtype_containing_the_raw_byte() {
use serde_test::{Token, assert_tokens};
for bits in 0..=u8::MAX {
assert_tokens(
&f8::from_bits(bits),
&[Token::NewtypeStruct { name: "f8" }, Token::U8(bits)],
);
}
}
#[cfg(all(not(miri), not(debug_assertions)))]
#[test]
#[ignore = "75,497,473 floats; opt-in release-only scalar and bulk verification"]
fn exhaustive_relevant_positive_bitpatterns() {
const FIRST: u32 = 0x3b00_0000;
const LAST: u32 = 0x3f80_0000;
const CHUNK: usize = 4096;
let mut inputs = [0.0; CHUNK];
let mut output = [f8::ZERO; CHUNK];
let mut start = FIRST;
while start <= LAST {
let len = ((LAST - start + 1) as usize).min(CHUNK);
for (offset, input) in inputs[..len].iter_mut().enumerate() {
*input = f32::from_bits(start + offset as u32);
}
f8::from_f32_slice(&inputs[..len], &mut output[..len]);
for (&input, &actual) in inputs[..len].iter().zip(&output[..len]) {
let expected = reference_encode(input);
let bits = input.to_bits();
assert_eq!(
f8::from_f32(input).to_bits(),
expected,
"scalar {bits:#010x}"
);
assert_eq!(actual.to_bits(), expected, "bulk {bits:#010x}");
}
start += len as u32;
}
}