Deduplicate fp_predict_f32 and fp_predict_f64 with a declarative macro

Replace the two near-identical floating-point predictor encoding
functions with a single fp_predict! macro parameterized on the float
type, using core::mem::size_of to derive the byte size.

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Sergey "Shnatsel" DavidoffandClaude committed 2026-02-26 15:29:29 +00:00
1 parent fdde3467fc
commit 8b0da5939a
1 file changed
+13 -29
+13 -29
View File
@@ -1,18 +1,21 @@
use crate::tags::{PhotometricInterpretation, SampleFormat}; use crate::tags::{PhotometricInterpretation, SampleFormat};
/// Apply floating-point predictor encoding to a row of f32 samples. /// Apply floating-point predictor encoding to a row of floating-point samples.
/// ///
/// The floating-point predictor works by: /// The floating-point predictor works by:
/// 1. Converting each f32 to big-endian bytes /// 1. Converting each value to big-endian bytes
/// 2. Shuffling bytes so all first bytes are together, all second bytes, etc. /// 2. Shuffling bytes so all first bytes are together, all second bytes, etc.
/// 3. Applying byte-level horizontal difference (wrapping_sub) across the entire buffer /// 3. Applying byte-level horizontal difference (wrapping_sub) across the entire buffer
/// ///
/// The decoder reverses this by: /// The decoder reverses this by:
/// 1. Applying cumulative sum (wrapping_add) on the entire byte stream /// 1. Applying cumulative sum (wrapping_add) on the entire byte stream
/// 2. De-shuffling bytes back into f32 values /// 2. De-shuffling bytes back into float values
fn fp_predict_f32(row: &[f32], samples: usize, result: &mut Vec<u8>) { macro_rules! fp_predict {
($name:ident, $float_type:ty) => {
fn $name(row: &[$float_type], samples: usize, result: &mut Vec<u8>) {
const BYTE_SIZE: usize = core::mem::size_of::<$float_type>();
let num_values = row.len(); let num_values = row.len();
let byte_count = num_values * 4; let byte_count = num_values * BYTE_SIZE;
let start_len = result.len(); let start_len = result.len();
// Reserve space for the output // Reserve space for the output
@@ -20,7 +23,7 @@ fn fp_predict_f32(row: &[f32], samples: usize, result: &mut Vec<u8>) {
// Step 1 & 2: Convert to big-endian and shuffle bytes // Step 1 & 2: Convert to big-endian and shuffle bytes
// All first bytes, then all second bytes, etc. // All first bytes, then all second bytes, etc.
for byte_idx in 0..4 { for byte_idx in 0..BYTE_SIZE {
for &value in row { for &value in row {
let bytes = value.to_be_bytes(); let bytes = value.to_be_bytes();
result.push(bytes[byte_idx]); result.push(bytes[byte_idx]);
@@ -35,31 +38,12 @@ fn fp_predict_f32(row: &[f32], samples: usize, result: &mut Vec<u8>) {
for i in (samples..byte_count).rev() { for i in (samples..byte_count).rev() {
output[i] = output[i].wrapping_sub(output[i - samples]); output[i] = output[i].wrapping_sub(output[i - samples]);
} }
}
};
} }
/// Apply floating-point predictor encoding to a row of f64 samples. fp_predict!(fp_predict_f32, f32);
fn fp_predict_f64(row: &[f64], samples: usize, result: &mut Vec<u8>) { fp_predict!(fp_predict_f64, f64);
let num_values = row.len();
let byte_count = num_values * 8;
let start_len = result.len();
// Reserve space for the output
result.reserve(byte_count);
// Step 1 & 2: Convert to big-endian and shuffle bytes
for byte_idx in 0..8 {
for &value in row {
let bytes = value.to_be_bytes();
result.push(bytes[byte_idx]);
}
}
// Step 3: Apply byte-level horizontal difference across the entire buffer
let output = &mut result[start_len..];
for i in (samples..byte_count).rev() {
output[i] = output[i].wrapping_sub(output[i - samples]);
}
}
macro_rules! integer_horizontal_predict { macro_rules! integer_horizontal_predict {
() => { () => {