use core::error::Error;
use core::fmt;
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct ReserveFraction {
exponent: u32,
}
impl ReserveFraction {
pub const DEFAULT: Self = Self { exponent: 3 };
pub const fn from_exponent(exponent: u32) -> Result<Self, ReserveFractionError> {
if exponent == 0 {
return Err(ReserveFractionError::ExponentZero);
}
Ok(Self { exponent })
}
#[must_use]
pub const fn exponent(self) -> u32 {
self.exponent
}
#[must_use]
pub const fn floor_reserved(self, n: usize) -> usize {
floor_div_pow2(n, self.exponent as u64)
}
#[must_use]
pub const fn floor_half_reserved(self, n: usize) -> usize {
floor_div_pow2(n, self.exponent as u64 + 1)
}
#[must_use]
pub const fn as_f64(self) -> Option<f64> {
match self.exponent {
1..=1_022 => {
let biased_exponent = 1_023_u64 - self.exponent as u64;
Some(f64::from_bits(biased_exponent << 52))
}
1_023..=1_074 => {
let significand_bit = 1_074_u32 - self.exponent;
Some(f64::from_bits(1_u64 << significand_bit))
}
_ => None,
}
}
}
impl TryFrom<f64> for ReserveFraction {
type Error = ReserveFractionError;
fn try_from(value: f64) -> Result<Self, Self::Error> {
if !value.is_finite() {
return Err(ReserveFractionError::NonFinite);
}
if value <= 0.0 {
return Err(ReserveFractionError::NonPositive);
}
if value >= 1.0 {
return Err(ReserveFractionError::NotBelowOne);
}
let bits = value.to_bits();
let biased_exponent = ((bits >> 52) & 0x7ff) as u32;
let significand = bits & ((1_u64 << 52) - 1);
let exponent = if biased_exponent == 0 {
if !significand.is_power_of_two() {
return Err(ReserveFractionError::NotInversePowerOfTwo);
}
1_074 - significand.trailing_zeros()
} else {
if significand != 0 {
return Err(ReserveFractionError::NotInversePowerOfTwo);
}
1_023 - biased_exponent
};
Self::from_exponent(exponent)
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum ReserveFractionError {
ExponentZero,
NonFinite,
NonPositive,
NotBelowOne,
NotInversePowerOfTwo,
}
impl fmt::Display for ReserveFractionError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::ExponentZero => f.write_str("reserve exponent must be positive"),
Self::NonFinite => f.write_str("reserve fraction must be finite"),
Self::NonPositive => f.write_str("reserve fraction must be positive"),
Self::NotBelowOne => f.write_str("reserve fraction must be less than one"),
Self::NotInversePowerOfTwo => {
f.write_str("reserve fraction must be an exact inverse power of two")
}
}
}
}
impl Error for ReserveFractionError {}
const fn floor_div_pow2(value: usize, exponent: u64) -> usize {
if exponent >= usize::BITS as u64 {
0
} else {
value >> exponent
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn default_is_exactly_one_eighth() {
assert_eq!(ReserveFraction::DEFAULT.exponent(), 3);
assert_eq!(ReserveFraction::DEFAULT.as_f64(), Some(0.125));
assert_eq!(
ReserveFraction::try_from(0.125),
Ok(ReserveFraction::DEFAULT)
);
}
}