#![warn(rust_2018_idioms, unreachable_pub, missing_docs)]
#![cfg_attr(not(feature = "std"), no_std)]
#![cfg_attr(docsrs, feature(doc_cfg))]
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
use core::cmp::Ord;
use core::{fmt, i64, marker::PhantomData};
use typenum::Unsigned;
#[cfg(feature = "i128")]
use crate::i256::I256;
use crate::ops::{sqrt::Sqrt, *};
use crate::string::Stringify;
mod const_fn;
mod errors;
mod float;
#[cfg(feature = "i128")]
mod i256;
mod macros;
#[cfg(feature = "parity")]
mod parity;
mod power_table;
mod string;
#[cfg(not(any(feature = "i16", feature = "i32", feature = "i64", feature = "i128")))]
compile_error!("Some of the next features must be enabled: \"i128\", \"i64\", \"i32\", \"i16\"");
pub use errors::*;
pub use typenum;
pub mod ops;
#[cfg(feature = "serde")]
#[cfg_attr(docsrs, doc(cfg(feature = "serde")))]
pub mod serde;
#[doc(hidden)]
pub mod _priv {
pub use crate::const_fn::*;
pub use crate::macros::Operand;
pub use crate::ops::*;
}
type Result<T, E = ArithmeticError> = core::result::Result<T, E>;
#[derive(Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(
docsrs,
doc(cfg(any(feature = "i128", feature = "i64", feature = "i32", feature = "i16")))
)]
#[repr(transparent)]
pub struct FixedPoint<I, P> {
inner: I,
_marker: PhantomData<P>,
}
pub trait Precision: Unsigned {}
impl<U: Unsigned> Precision for U {}
impl<I, P> FixedPoint<I, P> {
pub const fn from_bits(raw: I) -> Self {
FixedPoint {
inner: raw,
_marker: PhantomData,
}
}
pub const fn as_bits(&self) -> &I {
&self.inner
}
#[inline]
pub fn into_bits(self) -> I {
self.inner
}
}
macro_rules! impl_fixed_point {
(
$(#[$attr:meta])?
inner = $layout:tt;
promoted_to = $promotion:tt;
convert = $convert:expr;
try_from = [$($try_from:ty),*];
) => {
$(#[$attr])?
impl<P: Precision> FixedPoint<$layout, P> {
pub const PRECISION: i32 = P::I32;
pub const EPSILON: Self = Self::from_bits(1);
const COEF: $layout = const_fn::pow10(Self::PRECISION) as _;
const NEG_COEF: $layout = -Self::COEF;
const COEF_PROMOTED: $promotion = $convert(Self::COEF) as _;
}
$(#[$attr])?
impl<P: Precision> Zero for FixedPoint<$layout, P> {
const ZERO: Self = Self::from_bits(0);
}
$(#[$attr])?
impl<P: Precision> One for FixedPoint<$layout, P> {
const ONE: Self = Self::from_bits(Self::COEF);
}
$(#[$attr])?
impl<P: Precision> Bounded for FixedPoint<$layout, P> {
const MIN: Self = Self::from_bits($layout::MIN);
const MAX: Self = Self::from_bits($layout::MAX);
}
$(#[$attr])?
impl<P: Precision> RoundingMul for FixedPoint<$layout, P> {
type Output = Self;
type Error = ArithmeticError;
#[inline]
fn rmul(self, rhs: Self, mode: RoundMode) -> Result<Self> {
let value = $promotion::from(self.inner) * $promotion::from(rhs.inner);
let result = value / Self::COEF_PROMOTED;
let loss = value - result * Self::COEF_PROMOTED;
let mut result =
$layout::try_from(result).map_err(|_| ArithmeticError::Overflow)?;
let loss = $layout::try_from(loss).unwrap();
let sign = self.inner.signum() * rhs.inner.signum();
let add_signed_one = if mode == RoundMode::Nearest {
sign as i32 >= 0 && loss + loss >= Self::COEF
|| loss + loss <= Self::NEG_COEF
} else {
loss != 0 && mode as i32 == sign as i32
};
if add_signed_one {
result = result.checked_add(sign).ok_or(ArithmeticError::Overflow)?;
}
Ok(Self::from_bits(result))
}
}
$(#[$attr])?
impl<P: Precision> RoundingDiv for FixedPoint<$layout, P> {
type Output = Self;
type Error = ArithmeticError;
#[inline]
fn rdiv(self, rhs: Self, mode: RoundMode) -> Result<Self> {
if rhs.inner == 0 {
return Err(ArithmeticError::DivisionByZero);
}
let numerator = $promotion::from(self.inner) * Self::COEF_PROMOTED;
let denominator = $promotion::from(rhs.inner);
let result = numerator / denominator;
let loss = numerator - result * denominator;
let mut result =
$layout::try_from(result).map_err(|_| ArithmeticError::Overflow)?;
let loss = $layout::try_from(loss).unwrap();
if loss != 0 {
let sign = self.inner.signum() * rhs.inner.signum();
let add_signed_one = if mode == RoundMode::Nearest {
let loss_abs = loss.abs();
loss_abs + loss_abs >= rhs.inner.abs()
} else {
mode as i32 == sign as i32
};
if add_signed_one {
result = result.checked_add(sign).ok_or(ArithmeticError::Overflow)?;
}
}
Ok(Self::from_bits(result))
}
}
$(#[$attr])?
impl<P: Precision> RoundingDiv<$layout> for FixedPoint<$layout, P> {
type Output = Self;
type Error = ArithmeticError;
#[inline]
fn rdiv(self, rhs: $layout, mode: RoundMode) -> Result<Self> {
self.inner.rdiv(rhs, mode).map(Self::from_bits)
}
}
$(#[$attr])?
impl<P: Precision> RoundingDiv<FixedPoint<$layout, P>> for $layout {
type Output = FixedPoint<$layout, P>;
type Error = ArithmeticError;
#[inline]
fn rdiv(self, rhs: FixedPoint<$layout, P>, mode: RoundMode) -> Result<FixedPoint<$layout, P>> {
let lhs = FixedPoint::<$layout, P>::try_from(self).map_err(|_| ArithmeticError::Overflow)?;
lhs.rdiv(rhs, mode)
}
}
$(#[$attr])?
impl<P: Precision> CheckedAdd for FixedPoint<$layout, P> {
type Output = Self;
type Error = ArithmeticError;
#[inline]
fn cadd(self, rhs: Self) -> Result<Self> {
self.inner.cadd(rhs.inner).map(Self::from_bits)
}
#[inline]
fn saturating_add(self, rhs: Self) -> Self::Output {
Self::Output::from_bits(self.inner.saturating_add(rhs.inner))
}
}
$(#[$attr])?
impl<P: Precision> CheckedSub for FixedPoint<$layout, P> {
type Output = Self;
type Error = ArithmeticError;
#[inline]
fn csub(self, rhs: Self) -> Result<Self> {
self.inner.csub(rhs.inner).map(Self::from_bits)
}
#[inline]
fn saturating_sub(self, rhs: Self) -> Self::Output {
Self::Output::from_bits(self.inner.saturating_sub(rhs.inner))
}
}
$(#[$attr])?
impl<P: Precision> CheckedMul<$layout> for FixedPoint<$layout, P> {
type Output = Self;
type Error = ArithmeticError;
#[inline]
fn cmul(self, rhs: $layout) -> Result<Self> {
self.inner.cmul(rhs).map(Self::from_bits)
}
#[inline]
fn saturating_mul(self, rhs: $layout) -> Self::Output {
Self::Output::from_bits(self.inner.saturating_mul(rhs))
}
}
$(#[$attr])?
impl<P: Precision> CheckedMul<FixedPoint<$layout, P>> for $layout {
type Output = FixedPoint<$layout, P>;
type Error = ArithmeticError;
#[inline]
fn cmul(self, rhs: FixedPoint<$layout, P>) -> Result<FixedPoint<$layout, P>> {
rhs.cmul(self)
}
#[inline]
fn saturating_mul(self, rhs: FixedPoint<$layout, P>) -> Self::Output {
Self::Output::from_bits(self.saturating_mul(rhs.inner))
}
}
$(#[$attr])?
impl<P: Precision> FixedPoint<$layout, P> {
#[inline]
pub fn signum(self) -> $layout {
self.inner.signum()
}
#[inline]
pub fn recip(self, mode: RoundMode) -> Result<Self> {
Self::ONE.rdiv(self, mode)
}
#[inline]
pub fn cneg(self) -> Result<Self> {
self.inner
.checked_neg()
.map(Self::from_bits)
.ok_or_else(|| ArithmeticError::Overflow)
}
#[inline]
pub fn half_sum(a: Self, b: Self, mode: RoundMode) -> Self {
if a.inner.signum() != b.inner.signum() {
Self::from_bits(a.inner + b.inner).rdiv(2, mode).unwrap()
} else {
let min = a.inner.min(b.inner);
let max = a.inner.max(b.inner);
let half_diff = (max - min).rdiv(2, mode).unwrap();
Self::from_bits(min + half_diff)
}
}
#[inline]
pub fn integral(self, mode: RoundMode) -> $layout {
let sign = self.inner.signum();
let (mut int, frac) = (self.inner / Self::COEF, self.inner.abs() % Self::COEF);
let add_signed_one = if mode == RoundMode::Nearest {
frac + frac >= Self::COEF
} else {
mode as i32 == sign as i32 && frac > 0
};
if add_signed_one {
int += sign;
}
int
}
#[inline]
pub fn floor(self) -> Self {
Self::from_decimal(self.integral(RoundMode::Floor), 0).unwrap()
}
#[inline]
pub fn ceil(self) -> Self {
Self::from_decimal(self.integral(RoundMode::Ceil), 0).unwrap()
}
#[inline]
pub fn round(self) -> Self {
Self::from_decimal(self.integral(RoundMode::Nearest), 0).unwrap()
}
#[inline]
pub fn round_towards_zero_by(self, precision: Self) -> Self {
self.inner
.checked_div(precision.inner)
.and_then(|v| v.checked_mul(precision.inner))
.map_or(self, Self::from_bits)
}
#[inline]
pub fn next_power_of_ten(self) -> Result<Self> {
if self.inner < 0 {
return self.cneg()?.next_power_of_ten()?.cneg();
}
let lz = self.inner.leading_zeros() as usize;
assert!(lz > 0, "unexpected negative value");
let value = power_table::$layout[lz];
let value = if self.inner > value {
power_table::$layout[lz - 1]
} else {
value
};
if value == 0 {
return Err(ArithmeticError::Overflow);
}
Ok(Self::from_bits(value))
}
#[inline]
pub fn abs(self) -> Result<Self> {
if self.inner < 0 {
self.cneg()
} else {
Ok(self)
}
}
#[inline]
pub fn rsqrt(self, mode: RoundMode) -> Result<Self, ArithmeticError> {
if self.inner.is_negative() {
return Err(ArithmeticError::DomainViolation);
}
let squared = $promotion::from(self.inner) * Self::COEF_PROMOTED;
let lo = squared.sqrt()?;
let add_one = match mode {
RoundMode::Floor => false,
RoundMode::Nearest => {
let lo2 = lo * lo;
let hi2 = lo2 + lo + lo + $promotion::ONE;
squared - lo2 >= hi2 - squared
},
RoundMode::Ceil if lo * lo == squared => false,
RoundMode::Ceil => true,
};
let lo = $layout::try_from(lo).unwrap();
let inner = if add_one {
lo + $layout::ONE
} else {
lo
};
Ok(Self::from_bits(inner))
}
}
$(#[$attr])?
impl<P: Precision> fmt::Debug for FixedPoint<$layout, P> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut buf = Default::default();
self.stringify(&mut buf);
f.write_str(buf.as_str())
}
}
$(#[$attr])?
impl<P: Precision> fmt::Display for FixedPoint<$layout, P> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut buf = Default::default();
self.stringify(&mut buf);
f.write_str(buf.as_str())
}
}
$(#[$attr])?
impl<P: Precision> FixedPoint<$layout, P> {
pub fn from_decimal(mantissa: $layout, exponent: i32) -> Result<Self, ConvertError> {
if exponent < -Self::PRECISION || exponent > 10 {
return Err(ConvertError::new("unsupported exponent"));
}
let ten: $layout = 10;
let multiplier = ten.pow((exponent + Self::PRECISION) as u32);
mantissa
.checked_mul(multiplier)
.map(Self::from_bits)
.map_or_else(|| Err(ConvertError::new("too big mantissa")), Ok)
}
}
impl<P: Precision> From<FixedPoint<$layout, P>> for f64 {
fn from(value: FixedPoint<$layout, P>) -> Self {
let coef = FixedPoint::<$layout, P>::COEF;
let integral = (value.inner / coef) as f64;
let fractional = ((value.inner % coef) as f64) / (coef as f64);
integral + fractional
}
}
$(
impl<P: Precision> TryFrom<$try_from> for FixedPoint<$layout, P> {
type Error = ConvertError;
fn try_from(value: $try_from) -> Result<Self, Self::Error> {
$layout::try_from(value)
.map_err(|_| ConvertError::new("too big number"))?
.checked_mul(Self::COEF)
.map(Self::from_bits)
.ok_or(ConvertError::new("too big number"))
}
}
)*
};
}
#[cfg(any(feature = "i64", feature = "i32", feature = "i16"))]
const fn identity<T>(x: T) -> T {
x
}
#[cfg(feature = "i16")]
impl_fixed_point!(
#[cfg_attr(docsrs, doc(cfg(feature = "i16")))]
inner = i16;
promoted_to = i32;
convert = identity;
try_from = [i8, u8, i16, u16, i32, u32, i64, u64, i128, u128, isize, usize];
);
#[cfg(feature = "i32")]
impl_fixed_point!(
#[cfg_attr(docsrs, doc(cfg(feature = "i32")))]
inner = i32;
promoted_to = i64;
convert = identity;
try_from = [i8, u8, i16, u16, i32, u32, i64, u64, i128, u128, isize, usize];
);
#[cfg(feature = "i64")]
impl_fixed_point!(
#[cfg_attr(docsrs, doc(cfg(feature = "i64")))]
inner = i64;
promoted_to = i128;
convert = identity;
try_from = [i8, u8, i16, u16, i32, u32, i64, u64, i128, u128, isize, usize];
);
#[cfg(feature = "i128")]
impl_fixed_point!(
#[cfg_attr(docsrs, doc(cfg(feature = "i128")))]
inner = i128;
promoted_to = I256;
convert = I256::from_i128;
try_from = [i8, u8, i16, u16, i32, u32, i64, u64, i128, u128, isize, usize];
);