From 2c0a14849e7d68adafbf783bf1a4fc8043988d3b Mon Sep 17 00:00:00 2001 From: Will Manning Date: Tue, 24 Sep 2024 17:41:28 +0100 Subject: [PATCH 01/15] improve ALP size estimation --- encodings/alp/src/alp.rs | 49 ++++++++++++++++++++++++++--------- encodings/alp/src/compress.rs | 5 ++-- 2 files changed, 40 insertions(+), 14 deletions(-) diff --git a/encodings/alp/src/alp.rs b/encodings/alp/src/alp.rs index 27e24e0e4b6..5d73bbc8e32 100644 --- a/encodings/alp/src/alp.rs +++ b/encodings/alp/src/alp.rs @@ -2,7 +2,7 @@ use std::fmt::{Display, Formatter}; use std::mem::size_of; use itertools::Itertools; -use num_traits::{Float, NumCast, PrimInt, Zero}; +use num_traits::{CheckedSub, Float, NumCast, PrimInt, Saturating, ToPrimitive, Zero}; use serde::{Deserialize, Serialize}; use vortex_error::vortex_panic; @@ -21,7 +21,7 @@ impl Display for Exponents { } pub trait ALPFloat: Float + Display + 'static { - type ALPInt: PrimInt + Display; + type ALPInt: PrimInt + Display + ToPrimitive; const FRACTIONAL_BITS: u8; const MAX_EXPONENT: u8; @@ -30,10 +30,12 @@ pub trait ALPFloat: Float + Display + 'static { const IF10: &'static [Self]; /// Round to the nearest floating integer by shifting in and out of the low precision range. + #[inline] fn fast_round(self) -> Self { (self + Self::SWEET) - Self::SWEET } + #[inline] fn as_int(self) -> Option { ::from(self) } @@ -50,16 +52,14 @@ pub trait ALPFloat: Float + Display + 'static { .collect_vec() }); - // TODO(wmanning): idea, start with highest e, then find the best f - // after that, try e's in descending order, with a gap no larger than the original e - f - for e in 0..Self::MAX_EXPONENT { + for e in (0..Self::MAX_EXPONENT).rev() { for f in 0..e { - let (_, encoded, exc_pos, exc_patches) = Self::encode( + let (_, encoded, _, exc_patches) = Self::encode( sample.as_deref().unwrap_or(values), Some(Exponents { e, f }), ); - let size = - (encoded.len() + exc_patches.len()) * size_of::() + (exc_pos.len() * 4); + + let size = Self::estimate_encoded_size(&encoded, &exc_patches); if size < best_nbytes { best_nbytes = size; best_exp = Exponents { e, f }; @@ -72,6 +72,31 @@ pub trait ALPFloat: Float + Display + 'static { best_exp } + #[inline(always)] + fn estimate_encoded_size(encoded: &[Self::ALPInt], patches: &[Self]) -> usize { + let bits_per_encoded = encoded + .iter() + .minmax() + .into_option() + // estimating bits per encoded value assuming frame-of-reference + bitpacking-without-patches + .and_then(|(min, max)| max.checked_sub(min)) + .and_then(|range_size: ::ALPInt| range_size.to_u64()) + .and_then(|range_size| { + range_size + .checked_ilog2() + .map(|bits| (bits + 1) as usize) + .or(Some(0)) + }) + .unwrap_or(size_of::() * 8); + + let encoded_bytes = (encoded.len() * bits_per_encoded + 7) / 8; + // each patch is a value + a position + // in practice, patch positions are in [0, u16::MAX] because of how we chunk + let patch_bytes = patches.len() * (size_of::() + size_of::()); + + encoded_bytes + patch_bytes + } + fn encode( values: &[Self], exponents: Option, @@ -149,7 +174,7 @@ impl ALPFloat for f32 { 10000000.0, 100000000.0, 1000000000.0, - 10000000000.0, + 10000000000.0, // 10^10 ]; const IF10: &'static [Self] = &[ 1.0, @@ -162,7 +187,7 @@ impl ALPFloat for f32 { 0.0000001, 0.00000001, 0.000000001, - 0.0000000001, + 0.0000000001, // 10^-10 ]; } @@ -196,7 +221,7 @@ impl ALPFloat for f64 { 100000000000000000000.0, 1000000000000000000000.0, 10000000000000000000000.0, - 100000000000000000000000.0, + 100000000000000000000000.0, // 10^23 ]; const IF10: &'static [Self] = &[ @@ -223,6 +248,6 @@ impl ALPFloat for f64 { 0.00000000000000000001, 0.000000000000000000001, 0.0000000000000000000001, - 0.00000000000000000000001, + 0.00000000000000000000001, // 10^-23 ]; } diff --git a/encodings/alp/src/compress.rs b/encodings/alp/src/compress.rs index 33041bba683..40707e534ed 100644 --- a/encodings/alp/src/compress.rs +++ b/encodings/alp/src/compress.rs @@ -2,7 +2,7 @@ use vortex::array::{PrimitiveArray, Sparse, SparseArray}; use vortex::validity::Validity; use vortex::{Array, ArrayDType, ArrayDef, IntoArray, IntoArrayVariant}; use vortex_dtype::{NativePType, PType}; -use vortex_error::{vortex_bail, VortexExpect as _, VortexResult}; +use vortex_error::{vortex_bail,VortexExpect as _, VortexResult}; use vortex_scalar::Scalar; use crate::alp::ALPFloat; @@ -14,11 +14,12 @@ macro_rules! match_each_alp_float_ptype { ($self:expr, | $_:tt $enc:ident | $($body:tt)*) => ({ macro_rules! __with__ {( $_ $enc:ident ) => ( $($body)* )} use vortex_dtype::PType; + use vortex_error::vortex_panic; let ptype = $self; match ptype { PType::F32 => __with__! { f32 }, PType::F64 => __with__! { f64 }, - _ => panic!("ALP can only encode f32 and f64"), + _ => vortex_panic!("ALP can only encode f32 and f64, got {}", ptype), } }) } From bcd77619127a2da5d3c598e8493489086f69bb07 Mon Sep 17 00:00:00 2001 From: Will Manning Date: Tue, 24 Sep 2024 18:04:15 +0100 Subject: [PATCH 02/15] fmt --- encodings/alp/src/alp.rs | 2 +- encodings/alp/src/compress.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/encodings/alp/src/alp.rs b/encodings/alp/src/alp.rs index 5d73bbc8e32..6cf8f6b1b94 100644 --- a/encodings/alp/src/alp.rs +++ b/encodings/alp/src/alp.rs @@ -2,7 +2,7 @@ use std::fmt::{Display, Formatter}; use std::mem::size_of; use itertools::Itertools; -use num_traits::{CheckedSub, Float, NumCast, PrimInt, Saturating, ToPrimitive, Zero}; +use num_traits::{CheckedSub, Float, NumCast, PrimInt, ToPrimitive, Zero}; use serde::{Deserialize, Serialize}; use vortex_error::vortex_panic; diff --git a/encodings/alp/src/compress.rs b/encodings/alp/src/compress.rs index 40707e534ed..0029eb3051b 100644 --- a/encodings/alp/src/compress.rs +++ b/encodings/alp/src/compress.rs @@ -2,7 +2,7 @@ use vortex::array::{PrimitiveArray, Sparse, SparseArray}; use vortex::validity::Validity; use vortex::{Array, ArrayDType, ArrayDef, IntoArray, IntoArrayVariant}; use vortex_dtype::{NativePType, PType}; -use vortex_error::{vortex_bail,VortexExpect as _, VortexResult}; +use vortex_error::{vortex_bail, VortexExpect as _, VortexResult}; use vortex_scalar::Scalar; use crate::alp::ALPFloat; From ffd2b4d9dda213cab0e9523720ea61ab308a0043 Mon Sep 17 00:00:00 2001 From: Will Manning Date: Tue, 24 Sep 2024 23:17:50 +0100 Subject: [PATCH 03/15] fix test --- encodings/alp/src/compress.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/encodings/alp/src/compress.rs b/encodings/alp/src/compress.rs index 0029eb3051b..e05c89485bb 100644 --- a/encodings/alp/src/compress.rs +++ b/encodings/alp/src/compress.rs @@ -123,7 +123,7 @@ mod tests { encoded.encoded().as_primitive().maybe_null_slice::(), vec![1234; 1025] ); - assert_eq!(encoded.exponents(), Exponents { e: 4, f: 1 }); + assert_eq!(encoded.exponents(), Exponents { e: 9, f: 6 }); let decoded = decompress(encoded).unwrap(); assert_eq!( @@ -141,7 +141,7 @@ mod tests { encoded.encoded().as_primitive().maybe_null_slice::(), vec![0, 1234, 0] ); - assert_eq!(encoded.exponents(), Exponents { e: 4, f: 1 }); + assert_eq!(encoded.exponents(), Exponents { e: 9, f: 6 }); let decoded = decompress(encoded).unwrap(); let expected = vec![0f32, 1.234f32, 0f32]; @@ -159,7 +159,7 @@ mod tests { encoded.encoded().as_primitive().maybe_null_slice::(), vec![1234i64, 2718, 2718, 4000] // fill forward ); - assert_eq!(encoded.exponents(), Exponents { e: 3, f: 0 }); + assert_eq!(encoded.exponents(), Exponents { e: 16, f: 13 }); let decoded = decompress(encoded).unwrap(); assert_eq!(values, decoded.maybe_null_slice::()); @@ -179,7 +179,7 @@ mod tests { let encoded = alp_encode(&array).unwrap(); assert!(encoded.patches().is_some()); - assert_eq!(encoded.exponents(), Exponents { e: 3, f: 0 }); + assert_eq!(encoded.exponents(), Exponents { e: 16, f: 13 }); for idx in 0..3 { let s = scalar_at(encoded.as_ref(), idx).unwrap(); From 0aaedca8b297b651f747113b0351eaa38b108a54 Mon Sep 17 00:00:00 2001 From: Will Manning Date: Wed, 25 Sep 2024 00:49:51 +0100 Subject: [PATCH 04/15] branchless ALP --- encodings/alp/src/alp.rs | 83 ++++++++++++++++++++++++++-------------- 1 file changed, 54 insertions(+), 29 deletions(-) diff --git a/encodings/alp/src/alp.rs b/encodings/alp/src/alp.rs index 6cf8f6b1b94..ed2e286a8ba 100644 --- a/encodings/alp/src/alp.rs +++ b/encodings/alp/src/alp.rs @@ -2,7 +2,7 @@ use std::fmt::{Display, Formatter}; use std::mem::size_of; use itertools::Itertools; -use num_traits::{CheckedSub, Float, NumCast, PrimInt, ToPrimitive, Zero}; +use num_traits::{Bounded, CheckedSub, Float, NumCast, PrimInt, ToPrimitive, Zero}; use serde::{Deserialize, Serialize}; use vortex_error::vortex_panic; @@ -21,7 +21,7 @@ impl Display for Exponents { } pub trait ALPFloat: Float + Display + 'static { - type ALPInt: PrimInt + Display + ToPrimitive; + type ALPInt: PrimInt + Bounded + Display + ToPrimitive; const FRACTIONAL_BITS: u8; const MAX_EXPONENT: u8; @@ -103,42 +103,56 @@ pub trait ALPFloat: Float + Display + 'static { ) -> (Exponents, Vec, Vec, Vec) { let exp = exponents.unwrap_or_else(|| Self::find_best_exponents(values)); - let mut exc_pos = Vec::new(); - let mut exc_value = Vec::new(); - let mut prev = Self::ALPInt::zero(); - let encoded = values + // this is intentionally branchless + // TODO: batch this into 1024 values at a time to make it more cache friendly + let mut patch_count = 0; + let mut encoded = values .iter() - .enumerate() - .map(|(i, v)| { - match Self::encode_single(*v, exp) { - Ok(fi) => { - prev = fi; - fi - } - Err(exc) => { - exc_pos.push(i as u64); - exc_value.push(exc); - // Emit the last known good value. This helps with run-end encoding. - prev - } - } + .map(|v| { + let encoded = unsafe { Self::encode_single_unchecked(*v, exp) }; + let decoded = Self::decode_single(encoded, exp); + let neq: usize = (decoded != *v) as usize; + patch_count += neq; + encoded }) .collect_vec(); - (exp, encoded, exc_pos, exc_value) + let mut patch_indices = Vec::with_capacity(patch_count); + let mut patch_values = Vec::with_capacity(patch_count); + if patch_count > 0 { + let mut patch_index = 0; + for i in 0..encoded.len() { + let decoded = Self::decode_single(encoded[i], exp); + patch_indices[patch_index] = i as u64; + patch_values[patch_index] = values[i]; + patch_index += (decoded != values[i]) as usize; + } + assert_eq!(patch_index, patch_count); + + // find the first successfully encoded value (i.e., not patched) + let mut fill_value = Self::ALPInt::zero(); + for (i, v) in encoded.iter().enumerate() { + if patch_indices[i] != i as u64 { + fill_value = encoded[i]; + break; + } + } + + for patch_idx in patch_indices.iter() { + encoded[*patch_idx as usize] = fill_value; + } + } + + (exp, encoded, patch_indices, patch_values) } #[inline] fn encode_single(value: Self, exponents: Exponents) -> Result { - let encoded = (value * Self::F10[exponents.e as usize] * Self::IF10[exponents.f as usize]) - .fast_round(); - if let Some(e) = encoded.as_int() { - let decoded = Self::decode_single(e, exponents); - if decoded == value { - return Ok(e); - } + let encoded = unsafe { Self::encode_single_unchecked(value, exponents) }; + let decoded = Self::decode_single(encoded, exponents); + if decoded == value { + return Ok(encoded); } - Err(value) } @@ -154,6 +168,17 @@ pub trait ALPFloat: Float + Display + 'static { }); encoded_float * Self::F10[exponents.f as usize] * Self::IF10[exponents.e as usize] } + + /// # Safety + /// + /// The returned value may not decode back to the original value. + #[inline(always)] + unsafe fn encode_single_unchecked(value: Self, exponents: Exponents) -> Self::ALPInt { + (value * Self::F10[exponents.e as usize] * Self::IF10[exponents.f as usize]) + .fast_round() + .as_int() + .unwrap_or_else(Self::ALPInt::max_value) + } } impl ALPFloat for f32 { From d250b7eb6513965aee85fca22a2957e95b741813 Mon Sep 17 00:00:00 2001 From: Will Manning Date: Wed, 25 Sep 2024 00:51:42 +0100 Subject: [PATCH 05/15] fixes --- encodings/alp/src/alp.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/encodings/alp/src/alp.rs b/encodings/alp/src/alp.rs index ed2e286a8ba..03ca1674a26 100644 --- a/encodings/alp/src/alp.rs +++ b/encodings/alp/src/alp.rs @@ -131,7 +131,7 @@ pub trait ALPFloat: Float + Display + 'static { // find the first successfully encoded value (i.e., not patched) let mut fill_value = Self::ALPInt::zero(); - for (i, v) in encoded.iter().enumerate() { + for i in 0..encoded.len() { if patch_indices[i] != i as u64 { fill_value = encoded[i]; break; From e666c122430b5afbfc52158e3b1f1446048e18c1 Mon Sep 17 00:00:00 2001 From: Will Manning Date: Wed, 25 Sep 2024 01:34:21 +0100 Subject: [PATCH 06/15] hopefully very fast --- encodings/alp/src/alp.rs | 96 ++++++++++++++++++++++++++-------------- 1 file changed, 62 insertions(+), 34 deletions(-) diff --git a/encodings/alp/src/alp.rs b/encodings/alp/src/alp.rs index 03ca1674a26..3bf84802fa7 100644 --- a/encodings/alp/src/alp.rs +++ b/encodings/alp/src/alp.rs @@ -103,47 +103,75 @@ pub trait ALPFloat: Float + Display + 'static { ) -> (Exponents, Vec, Vec, Vec) { let exp = exponents.unwrap_or_else(|| Self::find_best_exponents(values)); + let mut encoded_output = Vec::with_capacity(values.len()); + let mut patch_indices = Vec::new(); + let mut patch_values = Vec::new(); + let mut fill_value: Option = None; + let mut has_filled = false; + // this is intentionally branchless // TODO: batch this into 1024 values at a time to make it more cache friendly - let mut patch_count = 0; - let mut encoded = values - .iter() - .map(|v| { - let encoded = unsafe { Self::encode_single_unchecked(*v, exp) }; - let decoded = Self::decode_single(encoded, exp); - let neq: usize = (decoded != *v) as usize; - patch_count += neq; - encoded - }) - .collect_vec(); - - let mut patch_indices = Vec::with_capacity(patch_count); - let mut patch_values = Vec::with_capacity(patch_count); - if patch_count > 0 { - let mut patch_index = 0; - for i in 0..encoded.len() { - let decoded = Self::decode_single(encoded[i], exp); - patch_indices[patch_index] = i as u64; - patch_values[patch_index] = values[i]; - patch_index += (decoded != values[i]) as usize; - } - assert_eq!(patch_index, patch_count); - - // find the first successfully encoded value (i.e., not patched) - let mut fill_value = Self::ALPInt::zero(); - for i in 0..encoded.len() { - if patch_indices[i] != i as u64 { - fill_value = encoded[i]; - break; + const CHUNK_SIZE: usize = 1024; + for (chunk_idx, chunk) in values.chunks(CHUNK_SIZE).enumerate() { + let mut chunk_patch_count = 0; + encoded_output.extend(chunk + .iter() + .map(|v| { + let encoded = unsafe { Self::encode_single_unchecked(*v, exp) }; + let decoded = Self::decode_single(encoded, exp); + let neq: usize = (decoded != *v) as usize; + chunk_patch_count += neq; + encoded + })); + let chunk_patch_count = chunk_patch_count; // immutable hereafter + + if chunk_patch_count > 0 { + let num_prev_encoded = chunk_idx * CHUNK_SIZE; + let num_prev_patches = patch_indices.len(); + + let mut patch_indices_mut = patch_indices.spare_capacity_mut(); + let mut patch_values_mut = patch_values.spare_capacity_mut(); + + let mut chunk_patch_index = 0; + for i in num_prev_encoded..encoded_output.len() { + let decoded = Self::decode_single(encoded_output[i], exp); + patch_indices_mut[chunk_patch_index] = i as u64; + patch_values_mut[chunk_patch_index] = values[i]; + chunk_patch_index += (decoded != values[i]) as usize; + } + assert_eq!(chunk_patch_index, chunk_patch_count); + unsafe { + patch_indices.set_len(num_prev_patches + chunk_patch_count); + patch_values.set_len(num_prev_patches + chunk_patch_count); } - } - for patch_idx in patch_indices.iter() { - encoded[*patch_idx as usize] = fill_value; + // find the first successfully encoded value (i.e., not patched) + if fill_value.is_none() { + assert_eq!(num_prev_encoded, num_prev_patches); + for i in num_prev_encoded..encoded_output.len() { + if patch_indices[i] != i as u64 { + fill_value = Some(encoded_output[i]); + break; + } + } + } + + if let Some(fill_value) = fill_value { + let patch_indices_to_fill = if !has_filled { + &patch_indices + } else { + &patch_indices[num_prev_patches..] + }; + + for patch_idx in patch_indices_to_fill.iter() { + encoded_output[*patch_idx as usize] = fill_value; + } + has_filled = true; + } } } - (exp, encoded, patch_indices, patch_values) + (exp, encoded_output, patch_indices, patch_values) } #[inline] From 470d51ecf2055c6470e3c1946105f4937985c1e6 Mon Sep 17 00:00:00 2001 From: Will Manning Date: Wed, 25 Sep 2024 01:48:56 +0100 Subject: [PATCH 07/15] fmt --- encodings/alp/src/alp.rs | 28 +++++++++++++--------------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/encodings/alp/src/alp.rs b/encodings/alp/src/alp.rs index 3bf84802fa7..d4a8d727840 100644 --- a/encodings/alp/src/alp.rs +++ b/encodings/alp/src/alp.rs @@ -2,7 +2,7 @@ use std::fmt::{Display, Formatter}; use std::mem::size_of; use itertools::Itertools; -use num_traits::{Bounded, CheckedSub, Float, NumCast, PrimInt, ToPrimitive, Zero}; +use num_traits::{Bounded, CheckedSub, Float, NumCast, PrimInt, ToPrimitive}; use serde::{Deserialize, Serialize}; use vortex_error::vortex_panic; @@ -114,29 +114,27 @@ pub trait ALPFloat: Float + Display + 'static { const CHUNK_SIZE: usize = 1024; for (chunk_idx, chunk) in values.chunks(CHUNK_SIZE).enumerate() { let mut chunk_patch_count = 0; - encoded_output.extend(chunk - .iter() - .map(|v| { - let encoded = unsafe { Self::encode_single_unchecked(*v, exp) }; - let decoded = Self::decode_single(encoded, exp); - let neq: usize = (decoded != *v) as usize; - chunk_patch_count += neq; - encoded - })); + encoded_output.extend(chunk.iter().map(|v| { + let encoded = unsafe { Self::encode_single_unchecked(*v, exp) }; + let decoded = Self::decode_single(encoded, exp); + let neq: usize = (decoded != *v) as usize; + chunk_patch_count += neq; + encoded + })); let chunk_patch_count = chunk_patch_count; // immutable hereafter if chunk_patch_count > 0 { let num_prev_encoded = chunk_idx * CHUNK_SIZE; let num_prev_patches = patch_indices.len(); - let mut patch_indices_mut = patch_indices.spare_capacity_mut(); - let mut patch_values_mut = patch_values.spare_capacity_mut(); + let patch_indices_mut = patch_indices.spare_capacity_mut(); + let patch_values_mut = patch_values.spare_capacity_mut(); let mut chunk_patch_index = 0; for i in num_prev_encoded..encoded_output.len() { let decoded = Self::decode_single(encoded_output[i], exp); - patch_indices_mut[chunk_patch_index] = i as u64; - patch_values_mut[chunk_patch_index] = values[i]; + patch_indices_mut[chunk_patch_index].write(i as u64); + patch_values_mut[chunk_patch_index].write(values[i]); chunk_patch_index += (decoded != values[i]) as usize; } assert_eq!(chunk_patch_index, chunk_patch_count); @@ -158,7 +156,7 @@ pub trait ALPFloat: Float + Display + 'static { if let Some(fill_value) = fill_value { let patch_indices_to_fill = if !has_filled { - &patch_indices + &patch_indices } else { &patch_indices[num_prev_patches..] }; From 43bee8d9ffd6bc55277e7a0f294cd9bc47b7851d Mon Sep 17 00:00:00 2001 From: Will Manning Date: Wed, 25 Sep 2024 02:04:59 +0100 Subject: [PATCH 08/15] works, but only 10% faster --- encodings/alp/src/alp.rs | 82 ++++++++++++++++++++--------------- encodings/alp/src/compress.rs | 2 +- 2 files changed, 47 insertions(+), 37 deletions(-) diff --git a/encodings/alp/src/alp.rs b/encodings/alp/src/alp.rs index d4a8d727840..2562a8f6f48 100644 --- a/encodings/alp/src/alp.rs +++ b/encodings/alp/src/alp.rs @@ -123,49 +123,59 @@ pub trait ALPFloat: Float + Display + 'static { })); let chunk_patch_count = chunk_patch_count; // immutable hereafter - if chunk_patch_count > 0 { - let num_prev_encoded = chunk_idx * CHUNK_SIZE; - let num_prev_patches = patch_indices.len(); + if chunk_patch_count == 0 { + continue; + } - let patch_indices_mut = patch_indices.spare_capacity_mut(); - let patch_values_mut = patch_values.spare_capacity_mut(); + // reserve space for the patches in this chunk (plus one because our loop may attempt to write one past the end) + patch_indices.reserve(chunk_patch_count + 1); + patch_values.reserve(chunk_patch_count + 1); + + let num_prev_encoded = chunk_idx * CHUNK_SIZE; + let num_prev_patches = patch_indices.len(); + + // record the patches in this chunk + let patch_indices_mut = patch_indices.spare_capacity_mut(); + let patch_values_mut = patch_values.spare_capacity_mut(); + let mut chunk_patch_index = 0; + for i in num_prev_encoded..encoded_output.len() { + let decoded = Self::decode_single(encoded_output[i], exp); + patch_indices_mut[chunk_patch_index].write(i as u64); + patch_values_mut[chunk_patch_index].write(values[i]); + chunk_patch_index += (decoded != values[i]) as usize; + } + assert_eq!(chunk_patch_index, chunk_patch_count); + unsafe { + patch_indices.set_len(num_prev_patches + chunk_patch_count); + patch_values.set_len(num_prev_patches + chunk_patch_count); + } - let mut chunk_patch_index = 0; + // find the first successfully encoded value (i.e., not patched) + // this is our fill value for missing values + if fill_value.is_none() { + assert_eq!(num_prev_encoded, num_prev_patches); for i in num_prev_encoded..encoded_output.len() { - let decoded = Self::decode_single(encoded_output[i], exp); - patch_indices_mut[chunk_patch_index].write(i as u64); - patch_values_mut[chunk_patch_index].write(values[i]); - chunk_patch_index += (decoded != values[i]) as usize; - } - assert_eq!(chunk_patch_index, chunk_patch_count); - unsafe { - patch_indices.set_len(num_prev_patches + chunk_patch_count); - patch_values.set_len(num_prev_patches + chunk_patch_count); - } - - // find the first successfully encoded value (i.e., not patched) - if fill_value.is_none() { - assert_eq!(num_prev_encoded, num_prev_patches); - for i in num_prev_encoded..encoded_output.len() { - if patch_indices[i] != i as u64 { - fill_value = Some(encoded_output[i]); - break; - } + if i >= patch_indices.len() || patch_indices[i] != i as u64 { + fill_value = Some(encoded_output[i]); + break; } } + } - if let Some(fill_value) = fill_value { - let patch_indices_to_fill = if !has_filled { - &patch_indices - } else { - &patch_indices[num_prev_patches..] - }; - - for patch_idx in patch_indices_to_fill.iter() { - encoded_output[*patch_idx as usize] = fill_value; - } - has_filled = true; + // replace the patched values in the encoded array with the fill value + // for better downstream compression + if let Some(fill_value) = fill_value { + // handle the edge case where the first N >= 1 chunks are all patches + let patch_indices_to_fill = if !has_filled { + &patch_indices + } else { + &patch_indices[num_prev_patches..] + }; + + for patch_idx in patch_indices_to_fill.iter() { + encoded_output[*patch_idx as usize] = fill_value; } + has_filled = true; } } diff --git a/encodings/alp/src/compress.rs b/encodings/alp/src/compress.rs index e05c89485bb..6c4dfeba7a5 100644 --- a/encodings/alp/src/compress.rs +++ b/encodings/alp/src/compress.rs @@ -157,7 +157,7 @@ mod tests { assert!(encoded.patches().is_some()); assert_eq!( encoded.encoded().as_primitive().maybe_null_slice::(), - vec![1234i64, 2718, 2718, 4000] // fill forward + vec![1234i64, 2718, 1234, 4000] // fill forward ); assert_eq!(encoded.exponents(), Exponents { e: 16, f: 13 }); From b3f4d183be3c4378537a4b3fdf8e7660a2a5af76 Mon Sep 17 00:00:00 2001 From: Will Manning Date: Wed, 25 Sep 2024 13:32:04 +0100 Subject: [PATCH 09/15] wip --- encodings/alp/src/alp.rs | 86 +++++++++++++++++++++++++++++++++++++--- 1 file changed, 81 insertions(+), 5 deletions(-) diff --git a/encodings/alp/src/alp.rs b/encodings/alp/src/alp.rs index 2562a8f6f48..caec3d9d8ae 100644 --- a/encodings/alp/src/alp.rs +++ b/encodings/alp/src/alp.rs @@ -20,6 +20,8 @@ impl Display for Exponents { } } +const ENCODE_CHUNK_SIZE: usize = 1024; + pub trait ALPFloat: Float + Display + 'static { type ALPInt: PrimInt + Bounded + Display + ToPrimitive; @@ -102,7 +104,7 @@ pub trait ALPFloat: Float + Display + 'static { exponents: Option, ) -> (Exponents, Vec, Vec, Vec) { let exp = exponents.unwrap_or_else(|| Self::find_best_exponents(values)); - + let mut encoded_output = Vec::with_capacity(values.len()); let mut patch_indices = Vec::new(); let mut patch_values = Vec::new(); @@ -111,8 +113,10 @@ pub trait ALPFloat: Float + Display + 'static { // this is intentionally branchless // TODO: batch this into 1024 values at a time to make it more cache friendly - const CHUNK_SIZE: usize = 1024; - for (chunk_idx, chunk) in values.chunks(CHUNK_SIZE).enumerate() { + for chunk in values.chunks(ENCODE_CHUNK_SIZE) { + let num_prev_encoded = encoded_output.len(); + let num_prev_patches = patch_indices.len(); + let mut chunk_patch_count = 0; encoded_output.extend(chunk.iter().map(|v| { let encoded = unsafe { Self::encode_single_unchecked(*v, exp) }; @@ -131,8 +135,6 @@ pub trait ALPFloat: Float + Display + 'static { patch_indices.reserve(chunk_patch_count + 1); patch_values.reserve(chunk_patch_count + 1); - let num_prev_encoded = chunk_idx * CHUNK_SIZE; - let num_prev_patches = patch_indices.len(); // record the patches in this chunk let patch_indices_mut = patch_indices.spare_capacity_mut(); @@ -217,6 +219,80 @@ pub trait ALPFloat: Float + Display + 'static { } } +fn encode_chunk_unchecked(chunk_idx: usize, chunk: &[T], exp: Exponents, encoded_output: &mut Vec, patch_indices: &mut Vec, patch_values: &mut Vec, fill_value: &mut Option) { + let num_prev_encoded = encoded_output.len(); + let num_prev_patches = patch_indices.len(); + assert_eq!(patch_indices.len(), patch_values.len()); + let has_filled = fill_value.is_some(); + + // encode the chunk, counting the number of patches + let mut chunk_patch_count = 0; + encoded_output.extend(chunk.iter().map(|v| { + let encoded = unsafe { T::encode_single_unchecked(*v, exp) }; + let decoded = T::decode_single(encoded, exp); + let neq: usize = (decoded != *v) as usize; + chunk_patch_count += neq; + encoded + })); + let chunk_patch_count = chunk_patch_count; // immutable hereafter + assert_eq!(encoded_output.len(), num_prev_encoded + chunk.len()); + + // if there are no patches, we are done + if chunk_patch_count == 0 { + return; + } + + // we need to gather the patches for this chunk + // preallocate space for the patches (plus one because our loop may attempt to write one past the end) + patch_indices.reserve(chunk_patch_count + 1); + patch_values.reserve(chunk_patch_count + 1); + + // record the patches in this chunk + let patch_indices_mut = patch_indices.spare_capacity_mut(); + let patch_values_mut = patch_values.spare_capacity_mut(); + let mut chunk_patch_index = 0; + for i in num_prev_encoded..encoded_output.len() { + let decoded = T::decode_single(encoded_output[i], exp); + // write() is only safe to call more than once because the values are primitive (i.e., Drop is a no-op) + patch_indices_mut[chunk_patch_index].write(i as u64); + patch_values_mut[chunk_patch_index].write(chunk[i]); + chunk_patch_index += (decoded != chunk[i]) as usize; + } + assert_eq!(chunk_patch_index, chunk_patch_count); + unsafe { + patch_indices.set_len(num_prev_patches + chunk_patch_count); + patch_values.set_len(num_prev_patches + chunk_patch_count); + } + + // find the first successfully encoded value (i.e., not patched) + // this is our fill value for missing values + if fill_value.is_none() { + assert_eq!(num_prev_encoded, num_prev_patches); + for i in num_prev_encoded..encoded_output.len() { + if i >= patch_indices.len() || patch_indices[i] != i as u64 { + *fill_value = Some(encoded_output[i]); + break; + } + } + } + + // replace the patched values in the encoded array with the fill value + // for better downstream compression + if let Some(fill_value) = fill_value { + // handle the edge case where the first N >= 1 chunks are all patches + let patch_indices_to_fill = if !has_filled { + &patch_indices + } else { + &patch_indices[num_prev_patches..] + }; + + for patch_idx in patch_indices_to_fill.iter() { + encoded_output[*patch_idx as usize] = fill_value; + } + has_filled = true; + } +} + impl ALPFloat for f32 { type ALPInt = i32; const FRACTIONAL_BITS: u8 = 23; From 01a1e518b923c1b21ca6d865624ebe316ce2a24e Mon Sep 17 00:00:00 2001 From: Will Manning Date: Wed, 25 Sep 2024 14:06:40 +0100 Subject: [PATCH 10/15] refactor --- encodings/alp/src/alp.rs | 132 +++++++++++++-------------------------- encodings/alp/src/lib.rs | 2 + 2 files changed, 46 insertions(+), 88 deletions(-) diff --git a/encodings/alp/src/alp.rs b/encodings/alp/src/alp.rs index caec3d9d8ae..f3107ec97d0 100644 --- a/encodings/alp/src/alp.rs +++ b/encodings/alp/src/alp.rs @@ -1,8 +1,9 @@ +use core::convert::FloatToInt; use std::fmt::{Display, Formatter}; use std::mem::size_of; use itertools::Itertools; -use num_traits::{Bounded, CheckedSub, Float, NumCast, PrimInt, ToPrimitive}; +use num_traits::{Bounded, CheckedSub, Float, PrimInt, ToPrimitive}; use serde::{Deserialize, Serialize}; use vortex_error::vortex_panic; @@ -20,9 +21,7 @@ impl Display for Exponents { } } -const ENCODE_CHUNK_SIZE: usize = 1024; - -pub trait ALPFloat: Float + Display + 'static { +pub trait ALPFloat: Float + Display + FloatToInt + 'static { type ALPInt: PrimInt + Bounded + Display + ToPrimitive; const FRACTIONAL_BITS: u8; @@ -37,10 +36,11 @@ pub trait ALPFloat: Float + Display + 'static { (self + Self::SWEET) - Self::SWEET } - #[inline] - fn as_int(self) -> Option { - ::from(self) - } + /// Equivalent to calling `as` to cast the primitive float to the target integer type. + fn as_int(self) -> Self::ALPInt; + + /// Convert from the integer type back to the float type using `as`. + fn from_int(n: Self::ALPInt) -> Self; fn find_best_exponents(values: &[Self]) -> Exponents { let mut best_exp = Exponents { e: 0, f: 0 }; @@ -74,7 +74,7 @@ pub trait ALPFloat: Float + Display + 'static { best_exp } - #[inline(always)] + #[inline] fn estimate_encoded_size(encoded: &[Self::ALPInt], patches: &[Self]) -> usize { let bits_per_encoded = encoded .iter() @@ -104,81 +104,17 @@ pub trait ALPFloat: Float + Display + 'static { exponents: Option, ) -> (Exponents, Vec, Vec, Vec) { let exp = exponents.unwrap_or_else(|| Self::find_best_exponents(values)); - + let mut encoded_output = Vec::with_capacity(values.len()); let mut patch_indices = Vec::new(); let mut patch_values = Vec::new(); let mut fill_value: Option = None; - let mut has_filled = false; // this is intentionally branchless // TODO: batch this into 1024 values at a time to make it more cache friendly - for chunk in values.chunks(ENCODE_CHUNK_SIZE) { - let num_prev_encoded = encoded_output.len(); - let num_prev_patches = patch_indices.len(); - - let mut chunk_patch_count = 0; - encoded_output.extend(chunk.iter().map(|v| { - let encoded = unsafe { Self::encode_single_unchecked(*v, exp) }; - let decoded = Self::decode_single(encoded, exp); - let neq: usize = (decoded != *v) as usize; - chunk_patch_count += neq; - encoded - })); - let chunk_patch_count = chunk_patch_count; // immutable hereafter - - if chunk_patch_count == 0 { - continue; - } - - // reserve space for the patches in this chunk (plus one because our loop may attempt to write one past the end) - patch_indices.reserve(chunk_patch_count + 1); - patch_values.reserve(chunk_patch_count + 1); - - - // record the patches in this chunk - let patch_indices_mut = patch_indices.spare_capacity_mut(); - let patch_values_mut = patch_values.spare_capacity_mut(); - let mut chunk_patch_index = 0; - for i in num_prev_encoded..encoded_output.len() { - let decoded = Self::decode_single(encoded_output[i], exp); - patch_indices_mut[chunk_patch_index].write(i as u64); - patch_values_mut[chunk_patch_index].write(values[i]); - chunk_patch_index += (decoded != values[i]) as usize; - } - assert_eq!(chunk_patch_index, chunk_patch_count); - unsafe { - patch_indices.set_len(num_prev_patches + chunk_patch_count); - patch_values.set_len(num_prev_patches + chunk_patch_count); - } - - // find the first successfully encoded value (i.e., not patched) - // this is our fill value for missing values - if fill_value.is_none() { - assert_eq!(num_prev_encoded, num_prev_patches); - for i in num_prev_encoded..encoded_output.len() { - if i >= patch_indices.len() || patch_indices[i] != i as u64 { - fill_value = Some(encoded_output[i]); - break; - } - } - } - - // replace the patched values in the encoded array with the fill value - // for better downstream compression - if let Some(fill_value) = fill_value { - // handle the edge case where the first N >= 1 chunks are all patches - let patch_indices_to_fill = if !has_filled { - &patch_indices - } else { - &patch_indices[num_prev_patches..] - }; - - for patch_idx in patch_indices_to_fill.iter() { - encoded_output[*patch_idx as usize] = fill_value; - } - has_filled = true; - } + let encode_chunk_size: usize = 1024; + for chunk in values.chunks(encode_chunk_size) { + encode_chunk_unchecked(chunk, exp, &mut encoded_output, &mut patch_indices, &mut patch_values, &mut fill_value); } (exp, encoded_output, patch_indices, patch_values) @@ -215,11 +151,17 @@ pub trait ALPFloat: Float + Display + 'static { (value * Self::F10[exponents.e as usize] * Self::IF10[exponents.f as usize]) .fast_round() .as_int() - .unwrap_or_else(Self::ALPInt::max_value) } } -fn encode_chunk_unchecked(chunk_idx: usize, chunk: &[T], exp: Exponents, encoded_output: &mut Vec, patch_indices: &mut Vec, patch_values: &mut Vec, fill_value: &mut Option) { +fn encode_chunk_unchecked( + chunk: &[T], + exp: Exponents, + encoded_output: &mut Vec, + patch_indices: &mut Vec, + patch_values: &mut Vec, + fill_value: &mut Option, +) { let num_prev_encoded = encoded_output.len(); let num_prev_patches = patch_indices.len(); assert_eq!(patch_indices.len(), patch_values.len()); @@ -280,16 +222,10 @@ fn encode_chunk_unchecked(chunk_idx: usize, chunk: &[T], exp: Expon // for better downstream compression if let Some(fill_value) = fill_value { // handle the edge case where the first N >= 1 chunks are all patches - let patch_indices_to_fill = if !has_filled { - &patch_indices - } else { - &patch_indices[num_prev_patches..] - }; - - for patch_idx in patch_indices_to_fill.iter() { - encoded_output[*patch_idx as usize] = fill_value; + let start_patch = if !has_filled { 0 } else { num_prev_patches }; + for patch_idx in &patch_indices[start_patch..] { + encoded_output[*patch_idx as usize] = *fill_value; } - has_filled = true; } } @@ -326,6 +262,16 @@ impl ALPFloat for f32 { 0.000000001, 0.0000000001, // 10^-10 ]; + + #[inline(always)] + fn as_int(self) -> Self::ALPInt { + self as _ + } + + #[inline(always)] + fn from_int(n: Self::ALPInt) -> Self { + n as _ + } } impl ALPFloat for f64 { @@ -387,4 +333,14 @@ impl ALPFloat for f64 { 0.0000000000000000000001, 0.00000000000000000000001, // 10^-23 ]; + + #[inline(always)] + fn as_int(self) -> Self::ALPInt { + self as _ + } + + #[inline(always)] + fn from_int(n: Self::ALPInt) -> Self { + n as _ + } } diff --git a/encodings/alp/src/lib.rs b/encodings/alp/src/lib.rs index fde87a65f25..e55507f1f52 100644 --- a/encodings/alp/src/lib.rs +++ b/encodings/alp/src/lib.rs @@ -1,3 +1,5 @@ +#![feature(convert_float_to_int)] + pub use alp::*; pub use array::*; pub use compress::*; From 954cbbf3199fc950b3b6631f5d3ea8f8d51d75ef Mon Sep 17 00:00:00 2001 From: Will Manning Date: Wed, 25 Sep 2024 15:12:52 +0100 Subject: [PATCH 11/15] fix fill bug --- encodings/alp/src/alp.rs | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/encodings/alp/src/alp.rs b/encodings/alp/src/alp.rs index f3107ec97d0..5d9cd99f1cd 100644 --- a/encodings/alp/src/alp.rs +++ b/encodings/alp/src/alp.rs @@ -179,6 +179,18 @@ fn encode_chunk_unchecked( let chunk_patch_count = chunk_patch_count; // immutable hereafter assert_eq!(encoded_output.len(), num_prev_encoded + chunk.len()); + // find the first successfully encoded value (i.e., not patched) + // this is our fill value for missing values + if fill_value.is_none() && (num_prev_encoded + chunk_patch_count < encoded_output.len()) { + assert_eq!(num_prev_encoded, num_prev_patches); + for i in num_prev_encoded..encoded_output.len() { + if i >= patch_indices.len() || patch_indices[i] != i as u64 { + *fill_value = Some(encoded_output[i]); + break; + } + } + } + // if there are no patches, we are done if chunk_patch_count == 0 { return; @@ -197,8 +209,8 @@ fn encode_chunk_unchecked( let decoded = T::decode_single(encoded_output[i], exp); // write() is only safe to call more than once because the values are primitive (i.e., Drop is a no-op) patch_indices_mut[chunk_patch_index].write(i as u64); - patch_values_mut[chunk_patch_index].write(chunk[i]); - chunk_patch_index += (decoded != chunk[i]) as usize; + patch_values_mut[chunk_patch_index].write(chunk[i - num_prev_encoded]); + chunk_patch_index += (decoded != chunk[i - num_prev_encoded]) as usize; } assert_eq!(chunk_patch_index, chunk_patch_count); unsafe { @@ -206,18 +218,6 @@ fn encode_chunk_unchecked( patch_values.set_len(num_prev_patches + chunk_patch_count); } - // find the first successfully encoded value (i.e., not patched) - // this is our fill value for missing values - if fill_value.is_none() { - assert_eq!(num_prev_encoded, num_prev_patches); - for i in num_prev_encoded..encoded_output.len() { - if i >= patch_indices.len() || patch_indices[i] != i as u64 { - *fill_value = Some(encoded_output[i]); - break; - } - } - } - // replace the patched values in the encoded array with the fill value // for better downstream compression if let Some(fill_value) = fill_value { From c91b6aa267c01a62ef32a6bc98a973d17cd66a06 Mon Sep 17 00:00:00 2001 From: Will Manning Date: Wed, 25 Sep 2024 15:31:18 +0100 Subject: [PATCH 12/15] fmt --- encodings/alp/src/alp.rs | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/encodings/alp/src/alp.rs b/encodings/alp/src/alp.rs index 5d9cd99f1cd..debe1d1c09e 100644 --- a/encodings/alp/src/alp.rs +++ b/encodings/alp/src/alp.rs @@ -111,10 +111,17 @@ pub trait ALPFloat: Float + Display + FloatToInt + 'static { let mut fill_value: Option = None; // this is intentionally branchless - // TODO: batch this into 1024 values at a time to make it more cache friendly - let encode_chunk_size: usize = 1024; + // we batch this into 16KB of values at a time to make it more L1 cache friendly + let encode_chunk_size: usize = (16 << 10) / size_of::(); for chunk in values.chunks(encode_chunk_size) { - encode_chunk_unchecked(chunk, exp, &mut encoded_output, &mut patch_indices, &mut patch_values, &mut fill_value); + encode_chunk_unchecked( + chunk, + exp, + &mut encoded_output, + &mut patch_indices, + &mut patch_values, + &mut fill_value, + ); } (exp, encoded_output, patch_indices, patch_values) @@ -262,7 +269,7 @@ impl ALPFloat for f32 { 0.000000001, 0.0000000001, // 10^-10 ]; - + #[inline(always)] fn as_int(self) -> Self::ALPInt { self as _ @@ -333,7 +340,7 @@ impl ALPFloat for f64 { 0.0000000000000000000001, 0.00000000000000000000001, // 10^-23 ]; - + #[inline(always)] fn as_int(self) -> Self::ALPInt { self as _ From ff98c69326a063c6e2ad5c147138cbe202f247a4 Mon Sep 17 00:00:00 2001 From: Will Manning Date: Wed, 25 Sep 2024 15:34:26 +0100 Subject: [PATCH 13/15] remove cruft --- encodings/alp/src/alp.rs | 7 +++---- encodings/alp/src/lib.rs | 2 -- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/encodings/alp/src/alp.rs b/encodings/alp/src/alp.rs index debe1d1c09e..32c39edcd2f 100644 --- a/encodings/alp/src/alp.rs +++ b/encodings/alp/src/alp.rs @@ -1,9 +1,8 @@ -use core::convert::FloatToInt; use std::fmt::{Display, Formatter}; use std::mem::size_of; use itertools::Itertools; -use num_traits::{Bounded, CheckedSub, Float, PrimInt, ToPrimitive}; +use num_traits::{CheckedSub, Float, PrimInt, ToPrimitive}; use serde::{Deserialize, Serialize}; use vortex_error::vortex_panic; @@ -21,8 +20,8 @@ impl Display for Exponents { } } -pub trait ALPFloat: Float + Display + FloatToInt + 'static { - type ALPInt: PrimInt + Bounded + Display + ToPrimitive; +pub trait ALPFloat: Float + Display + 'static { + type ALPInt: PrimInt + Display + ToPrimitive; const FRACTIONAL_BITS: u8; const MAX_EXPONENT: u8; diff --git a/encodings/alp/src/lib.rs b/encodings/alp/src/lib.rs index e55507f1f52..fde87a65f25 100644 --- a/encodings/alp/src/lib.rs +++ b/encodings/alp/src/lib.rs @@ -1,5 +1,3 @@ -#![feature(convert_float_to_int)] - pub use alp::*; pub use array::*; pub use compress::*; From d82e9141e55681fa36843d2bd230757149f0656a Mon Sep 17 00:00:00 2001 From: Will Manning Date: Wed, 25 Sep 2024 15:46:04 +0100 Subject: [PATCH 14/15] CR feedback --- encodings/alp/src/alp.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/encodings/alp/src/alp.rs b/encodings/alp/src/alp.rs index 32c39edcd2f..0fe67070b07 100644 --- a/encodings/alp/src/alp.rs +++ b/encodings/alp/src/alp.rs @@ -110,8 +110,8 @@ pub trait ALPFloat: Float + Display + 'static { let mut fill_value: Option = None; // this is intentionally branchless - // we batch this into 16KB of values at a time to make it more L1 cache friendly - let encode_chunk_size: usize = (16 << 10) / size_of::(); + // we batch this into 32KB of values at a time to make it more L1 cache friendly + let encode_chunk_size: usize = (32 << 10) / size_of::(); for chunk in values.chunks(encode_chunk_size) { encode_chunk_unchecked( chunk, @@ -178,7 +178,7 @@ fn encode_chunk_unchecked( encoded_output.extend(chunk.iter().map(|v| { let encoded = unsafe { T::encode_single_unchecked(*v, exp) }; let decoded = T::decode_single(encoded, exp); - let neq: usize = (decoded != *v) as usize; + let neq = (decoded != *v) as usize; chunk_patch_count += neq; encoded })); From 614f02c9af04b97bba47a52f1c0b82ab710c68fa Mon Sep 17 00:00:00 2001 From: Will Manning Date: Wed, 25 Sep 2024 16:04:15 +0100 Subject: [PATCH 15/15] one more --- encodings/alp/src/alp.rs | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/encodings/alp/src/alp.rs b/encodings/alp/src/alp.rs index 0fe67070b07..a7f6a8b1055 100644 --- a/encodings/alp/src/alp.rs +++ b/encodings/alp/src/alp.rs @@ -4,7 +4,6 @@ use std::mem::size_of; use itertools::Itertools; use num_traits::{CheckedSub, Float, PrimInt, ToPrimitive}; use serde::{Deserialize, Serialize}; -use vortex_error::vortex_panic; const SAMPLE_SIZE: usize = 32; @@ -138,15 +137,7 @@ pub trait ALPFloat: Float + Display + 'static { #[inline] fn decode_single(encoded: Self::ALPInt, exponents: Exponents) -> Self { - let encoded_float: Self = Self::from(encoded).unwrap_or_else(|| { - vortex_panic!( - "Failed to convert encoded value {} from {} to {} in ALPFloat::decode_single", - encoded, - std::any::type_name::(), - std::any::type_name::() - ) - }); - encoded_float * Self::F10[exponents.f as usize] * Self::IF10[exponents.e as usize] + Self::from_int(encoded) * Self::F10[exponents.f as usize] * Self::IF10[exponents.e as usize] } /// # Safety