#![no_std]
#![doc = include_str!("../README.md")]
use core::{
error::Error,
fmt::{self, Write},
mem::{size_of, transmute_copy},
ops::{
Add, AddAssign, BitAnd, BitAndAssign, BitOr, BitOrAssign, BitXor, BitXorAssign, Div,
DivAssign, Mul, MulAssign, Neg, Not, Rem, RemAssign, Shl, ShlAssign, Shr, ShrAssign, Sub,
SubAssign,
},
str::FromStr,
};
#[macro_export]
macro_rules! fixp {
($str:literal) => {
const {
match $crate::FixedPoint::from_str($str) {
Ok(parsed) => parsed,
Err(e) => e.into_panic(),
}
}
};
}
#[macro_export]
macro_rules! fixp_exact {
($str:literal) => {
const {
match $crate::FixedPoint::from_str_exact($str) {
Ok(parsed) => parsed,
Err(e) => e.into_panic(),
}
}
};
}
#[allow(unused)]
macro_rules! const_binop {
(.$op:ident($lhs:expr, $rhs:expr)) => {
unsafe {
if const { size_of::<T>() == size_of::<u8>() } {
transmute_copy::<u8, T>(
&transmute_copy::<T, u8>(&$lhs).$op(transmute_copy::<T, u8>(&$rhs)),
)
} else if const { size_of::<T>() == size_of::<u16>() } {
transmute_copy::<u16, T>(
&transmute_copy::<T, u16>(&$lhs).$op(transmute_copy::<T, u16>(&$rhs)),
)
} else if const { size_of::<T>() == size_of::<u32>() } {
transmute_copy::<u32, T>(
&transmute_copy::<T, u32>(&$lhs).$op(transmute_copy::<T, u32>(&$rhs)),
)
} else if const { size_of::<T>() == size_of::<u64>() } {
transmute_copy::<u64, T>(
&transmute_copy::<T, u64>(&$lhs).$op(transmute_copy::<T, u64>(&$rhs)),
)
} else if const { size_of::<T>() == size_of::<u128>() } {
transmute_copy::<u128, T>(&transmute_copy::<T, u128>(&$lhs).$op(transmute_copy::<
T,
u128,
>(
&$rhs
)))
} else {
unreachable!()
}
}
};
}
#[inline(always)]
const fn try_u32_into_int<T: Int>(value: u32) -> Option<T> {
unsafe {
if const { size_of::<T>() == size_of::<u8>() } {
Some(transmute_copy::<u8, T>(&match value {
0..=0xff => value as u8,
_ => return None,
}))
} else if const { size_of::<T>() == size_of::<u16>() } {
Some(transmute_copy::<u16, T>(&match value {
0..=0xffff => value as u16,
_ => return None,
}))
} else if const { size_of::<T>() == size_of::<u32>() } {
Some(transmute_copy::<u32, T>(&value))
} else if const { size_of::<T>() == size_of::<u64>() } {
Some(transmute_copy::<u64, T>(&(value as u64)))
} else if const { size_of::<T>() == size_of::<u128>() } {
Some(transmute_copy::<u128, T>(&(value as u128)))
} else {
unreachable!()
}
}
}
#[inline(always)]
const fn try_u64_into_int<T: Int>(value: u64) -> Option<T> {
unsafe {
if const { size_of::<T>() == size_of::<u8>() } {
Some(transmute_copy::<u8, T>(&match value {
0..=0xff => value as u8,
_ => return None,
}))
} else if const { size_of::<T>() == size_of::<u16>() } {
Some(transmute_copy::<u16, T>(&match value {
0..=0xffff => value as u16,
_ => return None,
}))
} else if const { size_of::<T>() == size_of::<u32>() } {
Some(transmute_copy::<u32, T>(&match value {
0..=0xffff_ffff => value as u32,
_ => return None,
}))
} else if const { size_of::<T>() == size_of::<u64>() } {
Some(transmute_copy::<u64, T>(&value))
} else if const { size_of::<T>() == size_of::<u128>() } {
Some(transmute_copy::<u128, T>(&(value as u128)))
} else {
unreachable!()
}
}
}
mod internal {
use super::*;
#[cfg(target_pointer_width = "16")]
type DoubleIsize = i32;
#[cfg(target_pointer_width = "32")]
type DoubleIsize = i64;
#[cfg(target_pointer_width = "64")]
type DoubleIsize = i128;
#[cfg(not(any(
target_pointer_width = "16",
target_pointer_width = "32",
target_pointer_width = "64"
)))]
compile_error!("unknown size of isize");
#[cfg(target_pointer_width = "16")]
type DoubleUsize = u32;
#[cfg(target_pointer_width = "32")]
type DoubleUsize = u64;
#[cfg(target_pointer_width = "64")]
type DoubleUsize = u128;
#[cfg(not(any(
target_pointer_width = "16",
target_pointer_width = "32",
target_pointer_width = "64"
)))]
compile_error!("unknown size of usize");
pub unsafe trait Int:
Copy
+ Eq
+ Ord
+ Add<Output = Self>
+ AddAssign
+ Sub<Output = Self>
+ SubAssign
+ Mul<Output = Self>
+ MulAssign
+ Div<Output = Self>
+ DivAssign
+ Rem<Output = Self>
+ RemAssign
+ Not<Output = Self>
+ BitAnd<Output = Self>
+ BitAndAssign
+ BitOr<Output = Self>
+ BitOrAssign
+ BitXor<Output = Self>
+ BitXorAssign
+ Shl<u32, Output = Self>
+ Shl<usize, Output = Self>
+ ShlAssign
+ ShlAssign<u32>
+ Shr<u32, Output = Self>
+ Shr<usize, Output = Self>
+ ShrAssign
+ ShrAssign<u32>
+ fmt::Binary
+ fmt::Debug
+ fmt::Display
+ fmt::LowerHex
+ fmt::Octal
+ fmt::UpperHex
{
type Signed: Int<Signed = Self::Signed, Unsigned = Self::Unsigned>;
type Unsigned: Int<Signed = Self::Signed, Unsigned = Self::Unsigned>;
const IS_SIGNED: bool;
const ZERO: Self;
const ONE: Self;
const MIN: Self;
const MAX: Self;
const MAX_AS_U128: u128;
const TOP_BIT: Self;
const BITS: usize;
fn as_i32(&self) -> i32;
fn as_f32(&self) -> f32;
fn as_f64(&self) -> f64;
fn ilog10(self) -> u32;
fn leading_zeros(self) -> u32;
fn div_euclid(self, rhs: Self) -> Self;
fn rem_euclid(self, rhs: Self) -> Self;
fn checked_add(self, rhs: Self) -> Option<Self>;
fn checked_sub(self, rhs: Self) -> Option<Self>;
fn checked_mul(self, rhs: Self) -> Option<Self>;
fn checked_div(self, rhs: Self) -> Option<Self>;
fn checked_div_euclid(self, rhs: Self) -> Option<Self>;
fn checked_rem(self, rhs: Self) -> Option<Self>;
fn checked_rem_euclid(self, rhs: Self) -> Option<Self>;
fn checked_shl(self, rhs: u32) -> Option<Self>;
fn checked_shr(self, rhs: u32) -> Option<Self>;
fn overflowing_add(self, rhs: Self) -> (Self, bool);
fn overflowing_sub(self, rhs: Self) -> (Self, bool);
fn overflowing_mul(self, rhs: Self) -> (Self, bool);
fn overflowing_div(self, rhs: Self) -> (Self, bool);
fn overflowing_div_euclid(self, rhs: Self) -> (Self, bool);
fn overflowing_rem(self, rhs: Self) -> (Self, bool);
fn overflowing_rem_euclid(self, rhs: Self) -> (Self, bool);
fn saturating_add(self, rhs: Self) -> Self;
fn saturating_sub(self, rhs: Self) -> Self;
fn saturating_mul(self, rhs: Self) -> Self;
fn saturating_div(self, rhs: Self) -> Self;
unsafe fn unchecked_add(self, rhs: Self) -> Self;
unsafe fn unchecked_sub(self, rhs: Self) -> Self;
unsafe fn unchecked_mul(self, rhs: Self) -> Self;
fn wrapping_add(self, rhs: Self) -> Self;
fn wrapping_sub(self, rhs: Self) -> Self;
fn wrapping_mul(self, rhs: Self) -> Self;
fn wrapping_div(self, rhs: Self) -> Self;
fn wrapping_div_euclid(self, rhs: Self) -> Self;
fn wrapping_rem(self, rhs: Self) -> Self;
fn wrapping_rem_euclid(self, rhs: Self) -> Self;
}
pub trait DoubleWidth {
type DoubleWidth;
fn from_double_width(value: Self::DoubleWidth) -> Self;
fn into_double_width(self) -> Self::DoubleWidth;
}
pub trait IntDoubleWidth: Int + DoubleWidth<DoubleWidth: Int> {}
impl<T: Int + DoubleWidth<DoubleWidth: Int>> IntDoubleWidth for T {}
macro_rules! impl_int {
($($t:ty[$st:ty, $ut:ty] $(: $wt:ty)?),* $(,)?) => {
$(
unsafe impl Int for $t {
type Signed = $st;
type Unsigned = $ut;
const IS_SIGNED: bool = <$t>::MIN != 0;
const ZERO: Self = 0;
const ONE: Self = 1;
const MIN: Self = <$t>::MIN;
const MAX: Self = <$t>::MAX;
const MAX_AS_U128: u128 = <$t>::MAX as u128;
const TOP_BIT: Self = (1 as $t) << (size_of::<$t>() * 8 - 1);
const BITS: usize = size_of::<$t>() * 8;
#[inline(always)]
fn as_i32(&self) -> i32 {
*self as i32
}
#[inline(always)]
fn as_f32(&self) -> f32 {
*self as f32
}
#[inline(always)]
fn as_f64(&self) -> f64 {
*self as f64
}
#[inline(always)]
fn ilog10(self) -> u32 {
self.ilog10()
}
#[inline(always)]
fn leading_zeros(self) -> u32{
self.leading_zeros()
}
#[inline(always)]
fn div_euclid(self, rhs: Self) -> Self {
self.div_euclid(rhs)
}
#[inline(always)]
fn rem_euclid(self, rhs: Self) -> Self {
self.rem_euclid(rhs)
}
#[inline(always)]
fn checked_add(self, rhs: Self) -> Option<Self> {
self.checked_add(rhs)
}
#[inline(always)]
fn checked_sub(self, rhs: Self) -> Option<Self> {
self.checked_sub(rhs)
}
#[inline(always)]
fn checked_mul(self, rhs: Self) -> Option<Self> {
self.checked_mul(rhs)
}
#[inline(always)]
fn checked_div(self, rhs: Self) -> Option<Self> {
self.checked_div(rhs)
}
#[inline(always)]
fn checked_div_euclid(self, rhs: Self) -> Option<Self> {
self.checked_div_euclid(rhs)
}
#[inline(always)]
fn checked_rem(self, rhs: Self) -> Option<Self> {
self.checked_rem(rhs)
}
#[inline(always)]
fn checked_rem_euclid(self, rhs: Self) -> Option<Self> {
self.checked_rem_euclid(rhs)
}
#[inline(always)]
fn checked_shl(self, rhs: u32) -> Option<Self> {
self.checked_shl(rhs)
}
#[inline(always)]
fn checked_shr(self, rhs: u32) -> Option<Self> {
self.checked_shr(rhs)
}
#[inline(always)]
fn overflowing_add(self, rhs: Self) -> (Self, bool) {
self.overflowing_add(rhs)
}
#[inline(always)]
fn overflowing_sub(self, rhs: Self) -> (Self, bool) {
self.overflowing_sub(rhs)
}
#[inline(always)]
fn overflowing_mul(self, rhs: Self) -> (Self, bool) {
self.overflowing_mul(rhs)
}
#[inline(always)]
fn overflowing_div(self, rhs: Self) -> (Self, bool) {
self.overflowing_div(rhs)
}
#[inline(always)]
fn overflowing_div_euclid(self, rhs: Self) -> (Self, bool) {
self.overflowing_div_euclid(rhs)
}
#[inline(always)]
fn overflowing_rem(self, rhs: Self) -> (Self, bool) {
self.overflowing_rem(rhs)
}
#[inline(always)]
fn overflowing_rem_euclid(self, rhs: Self) -> (Self, bool) {
self.overflowing_rem_euclid(rhs)
}
#[inline(always)]
fn saturating_add(self, rhs: Self) -> Self {
self.saturating_add(rhs)
}
#[inline(always)]
fn saturating_sub(self, rhs: Self) -> Self {
self.saturating_sub(rhs)
}
#[inline(always)]
fn saturating_mul(self, rhs: Self) -> Self {
self.saturating_mul(rhs)
}
#[inline(always)]
fn saturating_div(self, rhs: Self) -> Self {
self.saturating_div(rhs)
}
#[inline(always)]
unsafe fn unchecked_add(self, rhs: Self) -> Self {
unsafe {self.unchecked_add(rhs)}
}
#[inline(always)]
unsafe fn unchecked_sub(self, rhs: Self) -> Self {
unsafe {self.unchecked_sub(rhs)}
}
#[inline(always)]
unsafe fn unchecked_mul(self, rhs: Self) -> Self {
unsafe {self.unchecked_mul(rhs)}
}
#[inline(always)]
fn wrapping_add(self, rhs: Self) -> Self {
self.wrapping_add(rhs)
}
#[inline(always)]
fn wrapping_sub(self, rhs: Self) -> Self {
self.wrapping_sub(rhs)
}
#[inline(always)]
fn wrapping_mul(self, rhs: Self) -> Self {
self.wrapping_mul(rhs)
}
#[inline(always)]
fn wrapping_div(self, rhs: Self) -> Self {
self.wrapping_div(rhs)
}
#[inline(always)]
fn wrapping_div_euclid(self, rhs: Self) -> Self {
self.wrapping_div_euclid(rhs)
}
#[inline(always)]
fn wrapping_rem(self, rhs: Self) -> Self {
self.wrapping_rem(rhs)
}
#[inline(always)]
fn wrapping_rem_euclid(self, rhs: Self) -> Self {
self.wrapping_rem_euclid(rhs)
}
}
$(
impl DoubleWidth for $t {
type DoubleWidth = $wt;
#[inline(always)]
fn from_double_width(value: Self::DoubleWidth) -> Self {
value as $t
}
#[inline(always)]
fn into_double_width(self) -> Self::DoubleWidth {
self as $wt
}
}
)*
)*
};
}
impl_int!(
i8[i8, u8]: i16,
i16[i16, u16]: i32,
i32[i32, u32]: i64,
i64[i64, u64]: i128,
i128[i128, u128],
u8[i8, u8]: u16,
u16[i16, u16]: u32,
u32[i32, u32]: u64,
u64[i64, u64]: u128,
u128[i128, u128],
isize[isize, usize]: DoubleIsize,
usize[isize, usize]: DoubleUsize,
);
}
use internal::{Int, IntDoubleWidth};
macro_rules! fxp_mul_op {
(.$op:ident($lhs:expr, $rhs:expr)) => {
$lhs.into_double_width().$op($rhs.into_double_width())
};
}
macro_rules! fxp_mul_fix {
($ty:ty; $value:expr, $fract_bits:ident) => {
<$ty>::from_double_width($value >> $fract_bits)
};
}
macro_rules! fxp_mul {
($ty:ty; .$op:ident($lhs:expr, $rhs:expr), $fract_bits:ident) => {
fxp_mul_fix!($ty; fxp_mul_op!(.$op($lhs, $rhs)), $fract_bits)
};
}
macro_rules! fxp_div_op {
(.$op:ident($lhs:expr, $rhs:expr), $fract_bits:ident) => {
($lhs.into_double_width() << $fract_bits).$op($rhs.into_double_width())
};
}
macro_rules! fxp_div_fix {
($ty:ty; $value:expr) => {
<$ty>::from_double_width($value)
};
}
macro_rules! fxp_div {
($ty:ty; .$op:ident($lhs:expr, $rhs:expr), $fract_bits:ident) => {
fxp_div_fix!($ty; fxp_div_op!(.$op($lhs, $rhs), $fract_bits))
};
}
#[inline(always)]
const fn split_shl(value: u128, shift: u32) -> (u128, u128) {
let value = value.rotate_left(shift);
let mask = u128::MAX << shift;
(value & mask, value & !mask)
}
#[inline(always)]
const fn split_shr(value: u128, shift: u32) -> (u128, u128) {
let value = value.rotate_right(shift);
let mask = u128::MAX >> shift;
(value & mask, value & !mask)
}
#[inline(always)]
const fn shl_zero(value: u128, shift: u32) -> Option<u128> {
let (value, overflow) = split_shl(value, shift);
if overflow == 0 {
Some(value)
} else {
None
}
}
#[inline(always)]
const fn shr_zero(value: u128, shift: u32) -> Option<u128> {
let (value, overflow) = split_shr(value, shift);
if overflow == 0 {
Some(value)
} else {
None
}
}
#[repr(transparent)]
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct FixedPoint<T, const FRACT_BITS: usize>(T);
impl<T: Int, const FRACT_BITS: usize> Default for FixedPoint<T, FRACT_BITS> {
#[inline(always)]
fn default() -> Self {
Self::new()
}
}
macro_rules! fmt_fxp {
($trait:path, $radix:literal, $fmt:literal, $pfx:literal) => {
impl<T: Int + $trait, const FRACT_BITS: usize> $trait for FixedPoint<T, FRACT_BITS> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
if const { $radix == 10 && T::IS_SIGNED } && self.0 < T::ZERO {
f.write_char('-')?;
}
if const { $radix != 10 } && f.alternate() {
f.write_str($pfx)?;
}
let fmt_parts = self._fmt_parts::<$radix>();
write!(f, concat!("{", $fmt, "}"), fmt_parts.int)?;
if fmt_parts.fract != 0 {
let fract = if const { $radix == 10 } {
let mut fract = fmt_parts.fract;
while fract % 10 == 0 {
fract /= 10;
}
fract
} else if const { ($radix as u32).is_power_of_two() } {
let rbits = const { ($radix as u32).ilog2() };
fmt_parts.fract >> (fmt_parts.fract.trailing_zeros() / rbits * rbits)
} else {
unreachable!()
};
f.write_char('.')?;
for _ in 0..fmt_parts.fzero {
f.write_char('0')?;
}
write!(f, concat!("{", $fmt, "}"), fract)?;
}
Ok(())
}
}
};
}
fmt_fxp!(fmt::Display, 10, "", "");
fmt_fxp!(fmt::LowerHex, 16, ":x", "0x");
fmt_fxp!(fmt::UpperHex, 16, ":X", "0X");
fmt_fxp!(fmt::Octal, 8, ":o", "0o");
fmt_fxp!(fmt::Binary, 2, ":b", "0b");
impl<T: Int, const FRACT_BITS: usize> fmt::Debug for FixedPoint<T, FRACT_BITS>
where
FixedPoint<T, FRACT_BITS>: fmt::Display,
{
#[inline(always)]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
<Self as fmt::Display>::fmt(self, f)
}
}
struct FmtParts<T: Int> {
int: T::Unsigned,
fract: u128,
fzero: u32,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum ParseError {
OutOfRange,
Inexact,
InvalidInput(usize),
}
impl Error for ParseError {}
impl fmt::Display for ParseError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::OutOfRange => f.write_str("out of range"),
Self::Inexact => f.write_str("inexact"),
Self::InvalidInput(i) => write!(f, "invalid input at {i}"),
}
}
}
impl ParseError {
pub const fn into_panic(self) -> ! {
match self {
Self::OutOfRange => panic!("parse error: out of range"),
Self::Inexact => panic!("parse error: inexact"),
Self::InvalidInput(_) => panic!("parse error: invalid input"), }
}
}
struct Parsed {
value: u128,
i: usize,
digits: usize,
next_byte: Option<u8>,
exact: bool,
}
#[inline(always)]
const fn int_uabs<T: Int>(value: T) -> T::Unsigned {
unsafe {
if const { !T::IS_SIGNED } {
transmute_copy::<T, T::Unsigned>(&value)
} else if const { size_of::<T>() == size_of::<i8>() } {
transmute_copy::<u8, T::Unsigned>(&transmute_copy::<T, i8>(&value).unsigned_abs())
} else if const { size_of::<T>() == size_of::<i16>() } {
transmute_copy::<u16, T::Unsigned>(&transmute_copy::<T, i16>(&value).unsigned_abs())
} else if const { size_of::<T>() == size_of::<i32>() } {
transmute_copy::<u32, T::Unsigned>(&transmute_copy::<T, i32>(&value).unsigned_abs())
} else if const { size_of::<T>() == size_of::<i64>() } {
transmute_copy::<u64, T::Unsigned>(&transmute_copy::<T, i64>(&value).unsigned_abs())
} else if const { size_of::<T>() == size_of::<i128>() } {
transmute_copy::<u128, T::Unsigned>(&transmute_copy::<T, i128>(&value).unsigned_abs())
} else {
unreachable!()
}
}
}
#[inline(always)]
const fn uint_into_u128<T: Int>(value: T) -> u128 {
const { assert!(!T::IS_SIGNED) };
unsafe {
if const { size_of::<T>() == size_of::<u8>() } {
transmute_copy::<T, u8>(&value) as u128
} else if const { size_of::<T>() == size_of::<u16>() } {
transmute_copy::<T, u16>(&value) as u128
} else if const { size_of::<T>() == size_of::<u32>() } {
transmute_copy::<T, u32>(&value) as u128
} else if const { size_of::<T>() == size_of::<u64>() } {
transmute_copy::<T, u64>(&value) as u128
} else if const { size_of::<T>() == size_of::<u128>() } {
transmute_copy::<T, u128>(&value)
} else {
unreachable!()
}
}
}
impl<T: Int, const FRACT_BITS: usize> FixedPoint<T, FRACT_BITS> {
fn _fmt_parts<const RADIX: u32>(&self) -> FmtParts<T> {
const { assert!(RADIX == 10 || RADIX == 16 || RADIX == 8 || RADIX == 2) };
let inner = int_uabs(self.0);
let int = if const { FRACT_BITS == T::BITS } {
T::Unsigned::ZERO
} else {
inner >> FRACT_BITS
};
let mut fract = 0_u128;
let mut fzero = 0;
let mut fdigits = 0;
if const { FRACT_BITS != 0 } {
if const { RADIX.is_power_of_two() } {
let padding_bits = const {
if FRACT_BITS as u32 % RADIX.ilog2() != 0 {
RADIX.ilog2() - FRACT_BITS as u32 % RADIX.ilog2()
} else {
0
}
};
fract = uint_into_u128(inner) & const { (1_u128 << FRACT_BITS) - 1 };
fzero = (fract.leading_zeros() - const { u128::BITS - FRACT_BITS as u32 })
/ const { RADIX.ilog2() };
fract <<= padding_bits;
} else {
let mut fract_bits = self.0 << (T::BITS - FRACT_BITS);
let mut fadd = 1;
const { assert!(RADIX & 1 == 0) };
while fract_bits != T::ZERO {
let top_bit = fract_bits & T::TOP_BIT != T::ZERO;
fract_bits <<= 1;
fdigits += 1;
fract *= const { RADIX as u128 };
fadd *= const { RADIX as u128 / 2 };
if top_bit {
fract += fadd;
}
}
if fract != 0 {
fzero = fdigits - fract.ilog10() - 1;
}
}
}
FmtParts { int, fract, fzero }
}
#[inline(always)]
const fn check() {
const { assert!(FRACT_BITS <= T::BITS) };
}
#[inline(always)]
pub const fn new() -> Self {
const { Self::check() };
Self(T::ZERO)
}
pub const fn from_str(str: &str) -> Result<Self, ParseError> {
Self::_from_str(str, 0, false)
}
pub const fn from_str_exact(str: &str) -> Result<Self, ParseError> {
Self::_from_str(str, 0, true)
}
pub const fn from_str_radix(str: &str, radix: u32) -> Result<Self, ParseError> {
Self::_from_str(str, radix, false)
}
pub const fn from_str_radix_exact(str: &str, radix: u32) -> Result<Self, ParseError> {
Self::_from_str(str, radix, true)
}
const fn _from_str(str: &str, radix: u32, exact: bool) -> Result<Self, ParseError> {
const { Self::check() };
let bytes = str.as_bytes();
let mut i = 0;
let mut negative = false;
match bytes[0] {
b'+' => i += 1,
b'-' => {
negative = true;
i += 1;
}
_ => (),
}
let radix = if radix != 0 {
radix
} else if bytes.len() - i > 2 && bytes[i] == b'0' {
match bytes[i + 1] {
b'b' | b'B' => {
i += 2;
2
}
b'o' | b'O' => {
i += 2;
8
}
b'x' | b'X' => {
i += 2;
16
}
_ => 10,
}
} else {
10
};
assert!(radix >= 2 && radix <= 36);
let mut parsed = match Self::_from_bytes(bytes, i, radix, negative, exact) {
Ok(parsed) => parsed,
Err(e) => return Err(e),
};
if negative {
parsed = parsed._const_neg();
}
Ok(parsed)
}
const fn _from_bytes(
bytes: &[u8],
i: usize,
radix: u32,
negative: bool,
exact: bool,
) -> Result<FixedPoint<T, FRACT_BITS>, ParseError> {
let int_part = match Self::_parse_int(bytes, i, radix) {
Ok(parsed) => parsed,
Err(e) => return Err(e),
};
let Some(fxp_int) = shl_zero(int_part.value, FRACT_BITS as _) else {
return Err(ParseError::OutOfRange);
};
match int_part.next_byte {
None => Self::_into_fxp(fxp_int, negative),
Some(b'.') => {
let (fract_part, fpbits) = if radix.is_power_of_two() {
let fract_part = match Self::_parse_int(bytes, int_part.i + 1, radix) {
Ok(parsed) => parsed,
Err(e) => return Err(e),
};
let Some(fpbits) = fract_part
.digits
.checked_mul(radix.trailing_zeros() as usize)
else {
return Err(ParseError::OutOfRange);
};
(fract_part, fpbits)
} else {
let fract_part = match Self::_parse_fract(bytes, int_part.i + 1, radix) {
Ok(parsed) => parsed,
Err(e) => return Err(e),
};
if exact && !fract_part.exact {
return Err(ParseError::Inexact);
}
let fpbits = fract_part.digits;
(fract_part, fpbits)
};
match fract_part.next_byte {
None => {
let fxp = if FRACT_BITS < fpbits {
if exact {
match shr_zero(fract_part.value, (fpbits - FRACT_BITS) as u32) {
Some(value) => fxp_int | value,
None => return Err(ParseError::Inexact),
}
} else {
let (mut value, overflow) =
split_shr(fract_part.value, (fpbits - FRACT_BITS) as u32);
value |= fxp_int;
if (overflow as i128) < 0 && value & 1 != 0 {
value = value.saturating_add(1);
}
value
}
} else {
fxp_int | (fract_part.value << (FRACT_BITS - fpbits))
};
Self::_into_fxp(fxp, negative)
}
Some(_) => Err(ParseError::InvalidInput(fract_part.i)),
}
}
Some(_) => Err(ParseError::InvalidInput(int_part.i)),
}
}
const fn _parse_int(bytes: &[u8], mut i: usize, radix: u32) -> Result<Parsed, ParseError> {
let mut parsed = 0;
let mut underscore = true; let mut digits = 0;
let len = bytes.len();
let radix_is_po2 = radix.is_power_of_two();
let radix_trailing_zeros = radix.trailing_zeros();
while i < len {
'current_iter: {
let byte = bytes[i];
let bval = match byte {
b'0'..=b'9' => byte - b'0',
b'a'..=b'z' => byte - (b'a' - 10),
b'A'..=b'Z' => byte - (b'A' - 10),
b'_' => {
if underscore {
if digits == 0 {
return Err(ParseError::InvalidInput(i));
}
return Ok(Parsed {
value: parsed,
i,
digits,
next_byte: Some(byte),
exact: true,
});
} else {
underscore = true;
break 'current_iter;
}
}
_ => {
if digits == 0 {
return Err(ParseError::InvalidInput(i));
}
return Ok(Parsed {
value: parsed,
i,
digits,
next_byte: Some(byte),
exact: true,
});
}
};
if bval as u32 >= radix {
if digits == 0 {
return Err(ParseError::InvalidInput(i));
}
return Ok(Parsed {
value: parsed,
i,
digits,
next_byte: Some(byte),
exact: true,
});
}
digits += 1;
underscore = false;
let bval = bval as u128;
if radix_is_po2 {
parsed = match shl_zero(parsed, radix_trailing_zeros) {
Some(parsed) => parsed | bval,
None => return Err(ParseError::OutOfRange),
}
} else {
parsed = match parsed.checked_mul(radix as _) {
Some(parsed) => match parsed.checked_add(bval) {
Some(parsed) => parsed,
None => return Err(ParseError::OutOfRange),
},
None => return Err(ParseError::OutOfRange),
}
}
}
i += 1;
}
if digits == 0 {
return Err(ParseError::InvalidInput(i));
}
Ok(Parsed {
value: parsed,
i,
digits,
next_byte: None,
exact: true,
})
}
const fn _parse_fract(bytes: &[u8], mut i: usize, radix: u32) -> Result<Parsed, ParseError> {
assert!(radix & 1 == 0, "odd based fractionals aren't supported"); let len = if bytes.len() > i + u128::BITS as usize {
i + u128::BITS as usize
} else {
bytes.len()
};
let halfradix = (radix / 2) as u128;
let radix = radix as u128;
let mut parsed = 0;
let mut digits = 0;
let mut fval = 1_u128;
let mut carry = 0_u128;
let mut underscore = true; while i < len {
'current_iter: {
let byte = bytes[i];
let bval = match byte {
b'0'..=b'9' => byte - b'0',
b'a'..=b'z' => byte - (b'a' - 10),
b'A'..=b'Z' => byte - (b'A' - 10),
b'_' => {
if underscore {
if digits == 0 {
return Err(ParseError::InvalidInput(i));
}
return Ok(Parsed {
value: parsed,
i,
digits,
next_byte: Some(byte),
exact: carry == 0,
});
} else {
underscore = true;
break 'current_iter;
}
}
_ => {
if digits == 0 {
return Err(ParseError::InvalidInput(i));
}
return Ok(Parsed {
value: parsed,
i,
digits,
next_byte: Some(byte),
exact: carry == 0,
});
}
};
carry = match carry.checked_mul(radix) {
Some(carry) => match carry.checked_add(bval as _) {
Some(carry) => carry,
None => return Err(ParseError::OutOfRange),
},
None => return Err(ParseError::OutOfRange),
};
fval = match fval.checked_mul(halfradix) {
Some(fval) => fval,
None => return Err(ParseError::OutOfRange),
};
parsed <<= 1;
if carry >= fval {
carry -= fval;
parsed |= 1;
}
digits += 1;
underscore = false;
}
i += 1;
}
while i < bytes.len() {
'current_iter: {
let byte = bytes[i];
let _bval = match byte {
b'0'..=b'9' => byte - b'0',
b'a'..=b'z' => byte - (b'a' - 10),
b'A'..=b'Z' => byte - (b'A' - 10),
b'_' => {
if underscore {
return Ok(Parsed {
value: parsed,
i,
digits,
next_byte: Some(byte),
exact: carry == 0,
});
} else {
underscore = true;
break 'current_iter;
}
}
_ => {
return Ok(Parsed {
value: parsed,
i,
digits,
next_byte: Some(byte),
exact: carry == 0,
});
}
};
underscore = false;
carry = 1;
}
i += 1;
}
Ok(Parsed {
value: parsed,
i,
digits,
next_byte: None,
exact: carry == 0,
})
}
const fn _into_fxp(
value: u128,
negative: bool,
) -> Result<FixedPoint<T, FRACT_BITS>, ParseError> {
if if T::IS_SIGNED && negative {
value > T::MAX_AS_U128.saturating_add(1)
} else {
value > T::MAX_AS_U128
} {
Err(ParseError::OutOfRange)
} else if const { size_of::<T>() == size_of::<u8>() } {
unsafe { Ok(FixedPoint(transmute_copy::<u8, T>(&(value as u8)))) }
} else if const { size_of::<T>() == size_of::<u16>() } {
unsafe { Ok(FixedPoint(transmute_copy::<u16, T>(&(value as u16)))) }
} else if const { size_of::<T>() == size_of::<u32>() } {
unsafe { Ok(FixedPoint(transmute_copy::<u32, T>(&(value as u32)))) }
} else if const { size_of::<T>() == size_of::<u64>() } {
unsafe { Ok(FixedPoint(transmute_copy::<u64, T>(&(value as u64)))) }
} else if const { size_of::<T>() == size_of::<u128>() } {
unsafe { Ok(FixedPoint(transmute_copy::<u128, T>(&value))) }
} else {
unreachable!()
}
}
const fn _const_neg(self) -> Self {
if const { size_of::<T>() == size_of::<i8>() } {
Self(unsafe { transmute_copy::<i8, T>(&-transmute_copy::<T, i8>(&self.0)) })
} else if const { size_of::<T>() == size_of::<i16>() } {
Self(unsafe { transmute_copy::<i16, T>(&-transmute_copy::<T, i16>(&self.0)) })
} else if const { size_of::<T>() == size_of::<i32>() } {
Self(unsafe { transmute_copy::<i32, T>(&-transmute_copy::<T, i32>(&self.0)) })
} else if const { size_of::<T>() == size_of::<i64>() } {
Self(unsafe { transmute_copy::<i64, T>(&-transmute_copy::<T, i64>(&self.0)) })
} else if const { size_of::<T>() == size_of::<i128>() } {
Self(unsafe { transmute_copy::<i128, T>(&-transmute_copy::<T, i128>(&self.0)) })
} else {
unreachable!()
}
}
#[inline(always)]
pub fn checked_add(self, rhs: Self) -> Option<Self> {
self.0.checked_add(rhs.0).map(Self)
}
#[inline(always)]
pub fn checked_sub(self, rhs: Self) -> Option<Self> {
self.0.checked_sub(rhs.0).map(Self)
}
#[inline(always)]
pub fn overflowing_add(self, rhs: Self) -> (Self, bool) {
let (value, o) = self.0.overflowing_add(rhs.0);
(Self(value), o)
}
#[inline(always)]
pub fn overflowing_sub(self, rhs: Self) -> (Self, bool) {
let (value, o) = self.0.overflowing_sub(rhs.0);
(Self(value), o)
}
#[inline(always)]
pub fn saturating_add(self, rhs: Self) -> Self {
Self(self.0.saturating_add(rhs.0))
}
#[inline(always)]
pub fn saturating_sub(self, rhs: Self) -> Self {
Self(self.0.saturating_sub(rhs.0))
}
#[inline(always)]
pub unsafe fn unchecked_add(self, rhs: Self) -> Self {
Self(unsafe { self.0.unchecked_add(rhs.0) })
}
#[inline(always)]
pub unsafe fn unchecked_sub(self, rhs: Self) -> Self {
Self(unsafe { self.0.unchecked_sub(rhs.0) })
}
#[inline(always)]
pub fn wrapping_add(self, rhs: Self) -> Self {
Self(self.0.wrapping_add(rhs.0))
}
#[inline(always)]
pub fn wrapping_sub(self, rhs: Self) -> Self {
Self(self.0.wrapping_sub(rhs.0))
}
}
impl<T: IntDoubleWidth, const FRACT_BITS: usize> FixedPoint<T, FRACT_BITS> {
#[inline(always)]
pub fn div_euclid(self, rhs: Self) -> Self {
Self(fxp_div!(T; .div_euclid(self.0, rhs.0), FRACT_BITS))
}
#[inline(always)]
pub fn rem_euclid(self, rhs: Self) -> Self {
Self(fxp_div!(T; .rem_euclid(self.0, rhs.0), FRACT_BITS))
}
#[inline(always)]
pub fn checked_mul(self, rhs: Self) -> Option<Self> {
fxp_mul_op!(.checked_mul(self.0, rhs.0))
.map(|value| Self(fxp_mul_fix!(T; value, FRACT_BITS)))
}
#[inline(always)]
pub fn checked_div(self, rhs: Self) -> Option<Self> {
fxp_div_op!(.checked_div(self.0, rhs.0), FRACT_BITS)
.map(|value| Self(fxp_div_fix!(T; value)))
}
#[inline(always)]
pub fn checked_div_euclid(self, rhs: Self) -> Option<Self> {
fxp_div_op!(.checked_div_euclid(self.0, rhs.0), FRACT_BITS)
.map(|value| Self(fxp_div_fix!(T; value)))
}
#[inline(always)]
pub fn checked_rem(self, rhs: Self) -> Option<Self> {
fxp_div_op!(.checked_rem(self.0, rhs.0), FRACT_BITS)
.map(|value| Self(fxp_div_fix!(T; value)))
}
#[inline(always)]
pub fn checked_rem_euclid(self, rhs: Self) -> Option<Self> {
fxp_div_op!(.checked_rem_euclid(self.0, rhs.0), FRACT_BITS)
.map(|value| Self(fxp_div_fix!(T; value)))
}
#[inline(always)]
pub fn overflowing_mul(self, rhs: Self) -> (Self, bool) {
let (value, o) = fxp_mul_op!(.overflowing_mul(self.0, rhs.0));
(Self(fxp_mul_fix!(T; value, FRACT_BITS)), o)
}
#[inline(always)]
pub fn overflowing_div(self, rhs: Self) -> (Self, bool) {
let (value, o) = fxp_div_op!(.overflowing_div(self.0,rhs.0), FRACT_BITS);
(Self(fxp_div_fix!(T; value)), o)
}
#[inline(always)]
pub fn overflowing_div_euclid(self, rhs: Self) -> (Self, bool) {
let (value, o) = fxp_div_op!(.overflowing_div_euclid(self.0,rhs.0), FRACT_BITS);
(Self(fxp_div_fix!(T; value)), o)
}
#[inline(always)]
pub fn overflowing_rem(self, rhs: Self) -> (Self, bool) {
let (value, o) = fxp_div_op!(.overflowing_rem(self.0,rhs.0), FRACT_BITS);
(Self(fxp_div_fix!(T; value)), o)
}
#[inline(always)]
pub fn overflowing_rem_euclid(self, rhs: Self) -> (Self, bool) {
let (value, o) = fxp_div_op!(.overflowing_rem_euclid(self.0,rhs.0), FRACT_BITS);
(Self(fxp_div_fix!(T; value)), o)
}
#[inline(always)]
pub fn saturating_mul(self, rhs: Self) -> Self {
Self(fxp_mul!(T; .saturating_mul(self.0, rhs.0), FRACT_BITS))
}
#[inline(always)]
pub fn saturating_div(self, rhs: Self) -> Self {
Self(fxp_div!(T; .saturating_div(self.0, rhs.0), FRACT_BITS))
}
#[inline(always)]
pub unsafe fn unchecked_mul(self, rhs: Self) -> Self {
Self(unsafe { fxp_mul!(T; .unchecked_mul(self.0, rhs.0), FRACT_BITS) })
}
#[inline(always)]
pub fn wrapping_mul(self, rhs: Self) -> Self {
Self(fxp_mul!(T; .wrapping_mul(self.0, rhs.0), FRACT_BITS))
}
#[inline(always)]
pub fn wrapping_div(self, rhs: Self) -> Self {
Self(fxp_div!(T; .wrapping_div(self.0, rhs.0), FRACT_BITS))
}
#[inline(always)]
pub fn wrapping_div_euclid(self, rhs: Self) -> Self {
Self(fxp_div!(T; .wrapping_div_euclid(self.0, rhs.0), FRACT_BITS))
}
#[inline(always)]
pub fn wrapping_rem(self, rhs: Self) -> Self {
Self(fxp_div!(T; .wrapping_rem(self.0, rhs.0), FRACT_BITS))
}
#[inline(always)]
pub fn wrapping_rem_euclid(self, rhs: Self) -> Self {
Self(fxp_div!(T; .wrapping_rem_euclid(self.0, rhs.0), FRACT_BITS))
}
}
impl<T: Int, const FRACT_BITS: usize> From<FixedPoint<T, FRACT_BITS>> for f32 {
fn from(value: FixedPoint<T, FRACT_BITS>) -> Self {
value.0.as_f32()
/ if const { FRACT_BITS == T::BITS } {
T::TOP_BIT.as_f32() * 2.0
} else {
(T::ONE << FRACT_BITS).as_f32()
}
}
}
impl<T: Int, const FRACT_BITS: usize> From<FixedPoint<T, FRACT_BITS>> for f64 {
fn from(value: FixedPoint<T, FRACT_BITS>) -> Self {
value.0.as_f64()
/ if const { FRACT_BITS == T::BITS } {
T::TOP_BIT.as_f64() * 2.0
} else {
(T::ONE << FRACT_BITS).as_f64()
}
}
}
fn try_from_float<
T: Int,
const FRACT_BITS: usize,
FBits: Int,
const EXP_BITS: u32,
const MANTISSA_BITS: u32,
const EXP_OFFSET: i32,
>(
bits: FBits,
try_into_int: impl FnOnce(FBits) -> Option<T>,
) -> Result<FixedPoint<T, FRACT_BITS>, ParseError> {
const { assert!(EXP_BITS + MANTISSA_BITS + 1 == FBits::BITS as u32) };
let negative = (bits & (FBits::ONE << (FBits::BITS - 1))) != FBits::ZERO;
let saturated = Ok(FixedPoint(if negative { T::MIN } else { T::MAX }));
let exponent = (bits >> MANTISSA_BITS) & ((FBits::ONE << EXP_BITS) - FBits::ONE);
let mantissa = bits & ((FBits::ONE << MANTISSA_BITS) - FBits::ONE);
let (exponent, mantissa) = if exponent == FBits::ZERO {
if mantissa == FBits::ZERO {
return Ok(FixedPoint(T::ZERO));
} else {
(EXP_OFFSET + 1, mantissa)
}
} else if exponent == (FBits::ONE << MANTISSA_BITS) - FBits::ONE {
return if mantissa == FBits::ZERO {
saturated
} else {
Err(ParseError::OutOfRange)
};
} else {
(
exponent.as_i32() + EXP_OFFSET,
mantissa | (FBits::ONE << MANTISSA_BITS),
)
};
if !T::IS_SIGNED && negative {
return Ok(FixedPoint(T::ZERO));
}
let shift = exponent - MANTISSA_BITS as i32 + FRACT_BITS as i32;
let value = FixedPoint::<T, FRACT_BITS>(if shift < 0 {
if shift <= -(MANTISSA_BITS as i32 + 1) {
return Ok(FixedPoint(T::ZERO));
} else if let Some(value) = try_into_int(mantissa >> -shift as u32) {
if T::IS_SIGNED && value < T::ZERO {
return saturated;
} else if (mantissa >> (-shift as u32 - 1)) & FBits::ONE != FBits::ZERO {
if T::IS_SIGNED && negative {
value.wrapping_add(T::ONE) } else {
value.saturating_add(T::ONE)
}
} else {
value
}
} else {
return Ok(FixedPoint(T::ZERO));
}
} else if let Some(value) = try_into_int(mantissa) {
if shift >= T::BITS as i32 {
return saturated;
} else {
let shifted_out_mask = !((T::ONE << shift as u32) - T::ONE);
if value & shifted_out_mask != T::ZERO {
return saturated;
} else {
let value = value << shift as u32;
if T::IS_SIGNED && value < T::ZERO {
return saturated;
}
value
}
}
} else {
return saturated;
});
Ok(if negative { value._const_neg() } else { value })
}
impl<T: Int, const FRACT_BITS: usize> TryFrom<f32> for FixedPoint<T, FRACT_BITS> {
type Error = ParseError;
fn try_from(value: f32) -> Result<Self, Self::Error> {
try_from_float::<T, FRACT_BITS, u32, 8, 23, -127>(value.to_bits(), try_u32_into_int::<T>)
}
}
impl<T: Int, const FRACT_BITS: usize> TryFrom<f64> for FixedPoint<T, FRACT_BITS> {
type Error = ParseError;
fn try_from(value: f64) -> Result<Self, Self::Error> {
try_from_float::<T, FRACT_BITS, u64, 11, 52, -1023>(value.to_bits(), try_u64_into_int::<T>)
}
}
impl<T: Int, const FRACT_BITS: usize> FromStr for FixedPoint<T, FRACT_BITS> {
type Err = ParseError;
#[inline(always)]
fn from_str(s: &str) -> Result<Self, Self::Err> {
Self::from_str(s)
}
}
impl<T: Int, const FRACT_BITS: usize> Add for FixedPoint<T, FRACT_BITS> {
type Output = Self;
#[inline(always)]
fn add(self, rhs: Self) -> Self::Output {
Self(self.0 + rhs.0)
}
}
impl<T: Int, const FRACT_BITS: usize> AddAssign for FixedPoint<T, FRACT_BITS> {
#[inline(always)]
fn add_assign(&mut self, rhs: Self) {
self.0 += rhs.0;
}
}
impl<T: Int, const FRACT_BITS: usize> Sub for FixedPoint<T, FRACT_BITS> {
type Output = Self;
#[inline(always)]
fn sub(self, rhs: Self) -> Self::Output {
Self(self.0 - rhs.0)
}
}
impl<T: Int, const FRACT_BITS: usize> SubAssign for FixedPoint<T, FRACT_BITS> {
#[inline(always)]
fn sub_assign(&mut self, rhs: Self) {
self.0 -= rhs.0;
}
}
impl<T: IntDoubleWidth, const FRACT_BITS: usize> Mul for FixedPoint<T, FRACT_BITS> {
type Output = Self;
#[inline(always)]
fn mul(self, rhs: Self) -> Self::Output {
if FRACT_BITS == 0 {
Self(self.0 * rhs.0)
} else {
Self(fxp_mul!(T; .mul(self.0, rhs.0), FRACT_BITS))
}
}
}
impl<T: IntDoubleWidth, const FRACT_BITS: usize> MulAssign for FixedPoint<T, FRACT_BITS> {
#[inline(always)]
fn mul_assign(&mut self, rhs: Self) {
if FRACT_BITS == 0 {
self.0 *= rhs.0
} else {
self.0 = fxp_mul!(T; .mul(self.0, rhs.0), FRACT_BITS);
}
}
}
impl<T: IntDoubleWidth, const FRACT_BITS: usize> Div for FixedPoint<T, FRACT_BITS> {
type Output = Self;
#[inline(always)]
fn div(self, rhs: Self) -> Self::Output {
if FRACT_BITS == 0 {
Self(self.0 / rhs.0)
} else {
Self(fxp_div!(T; .div(self.0, rhs.0), FRACT_BITS))
}
}
}
impl<T: IntDoubleWidth, const FRACT_BITS: usize> DivAssign for FixedPoint<T, FRACT_BITS> {
#[inline(always)]
fn div_assign(&mut self, rhs: Self) {
if FRACT_BITS == 0 {
self.0 /= rhs.0;
} else {
self.0 = fxp_div!(T; .div(self.0, rhs.0), FRACT_BITS);
}
}
}
impl<T: IntDoubleWidth, const FRACT_BITS: usize> Rem for FixedPoint<T, FRACT_BITS> {
type Output = Self;
#[inline(always)]
fn rem(self, rhs: Self) -> Self::Output {
if FRACT_BITS == 0 {
Self(self.0 % rhs.0)
} else {
Self(fxp_div!(T; .rem(self.0, rhs.0), FRACT_BITS))
}
}
}
impl<T: IntDoubleWidth, const FRACT_BITS: usize> RemAssign for FixedPoint<T, FRACT_BITS> {
#[inline(always)]
fn rem_assign(&mut self, rhs: Self) {
if FRACT_BITS == 0 {
self.0 %= rhs.0;
} else {
self.0 = fxp_div!(T; .rem(self.0, rhs.0), FRACT_BITS);
}
}
}
impl<T: Int + Neg<Output = T>, const FRACT_BITS: usize> Neg for FixedPoint<T, FRACT_BITS> {
type Output = Self;
#[inline(always)]
fn neg(self) -> Self::Output {
Self(-self.0)
}
}
impl<T: Int, const FRACT_BITS: usize> Not for FixedPoint<T, FRACT_BITS> {
type Output = Self;
#[inline(always)]
fn not(self) -> Self::Output {
Self(!self.0)
}
}
#[cfg(test)]
mod tests {
use crate::{int_uabs, uint_into_u128, FixedPoint, Int};
use core::mem::transmute_copy;
extern crate std;
use std::format;
#[inline(always)]
const fn int_wrapping_add<T: Int>(value: T, add: T) -> T {
unsafe {
if const { size_of::<T>() == size_of::<u8>() } {
transmute_copy::<u8, T>(
&transmute_copy::<T, u8>(&value).wrapping_add(transmute_copy::<T, u8>(&add)),
)
} else if const { size_of::<T>() == size_of::<u16>() } {
transmute_copy::<u16, T>(
&transmute_copy::<T, u16>(&value).wrapping_add(transmute_copy::<T, u16>(&add)),
)
} else if const { size_of::<T>() == size_of::<u32>() } {
transmute_copy::<u32, T>(
&transmute_copy::<T, u32>(&value).wrapping_add(transmute_copy::<T, u32>(&add)),
)
} else if const { size_of::<T>() == size_of::<u64>() } {
transmute_copy::<u64, T>(
&transmute_copy::<T, u64>(&value).wrapping_add(transmute_copy::<T, u64>(&add)),
)
} else if const { size_of::<T>() == size_of::<u128>() } {
transmute_copy::<u128, T>(
&transmute_copy::<T, u128>(&value)
.wrapping_add(transmute_copy::<T, u128>(&add)),
)
} else {
unreachable!()
}
}
}
fn test_all_display<T: Int, const FRACT_BITS: usize>() {
let mut fx = FixedPoint::<T, FRACT_BITS>(T::ZERO);
for i in 0..const { uint_into_u128(int_uabs(T::MAX)) as u64 + 1 } {
let f = i as f64 / const { (1_u64 << FRACT_BITS) as f64 };
let fp: f64 = format!("{fx}").parse().unwrap();
assert_eq!(fp, f);
fx.0 = int_wrapping_add(fx.0, T::ONE);
}
}
fn test_all_display_bin<T: Int, const FRACT_BITS: usize>() {
let mut fx = FixedPoint::<T, FRACT_BITS>(T::ZERO);
for _ in 0..const { uint_into_u128(int_uabs(T::MAX)) as u64 + 1 } {
let s = format!("{fx:#b}");
if const { FRACT_BITS == T::BITS } {
assert_eq!(
s,
format!("0b0.{:0fwidth$b}", fx.0, fwidth = FRACT_BITS)
.trim_end_matches('0')
.trim_end_matches('.')
);
} else {
assert_eq!(
s,
format!(
"{:#b}.{:0fwidth$b}",
fx.0 >> FRACT_BITS,
fx.0 & ((T::ONE << FRACT_BITS) - T::ONE),
fwidth = FRACT_BITS
)
.trim_end_matches('0')
.trim_end_matches('.')
);
}
fx.0 = const_binop!(.wrapping_add(fx.0, T::ONE));
}
}
fn test_all_display_oct<T: Int, const FRACT_BITS: usize>() {
let mut fx = FixedPoint::<T, FRACT_BITS>(T::ZERO);
let padding_bits = const {
if FRACT_BITS % 3 != 0 {
3 - FRACT_BITS % 3
} else {
0
}
};
for _ in 0..const { uint_into_u128(int_uabs(T::MAX)) as u64 + 1 } {
let s = format!("{fx:#o}");
if const { FRACT_BITS == T::BITS } {
assert_eq!(
s,
format!(
"0o0.{:0fwidth$o}",
uint_into_u128(fx.0) << padding_bits,
fwidth = FRACT_BITS.div_ceil(3)
)
.trim_end_matches('0')
.trim_end_matches('.')
);
} else {
assert_eq!(
s,
format!(
"{:#o}.{:0fwidth$o}",
fx.0 >> FRACT_BITS,
uint_into_u128(fx.0 & ((T::ONE << FRACT_BITS) - T::ONE)) << padding_bits,
fwidth = FRACT_BITS.div_ceil(3)
)
.trim_end_matches('0')
.trim_end_matches('.')
);
}
fx.0 = const_binop!(.wrapping_add(fx.0, T::ONE));
}
}
fn test_all_display_hex<T: Int, const FRACT_BITS: usize>() {
let mut fx = FixedPoint::<T, FRACT_BITS>(T::ZERO);
let padding_bits = const {
if FRACT_BITS % 4 != 0 {
4 - FRACT_BITS % 4
} else {
0
}
};
for _ in 0..const { uint_into_u128(int_uabs(T::MAX)) as u64 + 1 } {
let s = format!("{fx:#x}");
if const { FRACT_BITS == T::BITS } {
assert_eq!(
s,
format!(
"0x0.{:0fwidth$x}",
uint_into_u128(fx.0) << padding_bits,
fwidth = FRACT_BITS.div_ceil(4)
)
.trim_end_matches('0')
.trim_end_matches('.')
);
} else {
assert_eq!(
s,
format!(
"{:#x}.{:0fwidth$x}",
fx.0 >> FRACT_BITS,
uint_into_u128(fx.0 & ((T::ONE << FRACT_BITS) - T::ONE)) << padding_bits,
fwidth = FRACT_BITS.div_ceil(4)
)
.trim_end_matches('0')
.trim_end_matches('.')
);
}
fx.0 = const_binop!(.wrapping_add(fx.0, T::ONE));
}
}
fn test_all_display_parse_roundtrip<T: Int, const FRACT_BITS: usize>() {
let mut fx = FixedPoint::<T, FRACT_BITS>(T::ZERO);
for _ in 0..const { uint_into_u128(int_uabs(T::MAX)) as u64 + 1 } {
let fp: FixedPoint<T, FRACT_BITS> = format!("{fx}").parse().unwrap();
assert_eq!(fp, fx);
fx.0 = const_binop!(.wrapping_add(fx.0, T::ONE));
}
}
fn test_all_display_bin_parse_roundtrip<T: Int, const FRACT_BITS: usize>() {
let mut fx = FixedPoint::<T, FRACT_BITS>(T::ZERO);
for _ in 0..const { uint_into_u128(int_uabs(T::MAX)) as u64 + 1 } {
let fp: FixedPoint<T, FRACT_BITS> = format!("{fx:#b}").parse().unwrap();
assert_eq!(fp, fx);
fx.0 = const_binop!(.wrapping_add(fx.0, T::ONE));
}
}
fn test_all_display_oct_parse_roundtrip<T: Int, const FRACT_BITS: usize>() {
let mut fx = FixedPoint::<T, FRACT_BITS>(T::ZERO);
for _ in 0..const { uint_into_u128(int_uabs(T::MAX)) as u64 + 1 } {
let fp: FixedPoint<T, FRACT_BITS> = format!("{fx:#o}").parse().unwrap();
assert_eq!(fp, fx);
fx.0 = const_binop!(.wrapping_add(fx.0, T::ONE));
}
}
fn test_all_display_hex_parse_roundtrip<T: Int, const FRACT_BITS: usize>() {
let mut fx = FixedPoint::<T, FRACT_BITS>(T::ZERO);
for _ in 0..const { uint_into_u128(int_uabs(T::MAX)) as u64 + 1 } {
let fp: FixedPoint<T, FRACT_BITS> = format!("{fx:#x}").parse().unwrap();
assert_eq!(fp, fx);
fx.0 = const_binop!(.wrapping_add(fx.0, T::ONE));
}
}
#[test]
fn display_u16_0() {
test_all_display::<u16, 0>();
}
#[test]
fn display_u16_8() {
test_all_display::<u16, 8>();
}
#[test]
fn display_u16_16() {
test_all_display::<u16, 16>();
}
#[test]
fn display_bin_u16_0() {
test_all_display_bin::<u16, 0>();
}
#[test]
fn display_bin_u16_8() {
test_all_display_bin::<u16, 8>();
}
#[test]
fn display_bin_u16_16() {
test_all_display_bin::<u16, 16>();
}
#[test]
fn display_oct_u16_0() {
test_all_display_oct::<u16, 0>();
}
#[test]
fn display_oct_u16_8() {
test_all_display_oct::<u16, 8>();
}
#[test]
fn display_oct_u16_16() {
test_all_display_oct::<u16, 16>();
}
#[test]
fn display_hex_u16_0() {
test_all_display_hex::<u16, 0>();
}
#[test]
fn display_hex_u16_8() {
test_all_display_hex::<u16, 8>();
}
#[test]
fn display_hex_u16_11() {
test_all_display_hex::<u16, 11>();
}
#[test]
fn display_hex_u16_16() {
test_all_display_hex::<u16, 16>();
}
#[test]
#[ignore = "slow"]
fn display_u32_16() {
test_all_display::<u32, 16>();
}
#[test]
fn display_parse_roundtrip_u16_0() {
test_all_display_parse_roundtrip::<u16, 0>();
}
#[test]
fn display_parse_roundtrip_u16_8() {
test_all_display_parse_roundtrip::<u16, 8>();
}
#[test]
fn display_parse_roundtrip_u16_16() {
test_all_display_parse_roundtrip::<u16, 16>();
}
#[test]
fn display_bin_parse_roundtrip_u16_0() {
test_all_display_bin_parse_roundtrip::<u16, 0>();
}
#[test]
fn display_bin_parse_roundtrip_u16_8() {
test_all_display_bin_parse_roundtrip::<u16, 8>();
}
#[test]
fn display_bin_parse_roundtrip_u16_16() {
test_all_display_bin_parse_roundtrip::<u16, 16>();
}
#[test]
fn display_oct_parse_roundtrip_u16_0() {
test_all_display_oct_parse_roundtrip::<u16, 0>();
}
#[test]
fn display_oct_parse_roundtrip_u16_8() {
test_all_display_oct_parse_roundtrip::<u16, 8>();
}
#[test]
fn display_oct_parse_roundtrip_u16_16() {
test_all_display_oct_parse_roundtrip::<u16, 16>();
}
#[test]
fn display_hex_parse_roundtrip_u16_0() {
test_all_display_hex_parse_roundtrip::<u16, 0>();
}
#[test]
fn display_hex_parse_roundtrip_u16_8() {
test_all_display_hex_parse_roundtrip::<u16, 8>();
}
#[test]
fn display_hex_parse_roundtrip_u16_11() {
test_all_display_hex_parse_roundtrip::<u16, 11>();
}
#[test]
fn display_hex_parse_roundtrip_u16_16() {
test_all_display_hex_parse_roundtrip::<u16, 16>();
}
#[test]
#[ignore = "slow"]
fn display_parse_roundtrip_u32_16() {
test_all_display_parse_roundtrip::<u32, 16>();
}
#[test]
fn add_i16_8() {
let a: FixedPoint<i16, 8> = fixp!("3");
let b = fixp!("-4.5");
assert_eq!(a + b, fixp!("-1.5"));
}
#[test]
fn sub_i16_8() {
let a: FixedPoint<i16, 8> = fixp!("3");
let b = fixp!("4.5");
assert_eq!(a - b, fixp!("-1.5"));
}
#[test]
fn mul_i16_8() {
let a: FixedPoint<i16, 8> = fixp!("3");
let b = fixp!("-4.5");
assert_eq!(a * b, fixp!("-13.5"));
}
#[test]
fn div_i16_8() {
let a: FixedPoint<i16, 8> = fixp!("13.5");
let b = fixp!("-4.5");
assert_eq!(a / b, fixp!("-3"));
}
#[test]
fn add_u16_8() {
let a: FixedPoint<u16, 8> = fixp!("3");
let b = fixp!("4.5");
assert_eq!(a + b, fixp!("7.5"));
}
#[test]
fn sub_u16_8() {
let a: FixedPoint<u16, 8> = fixp!("7");
let b = fixp!("4.5");
assert_eq!(a - b, fixp!("2.5"));
}
#[test]
fn mul_u16_8() {
let a: FixedPoint<u16, 8> = fixp!("3");
let b = fixp!("4.5");
assert_eq!(a * b, fixp!("13.5"));
}
#[test]
fn div_u16_8() {
let a: FixedPoint<u16, 8> = fixp!("13.5");
let b = fixp!("4.5");
assert_eq!(a / b, fixp!("3"));
}
}