feat: add display UI with splash screen, dashboard, and RSSI sparkline

Implement full SSD1306 OLED display support:
- Splash screen on boot (1.5s) with project name, version, board
- Dashboard showing radio state, config, counters, RSSI/SNR
- RSSI history sparkline (64-sample ring buffer, 2px-wide bars)
- DisplayOn/DisplayOff host commands routed via dedicated channel
- Pure render functions against generic DrawTarget (no hardware coupling)
- Display on/off control bypasses radio task entirely

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
swaitsandClaude Opus 4.6 committed 2026-03-29 20:42:54 -06:00
1 parent 7064110293
commit 3102fc747a
17624 files changed
+4564928 -38

No files matched your search

+1625
View File
File diff suppressed because it is too large. Load diff
+50
View File
@@ -0,0 +1,50 @@
// Copyright © 2018–2025 Trevor Spiteri
// This library is free software: you can redistribute it and/or
// modify it under the terms of either
//
// * the Apache License, Version 2.0 or
// * the MIT License
//
// at your option.
//
// You should have recieved copies of the Apache License and the MIT
// License along with the library. If not, see
// <https://www.apache.org/licenses/LICENSE-2.0> and
// <https://opensource.org/licenses/MIT>.
use crate::{
FixedI8, FixedI16, FixedI32, FixedI64, FixedI128, FixedU8, FixedU16, FixedU32, FixedU64,
FixedU128,
};
use borsh::io::{Read, Result, Write};
use borsh::{BorshDeserialize, BorshSerialize};
macro_rules! borsh_fixed {
($Fixed:ident is $TBits:ident) => {
impl<Frac> BorshSerialize for $Fixed<Frac> {
#[inline]
fn serialize<W: Write>(&self, writer: &mut W) -> Result<()> {
<$TBits as BorshSerialize>::serialize(&self.bits, writer)
}
}
impl<Frac> BorshDeserialize for $Fixed<Frac> {
#[inline]
fn deserialize_reader<R: Read>(reader: &mut R) -> Result<Self> {
<$TBits as BorshDeserialize>::deserialize_reader(reader).map($Fixed::from_bits)
}
}
};
}
borsh_fixed! { FixedI8 is i8 }
borsh_fixed! { FixedI16 is i16 }
borsh_fixed! { FixedI32 is i32 }
borsh_fixed! { FixedI64 is i64 }
borsh_fixed! { FixedI128 is i128 }
borsh_fixed! { FixedU8 is u8 }
borsh_fixed! { FixedU16 is u16 }
borsh_fixed! { FixedU32 is u32 }
borsh_fixed! { FixedU64 is u64 }
borsh_fixed! { FixedU128 is u128 }
+380
View File
@@ -0,0 +1,380 @@
// Copyright © 2018–2025 Trevor Spiteri
// This library is free software: you can redistribute it and/or
// modify it under the terms of either
//
// * the Apache License, Version 2.0 or
// * the MIT License
//
// at your option.
//
// You should have recieved copies of the Apache License and the MIT
// License along with the library. If not, see
// <https://www.apache.org/licenses/LICENSE-2.0> and
// <https://opensource.org/licenses/MIT>.
// Kept trimmed: no underscores at beginning or end of slice
#[derive(Clone, Copy, Debug)]
pub struct DigitsUnds<'a> {
bytes: &'a [u8],
digits: usize,
}
impl<'a> DigitsUnds<'a> {
pub const EMPTY: DigitsUnds<'a> = DigitsUnds::new(&[]);
pub const fn new(bytes: &'a [u8]) -> DigitsUnds<'a> {
let mut digits = 0;
let mut leading_unds = 0;
let mut trailing_unds = 0;
let mut rem_bytes = bytes;
while let Some((&byte, rem)) = rem_bytes.split_first() {
rem_bytes = rem;
if byte == b'_' {
trailing_unds += 1;
} else {
if digits == 0 {
leading_unds = trailing_unds;
}
digits += 1;
trailing_unds = 0;
}
}
let without_trailing_unds = bytes.split_at(bytes.len() - trailing_unds).0;
let without_leading_unds = without_trailing_unds.split_at(leading_unds).1;
DigitsUnds {
bytes: without_leading_unds,
digits,
}
}
#[inline]
pub const fn len(self) -> usize {
self.digits
}
#[inline]
pub const fn is_empty(self) -> bool {
self.digits == 0
}
pub const fn split_at(self, mid: usize) -> (DigitsUnds<'a>, DigitsUnds<'a>) {
let mut remaining_digits = mid;
let mut unds = 0;
let mut rem_bytes = self.bytes;
while let Some((&byte, rem)) = rem_bytes.split_first() {
rem_bytes = rem;
if byte == b'_' {
unds += 1;
} else {
remaining_digits -= 1;
if remaining_digits == 0 {
break;
}
}
}
if remaining_digits > 0 {
panic!("index out of bounds");
}
let first = DigitsUnds {
bytes: self.bytes.split_at(mid + unds).0,
digits: mid,
};
// skip over underscores between first part and last part
while let Some((&b'_', rem)) = rem_bytes.split_first() {
rem_bytes = rem;
}
(
first,
DigitsUnds {
bytes: rem_bytes,
digits: self.digits - mid,
},
)
}
#[inline]
pub const fn split_first(self) -> Option<(u8, DigitsUnds<'a>)> {
let Some((&first, mut rem_bytes)) = self.bytes.split_first() else {
return None;
};
// first byte is never underscore
debug_assert!(first != b'_');
// skip over underscores between first digit and last part
while let Some((&b'_', rem)) = rem_bytes.split_first() {
rem_bytes = rem;
}
Some((
first,
DigitsUnds {
bytes: rem_bytes,
digits: self.digits - 1,
},
))
}
#[inline]
const fn split_last(self) -> Option<(u8, DigitsUnds<'a>)> {
let Some((&last, mut rem_bytes)) = self.bytes.split_last() else {
return None;
};
// last byte is never underscore
debug_assert!(last != b'_');
// skip over underscores between first part and last digit
while let Some((&b'_', rem)) = rem_bytes.split_last() {
rem_bytes = rem;
}
Some((
last,
DigitsUnds {
bytes: rem_bytes,
digits: self.digits - 1,
},
))
}
const fn split_leading_zeros(self) -> (usize, DigitsUnds<'a>) {
let mut zeros = 0;
let mut rem = self;
while let Some((b'0', rest)) = rem.split_first() {
zeros += 1;
rem = rest;
}
(zeros, rem)
}
const fn split_trailing_zeros(self) -> (usize, DigitsUnds<'a>) {
let mut zeros = 0;
let mut rem = self;
while let Some((b'0', rest)) = rem.split_last() {
zeros += 1;
rem = rest;
}
(zeros, rem)
}
}
#[derive(Clone, Copy, Debug)]
pub struct DigitsExp<'a> {
leading_zeros: usize,
part1: DigitsUnds<'a>,
part2: DigitsUnds<'a>,
trailing_zeros: usize,
}
impl<'a> DigitsExp<'a> {
const EMPTY: DigitsExp<'a> = DigitsExp {
leading_zeros: 0,
part1: DigitsUnds::EMPTY,
part2: DigitsUnds::EMPTY,
trailing_zeros: 0,
};
const fn new1(digits: DigitsUnds<'a>) -> DigitsExp<'a> {
let (leading_zeros, rest) = digits.split_leading_zeros();
let (trailing_zeros, rest) = rest.split_trailing_zeros();
DigitsExp {
leading_zeros,
part1: rest,
part2: DigitsUnds::EMPTY,
trailing_zeros,
}
}
const fn new2(digits1: DigitsUnds<'a>, digits2: DigitsUnds<'a>) -> DigitsExp<'a> {
let (mut leading_zeros, mut digits1) = digits1.split_leading_zeros();
let digits2 = if digits1.is_empty() {
let (more_leading_zeros, new_digits1) = digits2.split_leading_zeros();
leading_zeros += more_leading_zeros;
digits1 = new_digits1;
DigitsUnds::EMPTY
} else {
digits2
};
let (mut trailing_zeros, digits2) = digits2.split_trailing_zeros();
if digits2.is_empty() {
let (more_trailing_zeros, new_digits1) = digits1.split_trailing_zeros();
trailing_zeros += more_trailing_zeros;
digits1 = new_digits1;
}
DigitsExp {
leading_zeros,
part1: digits1,
part2: digits2,
trailing_zeros,
}
}
// exp.unsigned_abs() must fit in usize, and results must have lengths that fit in usize
pub const fn new_int_frac(
int: DigitsUnds<'a>,
frac: DigitsUnds<'a>,
exp: i32,
) -> Option<(DigitsExp<'a>, DigitsExp<'a>)> {
if int.len() > usize::MAX - frac.len() {
return None;
}
let abs_exp = exp.unsigned_abs() as usize;
if abs_exp as u32 != exp.unsigned_abs() {
return None;
}
let (mut int, mut frac) = if exp == 0 {
(DigitsExp::new1(int), DigitsExp::new1(frac))
} else if exp < 0 {
if let Some(extra_zeros) = abs_exp.checked_sub(int.len()) {
let mut frac = DigitsExp::new2(int, frac);
frac.trailing_zeros = 0;
if extra_zeros > usize::MAX - frac.len() {
return None;
}
frac.leading_zeros += extra_zeros;
(DigitsExp::EMPTY, frac)
} else {
let int = int.split_at(int.len() - abs_exp);
(DigitsExp::new1(int.0), DigitsExp::new2(int.1, frac))
}
} else {
// exp > 0
if let Some(extra_zeros) = abs_exp.checked_sub(frac.len()) {
let mut int = DigitsExp::new2(int, frac);
int.leading_zeros = 0;
if extra_zeros > usize::MAX - int.len() {
return None;
}
int.trailing_zeros += extra_zeros;
(int, DigitsExp::EMPTY)
} else {
let frac = frac.split_at(abs_exp);
(DigitsExp::new2(int, frac.0), DigitsExp::new1(frac.1))
}
};
int.leading_zeros = 0;
if int.part1.is_empty() && int.part2.is_empty() {
int.trailing_zeros = 0;
}
frac.trailing_zeros = 0;
if frac.part2.is_empty() && frac.part1.is_empty() {
frac.leading_zeros = 0;
}
Some((int, frac))
}
#[inline]
pub const fn len(self) -> usize {
self.leading_zeros + self.part1.len() + self.part2.len() + self.trailing_zeros
}
#[inline]
pub const fn is_empty(self) -> bool {
self.len() == 0
}
pub const fn split_at(self, mut mid: usize) -> (DigitsExp<'a>, DigitsExp<'a>) {
let mut first = DigitsExp::EMPTY;
let mut last = self;
if mid == 0 {
return (first, last);
}
if mid < self.leading_zeros {
(first.leading_zeros, last.leading_zeros) = (mid, self.leading_zeros - mid);
return (first, last);
}
(first.leading_zeros, last.leading_zeros) = (self.leading_zeros, 0);
mid -= self.leading_zeros;
if mid == 0 {
return (first, last);
}
if mid < self.part1.len() {
(first.part1, last.part1) = self.part1.split_at(mid);
return (first, last);
}
first.part1 = self.part1;
last.part1 = self.part2;
last.part2 = DigitsUnds::EMPTY;
mid -= self.part1.len();
if mid == 0 {
return (first, last);
}
if mid < self.part2.len() {
(first.part2, last.part1) = self.part2.split_at(mid);
return (first, last);
}
first.part2 = self.part2;
last.leading_zeros = self.trailing_zeros;
last.part1 = DigitsUnds::EMPTY;
last.trailing_zeros = 0;
mid -= self.part2.len();
if mid == 0 {
return (first, last);
}
if mid < self.trailing_zeros {
(first.trailing_zeros, last.leading_zeros) = (mid, self.trailing_zeros - mid);
return (first, last);
}
(first.trailing_zeros, last.leading_zeros) = (self.trailing_zeros, 0);
mid -= self.trailing_zeros;
if mid == 0 {
return (first, last);
}
panic!("index out of bounds");
}
// no automatic renormalization done after split_first
#[inline]
pub const fn split_first(self) -> Option<(u8, DigitsExp<'a>)> {
if self.leading_zeros > 0 {
return Some((
b'0',
DigitsExp {
leading_zeros: self.leading_zeros - 1,
..self
},
));
}
if let Some((first, rest)) = self.part1.split_first() {
return Some((
first,
DigitsExp {
part1: rest,
..self
},
));
}
if let Some((first, rest)) = self.part2.split_first() {
return Some((
first,
DigitsExp {
part2: rest,
..self
},
));
}
if self.trailing_zeros > 0 {
return Some((
b'0',
DigitsExp {
trailing_zeros: self.trailing_zeros - 1,
..self
},
));
}
None
}
}
+200
View File
@@ -0,0 +1,200 @@
// Copyright © 2018–2025 Trevor Spiteri
// This library is free software: you can redistribute it and/or
// modify it under the terms of either
//
// * the Apache License, Version 2.0 or
// * the MIT License
//
// at your option.
//
// You should have recieved copies of the Apache License and the MIT
// License along with the library. If not, see
// <https://www.apache.org/licenses/LICENSE-2.0> and
// <https://opensource.org/licenses/MIT>.
#![allow(deprecated)]
use crate::types::extra::{LeEqU8, LeEqU16, LeEqU32, LeEqU64, LeEqU128};
use crate::{
F128, F128Bits, FixedI8, FixedI16, FixedI32, FixedI64, FixedI128, FixedU8, FixedU16, FixedU32,
FixedU64, FixedU128,
};
use az::{Cast, CheckedCast, OverflowingCast, SaturatingCast, UnwrappedCast, WrappingCast};
use half::{bf16 as half_bf16, f16 as half_f16};
macro_rules! cast {
($Src:ident($LeEqUSrc:ident); $Dst:ident($LeEqUDst:ident)) => {
impl<FracSrc: $LeEqUSrc, FracDst: $LeEqUDst> Cast<$Dst<FracDst>> for $Src<FracSrc> {
#[inline]
fn cast(self) -> $Dst<FracDst> {
self.to_num()
}
}
impl<FracSrc: $LeEqUSrc, FracDst: $LeEqUDst> CheckedCast<$Dst<FracDst>> for $Src<FracSrc> {
#[inline]
fn checked_cast(self) -> Option<$Dst<FracDst>> {
self.checked_to_num()
}
}
impl<FracSrc: $LeEqUSrc, FracDst: $LeEqUDst> SaturatingCast<$Dst<FracDst>>
for $Src<FracSrc>
{
#[inline]
fn saturating_cast(self) -> $Dst<FracDst> {
self.saturating_to_num()
}
}
impl<FracSrc: $LeEqUSrc, FracDst: $LeEqUDst> WrappingCast<$Dst<FracDst>> for $Src<FracSrc> {
#[inline]
fn wrapping_cast(self) -> $Dst<FracDst> {
self.wrapping_to_num()
}
}
impl<FracSrc: $LeEqUSrc, FracDst: $LeEqUDst> OverflowingCast<$Dst<FracDst>>
for $Src<FracSrc>
{
#[inline]
fn overflowing_cast(self) -> ($Dst<FracDst>, bool) {
self.overflowing_to_num()
}
}
impl<FracSrc: $LeEqUSrc, FracDst: $LeEqUDst> UnwrappedCast<$Dst<FracDst>>
for $Src<FracSrc>
{
#[inline]
#[track_caller]
fn unwrapped_cast(self) -> $Dst<FracDst> {
self.unwrapped_to_num()
}
}
};
($Fixed:ident($LeEqU:ident); $Dst:ident) => {
impl<Frac: $LeEqU> Cast<$Dst> for $Fixed<Frac> {
#[inline]
fn cast(self) -> $Dst {
self.to_num()
}
}
impl<Frac: $LeEqU> CheckedCast<$Dst> for $Fixed<Frac> {
#[inline]
fn checked_cast(self) -> Option<$Dst> {
self.checked_to_num()
}
}
impl<Frac: $LeEqU> SaturatingCast<$Dst> for $Fixed<Frac> {
#[inline]
fn saturating_cast(self) -> $Dst {
self.saturating_to_num()
}
}
impl<Frac: $LeEqU> WrappingCast<$Dst> for $Fixed<Frac> {
#[inline]
fn wrapping_cast(self) -> $Dst {
self.wrapping_to_num()
}
}
impl<Frac: $LeEqU> OverflowingCast<$Dst> for $Fixed<Frac> {
#[inline]
fn overflowing_cast(self) -> ($Dst, bool) {
self.overflowing_to_num()
}
}
impl<Frac: $LeEqU> UnwrappedCast<$Dst> for $Fixed<Frac> {
#[inline]
#[track_caller]
fn unwrapped_cast(self) -> $Dst {
self.unwrapped_to_num()
}
}
};
($Src:ident; $Fixed:ident($LeEqU:ident)) => {
impl<Frac: $LeEqU> Cast<$Fixed<Frac>> for $Src {
#[inline]
fn cast(self) -> $Fixed<Frac> {
<$Fixed<Frac>>::from_num(self)
}
}
impl<Frac: $LeEqU> CheckedCast<$Fixed<Frac>> for $Src {
#[inline]
fn checked_cast(self) -> Option<$Fixed<Frac>> {
<$Fixed<Frac>>::checked_from_num(self)
}
}
impl<Frac: $LeEqU> SaturatingCast<$Fixed<Frac>> for $Src {
#[inline]
fn saturating_cast(self) -> $Fixed<Frac> {
<$Fixed<Frac>>::saturating_from_num(self)
}
}
impl<Frac: $LeEqU> WrappingCast<$Fixed<Frac>> for $Src {
#[inline]
fn wrapping_cast(self) -> $Fixed<Frac> {
<$Fixed<Frac>>::wrapping_from_num(self)
}
}
impl<Frac: $LeEqU> OverflowingCast<$Fixed<Frac>> for $Src {
#[inline]
fn overflowing_cast(self) -> ($Fixed<Frac>, bool) {
<$Fixed<Frac>>::overflowing_from_num(self)
}
}
impl<Frac: $LeEqU> UnwrappedCast<$Fixed<Frac>> for $Src {
#[inline]
#[track_caller]
fn unwrapped_cast(self) -> $Fixed<Frac> {
<$Fixed<Frac>>::unwrapped_from_num(self)
}
}
};
}
macro_rules! cast_num {
($Src:ident($LeEqUSrc:ident); $($Dst:ident($LeEqUDst:ident),)*) => { $(
cast! { $Src($LeEqUSrc); $Dst($LeEqUDst) }
)* };
($Fixed:ident($LeEqU:ident); $($Num:ident,)*) => { $(
cast! { $Fixed($LeEqU); $Num }
cast! { $Num; $Fixed($LeEqU) }
)* };
($($Fixed:ident($LeEqU:ident),)*) => { $(
cast_num! {
$Fixed($LeEqU);
FixedI8(LeEqU8), FixedI16(LeEqU16), FixedI32(LeEqU32), FixedI64(LeEqU64),
FixedI128(LeEqU128),
FixedU8(LeEqU8), FixedU16(LeEqU16), FixedU32(LeEqU32), FixedU64(LeEqU64),
FixedU128(LeEqU128),
}
cast! { bool; $Fixed($LeEqU) }
cast_num! {
$Fixed($LeEqU);
i8, i16, i32, i64, i128, isize,
u8, u16, u32, u64, u128, usize,
half_f16, half_bf16, f32, f64, F128, F128Bits,
}
#[cfg(feature = "nightly-float")]
cast_num! { $Fixed($LeEqU); f16, f128, }
)* };
}
cast_num! {
FixedI8(LeEqU8), FixedI16(LeEqU16), FixedI32(LeEqU32), FixedI64(LeEqU64), FixedI128(LeEqU128),
FixedU8(LeEqU8), FixedU16(LeEqU16), FixedU32(LeEqU32), FixedU64(LeEqU64), FixedU128(LeEqU128),
}
+695
View File
@@ -0,0 +1,695 @@
// Copyright © 2018–2025 Trevor Spiteri
// This library is free software: you can redistribute it and/or
// modify it under the terms of either
//
// * the Apache License, Version 2.0 or
// * the MIT License
//
// at your option.
//
// You should have recieved copies of the Apache License and the MIT
// License along with the library. If not, see
// <https://www.apache.org/licenses/LICENSE-2.0> and
// <https://opensource.org/licenses/MIT>.
#![allow(deprecated)]
use crate::float_helper;
use crate::int_helper;
use crate::int_helper::IntFixed;
use crate::types::extra::Unsigned;
use crate::{
F128, F128Bits, FixedI8, FixedI16, FixedI32, FixedI64, FixedI128, FixedU8, FixedU16, FixedU32,
FixedU64, FixedU128,
};
use core::cmp::Ordering;
use core::ops::{Shl, Shr};
use half::{bf16 as half_bf16, f16 as half_f16};
macro_rules! fixed_cmp_int {
($Fixed:ident, $Int:ident) => {
impl<Frac: Unsigned> PartialEq<$Int> for $Fixed<Frac> {
#[inline]
fn eq(&self, rhs: &$Int) -> bool {
let fixed_rhs = IntFixed(*rhs).fixed();
PartialEq::eq(self, &fixed_rhs)
}
}
impl<Frac: Unsigned> PartialEq<$Fixed<Frac>> for $Int {
#[inline]
fn eq(&self, rhs: &$Fixed<Frac>) -> bool {
let fixed_lhs = IntFixed(*self).fixed();
PartialEq::eq(&fixed_lhs, rhs)
}
}
impl<Frac: Unsigned> PartialOrd<$Int> for $Fixed<Frac> {
#[inline]
fn partial_cmp(&self, rhs: &$Int) -> Option<Ordering> {
let fixed_rhs = IntFixed(*rhs).fixed();
PartialOrd::partial_cmp(self, &fixed_rhs)
}
#[inline]
fn lt(&self, rhs: &$Int) -> bool {
let fixed_rhs = IntFixed(*rhs).fixed();
PartialOrd::lt(self, &fixed_rhs)
}
#[inline]
fn le(&self, rhs: &$Int) -> bool {
let fixed_rhs = IntFixed(*rhs).fixed();
PartialOrd::le(self, &fixed_rhs)
}
#[inline]
fn gt(&self, rhs: &$Int) -> bool {
let fixed_rhs = IntFixed(*rhs).fixed();
PartialOrd::gt(self, &fixed_rhs)
}
#[inline]
fn ge(&self, rhs: &$Int) -> bool {
let fixed_rhs = IntFixed(*rhs).fixed();
PartialOrd::ge(self, &fixed_rhs)
}
}
impl<Frac: Unsigned> PartialOrd<$Fixed<Frac>> for $Int {
#[inline]
fn partial_cmp(&self, rhs: &$Fixed<Frac>) -> Option<Ordering> {
let fixed_lhs = IntFixed(*self).fixed();
PartialOrd::partial_cmp(&fixed_lhs, rhs)
}
#[inline]
fn lt(&self, rhs: &$Fixed<Frac>) -> bool {
let fixed_lhs = IntFixed(*self).fixed();
PartialOrd::lt(&fixed_lhs, rhs)
}
#[inline]
fn le(&self, rhs: &$Fixed<Frac>) -> bool {
let fixed_lhs = IntFixed(*self).fixed();
PartialOrd::le(&fixed_lhs, rhs)
}
#[inline]
fn gt(&self, rhs: &$Fixed<Frac>) -> bool {
let fixed_lhs = IntFixed(*self).fixed();
PartialOrd::gt(&fixed_lhs, rhs)
}
#[inline]
fn ge(&self, rhs: &$Fixed<Frac>) -> bool {
let fixed_lhs = IntFixed(*self).fixed();
PartialOrd::ge(&fixed_lhs, rhs)
}
}
};
}
// Zero must NOT be neg
struct Value<U> {
neg: bool,
abs: U,
bits: u32,
frac_bits: i32,
}
#[inline]
// lhs_frac >= rhs_frac
fn float_rhs_shl<U>(rhs_abs: U, bits: u32, lhs_frac: i32, rhs_frac: i32) -> Option<U>
where
U: Copy + Eq + TryFrom<u32> + Shl<u32, Output = U> + Shr<u32, Output = U>,
{
debug_assert!(lhs_frac >= rhs_frac);
let rhs_shl = lhs_frac.wrapping_sub(rhs_frac) as u32;
let Ok(rhs_zero) = U::try_from(0u32) else {
unreachable!();
};
if rhs_abs == rhs_zero {
Some(rhs_zero)
} else if rhs_shl >= bits {
None
} else {
let shifted = rhs_abs << rhs_shl;
if (shifted >> rhs_shl) == rhs_abs {
Some(shifted)
} else {
None
}
}
}
#[inline]
fn float_eq_even<U>(lhs: Value<U>, rhs: Value<U>) -> bool
where
U: Copy + Eq + TryFrom<u32> + Shl<u32, Output = U> + Shr<u32, Output = U>,
{
if lhs.frac_bits < rhs.frac_bits {
return float_eq_even(rhs, lhs);
}
if lhs.neg != rhs.neg {
return false;
}
// lhs.frac_bits >= rhs.frac_bits
match float_rhs_shl(rhs.abs, rhs.bits, lhs.frac_bits, rhs.frac_bits) {
None => false,
Some(shifted_rhs_abs) => lhs.abs == shifted_rhs_abs,
}
}
#[inline]
fn float_eq<Lhs, Rhs>(lhs: Value<Lhs>, rhs: Value<Rhs>) -> bool
where
Lhs: Copy + Eq + TryFrom<u32> + TryFrom<Rhs> + Shl<u32, Output = Lhs> + Shr<u32, Output = Lhs>,
Rhs: Copy + Eq + TryFrom<u32> + TryFrom<Lhs> + Shl<u32, Output = Rhs> + Shr<u32, Output = Rhs>,
{
if lhs.bits >= rhs.bits {
let Ok(rhs_abs) = Lhs::try_from(rhs.abs) else {
unreachable!();
};
let rhs = Value {
neg: rhs.neg,
abs: rhs_abs,
bits: lhs.bits,
frac_bits: rhs.frac_bits,
};
float_eq_even(lhs, rhs)
} else {
let Ok(lhs_abs) = Rhs::try_from(lhs.abs) else {
unreachable!();
};
let lhs = Value {
neg: lhs.neg,
abs: lhs_abs,
bits: rhs.bits,
frac_bits: lhs.frac_bits,
};
float_eq_even(lhs, rhs)
}
}
#[inline]
fn float_cmp_even<U>(lhs: Value<U>, rhs: Value<U>) -> Ordering
where
U: Copy + Ord + TryFrom<u32> + Shl<u32, Output = U> + Shr<u32, Output = U>,
{
if lhs.frac_bits < rhs.frac_bits {
return float_cmp_even(rhs, lhs).reverse();
}
if !lhs.neg && rhs.neg {
return Ordering::Greater;
}
if lhs.neg && !rhs.neg {
return Ordering::Less;
}
match float_rhs_shl(rhs.abs, rhs.bits, lhs.frac_bits, rhs.frac_bits) {
None => {
// rhs is so large it doesn't fit
if lhs.neg {
// both lhs and rhs are negative, and rhs is even more negative
Ordering::Greater
} else {
Ordering::Less
}
}
Some(shifted_rhs_abs) => {
if lhs.neg {
// both lhs are negative, so reverse order
shifted_rhs_abs.cmp(&lhs.abs)
} else {
lhs.abs.cmp(&shifted_rhs_abs)
}
}
}
}
#[inline]
fn float_cmp<Lhs, Rhs>(lhs: Value<Lhs>, rhs: Value<Rhs>) -> Ordering
where
Lhs: Copy + Ord + TryFrom<u32> + TryFrom<Rhs> + Shl<u32, Output = Lhs> + Shr<u32, Output = Lhs>,
Rhs: Copy + Ord + TryFrom<u32> + TryFrom<Lhs> + Shl<u32, Output = Rhs> + Shr<u32, Output = Rhs>,
{
if lhs.bits >= rhs.bits {
let Ok(rhs_abs) = Lhs::try_from(rhs.abs) else {
unreachable!();
};
let rhs = Value {
neg: rhs.neg,
abs: rhs_abs,
bits: lhs.bits,
frac_bits: rhs.frac_bits,
};
float_cmp_even(lhs, rhs)
} else {
let Ok(lhs_abs) = Rhs::try_from(lhs.abs) else {
unreachable!();
};
let lhs = Value {
neg: lhs.neg,
abs: lhs_abs,
bits: rhs.bits,
frac_bits: lhs.frac_bits,
};
float_cmp_even(lhs, rhs)
}
}
macro_rules! fixed_cmp_float {
($Fix:ident($Inner:ident), $Float:ident, $FloatBits:ident) => {
impl<Frac: Unsigned> PartialEq<$Float> for $Fix<Frac> {
#[inline]
fn eq(&self, rhs: &$Float) -> bool {
use float_helper::$Float::Kind;
let (lhs_neg, lhs_abs) = int_helper::$Inner::neg_abs(self.to_bits());
let lhs = Value {
neg: lhs_neg,
abs: lhs_abs,
bits: $Inner::BITS,
frac_bits: Frac::to_i32(),
};
let Kind::Finite {
neg: rhs_neg,
abs: rhs_abs,
frac_bits: rhs_frac,
} = float_helper::$Float::kind(*rhs)
else {
return false;
};
let rhs = Value {
neg: rhs_neg,
abs: rhs_abs,
bits: $FloatBits::BITS,
frac_bits: rhs_frac,
};
float_eq(lhs, rhs)
}
}
impl<Frac: Unsigned> PartialEq<$Fix<Frac>> for $Float {
#[inline]
fn eq(&self, rhs: &$Fix<Frac>) -> bool {
rhs.eq(self)
}
}
impl<Frac: Unsigned> PartialOrd<$Float> for $Fix<Frac> {
#[inline]
fn partial_cmp(&self, rhs: &$Float) -> Option<Ordering> {
use float_helper::$Float::Kind;
let (lhs_neg, lhs_abs) = int_helper::$Inner::neg_abs(self.to_bits());
let lhs = Value {
neg: lhs_neg,
abs: lhs_abs,
bits: $Inner::BITS,
frac_bits: Frac::to_i32(),
};
let (rhs_neg, rhs_abs, rhs_frac) = match float_helper::$Float::kind(*rhs) {
Kind::Finite {
neg,
abs,
frac_bits,
} => (neg, abs, frac_bits),
Kind::Infinite { neg } => {
return if neg {
Some(Ordering::Greater)
} else {
Some(Ordering::Less)
};
}
Kind::NaN => return None,
};
let rhs = Value {
neg: rhs_neg,
abs: rhs_abs,
bits: $FloatBits::BITS,
frac_bits: rhs_frac,
};
Some(float_cmp(lhs, rhs))
}
}
impl<Frac: Unsigned> PartialOrd<$Fix<Frac>> for $Float {
#[inline]
fn partial_cmp(&self, rhs: &$Fix<Frac>) -> Option<Ordering> {
rhs.partial_cmp(self).map(Ordering::reverse)
}
}
};
}
macro_rules! fixed_cmp_all {
($Fix:ident($LeEqU:ident, $Inner:ident)) => {
fixed_cmp_int! { $Fix, i8 }
fixed_cmp_int! { $Fix, i16 }
fixed_cmp_int! { $Fix, i32 }
fixed_cmp_int! { $Fix, i64 }
fixed_cmp_int! { $Fix, i128 }
fixed_cmp_int! { $Fix, isize }
fixed_cmp_int! { $Fix, u8 }
fixed_cmp_int! { $Fix, u16 }
fixed_cmp_int! { $Fix, u32 }
fixed_cmp_int! { $Fix, u64 }
fixed_cmp_int! { $Fix, u128 }
fixed_cmp_int! { $Fix, usize }
#[cfg(feature = "nightly-float")]
fixed_cmp_float! { $Fix($Inner), f16, u16 }
fixed_cmp_float! { $Fix($Inner), half_f16, u16 }
fixed_cmp_float! { $Fix($Inner), half_bf16, u16 }
fixed_cmp_float! { $Fix($Inner), f32, u32 }
fixed_cmp_float! { $Fix($Inner), f64, u64 }
#[cfg(feature = "nightly-float")]
fixed_cmp_float! { $Fix($Inner), f128, u128 }
fixed_cmp_float! { $Fix($Inner), F128, u128 }
fixed_cmp_float! { $Fix($Inner), F128Bits, u128 }
};
}
fixed_cmp_all! { FixedI8(LeEqU8, i8) }
fixed_cmp_all! { FixedI16(LeEqU16, i16) }
fixed_cmp_all! { FixedI32(LeEqU32, i32) }
fixed_cmp_all! { FixedI64(LeEqU64, i64) }
fixed_cmp_all! { FixedI128(LeEqU128, i128) }
fixed_cmp_all! { FixedU8(LeEqU8, u8) }
fixed_cmp_all! { FixedU16(LeEqU16, u16) }
fixed_cmp_all! { FixedU32(LeEqU32, u32) }
fixed_cmp_all! { FixedU64(LeEqU64, u64) }
fixed_cmp_all! { FixedU128(LeEqU128, u128) }
#[cfg(test)]
mod tests {
use crate::*;
use core::cmp::Ordering;
#[test]
fn cmp_signed() {
use core::cmp::Ordering::*;
let neg1_16 = FixedI32::<types::extra::U16>::NEG_ONE;
let neg1_20 = FixedI32::<types::extra::U20>::NEG_ONE;
let mut a = neg1_16;
let mut b = neg1_20;
// a = ffff.0000 = -1, b = fff.00000 = -1
assert!(a.eq(&b) && b.eq(&a));
assert_eq!(a.partial_cmp(&b), Some(Equal));
assert_eq!(b.partial_cmp(&a), Some(Equal));
assert_eq!(a, -1i8);
assert_eq!(b, -1i128);
a >>= 16;
b >>= 16;
// a = ffff.ffff = -2^-16, b = fff.ffff0 = -2^-16
assert!(a.eq(&b) && b.eq(&a));
assert_eq!(a.partial_cmp(&b), Some(Equal));
assert_eq!(b.partial_cmp(&a), Some(Equal));
assert!(a < 0.0);
assert_eq!(a.partial_cmp(&f32::INFINITY), Some(Less));
assert!(a < f32::INFINITY);
assert!(a != f32::INFINITY);
assert_eq!(a.partial_cmp(&f32::NEG_INFINITY), Some(Greater));
assert!(a > f32::NEG_INFINITY);
assert_eq!(a, -(-16f32).exp2());
assert!(a <= -(-16f32).exp2());
assert!(a >= -(-16f32).exp2());
assert!(a < (-16f32).exp2());
assert_ne!(a, -0.75 * (-16f32).exp2());
assert!(a < -0.75 * (-16f32).exp2());
assert!(a <= -0.75 * (-16f32).exp2());
assert!(a > -1.25 * (-16f32).exp2());
assert!(a >= -1.25 * (-16f32).exp2());
a >>= 1;
b >>= 1;
// a = ffff.ffff = -2^-16, b = fff.ffff8 = -2^-17
assert!(a.ne(&b) && b.ne(&a));
assert_eq!(a.partial_cmp(&b), Some(Less));
assert_eq!(b.partial_cmp(&a), Some(Greater));
a = neg1_16 << 11;
b = neg1_20 << 11;
// a = f800.0000 = -2^11, b = 800.00000 = -2^11
assert!(a.eq(&b) && b.eq(&a));
assert_eq!(a.partial_cmp(&b), Some(Equal));
assert_eq!(b.partial_cmp(&a), Some(Equal));
assert_eq!(a, -1i16 << 11);
assert_eq!(b, -1i64 << 11);
a <<= 1;
b <<= 1;
// a = f000.0000 = -2^-12, b = 000.00000 = 0
assert!(a.ne(&b) && b.ne(&a));
assert_eq!(a.partial_cmp(&b), Some(Less));
assert_eq!(b.partial_cmp(&a), Some(Greater));
assert!(a < 1u8);
assert_eq!(b, 0);
}
#[test]
fn cmp_unsigned() {
use core::cmp::Ordering::*;
let one_16 = FixedU32::<types::extra::U16>::ONE;
let one_20 = FixedU32::<types::extra::U20>::ONE;
let mut a = one_16;
let mut b = one_20;
// a = 0001.0000 = 1, b = 001.00000 = 1
assert!(a.eq(&b) && b.eq(&a));
assert_eq!(a.partial_cmp(&b), Some(Equal));
assert_eq!(b.partial_cmp(&a), Some(Equal));
assert_eq!(a, 1u8);
assert_eq!(b, 1i128);
a >>= 16;
b >>= 16;
// a = 0000.0001 = 2^-16, b = 000.00010 = 2^-16
assert!(a.eq(&b) && b.eq(&a));
assert_eq!(a.partial_cmp(&b), Some(Equal));
assert_eq!(b.partial_cmp(&a), Some(Equal));
assert!(a > 0.0);
assert_eq!(a.partial_cmp(&f32::INFINITY), Some(Less));
assert!(a < f32::INFINITY);
assert!(a != f32::INFINITY);
assert_eq!(a.partial_cmp(&f32::NEG_INFINITY), Some(Greater));
assert!(a > f32::NEG_INFINITY);
assert_eq!(a, (-16f64).exp2());
assert!(a <= (-16f64).exp2());
assert!(a >= (-16f64).exp2());
assert!(a > -(-16f64).exp2());
assert_ne!(a, 0.75 * (-16f64).exp2());
assert!(a > 0.75 * (-16f64).exp2());
assert!(a >= 0.75 * (-16f64).exp2());
assert!(a < 1.25 * (-16f64).exp2());
assert!(a <= 1.25 * (-16f64).exp2());
a >>= 1;
b >>= 1;
// a = 0000.0000 = 0, b = 000.00008 = 2^-17
assert!(a.ne(&b) && b.ne(&a));
assert_eq!(a.partial_cmp(&b), Some(Less));
assert_eq!(b.partial_cmp(&a), Some(Greater));
a = one_16 << 11;
b = one_20 << 11;
// a = 0800.0000 = 2^11, b = 800.00000 = 2^11
assert!(a.eq(&b) && b.eq(&a));
assert_eq!(a.partial_cmp(&b), Some(Equal));
assert_eq!(b.partial_cmp(&a), Some(Equal));
assert_eq!(a, 1i16 << 11);
assert_eq!(b, 1u64 << 11);
a <<= 1;
b <<= 1;
// a = 1000.0000 = 2^12, b = 000.00000 = 0
assert!(a.ne(&b) && b.ne(&a));
assert_eq!(a.partial_cmp(&b), Some(Greater));
assert_eq!(b.partial_cmp(&a), Some(Less));
assert!(a > -1i8);
assert_eq!(a, 1i32 << 12);
assert_eq!(b, 0);
}
#[test]
fn cmp_i0() {
use crate::types::*;
assert_eq!(I0F32::checked_from_num(0.5), None);
for &float in &[
-0.5,
-0.5 + f32::EPSILON,
-0.25,
-f32::EPSILON,
0.0,
f32::EPSILON,
0.25,
0.5 - f32::EPSILON,
] {
let fixed = I0F32::from_num(float);
let half = U0F32::from_num(0.5);
assert_eq!(fixed < half, float < 0.5, "{fixed} < {half}");
assert_eq!(fixed <= half, float <= 0.5, "{fixed} <= {half}");
assert_eq!(fixed == half, float == 0.5, "{fixed} == {half}");
assert_eq!(fixed >= half, float >= 0.5, "{fixed} >= {half}");
assert_eq!(fixed > half, float > 0.5, "{fixed} > {half}");
assert_eq!(
fixed.partial_cmp(&half),
float.partial_cmp(&0.5),
"{fixed}.partial_cmp(&{half})"
);
assert_eq!(half < fixed, fixed > half);
assert_eq!(half <= fixed, fixed >= half);
assert_eq!(half == fixed, fixed == half);
assert_eq!(half >= fixed, fixed <= half);
assert_eq!(half > fixed, fixed < half);
assert_eq!(
half.partial_cmp(&fixed),
fixed.partial_cmp(&half).map(Ordering::reverse)
);
let half = I1F31::from_num(0.5);
assert_eq!(fixed < half, float < 0.5, "{fixed} < {half}");
assert_eq!(fixed <= half, float <= 0.5, "{fixed} <= {half}");
assert_eq!(fixed == half, float == 0.5, "{fixed} == {half}");
assert_eq!(fixed >= half, float >= 0.5, "{fixed} >= {half}");
assert_eq!(fixed > half, float > 0.5, "{fixed} > {half}");
assert_eq!(
fixed.partial_cmp(&half),
float.partial_cmp(&0.5),
"{fixed}.partial_cmp(&{half})"
);
assert_eq!(half < fixed, fixed > half);
assert_eq!(half <= fixed, fixed >= half);
assert_eq!(half == fixed, fixed == half);
assert_eq!(half >= fixed, fixed <= half);
assert_eq!(half > fixed, fixed < half);
assert_eq!(
half.partial_cmp(&fixed),
fixed.partial_cmp(&half).map(Ordering::reverse)
);
let half = 0.5f32;
assert_eq!(fixed < half, float < 0.5, "{fixed} < {half}");
assert_eq!(fixed <= half, float <= 0.5, "{fixed} <= {half}");
assert_eq!(fixed == half, float == 0.5, "{fixed} == {half}");
assert_eq!(fixed >= half, float >= 0.5, "{fixed} >= {half}");
assert_eq!(fixed > half, float > 0.5, "{fixed} > {half}");
assert_eq!(
fixed.partial_cmp(&half),
float.partial_cmp(&0.5),
"{fixed}.partial_cmp(&{half})"
);
assert_eq!(half < fixed, fixed > half);
assert_eq!(half <= fixed, fixed >= half);
assert_eq!(half == fixed, fixed == half);
assert_eq!(half >= fixed, fixed <= half);
assert_eq!(half > fixed, fixed < half);
assert_eq!(
half.partial_cmp(&fixed),
fixed.partial_cmp(&half).map(Ordering::reverse)
);
let m1 = I32F0::from_num(-1.0);
assert_eq!(fixed < m1, float < -1.0, "{fixed} < {m1}");
assert_eq!(fixed <= m1, float <= -1.0, "{fixed} <= {m1}");
assert_eq!(fixed == m1, float == -1.0, "{fixed} == {m1}");
assert_eq!(fixed >= m1, float >= -1.0, "{fixed} >= {m1}");
assert_eq!(fixed > m1, float > -1.0, "{fixed} > {m1}");
assert_eq!(
fixed.partial_cmp(&m1),
float.partial_cmp(&-1.0),
"{fixed}.partial_cmp(&{m1})"
);
assert_eq!(m1 < fixed, fixed > m1);
assert_eq!(m1 <= fixed, fixed >= m1);
assert_eq!(m1 == fixed, fixed == m1);
assert_eq!(m1 >= fixed, fixed <= m1);
assert_eq!(m1 > fixed, fixed < m1);
assert_eq!(
m1.partial_cmp(&fixed),
fixed.partial_cmp(&m1).map(Ordering::reverse)
);
let m1 = I1F31::from_num(-1.0);
assert_eq!(fixed < m1, float < -1.0, "{fixed} < {m1}");
assert_eq!(fixed <= m1, float <= -1.0, "{fixed} <= {m1}");
assert_eq!(fixed == m1, float == -1.0, "{fixed} == {m1}");
assert_eq!(fixed >= m1, float >= -1.0, "{fixed} >= {m1}");
assert_eq!(fixed > m1, float > -1.0, "{fixed} > {m1}");
assert_eq!(
fixed.partial_cmp(&m1),
float.partial_cmp(&-1.0),
"{fixed}.partial_cmp(&{m1})"
);
assert_eq!(m1 < fixed, fixed > m1);
assert_eq!(m1 <= fixed, fixed >= m1);
assert_eq!(m1 == fixed, fixed == m1);
assert_eq!(m1 >= fixed, fixed <= m1);
assert_eq!(m1 > fixed, fixed < m1);
assert_eq!(
m1.partial_cmp(&fixed),
fixed.partial_cmp(&m1).map(Ordering::reverse)
);
let m1 = -1.0f32;
assert_eq!(fixed < m1, float < -1.0, "{fixed} < {m1}");
assert_eq!(fixed <= m1, float <= -1.0, "{fixed} <= {m1}");
assert_eq!(fixed == m1, float == -1.0, "{fixed} == {m1}");
assert_eq!(fixed >= m1, float >= -1.0, "{fixed} >= {m1}");
assert_eq!(fixed > m1, float > -1.0, "{fixed} > {m1}");
assert_eq!(
fixed.partial_cmp(&m1),
float.partial_cmp(&-1.0),
"{fixed}.partial_cmp(&{m1})"
);
assert_eq!(m1 < fixed, fixed > m1);
assert_eq!(m1 <= fixed, fixed >= m1);
assert_eq!(m1 == fixed, fixed == m1);
assert_eq!(m1 >= fixed, fixed <= m1);
assert_eq!(m1 > fixed, fixed < m1);
assert_eq!(
m1.partial_cmp(&fixed),
fixed.partial_cmp(&m1).map(Ordering::reverse)
);
let mhalf = I1F31::from_num(-0.5);
assert_eq!(fixed < mhalf, float < -0.5, "{fixed} < {mhalf}");
assert_eq!(fixed <= mhalf, float <= -0.5, "{fixed} <= {mhalf}");
assert_eq!(fixed == mhalf, float == -0.5, "{fixed} == {mhalf}");
assert_eq!(fixed >= mhalf, float >= -0.5, "{fixed} >= {mhalf}");
assert_eq!(fixed > mhalf, float > -0.5, "{fixed} > {mhalf}");
assert_eq!(
fixed.partial_cmp(&mhalf),
float.partial_cmp(&-0.5),
"{fixed}.partial_cmp(&{mhalf})"
);
assert_eq!(mhalf < fixed, fixed > mhalf);
assert_eq!(mhalf <= fixed, fixed >= mhalf);
assert_eq!(mhalf == fixed, fixed == mhalf);
assert_eq!(mhalf >= fixed, fixed <= mhalf);
assert_eq!(mhalf > fixed, fixed < mhalf);
assert_eq!(
mhalf.partial_cmp(&fixed),
fixed.partial_cmp(&mhalf).map(Ordering::reverse)
);
let mhalf = -0.5f32;
assert_eq!(fixed < mhalf, float < -0.5, "{fixed} < {mhalf}");
assert_eq!(fixed <= mhalf, float <= -0.5, "{fixed} <= {mhalf}");
assert_eq!(fixed == mhalf, float == -0.5, "{fixed} == {mhalf}");
assert_eq!(fixed >= mhalf, float >= -0.5, "{fixed} >= {mhalf}");
assert_eq!(fixed > mhalf, float > -0.5, "{fixed} > {mhalf}");
assert_eq!(
fixed.partial_cmp(&mhalf),
float.partial_cmp(&-0.5),
"{fixed}.partial_cmp(&{mhalf})"
);
assert_eq!(mhalf < fixed, fixed > mhalf);
assert_eq!(mhalf <= fixed, fixed >= mhalf);
assert_eq!(mhalf == fixed, fixed == mhalf);
assert_eq!(mhalf >= fixed, fixed <= mhalf);
assert_eq!(mhalf > fixed, fixed < mhalf);
assert_eq!(
mhalf.partial_cmp(&fixed),
fixed.partial_cmp(&mhalf).map(Ordering::reverse)
);
}
}
}
+498
View File
@@ -0,0 +1,498 @@
// Copyright © 2018–2025 Trevor Spiteri
// This library is free software: you can redistribute it and/or
// modify it under the terms of either
//
// * the Apache License, Version 2.0 or
// * the MIT License
//
// at your option.
//
// You should have recieved copies of the Apache License and the MIT
// License along with the library. If not, see
// <https://www.apache.org/licenses/LICENSE-2.0> and
// <https://opensource.org/licenses/MIT>.
use crate::types::extra::Unsigned;
use crate::{
FixedI8, FixedI16, FixedI32, FixedI64, FixedI128, FixedU8, FixedU16, FixedU32, FixedU64,
FixedU128,
};
use core::cmp::Ordering;
// Works by converting the signed number to unsigned, but does not change size
// of either number.
macro_rules! diff_sign {
($Sig:ident($Uns:ident, $UnsInner:ident), $OtherUns:ident) => {
impl<FracLhs: Unsigned, FracRhs: Unsigned> PartialEq<$Sig<FracRhs>> for $OtherUns<FracLhs> {
#[inline]
fn eq(&self, rhs: &$Sig<FracRhs>) -> bool {
if rhs.is_negative() {
return false;
}
let unsigned_rhs = $Uns::<FracRhs>::from_bits(rhs.to_bits() as $UnsInner);
PartialEq::eq(self, &unsigned_rhs)
}
}
impl<FracLhs: Unsigned, FracRhs: Unsigned> PartialEq<$OtherUns<FracRhs>> for $Sig<FracLhs> {
#[inline]
fn eq(&self, rhs: &$OtherUns<FracRhs>) -> bool {
if self.is_negative() {
return false;
}
let unsigned_lhs = $Uns::<FracLhs>::from_bits(self.to_bits() as $UnsInner);
PartialEq::eq(&unsigned_lhs, rhs)
}
}
impl<FracLhs: Unsigned, FracRhs: Unsigned> PartialOrd<$Sig<FracRhs>>
for $OtherUns<FracLhs>
{
#[inline]
fn partial_cmp(&self, rhs: &$Sig<FracRhs>) -> Option<Ordering> {
if rhs.is_negative() {
return Some(Ordering::Greater);
}
let unsigned_rhs = $Uns::<FracRhs>::from_bits(rhs.to_bits() as $UnsInner);
PartialOrd::partial_cmp(self, &unsigned_rhs)
}
#[inline]
fn lt(&self, rhs: &$Sig<FracRhs>) -> bool {
if rhs.is_negative() {
return false;
}
let unsigned_rhs = $Uns::<FracRhs>::from_bits(rhs.to_bits() as $UnsInner);
PartialOrd::lt(self, &unsigned_rhs)
}
#[inline]
fn le(&self, rhs: &$Sig<FracRhs>) -> bool {
if rhs.is_negative() {
return false;
}
let unsigned_rhs = $Uns::<FracRhs>::from_bits(rhs.to_bits() as $UnsInner);
PartialOrd::le(self, &unsigned_rhs)
}
#[inline]
fn gt(&self, rhs: &$Sig<FracRhs>) -> bool {
if rhs.is_negative() {
return true;
}
let unsigned_rhs = $Uns::<FracRhs>::from_bits(rhs.to_bits() as $UnsInner);
PartialOrd::gt(self, &unsigned_rhs)
}
#[inline]
fn ge(&self, rhs: &$Sig<FracRhs>) -> bool {
if rhs.is_negative() {
return true;
}
let unsigned_rhs = $Uns::<FracRhs>::from_bits(rhs.to_bits() as $UnsInner);
PartialOrd::ge(self, &unsigned_rhs)
}
}
impl<FracLhs: Unsigned, FracRhs: Unsigned> PartialOrd<$OtherUns<FracRhs>>
for $Sig<FracLhs>
{
#[inline]
fn partial_cmp(&self, rhs: &$OtherUns<FracRhs>) -> Option<Ordering> {
if self.is_negative() {
return Some(Ordering::Less);
}
let unsigned_lhs = $Uns::<FracLhs>::from_bits(self.to_bits() as $UnsInner);
PartialOrd::partial_cmp(&unsigned_lhs, rhs)
}
#[inline]
fn lt(&self, rhs: &$OtherUns<FracRhs>) -> bool {
if self.is_negative() {
return true;
}
let unsigned_lhs = $Uns::<FracLhs>::from_bits(self.to_bits() as $UnsInner);
PartialOrd::lt(&unsigned_lhs, rhs)
}
#[inline]
fn le(&self, rhs: &$OtherUns<FracRhs>) -> bool {
if self.is_negative() {
return true;
}
let unsigned_lhs = $Uns::<FracLhs>::from_bits(self.to_bits() as $UnsInner);
PartialOrd::le(&unsigned_lhs, rhs)
}
#[inline]
fn gt(&self, rhs: &$OtherUns<FracRhs>) -> bool {
if self.is_negative() {
return false;
}
let unsigned_lhs = $Uns::<FracLhs>::from_bits(self.to_bits() as $UnsInner);
PartialOrd::gt(&unsigned_lhs, rhs)
}
#[inline]
fn ge(&self, rhs: &$OtherUns<FracRhs>) -> bool {
if self.is_negative() {
return false;
}
let unsigned_lhs = $Uns::<FracLhs>::from_bits(self.to_bits() as $UnsInner);
PartialOrd::ge(&unsigned_lhs, rhs)
}
}
};
($Sig:ident($Uns:ident, $UnsInner:ident)) => {
diff_sign! { $Sig($Uns, $UnsInner), FixedU8 }
diff_sign! { $Sig($Uns, $UnsInner), FixedU16 }
diff_sign! { $Sig($Uns, $UnsInner), FixedU32 }
diff_sign! { $Sig($Uns, $UnsInner), FixedU64 }
diff_sign! { $Sig($Uns, $UnsInner), FixedU128 }
};
}
diff_sign! { FixedI8(FixedU8, u8) }
diff_sign! { FixedI16(FixedU16, u16) }
diff_sign! { FixedI32(FixedU32, u32) }
diff_sign! { FixedI64(FixedU64, u64) }
diff_sign! { FixedI128(FixedU128, u128) }
// Both numbers must have the same sign (both signed or both unsigned). Works by
// widening the narrow number to have the same width as the wide number.
macro_rules! diff_size {
($Nar:ident, $Wid:ident($WidInner:ident)) => {
impl<FracLhs: Unsigned, FracRhs: Unsigned> PartialEq<$Nar<FracRhs>> for $Wid<FracLhs> {
#[inline]
fn eq(&self, rhs: &$Nar<FracRhs>) -> bool {
let widened_rhs = $Wid::<FracRhs>::from_bits(rhs.to_bits() as $WidInner);
PartialEq::eq(self, &widened_rhs)
}
}
impl<FracLhs: Unsigned, FracRhs: Unsigned> PartialEq<$Wid<FracRhs>> for $Nar<FracLhs> {
#[inline]
fn eq(&self, rhs: &$Wid<FracRhs>) -> bool {
let widened_lhs = $Wid::<FracLhs>::from_bits(self.to_bits() as $WidInner);
PartialEq::eq(&widened_lhs, rhs)
}
}
impl<FracLhs: Unsigned, FracRhs: Unsigned> PartialOrd<$Nar<FracRhs>> for $Wid<FracLhs> {
#[inline]
fn partial_cmp(&self, rhs: &$Nar<FracRhs>) -> Option<Ordering> {
let widened_rhs = $Wid::<FracRhs>::from_bits(rhs.to_bits() as $WidInner);
PartialOrd::partial_cmp(self, &widened_rhs)
}
#[inline]
fn lt(&self, rhs: &$Nar<FracRhs>) -> bool {
let widened_rhs = $Wid::<FracRhs>::from_bits(rhs.to_bits() as $WidInner);
PartialOrd::lt(self, &widened_rhs)
}
#[inline]
fn le(&self, rhs: &$Nar<FracRhs>) -> bool {
let widened_rhs = $Wid::<FracRhs>::from_bits(rhs.to_bits() as $WidInner);
PartialOrd::le(self, &widened_rhs)
}
#[inline]
fn gt(&self, rhs: &$Nar<FracRhs>) -> bool {
let widened_rhs = $Wid::<FracRhs>::from_bits(rhs.to_bits() as $WidInner);
PartialOrd::gt(self, &widened_rhs)
}
#[inline]
fn ge(&self, rhs: &$Nar<FracRhs>) -> bool {
let widened_rhs = $Wid::<FracRhs>::from_bits(rhs.to_bits() as $WidInner);
PartialOrd::ge(self, &widened_rhs)
}
}
impl<FracLhs: Unsigned, FracRhs: Unsigned> PartialOrd<$Wid<FracRhs>> for $Nar<FracLhs> {
#[inline]
fn partial_cmp(&self, rhs: &$Wid<FracRhs>) -> Option<Ordering> {
let widened_lhs = $Wid::<FracLhs>::from_bits(self.to_bits() as $WidInner);
PartialOrd::partial_cmp(&widened_lhs, rhs)
}
#[inline]
fn lt(&self, rhs: &$Wid<FracRhs>) -> bool {
let widened_lhs = $Wid::<FracLhs>::from_bits(self.to_bits() as $WidInner);
PartialOrd::lt(&widened_lhs, rhs)
}
#[inline]
fn le(&self, rhs: &$Wid<FracRhs>) -> bool {
let widened_lhs = $Wid::<FracLhs>::from_bits(self.to_bits() as $WidInner);
PartialOrd::le(&widened_lhs, rhs)
}
#[inline]
fn gt(&self, rhs: &$Wid<FracRhs>) -> bool {
let widened_lhs = $Wid::<FracLhs>::from_bits(self.to_bits() as $WidInner);
PartialOrd::gt(&widened_lhs, rhs)
}
#[inline]
fn ge(&self, rhs: &$Wid<FracRhs>) -> bool {
let widened_lhs = $Wid::<FracLhs>::from_bits(self.to_bits() as $WidInner);
PartialOrd::ge(&widened_lhs, rhs)
}
}
};
}
diff_size! { FixedI8, FixedI16(i16) }
diff_size! { FixedI8, FixedI32(i32) }
diff_size! { FixedI8, FixedI64(i64) }
diff_size! { FixedI8, FixedI128(i128) }
diff_size! { FixedI16, FixedI32(i32) }
diff_size! { FixedI16, FixedI64(i64) }
diff_size! { FixedI16, FixedI128(i128) }
diff_size! { FixedI32, FixedI64(i64) }
diff_size! { FixedI32, FixedI128(i128) }
diff_size! { FixedI64, FixedI128(i128) }
diff_size! { FixedU8, FixedU16(u16) }
diff_size! { FixedU8, FixedU32(u32) }
diff_size! { FixedU8, FixedU64(u64) }
diff_size! { FixedU8, FixedU128(u128) }
diff_size! { FixedU16, FixedU32(u32) }
diff_size! { FixedU16, FixedU64(u64) }
diff_size! { FixedU16, FixedU128(u128) }
diff_size! { FixedU32, FixedU64(u64) }
diff_size! { FixedU32, FixedU128(u128) }
diff_size! { FixedU64, FixedU128(u128) }
macro_rules! cmp {
($Fixed:ident($Inner:ident)) => {
impl<FracLhs: Unsigned, FracRhs: Unsigned> PartialEq<$Fixed<FracRhs>> for $Fixed<FracLhs> {
#[inline]
fn eq(&self, rhs: &$Fixed<FracRhs>) -> bool {
let lhs = self.to_bits();
let rhs = rhs.to_bits();
// lhs_extra_frac and nbits are known exactly at compile time,
// so with optimizations the branch is selected at compile time
let lhs_extra_frac = FracLhs::to_i32().saturating_sub(FracRhs::to_i32());
let nbits = $Inner::BITS as i32;
if lhs_extra_frac <= -nbits {
let shifted_rhs = rhs >> (nbits - 1) >> 1;
let rhs_is_reduced = rhs != 0;
lhs == shifted_rhs && !rhs_is_reduced
} else if lhs_extra_frac < 0 {
let shifted_rhs = rhs >> -lhs_extra_frac;
let rhs_is_reduced = rhs != (shifted_rhs << -lhs_extra_frac);
lhs == shifted_rhs && !rhs_is_reduced
} else if lhs_extra_frac == 0 {
lhs == rhs
} else if lhs_extra_frac < nbits {
let shifted_lhs = lhs >> lhs_extra_frac;
let lhs_is_reduced = lhs != (shifted_lhs << lhs_extra_frac);
shifted_lhs == rhs && !lhs_is_reduced
} else {
let shifted_lhs = lhs >> (nbits - 1) >> 1;
let lhs_is_reduced = lhs != 0;
shifted_lhs == rhs && !lhs_is_reduced
}
}
}
impl<FracLhs: Unsigned, FracRhs: Unsigned> PartialOrd<$Fixed<FracRhs>> for $Fixed<FracLhs> {
#[inline]
fn partial_cmp(&self, rhs: &$Fixed<FracRhs>) -> Option<Ordering> {
let lhs = self.to_bits();
let rhs = rhs.to_bits();
// lhs_extra_frac and nbits are known exactly at compile time,
// so with optimizations the branch is selected at compile time
let lhs_extra_frac = FracLhs::to_i32().saturating_sub(FracRhs::to_i32());
let nbits = $Inner::BITS as i32;
if lhs_extra_frac <= -nbits {
let shifted_rhs = rhs >> (nbits - 1) >> 1;
let rhs_is_reduced = rhs != 0;
if lhs == shifted_rhs && rhs_is_reduced {
Some(Ordering::Less)
} else {
Some(Ord::cmp(&lhs, &shifted_rhs))
}
} else if lhs_extra_frac < 0 {
let shifted_rhs = rhs >> -lhs_extra_frac;
let rhs_is_reduced = rhs != (shifted_rhs << -lhs_extra_frac);
if lhs == shifted_rhs && rhs_is_reduced {
Some(Ordering::Less)
} else {
Some(Ord::cmp(&lhs, &shifted_rhs))
}
} else if lhs_extra_frac == 0 {
Some(Ord::cmp(&lhs, &rhs))
} else if lhs_extra_frac < nbits {
let shifted_lhs = lhs >> lhs_extra_frac;
let lhs_is_reduced = lhs != (shifted_lhs << lhs_extra_frac);
if shifted_lhs == rhs && lhs_is_reduced {
Some(Ordering::Greater)
} else {
Some(Ord::cmp(&shifted_lhs, &rhs))
}
} else {
let shifted_lhs = lhs >> (nbits - 1) >> 1;
let lhs_is_reduced = lhs != 0;
if shifted_lhs == rhs && lhs_is_reduced {
Some(Ordering::Greater)
} else {
Some(Ord::cmp(&shifted_lhs, &rhs))
}
}
}
#[inline]
fn lt(&self, rhs: &$Fixed<FracRhs>) -> bool {
let lhs = self.to_bits();
let rhs = rhs.to_bits();
// lhs_extra_frac and nbits are known exactly at compile time,
// so with optimizations the branch is selected at compile time
let lhs_extra_frac = FracLhs::to_i32().saturating_sub(FracRhs::to_i32());
let nbits = $Inner::BITS as i32;
if lhs_extra_frac <= -nbits {
let shifted_rhs = rhs >> (nbits - 1) >> 1;
let rhs_is_reduced = rhs != 0;
(lhs == shifted_rhs && rhs_is_reduced) || lhs < shifted_rhs
} else if lhs_extra_frac < 0 {
let shifted_rhs = rhs >> -lhs_extra_frac;
let rhs_is_reduced = rhs != (shifted_rhs << -lhs_extra_frac);
(lhs == shifted_rhs && rhs_is_reduced) || lhs < shifted_rhs
} else if lhs_extra_frac == 0 {
lhs < rhs
} else if lhs_extra_frac < nbits {
let shifted_lhs = lhs >> lhs_extra_frac;
shifted_lhs < rhs
} else {
let shifted_lhs = lhs >> (nbits - 1) >> 1;
shifted_lhs < rhs
}
}
#[inline]
fn le(&self, rhs: &$Fixed<FracRhs>) -> bool {
let lhs = self.to_bits();
let rhs = rhs.to_bits();
// lhs_extra_frac and nbits are known exactly at compile time,
// so with optimizations the branch is selected at compile time
let lhs_extra_frac = FracLhs::to_i32().saturating_sub(FracRhs::to_i32());
let nbits = $Inner::BITS as i32;
if lhs_extra_frac <= -nbits {
let shifted_rhs = rhs >> (nbits - 1) >> 1;
lhs <= shifted_rhs
} else if lhs_extra_frac < 0 {
let shifted_rhs = rhs >> -lhs_extra_frac;
lhs <= shifted_rhs
} else if lhs_extra_frac == 0 {
lhs <= rhs
} else if lhs_extra_frac < nbits {
let shifted_lhs = lhs >> lhs_extra_frac;
let lhs_is_reduced = lhs != (shifted_lhs << lhs_extra_frac);
!(shifted_lhs == rhs && lhs_is_reduced) && shifted_lhs <= rhs
} else {
let shifted_lhs = lhs >> (nbits - 1) >> 1;
let lhs_is_reduced = lhs != 0;
!(shifted_lhs == rhs && lhs_is_reduced) && shifted_lhs <= rhs
}
}
#[inline]
fn gt(&self, rhs: &$Fixed<FracRhs>) -> bool {
let lhs = self.to_bits();
let rhs = rhs.to_bits();
// lhs_extra_frac and nbits are known exactly at compile time,
// so with optimizations the branch is selected at compile time
let lhs_extra_frac = FracLhs::to_i32().saturating_sub(FracRhs::to_i32());
let nbits = $Inner::BITS as i32;
if lhs_extra_frac <= -nbits {
let shifted_rhs = rhs >> (nbits - 1) >> 1;
lhs > shifted_rhs
} else if lhs_extra_frac < 0 {
let shifted_rhs = rhs >> -lhs_extra_frac;
lhs > shifted_rhs
} else if lhs_extra_frac == 0 {
lhs > rhs
} else if lhs_extra_frac < nbits {
let shifted_lhs = lhs >> lhs_extra_frac;
let lhs_is_reduced = lhs != (shifted_lhs << lhs_extra_frac);
(shifted_lhs == rhs && lhs_is_reduced) || shifted_lhs > rhs
} else {
let shifted_lhs = lhs >> (nbits - 1) >> 1;
let lhs_is_reduced = lhs != 0;
(shifted_lhs == rhs && lhs_is_reduced) || shifted_lhs > rhs
}
}
#[inline]
fn ge(&self, rhs: &$Fixed<FracRhs>) -> bool {
let lhs = self.to_bits();
let rhs = rhs.to_bits();
// lhs_extra_frac and nbits are known exactly at compile time,
// so with optimizations the branch is selected at compile time
let lhs_extra_frac = FracLhs::to_i32().saturating_sub(FracRhs::to_i32());
let nbits = $Inner::BITS as i32;
if lhs_extra_frac <= -nbits {
let shifted_rhs = rhs >> (nbits - 1) >> 1;
let rhs_is_reduced = rhs != 0;
!(lhs == shifted_rhs && rhs_is_reduced) && lhs >= shifted_rhs
} else if lhs_extra_frac < 0 {
let shifted_rhs = rhs >> -lhs_extra_frac;
let rhs_is_reduced = rhs != (shifted_rhs << -lhs_extra_frac);
!(lhs == shifted_rhs && rhs_is_reduced) && lhs >= shifted_rhs
} else if lhs_extra_frac == 0 {
lhs >= rhs
} else if lhs_extra_frac < nbits {
let shifted_lhs = lhs >> lhs_extra_frac;
shifted_lhs >= rhs
} else {
let shifted_lhs = lhs >> (nbits - 1) >> 1;
shifted_lhs >= rhs
}
}
}
impl<Frac: Unsigned> Eq for $Fixed<Frac> {}
impl<Frac: Unsigned> Ord for $Fixed<Frac> {
#[inline]
fn cmp(&self, rhs: &$Fixed<Frac>) -> Ordering {
let lhs = self.to_bits();
let rhs = rhs.to_bits();
Ord::cmp(&lhs, &rhs)
}
}
};
}
cmp! { FixedI8(i8) }
cmp! { FixedI16(i16) }
cmp! { FixedI32(i32) }
cmp! { FixedI64(i64) }
cmp! { FixedI128(i128) }
cmp! { FixedU8(u8) }
cmp! { FixedU16(u16) }
cmp! { FixedU32(u32) }
cmp! { FixedU64(u64) }
cmp! { FixedU128(u128) }
#[cfg(test)]
mod tests {
#[test]
fn issue_57() {
use crate::types::I80F48;
let a: u64 = 66000;
let b: u64 = 1000;
assert!(I80F48::from(a) > b);
}
}
+606
View File
@@ -0,0 +1,606 @@
// Copyright © 2018–2025 Trevor Spiteri
// This library is free software: you can redistribute it and/or
// modify it under the terms of either
//
// * the Apache License, Version 2.0 or
// * the MIT License
//
// at your option.
//
// You should have recieved copies of the Apache License and the MIT
// License along with the library. If not, see
// <https://www.apache.org/licenses/LICENSE-2.0> and
// <https://opensource.org/licenses/MIT>.
/*!
Mathematical constants.
The constants have the maximum precision possible for a fixed-point
number, and are rounded down at that precision.
# Examples
```rust
use fixed::consts;
use fixed::types::I4F28;
let tau = I4F28::from_num(consts::TAU);
println!("τ = 2π with eight binary places is {tau:.8b}");
assert_eq!(format!("{tau:.8b}"), "110.01001000");
println!("τ = 2π with eight decimal places is {tau:.8}");
assert_eq!(format!("{tau:.8}"), "6.28318531");
```
*/
use crate::types::{U0F128, U1F127, U2F126, U3F125};
/*
```rust
use core::cmp::Ord;
use rug::float::{Constant, Round};
use rug::{Assign, Float};
fn decimal_string(val: &Float, num_digits: usize) -> String {
let (sign, mut digits, exp) = val.to_sign_string_exp_round(10, Some(num_digits), Round::Down);
if let Some(exp) = exp {
if exp <= 0 {
let one_minus_exp = usize::try_from(1 - exp).unwrap();
digits.insert_str(0, &"0".repeat(one_minus_exp));
digits.insert(1, '.');
} else {
let exp = usize::try_from(exp).unwrap();
if exp >= num_digits {
digits.push_str(&"0".repeat(exp - num_digits));
} else {
digits.insert(exp, '.');
}
}
}
if sign {
digits.insert(0, '-');
}
digits
}
fn hex_bits(val: &Float, frac_bits: i32) -> String {
let val = val.clone() << frac_bits;
let int = val.to_integer_round(Round::Down).unwrap().0;
let mut s = format!("0x{int:016X}");
for i in 0..7 {
s.insert(6 + 5 * i, '_');
}
s
}
fn print(doc: &str, name: &str, val: Float) {
println!("/// {} = {}…", doc, decimal_string(&val, 6));
println!("// {} = {}...", name, decimal_string(&val, 40));
let int_bits = val.get_exp().unwrap().max(0);
let frac_bits = 128 - int_bits;
print!("pub const {}: U{}F{} = U{1}F{2}", name, int_bits, frac_bits,);
println!("::from_bits({});", hex_bits(&val, frac_bits));
println!();
}
fn float<T>(t: T) -> Float
where
Float: Assign<T>,
{
Float::with_val(1000, t)
}
fn main() {
print("A turn, τ", "TAU", float(Constant::Pi) * 2);
print("τ/2", "FRAC_TAU_2", float(Constant::Pi));
print("τ/3", "FRAC_TAU_3", float(Constant::Pi) * 2 / 3);
print("τ/4", "FRAC_TAU_4", float(Constant::Pi) / 2);
print("τ/6", "FRAC_TAU_6", float(Constant::Pi) / 3);
print("τ/8", "FRAC_TAU_8", float(Constant::Pi) / 4);
print("τ/12", "FRAC_TAU_12", float(Constant::Pi) / 6);
print("1/τ", "FRAC_1_TAU", 0.5 / float(Constant::Pi));
print("2/τ", "FRAC_2_TAU", 1 / float(Constant::Pi));
print("4/τ", "FRAC_4_TAU", 2 / float(Constant::Pi));
print("Archimedes’ constant, π", "PI", float(Constant::Pi));
print("π/2", "FRAC_PI_2", float(Constant::Pi) / 2);
print("π/3", "FRAC_PI_3", float(Constant::Pi) / 3);
print("π/4", "FRAC_PI_4", float(Constant::Pi) / 4);
print("π/6", "FRAC_PI_6", float(Constant::Pi) / 6);
print("π/8", "FRAC_PI_8", float(Constant::Pi) / 8);
print("1/π", "FRAC_1_PI", 1 / float(Constant::Pi));
print("2/π", "FRAC_2_PI", 2 / float(Constant::Pi));
print("√π", "SQRT_PI", float(Constant::Pi).sqrt());
print("√2π", "SQRT_2PI", (float(Constant::Pi) * 2u32).sqrt());
print("1/√π", "FRAC_1_SQRT_PI", 1 / float(Constant::Pi).sqrt());
print("1/√2π", "FRAC_1_SQRT_2PI", 1 / (float(Constant::Pi) * 2u32).sqrt());
print("2/√π", "FRAC_2_SQRT_PI", 2 / float(Constant::Pi).sqrt());
print("√2", "SQRT_2", float(2).sqrt());
print("1/√2", "FRAC_1_SQRT_2", float(0.5).sqrt());
print("√3", "SQRT_3", float(3).sqrt());
print("1/√3", "FRAC_1_SQRT_3", float(3).recip().sqrt());
print("Euler’s number, e", "E", float(1).exp());
print("√e", "SQRT_E", float(0.5).exp());
print("log<sub>2</sub> 10", "LOG2_10", float(10).log2());
print("log<sub>2</sub> e", "LOG2_E", float(1).exp().log2());
print("log<sub>10</sub> 2", "LOG10_2", float(2).log10());
print("log<sub>10</sub> e", "LOG10_E", float(1).exp().log10());
print("ln 2", "LN_2", float(2).ln());
print("ln 10", "LN_10", float(10).ln());
print("The golden ratio, φ", "PHI", float(1.25).sqrt() + 0.5);
print("The golden ratio conjugate, Φ = 1/φ", "FRAC_1_PHI", float(1.25).sqrt() - 0.5);
print("√φ", "SQRT_PHI", (float(1.25).sqrt() + 0.5f32).sqrt());
print("The Euler-Mascheroni constant, γ", "GAMMA", float(Constant::Euler));
print("Catalan’s constant", "CATALAN", float(Constant::Catalan));
}
```
*/
/// A turn, τ = 6.28318…
// TAU = 6.283185307179586476925286766559005768394...
pub const TAU: U3F125 = U3F125::from_bits(0xC90F_DAA2_2168_C234_C4C6_628B_80DC_1CD1);
/// τ/2 = 3.14159…
// FRAC_TAU_2 = 3.141592653589793238462643383279502884197...
pub const FRAC_TAU_2: U2F126 = U2F126::from_bits(0xC90F_DAA2_2168_C234_C4C6_628B_80DC_1CD1);
/// τ/3 = 2.09439…
// FRAC_TAU_3 = 2.094395102393195492308428922186335256131...
pub const FRAC_TAU_3: U2F126 = U2F126::from_bits(0x860A_91C1_6B9B_2C23_2DD9_9707_AB3D_688B);
/// τ/4 = 1.57079…
// FRAC_TAU_4 = 1.570796326794896619231321691639751442098...
pub const FRAC_TAU_4: U1F127 = U1F127::from_bits(0xC90F_DAA2_2168_C234_C4C6_628B_80DC_1CD1);
/// τ/6 = 1.04719…
// FRAC_TAU_6 = 1.047197551196597746154214461093167628065...
pub const FRAC_TAU_6: U1F127 = U1F127::from_bits(0x860A_91C1_6B9B_2C23_2DD9_9707_AB3D_688B);
/// τ/8 = 0.785398…
// FRAC_TAU_8 = 0.7853981633974483096156608458198757210492...
pub const FRAC_TAU_8: U0F128 = U0F128::from_bits(0xC90F_DAA2_2168_C234_C4C6_628B_80DC_1CD1);
/// τ/12 = 0.523598…
// FRAC_TAU_12 = 0.5235987755982988730771072305465838140328...
pub const FRAC_TAU_12: U0F128 = U0F128::from_bits(0x860A_91C1_6B9B_2C23_2DD9_9707_AB3D_688B);
/// 1/τ = 0.159154…
// FRAC_1_TAU = 0.1591549430918953357688837633725143620344...
pub const FRAC_1_TAU: U0F128 = U0F128::from_bits(0x28BE_60DB_9391_054A_7F09_D5F4_7D4D_3770);
/// 2/τ = 0.318309…
// FRAC_2_TAU = 0.3183098861837906715377675267450287240689...
pub const FRAC_2_TAU: U0F128 = U0F128::from_bits(0x517C_C1B7_2722_0A94_FE13_ABE8_FA9A_6EE0);
/// 4/τ = 0.636619…
// FRAC_4_TAU = 0.6366197723675813430755350534900574481378...
pub const FRAC_4_TAU: U0F128 = U0F128::from_bits(0xA2F9_836E_4E44_1529_FC27_57D1_F534_DDC0);
/// Archimedes’ constant, π = 3.14159…
// PI = 3.141592653589793238462643383279502884197...
pub const PI: U2F126 = U2F126::from_bits(0xC90F_DAA2_2168_C234_C4C6_628B_80DC_1CD1);
/// π/2 = 1.57079…
// FRAC_PI_2 = 1.570796326794896619231321691639751442098...
pub const FRAC_PI_2: U1F127 = U1F127::from_bits(0xC90F_DAA2_2168_C234_C4C6_628B_80DC_1CD1);
/// π/3 = 1.04719…
// FRAC_PI_3 = 1.047197551196597746154214461093167628065...
pub const FRAC_PI_3: U1F127 = U1F127::from_bits(0x860A_91C1_6B9B_2C23_2DD9_9707_AB3D_688B);
/// π/4 = 0.785398…
// FRAC_PI_4 = 0.7853981633974483096156608458198757210492...
pub const FRAC_PI_4: U0F128 = U0F128::from_bits(0xC90F_DAA2_2168_C234_C4C6_628B_80DC_1CD1);
/// π/6 = 0.523598…
// FRAC_PI_6 = 0.5235987755982988730771072305465838140328...
pub const FRAC_PI_6: U0F128 = U0F128::from_bits(0x860A_91C1_6B9B_2C23_2DD9_9707_AB3D_688B);
/// π/8 = 0.392699…
// FRAC_PI_8 = 0.3926990816987241548078304229099378605246...
pub const FRAC_PI_8: U0F128 = U0F128::from_bits(0x6487_ED51_10B4_611A_6263_3145_C06E_0E68);
/// 1/π = 0.318309…
// FRAC_1_PI = 0.3183098861837906715377675267450287240689...
pub const FRAC_1_PI: U0F128 = U0F128::from_bits(0x517C_C1B7_2722_0A94_FE13_ABE8_FA9A_6EE0);
/// 2/π = 0.636619…
// FRAC_2_PI = 0.6366197723675813430755350534900574481378...
pub const FRAC_2_PI: U0F128 = U0F128::from_bits(0xA2F9_836E_4E44_1529_FC27_57D1_F534_DDC0);
/// √π = 1.77245…
// SQRT_PI = 1.772453850905516027298167483341145182797...
pub const SQRT_PI: U1F127 = U1F127::from_bits(0xE2DF_C48D_A77B_553C_E1D8_2906_AEDC_9C1F);
/// √2π = 2.50662…
// SQRT_2PI = 2.506628274631000502415765284811045253006...
pub const SQRT_2PI: U2F126 = U2F126::from_bits(0xA06C_98FF_B138_2CB2_BE52_0FD7_3916_7717);
/// 1/√π = 0.564189…
// FRAC_1_SQRT_PI = 0.5641895835477562869480794515607725858440...
pub const FRAC_1_SQRT_PI: U0F128 = U0F128::from_bits(0x906E_BA82_14DB_688D_71D4_8A7F_6BFE_C344);
/// 1/√2π = 0.398942…
// FRAC_1_SQRT_2PI = 0.3989422804014326779399460599343818684758...
pub const FRAC_1_SQRT_2PI: U0F128 = U0F128::from_bits(0x6621_14CF_50D9_4234_3F2C_F140_2EAE_38BF);
/// 2/√π = 1.12837…
// FRAC_2_SQRT_PI = 1.128379167095512573896158903121545171688...
pub const FRAC_2_SQRT_PI: U1F127 = U1F127::from_bits(0x906E_BA82_14DB_688D_71D4_8A7F_6BFE_C344);
/// √2 = 1.41421…
// SQRT_2 = 1.414213562373095048801688724209698078569...
pub const SQRT_2: U1F127 = U1F127::from_bits(0xB504_F333_F9DE_6484_597D_89B3_754A_BE9F);
/// 1/√2 = 0.707106…
// FRAC_1_SQRT_2 = 0.7071067811865475244008443621048490392848...
pub const FRAC_1_SQRT_2: U0F128 = U0F128::from_bits(0xB504_F333_F9DE_6484_597D_89B3_754A_BE9F);
/// √3 = 1.73205…
// SQRT_3 = 1.732050807568877293527446341505872366942...
pub const SQRT_3: U1F127 = U1F127::from_bits(0xDDB3_D742_C265_539D_92BA_16B8_3C5C_1DC4);
/// 1/√3 = 0.577350…
// FRAC_1_SQRT_3 = 0.5773502691896257645091487805019574556476...
pub const FRAC_1_SQRT_3: U0F128 = U0F128::from_bits(0x93CD_3A2C_8198_E269_0C7C_0F25_7D92_BE83);
/// Euler’s number, e = 2.71828…
// E = 2.718281828459045235360287471352662497757...
pub const E: U2F126 = U2F126::from_bits(0xADF8_5458_A2BB_4A9A_AFDC_5620_273D_3CF1);
/// √e = 1.64872…
// SQRT_E = 1.648721270700128146848650787814163571653...
pub const SQRT_E: U1F127 = U1F127::from_bits(0xD309_4C70_F034_DE4B_96FF_7D5B_6F99_FCD8);
/// log<sub>2</sub> 10 = 3.32192…
// LOG2_10 = 3.321928094887362347870319429489390175864...
pub const LOG2_10: U2F126 = U2F126::from_bits(0xD49A_784B_CD1B_8AFE_492B_F6FF_4DAF_DB4C);
/// log<sub>2</sub> e = 1.44269…
// LOG2_E = 1.442695040888963407359924681001892137426...
pub const LOG2_E: U1F127 = U1F127::from_bits(0xB8AA_3B29_5C17_F0BB_BE87_FED0_691D_3E88);
/// log<sub>10</sub> 2 = 0.301029…
// LOG10_2 = 0.3010299956639811952137388947244930267681...
pub const LOG10_2: U0F128 = U0F128::from_bits(0x4D10_4D42_7DE7_FBCC_47C4_ACD6_05BE_48BC);
/// log<sub>10</sub> e = 0.434294…
// LOG10_E = 0.4342944819032518276511289189166050822943...
pub const LOG10_E: U0F128 = U0F128::from_bits(0x6F2D_EC54_9B94_38CA_9AAD_D557_D699_EE19);
/// ln 2 = 0.693147…
// LN_2 = 0.6931471805599453094172321214581765680755...
pub const LN_2: U0F128 = U0F128::from_bits(0xB172_17F7_D1CF_79AB_C9E3_B398_03F2_F6AF);
/// ln 10 = 2.30258…
// LN_10 = 2.302585092994045684017991454684364207601...
pub const LN_10: U2F126 = U2F126::from_bits(0x935D_8DDD_AAA8_AC16_EA56_D62B_82D3_0A28);
/// The golden ratio, φ = 1.61803…
// PHI = 1.618033988749894848204586834365638117720...
pub const PHI: U1F127 = U1F127::from_bits(0xCF1B_BCDC_BFA5_3E0A_F9CE_6030_2E76_E41A);
/// The golden ratio conjugate, Φ = 1/φ = 0.618033…
// FRAC_1_PHI = 0.6180339887498948482045868343656381177203...
pub const FRAC_1_PHI: U0F128 = U0F128::from_bits(0x9E37_79B9_7F4A_7C15_F39C_C060_5CED_C834);
/// √φ = 1.27201…
// SQRT_PHI = 1.272019649514068964252422461737491491715...
pub const SQRT_PHI: U1F127 = U1F127::from_bits(0xA2D1_8A35_4422_AF49_2AA2_8089_0F62_6C86);
/// The Euler-Mascheroni constant, γ = 0.577215…
// GAMMA = 0.5772156649015328606065120900824024310421...
pub const GAMMA: U0F128 = U0F128::from_bits(0x93C4_67E3_7DB0_C7A4_D1BE_3F81_0152_CB56);
/// Catalan’s constant = 0.915965…
// CATALAN = 0.9159655941772190150546035149323841107741...
pub const CATALAN: U0F128 = U0F128::from_bits(0xEA7C_B89F_409A_E845_2158_22E3_7D32_D0C6);
#[cfg(test)]
mod tests {
use crate::consts::*;
use crate::traits::{Fixed, FromFixed};
use core::{f32, f64};
#[test]
fn cmp_f16() {
use half;
use half::f16 as half_f16;
assert_eq!(
half_f16::from_fixed(TAU),
half_f16::from_f32(f32::consts::PI * 2.0)
);
assert_eq!(half_f16::from_fixed(FRAC_TAU_2), half_f16::PI);
assert_eq!(
half_f16::from_fixed(FRAC_TAU_3),
half_f16::from_f32(f32::consts::FRAC_PI_3 * 2.0)
);
assert_eq!(half_f16::from_fixed(FRAC_TAU_4), half_f16::FRAC_PI_2);
assert_eq!(half_f16::from_fixed(FRAC_TAU_6), half_f16::FRAC_PI_3);
assert_eq!(half_f16::from_fixed(FRAC_TAU_8), half_f16::FRAC_PI_4);
assert_eq!(half_f16::from_fixed(FRAC_TAU_12), half_f16::FRAC_PI_6);
assert_eq!(
half_f16::from_fixed(FRAC_1_TAU),
half_f16::from_f32(f32::consts::FRAC_1_PI * 0.5)
);
assert_eq!(half_f16::from_fixed(FRAC_2_TAU), half_f16::FRAC_1_PI);
assert_eq!(half_f16::from_fixed(FRAC_4_TAU), half_f16::FRAC_2_PI);
assert_eq!(half_f16::from_fixed(PI), half_f16::PI);
assert_eq!(half_f16::from_fixed(FRAC_PI_2), half_f16::FRAC_PI_2);
assert_eq!(half_f16::from_fixed(FRAC_PI_3), half_f16::FRAC_PI_3);
assert_eq!(half_f16::from_fixed(FRAC_PI_4), half_f16::FRAC_PI_4);
assert_eq!(half_f16::from_fixed(FRAC_PI_6), half_f16::FRAC_PI_6);
assert_eq!(half_f16::from_fixed(FRAC_PI_8), half_f16::FRAC_PI_8);
assert_eq!(half_f16::from_fixed(FRAC_1_PI), half_f16::FRAC_1_PI);
assert_eq!(half_f16::from_fixed(FRAC_2_PI), half_f16::FRAC_2_PI);
assert_eq!(
half_f16::from_fixed(SQRT_PI),
half_f16::from_f32(f32::consts::PI.sqrt())
);
assert_eq!(
half_f16::from_fixed(FRAC_1_SQRT_PI),
half_f16::from_f32(f32::consts::FRAC_2_SQRT_PI / 2.0)
);
assert_eq!(
half_f16::from_fixed(FRAC_2_SQRT_PI),
half_f16::FRAC_2_SQRT_PI
);
assert_eq!(half_f16::from_fixed(SQRT_2), half_f16::SQRT_2);
assert_eq!(half_f16::from_fixed(FRAC_1_SQRT_2), half_f16::FRAC_1_SQRT_2);
assert_eq!(
half_f16::from_fixed(SQRT_3),
half_f16::from_f32(3f32.sqrt())
);
assert_eq!(
half_f16::from_fixed(FRAC_1_SQRT_3),
half_f16::from_f32(3f32.powf(-0.5))
);
assert_eq!(half_f16::from_fixed(E), half_f16::E);
assert_eq!(
half_f16::from_fixed(SQRT_E),
half_f16::from_f32(0.5f32.exp())
);
assert_eq!(half_f16::from_fixed(LOG2_10), half_f16::LOG2_10);
assert_eq!(half_f16::from_fixed(LOG2_E), half_f16::LOG2_E);
assert_eq!(half_f16::from_fixed(LOG10_2), half_f16::LOG10_2);
assert_eq!(half_f16::from_fixed(LOG10_E), half_f16::LOG10_E);
assert_eq!(half_f16::from_fixed(LN_2), half_f16::LN_2);
assert_eq!(half_f16::from_fixed(LN_10), half_f16::LN_10);
assert_eq!(
half_f16::from_fixed(PHI),
half_f16::from_f32(1.25f32.sqrt() + 0.5)
);
assert_eq!(
half_f16::from_fixed(FRAC_1_PHI),
half_f16::from_f32(1.25f32.sqrt() - 0.5)
);
assert_eq!(
half_f16::from_fixed(SQRT_PHI),
half_f16::from_f32((1.25f32.sqrt() + 0.5f32).sqrt())
);
}
#[test]
fn cmp_bf16() {
use half;
use half::bf16 as half_bf16;
assert_eq!(
half_bf16::from_fixed(TAU),
half_bf16::from_f32(f32::consts::PI * 2.0)
);
assert_eq!(half_bf16::from_fixed(FRAC_TAU_2), half_bf16::PI);
assert_eq!(
half_bf16::from_fixed(FRAC_TAU_3),
half_bf16::from_f32(f32::consts::FRAC_PI_3 * 2.0)
);
assert_eq!(half_bf16::from_fixed(FRAC_TAU_4), half_bf16::FRAC_PI_2);
assert_eq!(half_bf16::from_fixed(FRAC_TAU_6), half_bf16::FRAC_PI_3);
assert_eq!(half_bf16::from_fixed(FRAC_TAU_8), half_bf16::FRAC_PI_4);
assert_eq!(half_bf16::from_fixed(FRAC_TAU_12), half_bf16::FRAC_PI_6);
assert_eq!(
half_bf16::from_fixed(FRAC_1_TAU),
half_bf16::from_f32(f32::consts::FRAC_1_PI * 0.5)
);
assert_eq!(half_bf16::from_fixed(FRAC_2_TAU), half_bf16::FRAC_1_PI);
assert_eq!(half_bf16::from_fixed(FRAC_4_TAU), half_bf16::FRAC_2_PI);
assert_eq!(half_bf16::from_fixed(PI), half_bf16::PI);
assert_eq!(half_bf16::from_fixed(FRAC_PI_2), half_bf16::FRAC_PI_2);
assert_eq!(half_bf16::from_fixed(FRAC_PI_3), half_bf16::FRAC_PI_3);
assert_eq!(half_bf16::from_fixed(FRAC_PI_4), half_bf16::FRAC_PI_4);
assert_eq!(half_bf16::from_fixed(FRAC_PI_6), half_bf16::FRAC_PI_6);
assert_eq!(half_bf16::from_fixed(FRAC_PI_8), half_bf16::FRAC_PI_8);
assert_eq!(half_bf16::from_fixed(FRAC_1_PI), half_bf16::FRAC_1_PI);
assert_eq!(half_bf16::from_fixed(FRAC_2_PI), half_bf16::FRAC_2_PI);
assert_eq!(
half_bf16::from_fixed(SQRT_PI),
half_bf16::from_f32(f32::consts::PI.sqrt())
);
assert_eq!(
half_bf16::from_fixed(FRAC_1_SQRT_PI),
half_bf16::from_f32(f32::consts::FRAC_2_SQRT_PI / 2.0)
);
assert_eq!(
half_bf16::from_fixed(FRAC_2_SQRT_PI),
half_bf16::FRAC_2_SQRT_PI
);
assert_eq!(half_bf16::from_fixed(SQRT_2), half_bf16::SQRT_2);
assert_eq!(
half_bf16::from_fixed(FRAC_1_SQRT_2),
half_bf16::FRAC_1_SQRT_2
);
assert_eq!(
half_bf16::from_fixed(SQRT_3),
half_bf16::from_f32(3f32.sqrt())
);
assert_eq!(
half_bf16::from_fixed(FRAC_1_SQRT_3),
half_bf16::from_f32(3f32.powf(-0.5))
);
assert_eq!(half_bf16::from_fixed(E), half_bf16::E);
assert_eq!(
half_bf16::from_fixed(SQRT_E),
half_bf16::from_f32(0.5f32.exp())
);
assert_eq!(half_bf16::from_fixed(LOG2_10), half_bf16::LOG2_10);
assert_eq!(half_bf16::from_fixed(LOG2_E), half_bf16::LOG2_E);
assert_eq!(half_bf16::from_fixed(LOG10_2), half_bf16::LOG10_2);
assert_eq!(half_bf16::from_fixed(LOG10_E), half_bf16::LOG10_E);
assert_eq!(half_bf16::from_fixed(LN_2), half_bf16::LN_2);
assert_eq!(half_bf16::from_fixed(LN_10), half_bf16::LN_10);
assert_eq!(
half_bf16::from_fixed(PHI),
half_bf16::from_f32(1.25f32.sqrt() + 0.5)
);
assert_eq!(
half_bf16::from_fixed(FRAC_1_PHI),
half_bf16::from_f32(1.25f32.sqrt() - 0.5)
);
assert_eq!(
half_bf16::from_fixed(SQRT_PHI),
half_bf16::from_f32((1.25f32.sqrt() + 0.5f32).sqrt())
);
}
#[test]
fn cmp_f32() {
assert_eq!(f32::from_fixed(TAU), f32::consts::PI * 2.0);
assert_eq!(f32::from_fixed(FRAC_TAU_2), f32::consts::PI);
assert_eq!(f32::from_fixed(FRAC_TAU_3), f32::consts::FRAC_PI_3 * 2.0);
assert_eq!(f32::from_fixed(FRAC_TAU_4), f32::consts::FRAC_PI_2);
assert_eq!(f32::from_fixed(FRAC_TAU_6), f32::consts::FRAC_PI_3);
assert_eq!(f32::from_fixed(FRAC_TAU_8), f32::consts::FRAC_PI_4);
assert_eq!(f32::from_fixed(FRAC_TAU_12), f32::consts::FRAC_PI_6);
assert_eq!(f32::from_fixed(FRAC_1_TAU), f32::consts::FRAC_1_PI * 0.5);
assert_eq!(f32::from_fixed(FRAC_2_TAU), f32::consts::FRAC_1_PI);
assert_eq!(f32::from_fixed(FRAC_4_TAU), f32::consts::FRAC_2_PI);
assert_eq!(f32::from_fixed(PI), f32::consts::PI);
assert_eq!(f32::from_fixed(FRAC_PI_2), f32::consts::FRAC_PI_2);
assert_eq!(f32::from_fixed(FRAC_PI_3), f32::consts::FRAC_PI_3);
assert_eq!(f32::from_fixed(FRAC_PI_4), f32::consts::FRAC_PI_4);
assert_eq!(f32::from_fixed(FRAC_PI_6), f32::consts::FRAC_PI_6);
assert_eq!(f32::from_fixed(FRAC_PI_8), f32::consts::FRAC_PI_8);
assert_eq!(f32::from_fixed(FRAC_1_PI), f32::consts::FRAC_1_PI);
assert_eq!(f32::from_fixed(FRAC_2_PI), f32::consts::FRAC_2_PI);
assert_eq!(f32::from_fixed(SQRT_PI), f64::consts::PI.sqrt() as f32);
assert_eq!(
f32::from_fixed(FRAC_1_SQRT_PI),
f32::consts::FRAC_2_SQRT_PI / 2.0
);
assert_eq!(f32::from_fixed(FRAC_2_SQRT_PI), f32::consts::FRAC_2_SQRT_PI);
assert_eq!(f32::from_fixed(SQRT_2), f32::consts::SQRT_2);
assert_eq!(f32::from_fixed(FRAC_1_SQRT_2), f32::consts::FRAC_1_SQRT_2);
assert_eq!(f32::from_fixed(SQRT_3), 3f32.sqrt());
assert_eq!(f32::from_fixed(FRAC_1_SQRT_3), 3f32.powf(-0.5));
assert_eq!(f32::from_fixed(E), f32::consts::E);
assert_eq!(f32::from_fixed(SQRT_E), 0.5f32.exp());
assert_eq!(f32::from_fixed(LOG2_10), f32::consts::LOG2_10);
assert_eq!(f32::from_fixed(LOG2_E), f32::consts::LOG2_E);
assert_eq!(f32::from_fixed(LOG10_2), f32::consts::LOG10_2);
assert_eq!(f32::from_fixed(LOG10_E), f32::consts::LOG10_E);
assert_eq!(f32::from_fixed(LN_2), f32::consts::LN_2);
assert_eq!(f32::from_fixed(LN_10), f32::consts::LN_10);
assert_eq!(f32::from_fixed(PHI), 1.25f32.sqrt() + 0.5);
assert_eq!(f32::from_fixed(FRAC_1_PHI), (1.25f64.sqrt() - 0.5) as f32);
assert_eq!(
f32::from_fixed(SQRT_PHI),
(1.25f64.sqrt() + 0.5f64).sqrt() as f32
);
}
#[test]
fn cmp_f64() {
assert_eq!(f64::from_fixed(TAU), f64::consts::PI * 2.0);
assert_eq!(f64::from_fixed(FRAC_TAU_2), f64::consts::PI);
assert_eq!(f64::from_fixed(FRAC_TAU_3), f64::consts::FRAC_PI_3 * 2.0);
assert_eq!(f64::from_fixed(FRAC_TAU_4), f64::consts::FRAC_PI_2);
assert_eq!(f64::from_fixed(FRAC_TAU_6), f64::consts::FRAC_PI_3);
assert_eq!(f64::from_fixed(FRAC_TAU_8), f64::consts::FRAC_PI_4);
assert_eq!(f64::from_fixed(FRAC_TAU_12), f64::consts::FRAC_PI_6);
assert_eq!(f64::from_fixed(FRAC_1_TAU), f64::consts::FRAC_1_PI * 0.5);
assert_eq!(f64::from_fixed(FRAC_2_TAU), f64::consts::FRAC_1_PI);
assert_eq!(f64::from_fixed(FRAC_4_TAU), f64::consts::FRAC_2_PI);
assert_eq!(f64::from_fixed(PI), f64::consts::PI);
assert_eq!(f64::from_fixed(FRAC_PI_2), f64::consts::FRAC_PI_2);
assert_eq!(f64::from_fixed(FRAC_PI_3), f64::consts::FRAC_PI_3);
assert_eq!(f64::from_fixed(FRAC_PI_4), f64::consts::FRAC_PI_4);
assert_eq!(f64::from_fixed(FRAC_PI_6), f64::consts::FRAC_PI_6);
assert_eq!(f64::from_fixed(FRAC_PI_8), f64::consts::FRAC_PI_8);
assert_eq!(f64::from_fixed(FRAC_1_PI), f64::consts::FRAC_1_PI);
assert_eq!(f64::from_fixed(FRAC_2_PI), f64::consts::FRAC_2_PI);
// Since 1 < SQRT_PI < 2, we use EPSILON.
assert!((f64::from_fixed(SQRT_PI) - f64::consts::PI.sqrt()).abs() <= f64::EPSILON);
assert_eq!(
f64::from_fixed(FRAC_1_SQRT_PI),
f64::consts::FRAC_2_SQRT_PI / 2.0
);
assert_eq!(f64::from_fixed(FRAC_2_SQRT_PI), f64::consts::FRAC_2_SQRT_PI);
assert_eq!(f64::from_fixed(SQRT_2), f64::consts::SQRT_2);
assert_eq!(f64::from_fixed(FRAC_1_SQRT_2), f64::consts::FRAC_1_SQRT_2);
assert_eq!(f64::from_fixed(SQRT_3), 3f64.sqrt());
assert_eq!(f64::from_fixed(FRAC_1_SQRT_3), 3f64.powf(-0.5));
assert_eq!(f64::from_fixed(E), f64::consts::E);
assert_eq!(f64::from_fixed(SQRT_E), 0.5f64.exp());
assert_eq!(f64::from_fixed(LOG2_10), f64::consts::LOG2_10);
assert_eq!(f64::from_fixed(LOG2_E), f64::consts::LOG2_E);
assert_eq!(f64::from_fixed(LOG10_2), f64::consts::LOG10_2);
assert_eq!(f64::from_fixed(LOG10_E), f64::consts::LOG10_E);
assert_eq!(f64::from_fixed(LN_2), f64::consts::LN_2);
assert_eq!(f64::from_fixed(LN_10), f64::consts::LN_10);
assert_eq!(f64::from_fixed(PHI), 1.25f64.sqrt() + 0.5);
// Since 0.5 < FRAC_1_PHI < 1, we use EPSILON / 2.
assert!((f64::from_fixed(FRAC_1_PHI) - (1.25f64.sqrt() - 0.5)).abs() <= f64::EPSILON / 2.0);
// Since 1 < SQRT_PHI < 2, we use EPSILON.
assert!(
(f64::from_fixed(SQRT_PHI) - (1.25f64.sqrt() + 0.5f64).sqrt()).abs() <= f64::EPSILON
);
}
fn compare_parse<F: Fixed>(f: F, s: &str)
where
F::Bits: TryFrom<u8>,
{
let sf = F::from_str(s).unwrap();
let f_plus = f + F::from_bits(F::Bits::try_from(1).ok().unwrap());
assert!(f <= sf && sf <= f_plus);
}
#[test]
fn cmp_parse() {
compare_parse(TAU, "6.283185307179586476925286766559005768394");
compare_parse(FRAC_TAU_2, "3.141592653589793238462643383279502884197");
compare_parse(FRAC_TAU_3, "2.094395102393195492308428922186335256131");
compare_parse(FRAC_TAU_4, "1.570796326794896619231321691639751442098");
compare_parse(FRAC_TAU_6, "1.047197551196597746154214461093167628065");
compare_parse(FRAC_TAU_8, "0.7853981633974483096156608458198757210492");
compare_parse(FRAC_TAU_12, "0.5235987755982988730771072305465838140328");
compare_parse(FRAC_1_TAU, "0.1591549430918953357688837633725143620344");
compare_parse(FRAC_2_TAU, "0.3183098861837906715377675267450287240689");
compare_parse(FRAC_4_TAU, "0.6366197723675813430755350534900574481378");
compare_parse(PI, "3.141592653589793238462643383279502884197");
compare_parse(FRAC_PI_2, "1.570796326794896619231321691639751442098");
compare_parse(FRAC_PI_3, "1.047197551196597746154214461093167628065");
compare_parse(FRAC_PI_4, "0.7853981633974483096156608458198757210492");
compare_parse(FRAC_PI_6, "0.5235987755982988730771072305465838140328");
compare_parse(FRAC_PI_8, "0.3926990816987241548078304229099378605246");
compare_parse(FRAC_1_PI, "0.3183098861837906715377675267450287240689");
compare_parse(FRAC_2_PI, "0.6366197723675813430755350534900574481378");
compare_parse(SQRT_PI, "1.772453850905516027298167483341145182797");
compare_parse(FRAC_1_SQRT_PI, "0.5641895835477562869480794515607725858440");
compare_parse(FRAC_2_SQRT_PI, "1.128379167095512573896158903121545171688");
compare_parse(SQRT_2, "1.414213562373095048801688724209698078569");
compare_parse(FRAC_1_SQRT_2, "0.7071067811865475244008443621048490392848");
compare_parse(SQRT_3, "1.732050807568877293527446341505872366942");
compare_parse(FRAC_1_SQRT_3, "0.5773502691896257645091487805019574556476");
compare_parse(E, "2.718281828459045235360287471352662497757");
compare_parse(SQRT_E, "1.648721270700128146848650787814163571653");
compare_parse(LOG2_10, "3.321928094887362347870319429489390175864");
compare_parse(LOG2_E, "1.442695040888963407359924681001892137426");
compare_parse(LOG10_2, "0.3010299956639811952137388947244930267681");
compare_parse(LOG10_E, "0.4342944819032518276511289189166050822943");
compare_parse(LN_2, "0.6931471805599453094172321214581765680755");
compare_parse(LN_10, "2.302585092994045684017991454684364207601");
compare_parse(PHI, "1.618033988749894848204586834365638117720");
compare_parse(FRAC_1_PHI, "0.6180339887498948482045868343656381177203");
compare_parse(SQRT_PHI, "1.272019649514068964252422461737491491715");
compare_parse(GAMMA, "0.5772156649015328606065120900824024310421");
compare_parse(CATALAN, "0.9159655941772190150546035149323841107741");
}
}
+1924
View File
File diff suppressed because it is too large. Load diff
+134
View File
@@ -0,0 +1,134 @@
// Copyright © 2018–2025 Trevor Spiteri
// This library is free software: you can redistribute it and/or
// modify it under the terms of either
//
// * the Apache License, Version 2.0 or
// * the MIT License
//
// at your option.
//
// You should have recieved copies of the Apache License and the MIT
// License along with the library. If not, see
// <https://www.apache.org/licenses/LICENSE-2.0> and
// <https://opensource.org/licenses/MIT>.
use core::cell::Cell;
use core::fmt::{Debug, Formatter, Result as FmtResult, Write};
#[cfg(target_has_atomic = "32")]
use core::sync::atomic::{AtomicU32, Ordering};
// This is an ugly hack to check whether a `Formatter` has `debug_lower_hex` or
// `debug_upper_hex`.
//
// We do a dummy write with format string "{:x?}" to get `debug_lower_hex`, and
// a dummy write with format string "{:X?}" to get `debug_upper_hex`. Each time,
// we get the flags using the deprecated `Formatter::flags`.
//
// If AtomicU32 is supported, we cache the flags.
#[cfg(target_has_atomic = "32")]
static LOWER_FLAGS: AtomicU32 = AtomicU32::new(0);
#[cfg(target_has_atomic = "32")]
static UPPER_FLAGS: AtomicU32 = AtomicU32::new(0);
fn get_flags(f: &Formatter) -> u32 {
#[allow(deprecated)]
f.flags()
}
struct StoreFlags(Cell<u32>);
impl Debug for StoreFlags {
fn fmt(&self, f: &mut Formatter) -> FmtResult {
self.0.set(get_flags(f));
Ok(())
}
}
struct Discard;
impl Write for Discard {
fn write_str(&mut self, _s: &str) -> FmtResult {
Ok(())
}
}
pub enum IsDebugHex {
No,
Lower,
Upper,
}
pub fn is_debug_hex(f: &Formatter) -> IsDebugHex {
let flags = get_flags(f);
// avoid doing unnecessary work if flags is zero
if flags == 0 {
return IsDebugHex::No;
}
let (lower_mask, upper_mask) = get_flag_masks();
if flags & lower_mask != 0 {
IsDebugHex::Lower
} else if flags & upper_mask != 0 {
IsDebugHex::Upper
} else {
IsDebugHex::No
}
}
#[cfg(target_has_atomic = "32")]
fn load_cache() -> Option<(u32, u32)> {
let cached_lower = LOWER_FLAGS.load(Ordering::Relaxed);
let cached_upper = UPPER_FLAGS.load(Ordering::Relaxed);
if cached_lower == u32::MAX || cached_upper == u32::MAX {
// error was detected, so no need to repeat the error generation
return Some((0, 0));
}
if cached_lower == 0 || cached_upper == 0 {
return None;
}
Some((cached_lower, cached_upper))
}
#[cfg(target_has_atomic = "32")]
fn store_cache(mut lower: u32, mut upper: u32) {
if lower == 0 || upper == 0 {
lower = u32::MAX;
upper = u32::MAX;
}
LOWER_FLAGS.store(lower, Ordering::Relaxed);
UPPER_FLAGS.store(upper, Ordering::Relaxed);
}
#[cfg(not(target_has_atomic = "32"))]
fn load_cache() -> Option<(u32, u32)> {
None
}
#[cfg(not(target_has_atomic = "32"))]
fn store_cache(_lower: u32, _upper: u32) {}
fn get_flag_masks() -> (u32, u32) {
if let Some(cached) = load_cache() {
return cached;
}
let store_flags = StoreFlags(Cell::new(0));
if write!(Discard, "{store_flags:x?}").is_err() {
store_cache(u32::MAX, u32::MAX);
return (0, 0);
}
let lower_flags = store_flags.0.get();
if write!(Discard, "{store_flags:X?}").is_err() {
store_cache(u32::MAX, u32::MAX);
return (0, 0);
}
let upper_flags = store_flags.0.get();
let lower_mask = lower_flags & !upper_flags;
let upper_mask = upper_flags & !lower_flags;
store_cache(lower_mask, upper_mask);
(lower_mask, upper_mask)
}
+1193
View File
File diff suppressed because it is too large. Load diff
+1265
View File
File diff suppressed because it is too large. Load diff
+232
View File
@@ -0,0 +1,232 @@
// Copyright © 2018–2025 Trevor Spiteri
// This library is free software: you can redistribute it and/or
// modify it under the terms of either
//
// * the Apache License, Version 2.0 or
// * the MIT License
//
// at your option.
//
// You should have recieved copies of the Apache License and the MIT
// License along with the library. If not, see
// <https://www.apache.org/licenses/LICENSE-2.0> and
// <https://opensource.org/licenses/MIT>.
#![allow(deprecated)]
macro_rules! make_helper {
($Float:ident($Bits:ty, $IBits:ident, $prec:expr) $(; use $path:path)?) => {
#[allow(non_snake_case)]
pub mod $Float {
use crate::helpers::{FloatKind, ToFixedHelper, ToFloatHelper, Widest};
use crate::int_helper;
use core::cmp::Ordering;
$(use $path as $Float;)?
const PREC: u32 = $prec;
const EXP_BIAS: i32 = (1 << (<$Bits>::BITS - PREC - 1)) - 1;
const EXP_MIN: i32 = 1 - EXP_BIAS;
const EXP_MAX: i32 = EXP_BIAS;
pub const SIGN_MASK: $Bits = 1 << (<$Bits>::BITS - 1);
pub const EXP_MASK: $Bits = !(SIGN_MASK | MANT_MASK);
const MANT_MASK: $Bits = (1 << (PREC - 1)) - 1;
// zero is NOT negative, that is zero is represented as
// Kind::Finite { neg: false, abs: 0, frac_bits: 0 },
pub enum Kind {
NaN,
Infinite { neg: bool },
Finite { neg: bool, abs: $Bits, frac_bits: i32 },
}
#[inline]
pub fn kind(val: $Float) -> Kind {
let (neg, mut exp, mut mantissa) = parts(val);
if exp > EXP_MAX {
return if mantissa == 0 {
Kind::Infinite { neg }
} else {
Kind::NaN
};
}
// if not subnormal, add implicit bit
if exp >= EXP_MIN {
mantissa |= 1 << (PREC - 1);
} else {
exp = EXP_MIN;
}
if mantissa == 0 {
return Kind::Finite {
neg: false,
abs: 0,
frac_bits: 0,
};
}
Kind::Finite {
neg,
abs: mantissa,
frac_bits: PREC as i32 - 1 - exp,
}
}
#[inline]
fn parts(val: $Float) -> (bool, i32, $Bits) {
let bits = val.to_bits();
let neg = bits & SIGN_MASK != 0;
let biased_exp = (bits & EXP_MASK) >> (PREC - 1);
let exp = biased_exp as i32 - EXP_BIAS;
let mant = bits & MANT_MASK;
(neg, exp, mant)
}
#[inline]
pub fn from_to_float_helper(
val: ToFloatHelper,
frac_bits: u32,
int_bits: u32,
) -> $Float {
let fix_bits = frac_bits + int_bits;
let bits_sign = if val.neg { SIGN_MASK } else { 0 };
let extra_zeros = 128 - fix_bits;
let leading_zeros = val.abs.leading_zeros() - extra_zeros;
let signif_bits = fix_bits - leading_zeros;
if signif_bits == 0 {
return $Float::from_bits(bits_sign);
}
// remove leading zeros and implicit one
let mut mantissa = val.abs << leading_zeros << 1;
let exponent = int_bits as i32 - 1 - leading_zeros as i32;
let biased_exponent = if exponent > EXP_MAX {
return $Float::from_bits(EXP_MASK | bits_sign);
} else if exponent < EXP_MIN {
let lost_prec = EXP_MIN - exponent;
if lost_prec as u32 >= (int_bits + frac_bits) {
mantissa = 0;
} else {
// reinsert implicit one
mantissa = (mantissa >> 1) | !(!0 >> 1);
mantissa >>= lost_prec - 1;
}
0
} else {
(exponent + EXP_MAX) as $Bits
};
// check for rounding
let round_up = (fix_bits >= PREC) && {
let shift = PREC - 1;
let mid_bit = !(!0 >> 1) >> (shift + extra_zeros);
let lower_bits = mid_bit - 1;
if mantissa & mid_bit == 0 {
false
} else if mantissa & lower_bits != 0 {
true
} else {
// round to even
mantissa & (mid_bit << 1) != 0
}
};
let bits_exp = biased_exponent << (PREC - 1);
let bits_mantissa = (if fix_bits >= PREC - 1 {
(mantissa >> (fix_bits - (PREC - 1))) as $Bits
} else {
(mantissa as $Bits) << (PREC - 1 - fix_bits)
}) & !(!0 << (PREC - 1));
let mut bits_exp_mantissa = bits_exp | bits_mantissa;
if round_up {
bits_exp_mantissa += 1;
}
$Float::from_bits(bits_sign | bits_exp_mantissa)
}
#[inline]
pub fn to_float_kind(val: $Float, dst_frac_bits: u32, dst_int_bits: u32) -> FloatKind {
let prec = PREC as i32;
let (neg, mut exp, mut mantissa) = parts(val);
if exp > EXP_MAX {
return if mantissa == 0 {
FloatKind::Infinite { neg }
} else {
FloatKind::NaN
};
}
// if not subnormal add implicit bit, otherwise update exp
if exp >= EXP_MIN {
mantissa |= 1 << (prec - 1);
} else {
exp = EXP_MIN;
}
if mantissa == 0 {
let conv = ToFixedHelper {
bits: Widest::Unsigned(0),
dir: Ordering::Equal,
overflow: false,
};
return FloatKind::Finite { neg, conv };
}
let mut src_frac_bits = prec - 1 - exp;
let need_to_shr = src_frac_bits - dst_frac_bits as i32;
if need_to_shr > prec {
let dir = if neg {
Ordering::Greater
} else {
Ordering::Less
};
let conv = ToFixedHelper {
bits: Widest::Unsigned(0),
dir,
overflow: false,
};
return FloatKind::Finite { neg, conv };
}
let mut dir = Ordering::Equal;
if need_to_shr > 0 {
let removed_bits = mantissa & !(!0 << need_to_shr);
let will_be_lsb = 1 << need_to_shr;
let tie = will_be_lsb >> 1;
if removed_bits == 0 {
// removed nothing
} else if removed_bits < tie {
dir = Ordering::Less;
} else if removed_bits > tie || mantissa & will_be_lsb != 0 {
mantissa += will_be_lsb;
dir = Ordering::Greater;
} else {
dir = Ordering::Less;
};
mantissa >>= need_to_shr;
src_frac_bits -= need_to_shr;
}
let mut mantissa = mantissa as $IBits;
if neg {
mantissa = -mantissa;
dir = dir.reverse();
}
let mut conv = int_helper::$IBits::to_fixed_helper(
mantissa,
src_frac_bits,
dst_frac_bits,
dst_int_bits,
);
conv.dir = dir;
FloatKind::Finite { neg, conv }
}
}
};
}
#[cfg(feature = "nightly-float")]
make_helper! { f16(u16, i16, 11) }
make_helper! { half_f16(u16, i16, 11); use half::f16 }
make_helper! { half_bf16(u16, i16, 8); use half::bf16 }
make_helper! { f32(u32, i32, 24) }
make_helper! { f64(u64, i64, 53) }
#[cfg(feature = "nightly-float")]
make_helper! { f128(u128, i128, 113) }
make_helper! { F128(u128, i128, 113); use crate::F128 }
make_helper! { F128Bits(u128, i128, 113); use crate::F128Bits }
+2821
View File
File diff suppressed because it is too large. Load diff
+159
View File
@@ -0,0 +1,159 @@
// Copyright © 2018–2025 Trevor Spiteri
// This library is free software: you can redistribute it and/or
// modify it under the terms of either
//
// * the Apache License, Version 2.0 or
// * the MIT License
//
// at your option.
//
// You should have recieved copies of the Apache License and the MIT
// License along with the library. If not, see
// <https://www.apache.org/licenses/LICENSE-2.0> and
// <https://opensource.org/licenses/MIT>.
use crate::int_helper;
use crate::types::extra::{LeEqU8, LeEqU16, LeEqU32, LeEqU64, LeEqU128};
use crate::{
FixedI8, FixedI16, FixedI32, FixedI64, FixedI128, FixedU8, FixedU16, FixedU32, FixedU64,
FixedU128,
};
use core::cmp::Ordering;
// Unsigned can have 0 ≤ x < 2^128, that is its msb can be 0 or 1.
// Negative can have -2^127 ≤ x < 0, that is its msb must be 1.
pub enum Widest {
Unsigned(u128),
Negative(i128),
}
pub struct ToFixedHelper {
pub(crate) bits: Widest,
pub(crate) dir: Ordering,
pub(crate) overflow: bool,
}
pub struct ToFloatHelper {
pub(crate) neg: bool,
pub(crate) abs: u128,
}
pub struct FromFloatHelper {
pub(crate) kind: FloatKind,
}
pub enum FloatKind {
NaN,
Infinite { neg: bool },
Finite { neg: bool, conv: ToFixedHelper },
}
pub struct Private;
pub trait Sealed: Copy {
fn to_fixed_helper(self, _: Private, dst_frac_nbits: u32, dst_int_nbits: u32) -> ToFixedHelper;
fn to_float_helper(self, _: Private) -> ToFloatHelper;
fn saturating_from_float_helper(_: Private, src: FromFloatHelper) -> Self;
fn overflowing_from_float_helper(_: Private, src: FromFloatHelper) -> (Self, bool);
}
macro_rules! impl_sealed {
($Fixed:ident($LeEqU:ident, $Signedness:ident, $Inner:ident)) => {
impl<Frac: $LeEqU> Sealed for $Fixed<Frac> {
#[inline]
fn to_fixed_helper(
self,
_: Private,
dst_frac_nbits: u32,
dst_int_nbits: u32,
) -> ToFixedHelper {
int_helper::$Inner::to_fixed_helper(
self.to_bits(),
Self::FRAC_NBITS as i32,
dst_frac_nbits,
dst_int_nbits,
)
}
#[inline]
fn to_float_helper(self, _: Private) -> ToFloatHelper {
let (neg, abs) = int_helper::$Inner::neg_abs(self.to_bits());
let abs = abs.into();
ToFloatHelper { neg, abs }
}
#[inline]
fn saturating_from_float_helper(_: Private, src: FromFloatHelper) -> Self {
let neg = match src.kind {
FloatKind::NaN => panic!("NaN"),
FloatKind::Infinite { neg } => neg,
FloatKind::Finite { neg, .. } => neg,
};
let saturated = if neg { Self::MIN } else { Self::MAX };
let FloatKind::Finite { conv, .. } = src.kind else {
return saturated;
};
if conv.overflow {
return saturated;
}
let bits = if_signed_unsigned!(
$Signedness,
match conv.bits {
Widest::Unsigned(bits) => {
let bits = bits as _;
if bits < 0 {
return Self::MAX;
}
bits
}
Widest::Negative(bits) => bits as _,
},
match conv.bits {
Widest::Unsigned(bits) => bits as _,
Widest::Negative(_) => return Self::MIN,
},
);
Self::from_bits(bits)
}
#[inline]
#[track_caller]
fn overflowing_from_float_helper(_: Private, src: FromFloatHelper) -> (Self, bool) {
let conv = match src.kind {
FloatKind::NaN => panic!("NaN"),
FloatKind::Infinite { .. } => panic!("infinite"),
FloatKind::Finite { conv, .. } => conv,
};
let mut new_overflow = false;
let bits = if_signed_unsigned!(
$Signedness,
match conv.bits {
Widest::Unsigned(bits) => {
let bits = bits as _;
if bits < 0 {
new_overflow = true;
}
bits
}
Widest::Negative(bits) => bits as _,
},
match conv.bits {
Widest::Unsigned(bits) => bits as _,
Widest::Negative(bits) => {
new_overflow = true;
bits as _
}
},
);
(Self::from_bits(bits), conv.overflow || new_overflow)
}
}
};
}
impl_sealed! { FixedI8(LeEqU8, Signed, i8) }
impl_sealed! { FixedI16(LeEqU16, Signed, i16) }
impl_sealed! { FixedI32(LeEqU32, Signed, i32) }
impl_sealed! { FixedI64(LeEqU64, Signed, i64) }
impl_sealed! { FixedI128(LeEqU128, Signed, i128) }
impl_sealed! { FixedU8(LeEqU8, Unsigned, u8) }
impl_sealed! { FixedU16(LeEqU16, Unsigned, u16) }
impl_sealed! { FixedU32(LeEqU32, Unsigned, u32) }
impl_sealed! { FixedU64(LeEqU64, Unsigned, u64) }
impl_sealed! { FixedU128(LeEqU128, Unsigned, u128) }
+246
View File
@@ -0,0 +1,246 @@
// Copyright © 2018–2025 Trevor Spiteri
// This library is free software: you can redistribute it and/or
// modify it under the terms of either
//
// * the Apache License, Version 2.0 or
// * the MIT License
//
// at your option.
//
// You should have recieved copies of the Apache License and the MIT
// License along with the library. If not, see
// <https://www.apache.org/licenses/LICENSE-2.0> and
// <https://opensource.org/licenses/MIT>.
use crate::int256;
use crate::int256::U256;
use core::num::NonZero;
// The square root method used is based on code by Martin Guy @UKC, June 1985.
// His method of square root by abacus method is from a book on programming
// abaci by Mr C. Woo.
// http://medialab.freaknet.org/martin/src/sqrt/
macro_rules! impl_hypot {
($Single:ident, $Double:ident $(, $Half:ident)?) => {
pub const fn $Single(a: $Single, b: $Single) -> ($Single, bool) {
$(
if a <= ($Half::MAX as $Single) && b <= ($Half::MAX as $Single) {
let val = match $Half(a as $Half, b as $Half) {
(val, false) => val as $Single,
(val, true) => (val as $Single) + (1 << $Half::BITS),
};
return (val, false);
}
)?
let aa = (a as $Double) * (a as $Double);
let bb = (b as $Double) * (b as $Double);
let (sum, overflow) = aa.overflowing_add(bb);
let mut x = sum;
let mut y;
let mut bit;
if overflow {
// Perform initialization and two iterations assuming word wider than n bits.
// Initialization:
// Set y = 0
// Set bit = 1 << n
// First iteration:
// Since x >= y + bit (because the overflow is equivalent to 1 << n on x):
// x -= y + bit (removing the effect of the overflow on x)
// y = (y >> 1) + bit (y becomes 1 << n)
// bit >>= 2 (bit becomes 1 << (n - 2))
// Second iteration:
// Now x < y (because y is 1 << n):
// y >>= 1 (y becomes 1 << (n - 1))
// bit >>= 2 (bit becomes 1 << (n - 4))
// In effect, we can set y = 1 << (n - 1), and bit = 1 << (n - 4)
y = 1 << ($Double::BITS - 1);
bit = 1 << ($Double::BITS - 4);
} else {
let sum_lz = match NonZero::<$Double>::new(sum) {
None => return (0, false),
Some(s) => s.leading_zeros(),
};
y = 0;
bit = 1 << ($Double::BITS - 2 - sum_lz / 2 * 2);
}
while bit != 0 {
let y_plus_bit = y + bit;
y >>= 1;
if x >= y_plus_bit {
x -= y_plus_bit;
y += bit;
}
bit >>= 2;
}
debug_assert!((y >> $Single::BITS) as $Single == overflow as $Single);
(y as $Single, overflow)
}
};
}
impl_hypot! { u8, u16 }
impl_hypot! { u16, u32, u8 }
impl_hypot! { u32, u64, u16 }
impl_hypot! { u64, u128, u32 }
pub const fn u128(a: u128, b: u128) -> (u128, bool) {
if a <= (u64::MAX as u128) && b <= (u64::MAX as u128) {
let val = match u64(a as u64, b as u64) {
(val, false) => val as u128,
(val, true) => (val as u128) + (1 << 64),
};
return (val, false);
}
let aa = int256::wide_mul_u128(a, a);
let bb = int256::wide_mul_u128(b, b);
let (sum, overflow) = int256::overflowing_add_u256(aa, bb);
let mut x = sum;
let mut y;
let mut bit;
if overflow {
// Perform initialization and two iterations assuming word wider than n bits.
// Initialization:
// Set y = 0
// Set bit = 1 << n
// First iteration:
// Since x >= y + bit (because the overflow is equivalent to 1 << n on x):
// x -= y + bit (removing the effect of the overflow on x)
// y = (y >> 1) + bit (y becomes 1 << n)
// bit >>= 2 (bit becomes 1 << (n - 2))
// Second iteration:
// Now x < y (because y is 1 << n):
// y >>= 1 (y becomes 1 << (n - 1))
// bit >>= 2 (bit becomes 1 << (n - 4))
// In effect, we can set y = 1 << (n - 1), and bit = 1 << (n - 4)
y = U256 {
lo: 0,
hi: 1 << 127,
};
bit = 1 << 124;
} else {
y = U256 { lo: 0, hi: 0 };
bit = match NonZero::<u128>::new(sum.hi) {
None => panic!("small operands; should have used crate::hypot::u64"),
Some(s) => 1 << (126 - s.leading_zeros() / 2 * 2),
};
}
while bit != 0 {
let y_hi_plus_bit = y.hi + bit;
y.hi >>= 1;
if x.hi >= y_hi_plus_bit {
x.hi -= y_hi_plus_bit;
y.hi += bit;
}
bit >>= 2;
}
bit = 1 << 126;
while bit != 0 {
let y_plus_bit = U256 {
lo: y.lo + bit,
hi: y.hi,
};
y.lo = (y.lo >> 1) + ((y.hi & 1) << 127);
y.hi >>= 1;
if (x.hi > y_plus_bit.hi) || (x.hi == y_plus_bit.hi && x.lo >= y_plus_bit.lo) {
x = int256::wrapping_sub_u256(x, y_plus_bit);
y.lo += bit;
}
bit >>= 2
}
debug_assert!(y.hi == overflow as u128);
(y.lo, overflow)
}
#[cfg(test)]
mod tests {
use crate::hypot;
use crate::types::{U1F7, U1F15, U1F31, U1F63, U1F127};
#[test]
fn check_max() {
assert_eq!(hypot::u8(u8::MAX, u8::MAX), (104, true));
assert_eq!(hypot::u16(u16::MAX, u16::MAX), (27_144, true));
assert_eq!(hypot::u32(u32::MAX, u32::MAX), (1_779_033_702, true));
assert_eq!(
hypot::u64(u64::MAX, u64::MAX),
(7_640_891_576_956_012_807, true)
);
assert_eq!(
hypot::u128(u128::MAX, u128::MAX),
(140_949_571_415_070_559_626_692_937_523_481_902_396, true)
);
}
#[test]
fn check_zero() {
assert_eq!(hypot::u8(0, 0), (0, false));
assert_eq!(hypot::u16(0, 0), (0, false));
assert_eq!(hypot::u32(0, 0), (0, false));
assert_eq!(hypot::u64(0, 0), (0, false));
assert_eq!(hypot::u128(0, 0), (0, false));
}
#[test]
fn check_zero_max() {
assert_eq!(hypot::u8(u8::MAX, 0), (u8::MAX, false));
assert_eq!(hypot::u8(0, u8::MAX), (u8::MAX, false));
assert_eq!(hypot::u16(u16::MAX, 0), (u16::MAX, false));
assert_eq!(hypot::u16(0, u16::MAX), (u16::MAX, false));
assert_eq!(hypot::u32(u32::MAX, 0), (u32::MAX, false));
assert_eq!(hypot::u32(0, u32::MAX), (u32::MAX, false));
assert_eq!(hypot::u64(u64::MAX, 0), (u64::MAX, false));
assert_eq!(hypot::u64(0, u64::MAX), (u64::MAX, false));
assert_eq!(hypot::u128(u128::MAX, 0), (u128::MAX, false));
assert_eq!(hypot::u128(0, u128::MAX), (u128::MAX, false));
}
#[test]
fn check_max_plus() {
// hypot(2^n - 1, x) = 2^n; x = sqrt(2^(n+1) - 1)
// e.g. for u32, sqrt(2^33 - 1) = 92681.9
assert_eq!(hypot::u8(u8::MAX, 22), (u8::MAX, false));
assert_eq!(hypot::u8(u8::MAX, 23), (0, true));
assert_eq!(hypot::u16(u16::MAX, 362), (u16::MAX, false));
assert_eq!(hypot::u16(u16::MAX, 363), (0, true));
assert_eq!(hypot::u32(u32::MAX, 92_681), (u32::MAX, false));
assert_eq!(hypot::u32(u32::MAX, 92_682), (0, true));
assert_eq!(hypot::u64(u64::MAX, 6_074_000_999), (u64::MAX, false));
assert_eq!(hypot::u64(u64::MAX, 6_074_001_000), (0, true));
assert_eq!(
hypot::u128(u128::MAX, 26_087_635_650_665_564_424),
(u128::MAX, false)
);
assert_eq!(
hypot::u128(u128::MAX, 26_087_635_650_665_564_425),
(0, true)
);
}
#[test]
fn check_sqrt_2() {
assert_eq!(hypot::u8(1 << 7, 1 << 7), (U1F7::SQRT_2.to_bits(), false));
assert_eq!(
hypot::u16(1 << 15, 1 << 15),
(U1F15::SQRT_2.to_bits(), false)
);
assert_eq!(
hypot::u32(1 << 31, 1 << 31),
(U1F31::SQRT_2.to_bits(), false)
);
assert_eq!(
hypot::u64(1 << 63, 1 << 63),
(U1F63::SQRT_2.to_bits(), false)
);
assert_eq!(
hypot::u128(1 << 127, 1 << 127),
(U1F127::SQRT_2.to_bits(), false)
);
}
}
+72
View File
@@ -0,0 +1,72 @@
// Copyright © 2018–2025 Trevor Spiteri
// This library is free software: you can redistribute it and/or
// modify it under the terms of either
//
// * the Apache License, Version 2.0 or
// * the MIT License
//
// at your option.
//
// You should have recieved copies of the Apache License and the MIT
// License along with the library. If not, see
// <https://www.apache.org/licenses/LICENSE-2.0> and
// <https://opensource.org/licenses/MIT>.
use crate::types::extra::{LeEqU8, LeEqU16, LeEqU32, LeEqU64, LeEqU128};
use crate::{
FixedI8, FixedI16, FixedI32, FixedI64, FixedI128, FixedU8, FixedU16, FixedU32, FixedU64,
FixedU128, Unwrapped, Wrapping,
};
use arbitrary::{Arbitrary, Result as ArbitraryResult, Unstructured};
macro_rules! impl_trait {
($Fixed:ident, $LeEqU:ident, $Inner:ident) => {
impl<'a, Frac> Arbitrary<'a> for $Fixed<Frac> {
#[inline]
fn arbitrary(u: &mut Unstructured<'a>) -> ArbitraryResult<Self> {
Ok(Self::from_bits(<$Inner as Arbitrary<'a>>::arbitrary(u)?))
}
#[inline]
fn size_hint(depth: usize) -> (usize, Option<usize>) {
<$Inner as Arbitrary<'a>>::size_hint(depth)
}
}
impl<'a, Frac: $LeEqU> Arbitrary<'a> for Wrapping<$Fixed<Frac>> {
#[inline]
fn arbitrary(u: &mut Unstructured<'a>) -> ArbitraryResult<Self> {
Ok(Self::from_bits(<$Inner as Arbitrary<'a>>::arbitrary(u)?))
}
#[inline]
fn size_hint(depth: usize) -> (usize, Option<usize>) {
<$Inner as Arbitrary<'a>>::size_hint(depth)
}
}
impl<'a, Frac: $LeEqU> Arbitrary<'a> for Unwrapped<$Fixed<Frac>> {
#[inline]
fn arbitrary(u: &mut Unstructured<'a>) -> ArbitraryResult<Self> {
Ok(Self::from_bits(<$Inner as Arbitrary<'a>>::arbitrary(u)?))
}
#[inline]
fn size_hint(depth: usize) -> (usize, Option<usize>) {
<$Inner as Arbitrary<'a>>::size_hint(depth)
}
}
};
}
impl_trait! { FixedI8, LeEqU8, i8 }
impl_trait! { FixedI16, LeEqU16, i16 }
impl_trait! { FixedI32, LeEqU32, i32 }
impl_trait! { FixedI64, LeEqU64, i64 }
impl_trait! { FixedI128, LeEqU128, i128 }
impl_trait! { FixedU8, LeEqU8, u8 }
impl_trait! { FixedU16, LeEqU16, u16 }
impl_trait! { FixedU32, LeEqU32, u32 }
impl_trait! { FixedU64, LeEqU64, u64 }
impl_trait! { FixedU128, LeEqU128, u128 }
+66
View File
@@ -0,0 +1,66 @@
// Copyright © 2018–2025 Trevor Spiteri
// This library is free software: you can redistribute it and/or
// modify it under the terms of either
//
// * the Apache License, Version 2.0 or
// * the MIT License
//
// at your option.
//
// You should have recieved copies of the Apache License and the MIT
// License along with the library. If not, see
// <https://www.apache.org/licenses/LICENSE-2.0> and
// <https://opensource.org/licenses/MIT>.
use crate::types::extra::{LeEqU8, LeEqU16, LeEqU32, LeEqU64, LeEqU128};
use crate::{
FixedI8, FixedI16, FixedI32, FixedI64, FixedI128, FixedU8, FixedU16, FixedU32, FixedU64,
FixedU128, Unwrapped, Wrapping,
};
use bytemuck::{Contiguous, Pod, TransparentWrapper, Zeroable};
macro_rules! unsafe_impl_traits {
($Fixed:ident, $LeEqU:ident, $Inner:ident) => {
unsafe impl<Frac> Zeroable for $Fixed<Frac> {}
unsafe impl<Frac: 'static> Pod for $Fixed<Frac> {}
unsafe impl<Frac: 'static> Contiguous for $Fixed<Frac> {
type Int = $Inner;
const MAX_VALUE: $Inner = $Inner::MAX;
const MIN_VALUE: $Inner = $Inner::MIN;
}
unsafe impl<Frac> TransparentWrapper<$Inner> for $Fixed<Frac> {}
unsafe impl<Frac: $LeEqU> Zeroable for Wrapping<$Fixed<Frac>> {}
unsafe impl<Frac: $LeEqU> Pod for Wrapping<$Fixed<Frac>> {}
unsafe impl<Frac: $LeEqU> Contiguous for Wrapping<$Fixed<Frac>> {
type Int = $Inner;
const MAX_VALUE: $Inner = $Inner::MAX;
const MIN_VALUE: $Inner = $Inner::MIN;
}
unsafe impl<Frac: $LeEqU> TransparentWrapper<$Fixed<Frac>> for Wrapping<$Fixed<Frac>> {}
unsafe impl<Frac: $LeEqU> Zeroable for Unwrapped<$Fixed<Frac>> {}
unsafe impl<Frac: $LeEqU> Pod for Unwrapped<$Fixed<Frac>> {}
unsafe impl<Frac: $LeEqU> Contiguous for Unwrapped<$Fixed<Frac>> {
type Int = $Inner;
const MAX_VALUE: $Inner = $Inner::MAX;
const MIN_VALUE: $Inner = $Inner::MIN;
}
unsafe impl<Frac: $LeEqU> TransparentWrapper<$Fixed<Frac>> for Unwrapped<$Fixed<Frac>> {}
};
}
// SAFETY: all fixed-point numbers are repr(transparent) over primitive integer
// types which are both Pod and Zeroable, and Wrapping and Unwrapped are both
// repr(transparent) over fixed-point numbers.
unsafe_impl_traits! { FixedI8, LeEqU8, i8 }
unsafe_impl_traits! { FixedI16, LeEqU16, i16 }
unsafe_impl_traits! { FixedI32, LeEqU32, i32 }
unsafe_impl_traits! { FixedI64, LeEqU64, i64 }
unsafe_impl_traits! { FixedI128, LeEqU128, i128 }
unsafe_impl_traits! { FixedU8, LeEqU8, u8 }
unsafe_impl_traits! { FixedU16, LeEqU16, u16 }
unsafe_impl_traits! { FixedU32, LeEqU32, u32 }
unsafe_impl_traits! { FixedU64, LeEqU64, u64 }
unsafe_impl_traits! { FixedU128, LeEqU128, u128 }
+587
View File
@@ -0,0 +1,587 @@
// Copyright © 2018–2025 Trevor Spiteri
// This library is free software: you can redistribute it and/or
// modify it under the terms of either
//
// * the Apache License, Version 2.0 or
// * the MIT License
//
// at your option.
//
// You should have recieved copies of the Apache License and the MIT
// License along with the library. If not, see
// <https://www.apache.org/licenses/LICENSE-2.0> and
// <https://opensource.org/licenses/MIT>.
use crate::consts;
use crate::traits::Fixed;
use crate::types::extra::{
IsLessOrEqual, LeEqU8, LeEqU16, LeEqU32, LeEqU64, LeEqU128, True, U6, U7, U14, U15, U30, U31,
U62, U63, U126, U127,
};
use crate::{
FixedI8, FixedI16, FixedI32, FixedI64, FixedI128, FixedU8, FixedU16, FixedU32, FixedU64,
FixedU128, ParseFixedError,
};
use core::fmt::{Display, Formatter, Result as FmtResult};
use num_traits::bounds::Bounded;
use num_traits::cast::{FromPrimitive, ToPrimitive};
use num_traits::float::FloatConst;
use num_traits::identities::{ConstOne, ConstZero, One, Zero};
use core::error::Error;
use num_traits::Num;
use num_traits::ops::bytes::{FromBytes, ToBytes};
use num_traits::ops::checked::{
CheckedAdd, CheckedDiv, CheckedMul, CheckedNeg, CheckedRem, CheckedShl, CheckedShr, CheckedSub,
};
use num_traits::ops::inv::Inv;
use num_traits::ops::mul_add::{MulAdd, MulAddAssign};
use num_traits::ops::overflowing::{OverflowingAdd, OverflowingMul, OverflowingSub};
use num_traits::ops::saturating::{SaturatingAdd, SaturatingMul, SaturatingSub};
use num_traits::ops::wrapping::{
WrappingAdd, WrappingMul, WrappingNeg, WrappingShl, WrappingShr, WrappingSub,
};
use num_traits::sign::{Signed, Unsigned};
/// An error which can be returned when parsing a fixed-point number
/// with a given radix.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum RadixParseFixedError {
/// The radix is not 2, 8, 10 or 16.
UnsupportedRadix,
/// The string could not be parsed as a fixed-point number.
ParseFixedError(ParseFixedError),
}
impl RadixParseFixedError {
fn message(&self) -> &str {
match self {
RadixParseFixedError::UnsupportedRadix => "unsupported radix",
RadixParseFixedError::ParseFixedError(e) => e.message(),
}
}
}
impl Display for RadixParseFixedError {
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
Display::fmt(self.message(), f)
}
}
impl Error for RadixParseFixedError {
fn description(&self) -> &str {
self.message()
}
}
macro_rules! impl_traits {
($Fixed:ident, $LeEqU:ident, $OneMaxFrac:ident, $Signedness:ident) => {
impl<Frac> Bounded for $Fixed<Frac> {
#[inline]
fn min_value() -> Self {
Self::MIN
}
#[inline]
fn max_value() -> Self {
Self::MAX
}
}
impl<Frac> Zero for $Fixed<Frac> {
#[inline]
fn zero() -> Self {
Self::ZERO
}
#[inline]
fn is_zero(&self) -> bool {
(*self).is_zero()
}
}
impl<Frac> ConstZero for $Fixed<Frac> {
const ZERO: Self = Self::ZERO;
}
impl<Frac: $LeEqU> One for $Fixed<Frac>
where
Frac: IsLessOrEqual<$OneMaxFrac, Output = True>,
{
#[inline]
fn one() -> Self {
Self::ONE
}
}
impl<Frac: $LeEqU> ConstOne for $Fixed<Frac>
where
Frac: IsLessOrEqual<$OneMaxFrac, Output = True>,
{
const ONE: Self = Self::ONE;
}
impl<Frac: $LeEqU> Num for $Fixed<Frac>
where
Frac: IsLessOrEqual<$OneMaxFrac, Output = True>,
{
type FromStrRadixErr = RadixParseFixedError;
#[inline]
fn from_str_radix(str: &str, radix: u32) -> Result<Self, Self::FromStrRadixErr> {
match radix {
2 => Self::from_str_binary(str),
8 => Self::from_str_octal(str),
10 => str.parse(),
16 => Self::from_str_hex(str),
_ => return Err(RadixParseFixedError::UnsupportedRadix),
}
.map_err(RadixParseFixedError::ParseFixedError)
}
}
impl<Frac: $LeEqU> Inv for $Fixed<Frac> {
type Output = Self;
#[inline]
fn inv(self) -> Self::Output {
self.recip()
}
}
if_signed! {
$Signedness;
impl<Frac: $LeEqU> Signed for $Fixed<Frac>
where
Frac: IsLessOrEqual<$OneMaxFrac, Output = True>,
{
#[inline]
fn abs(&self) -> Self {
(*self).abs()
}
#[inline]
fn abs_sub(&self, other: &Self) -> Self {
if *self < *other {
Self::ZERO
} else {
*self - *other
}
}
#[inline]
fn signum(&self) -> Self {
(*self).signum()
}
#[inline]
fn is_positive(&self) -> bool {
(*self).is_positive()
}
#[inline]
fn is_negative(&self) -> bool {
(*self).is_negative()
}
}
}
if_unsigned! {
$Signedness;
impl<Frac: $LeEqU> Unsigned for $Fixed<Frac>
where
Frac: IsLessOrEqual<$OneMaxFrac, Output = True>,
{
}
}
impl<Frac> CheckedAdd for $Fixed<Frac> {
#[inline]
fn checked_add(&self, v: &Self) -> Option<Self> {
(*self).checked_add(*v)
}
}
impl<Frac> CheckedSub for $Fixed<Frac> {
#[inline]
fn checked_sub(&self, v: &Self) -> Option<Self> {
(*self).checked_sub(*v)
}
}
impl<Frac> CheckedNeg for $Fixed<Frac> {
#[inline]
fn checked_neg(&self) -> Option<Self> {
(*self).checked_neg()
}
}
impl<Frac: $LeEqU> CheckedMul for $Fixed<Frac> {
#[inline]
fn checked_mul(&self, v: &Self) -> Option<Self> {
(*self).checked_mul(*v)
}
}
impl<Frac: $LeEqU> CheckedDiv for $Fixed<Frac> {
#[inline]
fn checked_div(&self, v: &Self) -> Option<Self> {
(*self).checked_div(*v)
}
}
impl<Frac> CheckedRem for $Fixed<Frac> {
#[inline]
fn checked_rem(&self, v: &Self) -> Option<Self> {
(*self).checked_rem(*v)
}
}
impl<Frac> CheckedShl for $Fixed<Frac> {
#[inline]
fn checked_shl(&self, rhs: u32) -> Option<Self> {
(*self).checked_shl(rhs)
}
}
impl<Frac> CheckedShr for $Fixed<Frac> {
#[inline]
fn checked_shr(&self, rhs: u32) -> Option<Self> {
(*self).checked_shr(rhs)
}
}
impl<Frac> SaturatingAdd for $Fixed<Frac> {
#[inline]
fn saturating_add(&self, v: &Self) -> Self {
(*self).saturating_add(*v)
}
}
impl<Frac> SaturatingSub for $Fixed<Frac> {
#[inline]
fn saturating_sub(&self, v: &Self) -> Self {
(*self).saturating_sub(*v)
}
}
impl<Frac: $LeEqU> SaturatingMul for $Fixed<Frac> {
#[inline]
fn saturating_mul(&self, v: &Self) -> Self {
(*self).saturating_mul(*v)
}
}
impl<Frac> WrappingAdd for $Fixed<Frac> {
#[inline]
fn wrapping_add(&self, v: &Self) -> Self {
(*self).wrapping_add(*v)
}
}
impl<Frac> WrappingSub for $Fixed<Frac> {
#[inline]
fn wrapping_sub(&self, v: &Self) -> Self {
(*self).wrapping_sub(*v)
}
}
impl<Frac> WrappingNeg for $Fixed<Frac> {
#[inline]
fn wrapping_neg(&self) -> Self {
(*self).wrapping_neg()
}
}
impl<Frac: $LeEqU> WrappingMul for $Fixed<Frac> {
#[inline]
fn wrapping_mul(&self, v: &Self) -> Self {
(*self).wrapping_mul(*v)
}
}
impl<Frac> WrappingShl for $Fixed<Frac> {
#[inline]
fn wrapping_shl(&self, rhs: u32) -> Self {
(*self).wrapping_shl(rhs)
}
}
impl<Frac> WrappingShr for $Fixed<Frac> {
#[inline]
fn wrapping_shr(&self, rhs: u32) -> Self {
(*self).wrapping_shr(rhs)
}
}
impl<Frac> OverflowingAdd for $Fixed<Frac> {
#[inline]
fn overflowing_add(&self, v: &Self) -> (Self, bool) {
(*self).overflowing_add(*v)
}
}
impl<Frac> OverflowingSub for $Fixed<Frac> {
#[inline]
fn overflowing_sub(&self, v: &Self) -> (Self, bool) {
(*self).overflowing_sub(*v)
}
}
impl<Frac: $LeEqU> OverflowingMul for $Fixed<Frac> {
#[inline]
fn overflowing_mul(&self, v: &Self) -> (Self, bool) {
(*self).overflowing_mul(*v)
}
}
impl<Frac, MulFrac: $LeEqU> MulAdd<$Fixed<MulFrac>> for $Fixed<Frac> {
type Output = $Fixed<Frac>;
#[inline]
fn mul_add(self, a: $Fixed<MulFrac>, b: $Fixed<Frac>) -> $Fixed<Frac> {
self.mul_add(a, b)
}
}
impl<Frac, MulFrac: $LeEqU> MulAddAssign<$Fixed<MulFrac>> for $Fixed<Frac> {
#[inline]
fn mul_add_assign(&mut self, a: $Fixed<MulFrac>, b: $Fixed<Frac>) {
*self = self.mul_add(a, b)
}
}
impl<Frac: $LeEqU> FloatConst for $Fixed<Frac> {
#[inline]
fn E() -> Self {
consts::E.to_num()
}
#[inline]
fn FRAC_1_PI() -> Self {
consts::FRAC_1_PI.to_num()
}
#[inline]
fn FRAC_1_SQRT_2() -> Self {
consts::FRAC_1_SQRT_2.to_num()
}
#[inline]
fn FRAC_2_PI() -> Self {
consts::FRAC_2_PI.to_num()
}
#[inline]
fn FRAC_2_SQRT_PI() -> Self {
consts::FRAC_2_SQRT_PI.to_num()
}
#[inline]
fn FRAC_PI_2() -> Self {
consts::FRAC_PI_2.to_num()
}
#[inline]
fn FRAC_PI_3() -> Self {
consts::FRAC_PI_3.to_num()
}
#[inline]
fn FRAC_PI_4() -> Self {
consts::FRAC_PI_4.to_num()
}
#[inline]
fn FRAC_PI_6() -> Self {
consts::FRAC_PI_6.to_num()
}
#[inline]
fn FRAC_PI_8() -> Self {
consts::FRAC_PI_8.to_num()
}
#[inline]
fn LN_10() -> Self {
consts::LN_10.to_num()
}
#[inline]
fn LN_2() -> Self {
consts::LN_2.to_num()
}
#[inline]
fn LOG10_E() -> Self {
consts::LOG10_E.to_num()
}
#[inline]
fn LOG2_E() -> Self {
consts::LOG2_E.to_num()
}
#[inline]
fn PI() -> Self {
consts::PI.to_num()
}
#[inline]
fn SQRT_2() -> Self {
consts::SQRT_2.to_num()
}
#[inline]
fn TAU() -> Self {
consts::TAU.to_num()
}
#[inline]
fn LOG10_2() -> Self {
consts::LOG10_2.to_num()
}
#[inline]
fn LOG2_10() -> Self {
consts::LOG2_10.to_num()
}
}
impl<Frac: $LeEqU> ToPrimitive for $Fixed<Frac> {
#[inline]
fn to_i64(&self) -> Option<i64> {
self.checked_to_num()
}
#[inline]
fn to_u64(&self) -> Option<u64> {
self.checked_to_num()
}
#[inline]
fn to_isize(&self) -> Option<isize> {
self.checked_to_num()
}
#[inline]
fn to_i8(&self) -> Option<i8> {
self.checked_to_num()
}
#[inline]
fn to_i16(&self) -> Option<i16> {
self.checked_to_num()
}
#[inline]
fn to_i32(&self) -> Option<i32> {
self.checked_to_num()
}
#[inline]
fn to_i128(&self) -> Option<i128> {
self.checked_to_num()
}
#[inline]
fn to_usize(&self) -> Option<usize> {
self.checked_to_num()
}
#[inline]
fn to_u8(&self) -> Option<u8> {
self.checked_to_num()
}
#[inline]
fn to_u16(&self) -> Option<u16> {
self.checked_to_num()
}
#[inline]
fn to_u32(&self) -> Option<u32> {
self.checked_to_num()
}
#[inline]
fn to_u128(&self) -> Option<u128> {
self.checked_to_num()
}
#[inline]
fn to_f32(&self) -> Option<f32> {
self.checked_to_num()
}
#[inline]
fn to_f64(&self) -> Option<f64> {
self.checked_to_num()
}
}
impl<Frac: $LeEqU> FromPrimitive for $Fixed<Frac> {
#[inline]
fn from_i64(n: i64) -> Option<Self> {
Self::checked_from_num(n)
}
#[inline]
fn from_u64(n: u64) -> Option<Self> {
Self::checked_from_num(n)
}
#[inline]
fn from_isize(n: isize) -> Option<Self> {
Self::checked_from_num(n)
}
#[inline]
fn from_i8(n: i8) -> Option<Self> {
Self::checked_from_num(n)
}
#[inline]
fn from_i16(n: i16) -> Option<Self> {
Self::checked_from_num(n)
}
#[inline]
fn from_i32(n: i32) -> Option<Self> {
Self::checked_from_num(n)
}
#[inline]
fn from_i128(n: i128) -> Option<Self> {
Self::checked_from_num(n)
}
#[inline]
fn from_usize(n: usize) -> Option<Self> {
Self::checked_from_num(n)
}
#[inline]
fn from_u8(n: u8) -> Option<Self> {
Self::checked_from_num(n)
}
#[inline]
fn from_u16(n: u16) -> Option<Self> {
Self::checked_from_num(n)
}
#[inline]
fn from_u32(n: u32) -> Option<Self> {
Self::checked_from_num(n)
}
#[inline]
fn from_u128(n: u128) -> Option<Self> {
Self::checked_from_num(n)
}
#[inline]
fn from_f32(n: f32) -> Option<Self> {
Self::checked_from_num(n)
}
#[inline]
fn from_f64(n: f64) -> Option<Self> {
Self::checked_from_num(n)
}
}
impl<Frac: $LeEqU> ToBytes for $Fixed<Frac> {
type Bytes = <Self as Fixed>::Bytes;
fn to_be_bytes(&self) -> Self::Bytes {
(*self).to_be_bytes()
}
fn to_le_bytes(&self) -> Self::Bytes {
(*self).to_le_bytes()
}
fn to_ne_bytes(&self) -> Self::Bytes {
(*self).to_ne_bytes()
}
}
impl<Frac: $LeEqU> FromBytes for $Fixed<Frac> {
type Bytes = <Self as Fixed>::Bytes;
fn from_be_bytes(bytes: &Self::Bytes) -> Self {
Self::from_be_bytes(*bytes)
}
fn from_le_bytes(bytes: &Self::Bytes) -> Self {
Self::from_le_bytes(*bytes)
}
fn from_ne_bytes(bytes: &Self::Bytes) -> Self {
Self::from_ne_bytes(*bytes)
}
}
};
}
impl_traits! { FixedI8, LeEqU8, U6, Signed }
impl_traits! { FixedI16, LeEqU16, U14, Signed }
impl_traits! { FixedI32, LeEqU32, U30, Signed }
impl_traits! { FixedI64, LeEqU64, U62, Signed }
impl_traits! { FixedI128, LeEqU128, U126, Signed }
impl_traits! { FixedU8, LeEqU8, U7, Unsigned }
impl_traits! { FixedU16, LeEqU16, U15, Unsigned }
impl_traits! { FixedU32, LeEqU32, U31, Unsigned }
impl_traits! { FixedU64, LeEqU64, U63, Unsigned }
impl_traits! { FixedU128, LeEqU128, U127, Unsigned }
+435
View File
@@ -0,0 +1,435 @@
// Copyright © 2018–2025 Trevor Spiteri
// This library is free software: you can redistribute it and/or
// modify it under the terms of either
//
// * the Apache License, Version 2.0 or
// * the MIT License
//
// at your option.
//
// You should have recieved copies of the Apache License and the MIT
// License along with the library. If not, see
// <https://www.apache.org/licenses/LICENSE-2.0> and
// <https://opensource.org/licenses/MIT>.
use core::hint;
use core::num::NonZero;
#[derive(Clone, Copy, Debug)]
pub struct U256 {
pub lo: u128,
pub hi: u128,
}
#[derive(Clone, Copy, Debug)]
pub struct I256 {
pub lo: u128,
pub hi: i128,
}
#[inline]
pub const fn u256_wrapping_as_i256(a: U256) -> I256 {
I256 {
lo: a.lo,
hi: a.hi as i128,
}
}
#[inline]
pub const fn wrapping_add_u256_u128(a: U256, b: u128) -> U256 {
let (lo, carry) = a.lo.overflowing_add(b);
let hi = a.hi.wrapping_add(carry as u128);
U256 { lo, hi }
}
#[inline]
pub const fn overflowing_add_u128_u256(a: u128, b: U256) -> (u128, bool) {
let (lo, carry) = a.overflowing_add(b.lo);
(lo, carry | (b.hi != 0))
}
#[inline]
pub const fn overflowing_sub_u128_u256(a: u128, b: U256) -> (u128, bool) {
let (lo, borrow) = a.overflowing_sub(b.lo);
(lo, borrow | (b.hi != 0))
}
#[inline]
pub const fn wrapping_neg_u256(a: U256) -> U256 {
let (lo, carry) = (!a.lo).overflowing_add(1);
let hi = (!a.hi).wrapping_add(carry as u128);
U256 { lo, hi }
}
#[inline]
pub const fn overflowing_add_i256_i128(a: I256, b: i128) -> (I256, bool) {
let b = I256 {
lo: b as u128,
hi: b >> 127,
};
let (lo, carry) = a.lo.overflowing_add(b.lo);
// b.hi is in {-1, 0}, and carry is in {0, 1}, so we can add them wrappingly
let b_hi_plus_carry = b.hi.wrapping_add(carry as i128);
let (hi, overflow) = a.hi.overflowing_add(b_hi_plus_carry);
(I256 { lo, hi }, overflow)
}
#[inline]
const fn u128_lo_hi(u: u128) -> (u64, u64) {
(u as u64, (u >> 64) as u64)
}
#[inline]
const fn i128_lo_hi(i: i128) -> (u64, i64) {
(i as u64, (i >> 64) as i64)
}
#[inline]
const fn mul_u64_u64(a: u64, b: u64) -> u128 {
(a as u128).wrapping_mul(b as u128)
}
#[inline]
const fn mul_i64_i64(a: i64, b: i64) -> i128 {
(a as i128).wrapping_mul(b as i128)
}
#[inline]
const fn mul_u64_i64(a: u64, b: i64) -> i128 {
let a = a as i64;
// if a has become negative, we need to add 2^64 * b to the answer
let correction = if a.is_negative() {
(b as i128) << 64
} else {
0
};
(a as i128).wrapping_mul(b as i128).wrapping_add(correction)
}
#[inline]
pub const fn wide_mul_u128(lhs: u128, rhs: u128) -> U256 {
let (ll, lh) = u128_lo_hi(lhs);
let (rl, rh) = u128_lo_hi(rhs);
// 0 <= ll_rl <= 2^128 - 2^65 + 1; ll_rl unit is 1
let ll_rl = mul_u64_u64(ll, rl);
// 0 <= lh_rl <= 2^128 - 2^65 + 1; lh_rl unit is 2^64
let lh_rl = mul_u64_u64(lh, rl);
// 0 <= ll_rh <= 2^128 - 2^65 + 1; ll_rh unit is 2^64
let ll_rh = mul_u64_u64(ll, rh);
// 0 <= lh_rh <= 2^128 - 2^65 + 1; lh_rh unit is 2^128
let lh_rh = mul_u64_u64(lh, rh);
// 0 <= col0 <= 2^64 - 1
// 0 <= col64a <= 2^64 - 2
let (col0, col64a) = u128_lo_hi(ll_rl);
// 0 <= col64b <= 2^128 - 2^64 - 1
let col64b = (col64a as u128).wrapping_add(lh_rl);
// 0 <= col64c <= 2^64 - 1
// 0 <= col128a <= 2^64 - 2
let (col64c, col128a) = u128_lo_hi(col64b);
// 0 <= col64d <= 2^128 - 2^64
let col64d = (col64c as u128).wrapping_add(ll_rh);
// 0 <= col64 <= 2^64 - 1
// 0 <= col128b <= 2^64 - 1
let (col64, col128b) = u128_lo_hi(col64d);
// Since both col0 and col64 fit in 64 bits, ans0 sum will never overflow.
let ans0 = (col0 as u128) | ((col64 as u128) << 64);
// Since lhs * rhs fits in 256 bits, ans128 sum will never overflow.
let ans128 = lh_rh
.wrapping_add(col128a as u128)
.wrapping_add(col128b as u128);
U256 {
lo: ans0,
hi: ans128,
}
}
#[inline]
pub const fn wide_mul_i128(lhs: i128, rhs: i128) -> I256 {
let (ll, lh) = i128_lo_hi(lhs);
let (rl, rh) = i128_lo_hi(rhs);
// 0 <= ll_rl <= 2^128 - 2^65 + 1; ll_rl unit is 1; must be unsigned to hold all range!
let ll_rl = mul_u64_u64(ll, rl);
// -2^127 + 2^63 <= lh_rl <= 2^127 - 2^64 - 2^63 + 1; lh_rl unit is 2^64
let lh_rl = mul_u64_i64(rl, lh);
// -2^127 + 2^63 <= ll_rh <= 2^127 - 2^64 - 2^63 + 1; ll_rh unit is 2^64
let ll_rh = mul_u64_i64(ll, rh);
// -2^126 + 2^63 <= lh_rh <= 2^126; lh_rh unit is 2^128
let lh_rh = mul_i64_i64(lh, rh);
// 0 <= col0 <= 2^64 - 1
// 0 <= col64a <= 2^64 - 2
let (col0, col64a) = u128_lo_hi(ll_rl);
// -2^127 + 2^63 <= col64b <= 2^127 - 2^63 - 1
let col64b = (col64a as i128).wrapping_add(lh_rl);
// 0 <= col64c <= 2^64 - 1
// -2^63 <= col128a <= 2^63 - 1
let (col64c, col128a) = i128_lo_hi(col64b);
// -2^127 + 2^63 <= col64d <= 2^127 - 2^63
let col64d = (col64c as i128).wrapping_add(ll_rh);
// 0 <= col64 <= 2^64 - 1
// -2^63 <= col128b <= 2^63 - 1
let (col64, col128b) = i128_lo_hi(col64d);
// Since both col0 and col64 fit in 64 bits, ans0 sum will never overflow.
let ans0 = (col0 as u128) | ((col64 as u128) << 64);
// Since lhs * rhs fits in 256 bits, ans128 sum will never overflow.
let ans128 = lh_rh
.wrapping_add(col128a as i128)
.wrapping_add(col128b as i128);
I256 {
lo: ans0,
hi: ans128,
}
}
#[inline]
pub const fn shl_u256_max_128(a: U256, sh: u32) -> U256 {
if sh == 0 {
a
} else if sh == 128 {
U256 { lo: a.hi, hi: 0 }
} else {
U256 {
lo: (a.lo >> sh) | (a.hi << (128 - sh)),
hi: a.hi >> sh,
}
}
}
#[inline]
pub const fn shl_i256_max_128(a: I256, sh: u32) -> I256 {
if sh == 0 {
a
} else if sh == 128 {
I256 {
lo: a.hi as u128,
hi: a.hi >> 127,
}
} else {
I256 {
lo: (a.lo >> sh) | (a.hi << (128 - sh)) as u128,
hi: a.hi >> sh,
}
}
}
/// # Safety
///
/// d must have msb set.
#[inline]
const unsafe fn div_half_u128(r: u128, d: u128, next_half: u64) -> (u128, u128) {
let (dl, dh) = u128_lo_hi(d);
// SAFETY: we know d has the most significant bit set because we normalized
unsafe {
hint::assert_unchecked(dh != 0);
}
let (mut q, rr) = (r / (dh as u128), r % (dh as u128));
let m = q * (dl as u128);
let mut r = (next_half as u128) + (rr << 64);
if r < m {
q -= 1;
let (new_r, overflow) = r.overflowing_add(d);
r = if !overflow && new_r < m {
q -= 1;
new_r.wrapping_add(d)
} else {
new_r
};
}
r = r.wrapping_sub(m);
(r, q)
}
#[inline]
pub const fn div_rem_u256_u128(mut n: U256, d: NonZero<u128>) -> (U256, u128) {
let zeros = d.leading_zeros();
let (mut r, d) = if zeros == 0 {
(0, d.get())
} else {
let n2 = n.hi >> (128 - zeros);
n.hi = (n.hi << zeros) | (n.lo >> (128 - zeros));
n.lo <<= zeros;
(n2, d.get() << zeros)
};
// SAFETY: we know that d has msb set because it is not zero and it was
// shifted to the right by the number of leading zeros.
let (nhl, nhh) = u128_lo_hi(n.hi);
let (new_r, qhh) = unsafe { div_half_u128(r, d, nhh) };
r = new_r;
let (new_r, qhl) = unsafe { div_half_u128(r, d, nhl) };
r = new_r;
let (nll, nlh) = u128_lo_hi(n.lo);
let (new_r, qlh) = unsafe { div_half_u128(r, d, nlh) };
r = new_r;
let (new_r, qll) = unsafe { div_half_u128(r, d, nll) };
r = new_r;
let q = U256 {
lo: qll + (qlh << 64),
hi: qhl + (qhh << 64),
};
r >>= zeros;
(q, r)
}
// must not result in overflow
#[inline]
pub const fn div_rem_i256_i128_no_overflow(n: I256, d: NonZero<i128>) -> (I256, i128) {
let (n_neg, n_abs) = if n.hi < 0 {
let (nl, overflow) = n.lo.overflowing_neg();
let nh = n.hi.wrapping_neg().wrapping_sub(overflow as i128) as u128;
(true, U256 { lo: nl, hi: nh })
} else {
let nl = n.lo;
let nh = n.hi as u128;
(false, U256 { lo: nl, hi: nh })
};
let (d_neg, d_abs) = if d.get() < 0 {
// SAFETY: d and -d are not zero
let ud = unsafe { NonZero::<u128>::new_unchecked(d.get().wrapping_neg() as u128) };
(true, ud)
} else {
// SAFETY: d is not zero
let ud = unsafe { NonZero::<u128>::new_unchecked(d.get() as u128) };
(false, ud)
};
let (q_abs, r_abs) = div_rem_u256_u128(n_abs, d_abs);
let q = if n_neg == d_neg {
let ql = q_abs.lo;
let qh = q_abs.hi as i128;
I256 { lo: ql, hi: qh }
} else {
let (ql, overflow) = q_abs.lo.overflowing_neg();
let qh = q_abs.hi.wrapping_neg().wrapping_sub(overflow as u128) as i128;
I256 { lo: ql, hi: qh }
};
let r = if n_neg {
r_abs.wrapping_neg() as i128
} else {
r_abs as i128
};
(q, r)
}
#[inline]
pub const fn overflowing_add_u256(a: U256, b: U256) -> (U256, bool) {
let (lo, carry0) = a.lo.overflowing_add(b.lo);
let (hi, carry1_a) = a.hi.overflowing_add(b.hi);
let (hi, carry1_b) = hi.overflowing_add(carry0 as u128);
(U256 { lo, hi }, carry1_a | carry1_b)
}
#[inline]
pub const fn wrapping_sub_u256(a: U256, b: U256) -> U256 {
let (lo, borrow) = a.lo.overflowing_sub(b.lo);
let hi = a.hi.wrapping_sub(b.hi).wrapping_sub(borrow as u128);
U256 { lo, hi }
}
#[inline]
pub const fn overflowing_shl_u256_into_u128(a: U256, sh: u32) -> (u128, bool) {
if sh == 128 {
(a.hi, false)
} else if sh == 0 {
(a.lo, a.hi != 0)
} else {
let lo = a.lo >> sh;
let hi = a.hi << (128 - sh);
(lo | hi, a.hi >> sh != 0)
}
}
#[inline]
pub const fn overflowing_shl_i256_into_i128(a: I256, sh: u32) -> (i128, bool) {
if sh == 128 {
(a.hi, false)
} else if sh == 0 {
let ans = a.lo as i128;
(ans, a.hi != ans >> 127)
} else {
let lo = (a.lo >> sh) as i128;
let hi = a.hi << (128 - sh);
let ans = lo | hi;
(ans, a.hi >> sh != ans >> 127)
}
}
#[cfg(test)]
mod tests {
use super::*;
fn check_udiv_rem(num: U256, den: u128) {
let (quot, rem) = div_rem_u256_u128(num, NonZero::<u128>::new(den).unwrap());
assert!(rem <= den);
let ql_d = wide_mul_u128(quot.lo, den);
let qh_d = wide_mul_u128(quot.hi, den);
assert!(qh_d.hi == 0);
let prod_lo = ql_d.lo;
let prod_hi = ql_d.hi.checked_add(qh_d.lo).unwrap();
let (sum_lo, carry) = prod_lo.overflowing_add(rem);
let sum_hi = prod_hi.checked_add(u128::from(carry)).unwrap();
assert!(sum_lo == num.lo && sum_hi == num.hi);
}
fn check_idiv_rem_signs(num: I256, den: i128) {
let (quot, rem) = div_rem_i256_i128_no_overflow(num, NonZero::<i128>::new(den).unwrap());
assert!(rem.unsigned_abs() <= den.unsigned_abs());
if num.hi < 0 {
assert!(rem <= 0);
} else {
assert!(rem >= 0);
}
if (num.hi < 0) == (den < 0) {
assert!(quot.hi >= 0);
} else {
assert!(quot.hi <= 0);
}
}
#[test]
fn test_udiv_rem() {
for d in 1u8..=255 {
for n1 in (0u8..=255).step_by(15) {
for n0 in (0u8..=255).step_by(15) {
let d = (u128::from(d) << 120) | 1;
let n1 = (u128::from(n1) << 120) | 1;
let n0 = (u128::from(n0) << 120) | 1;
check_udiv_rem(U256 { lo: n0, hi: n1 }, d);
}
}
}
}
#[test]
fn test_idiv_rem_signs() {
for d in -128..=127 {
if d == 0 {
continue;
}
for n1 in (-120..=120).step_by(15) {
for n0 in (0u8..=255).step_by(15) {
let d = (i128::from(d) << 120) | 1;
let n1 = (i128::from(n1) << 120) | 1;
let n0 = (u128::from(n0) << 120) | 1;
check_idiv_rem_signs(I256 { lo: n0, hi: n1 }, d);
}
}
}
}
}
+207
View File
@@ -0,0 +1,207 @@
// Copyright © 2018–2025 Trevor Spiteri
// This library is free software: you can redistribute it and/or
// modify it under the terms of either
//
// * the Apache License, Version 2.0 or
// * the MIT License
//
// at your option.
//
// You should have recieved copies of the Apache License and the MIT
// License along with the library. If not, see
// <https://www.apache.org/licenses/LICENSE-2.0> and
// <https://opensource.org/licenses/MIT>.
use crate::types::extra::U0;
use crate::{
FixedI8, FixedI16, FixedI32, FixedI64, FixedI128, FixedU8, FixedU16, FixedU32, FixedU64,
FixedU128,
};
macro_rules! make_helper_common {
($t:ident) => {
use crate::helpers::{ToFixedHelper, Widest};
use core::cmp::Ordering;
};
}
macro_rules! make_helper {
($i: ident, $u:ident) => {
pub mod $i {
make_helper_common! { $i }
#[inline]
pub const fn neg_abs(val: $i) -> (bool, $u) {
(val.is_negative(), val.unsigned_abs())
}
#[inline]
pub fn to_fixed_helper(
val: $i,
src_frac_bits: i32,
dst_frac_bits: u32,
dst_int_bits: u32,
) -> ToFixedHelper {
let src_bits = $i::BITS as i32;
let dst_bits = (dst_frac_bits + dst_int_bits) as i32;
if val == 0 {
return ToFixedHelper {
bits: Widest::Unsigned(0),
dir: Ordering::Equal,
overflow: false,
};
}
let need_to_shr = src_frac_bits - dst_frac_bits as i32;
let leading = if val >= 0 {
val.leading_zeros()
} else {
(!val).leading_zeros() - 1
};
let overflow = src_bits - dst_bits > need_to_shr + leading as i32;
let bits_128 = i128::from(val);
let (bits, lost_bits) = match need_to_shr {
-0x7fff_ffff..=-128 => (0, false),
-127..=-1 => (bits_128 << -need_to_shr, false),
0 => (bits_128, false),
1..=127 => {
let shifted = bits_128 >> need_to_shr;
(shifted, shifted << need_to_shr != bits_128)
}
128..=0x7fff_ffff => (bits_128 >> 127, true),
_ => unreachable!(),
};
let dir = if lost_bits {
Ordering::Less
} else {
Ordering::Equal
};
let bits = if val >= 0 {
Widest::Unsigned(bits as u128)
} else {
Widest::Negative(bits)
};
ToFixedHelper {
bits,
dir,
overflow,
}
}
}
pub mod $u {
make_helper_common! { $u }
#[inline]
pub fn neg_abs(val: $u) -> (bool, $u) {
(false, val)
}
#[inline]
pub fn to_fixed_helper(
val: $u,
src_frac_bits: i32,
dst_frac_bits: u32,
dst_int_bits: u32,
) -> ToFixedHelper {
let src_bits = $u::BITS as i32;
let dst_bits = (dst_frac_bits + dst_int_bits) as i32;
if val == 0 {
return ToFixedHelper {
bits: Widest::Unsigned(0),
dir: Ordering::Equal,
overflow: false,
};
}
let leading_zeros = val.leading_zeros();
let need_to_shr = src_frac_bits - dst_frac_bits as i32;
let overflow = src_bits - dst_bits > need_to_shr + leading_zeros as i32;
let bits_128 = u128::from(val);
let (bits, lost_bits) = match need_to_shr {
-0x7fff_ffff..=-128 => (0, false),
-127..=-1 => (bits_128 << -need_to_shr, false),
0 => (bits_128, false),
1..=127 => {
let shifted = bits_128 >> need_to_shr;
(shifted, shifted << need_to_shr != bits_128)
}
128..=0x7fff_ffff => (0, true),
_ => unreachable!(),
};
let dir = if lost_bits {
Ordering::Less
} else {
Ordering::Equal
};
ToFixedHelper {
bits: Widest::Unsigned(bits),
dir,
overflow,
}
}
}
};
}
make_helper! { i8, u8 }
make_helper! { i16, u16 }
make_helper! { i32, u32 }
make_helper! { i64, u64 }
make_helper! { i128, u128 }
pub struct IntFixed<T>(pub T);
macro_rules! make_int_fixed {
($I:ident -> $F:ident) => {
impl IntFixed<$I> {
#[inline]
pub fn fixed(self) -> $F<U0> {
$F::<U0>::from_bits(self.0)
}
#[inline]
pub fn int(f: $F<U0>) -> $I {
f.to_bits()
}
}
};
($T:ident as $I:ident -> $F:ident) => {
impl IntFixed<$T> {
#[inline]
pub fn fixed(self) -> $F<U0> {
$F::<U0>::from_bits(self.0 as $I)
}
#[inline]
pub fn int(f: $F<U0>) -> $T {
f.to_bits() as $T
}
}
};
}
make_int_fixed! { i8 -> FixedI8 }
make_int_fixed! { i16 -> FixedI16 }
make_int_fixed! { i32 -> FixedI32 }
make_int_fixed! { i64 -> FixedI64 }
make_int_fixed! { i128 -> FixedI128 }
#[cfg(target_pointer_width = "16")]
make_int_fixed! { isize as i16 -> FixedI16 }
#[cfg(target_pointer_width = "32")]
make_int_fixed! { isize as i32 -> FixedI32 }
#[cfg(target_pointer_width = "64")]
make_int_fixed! { isize as i64 -> FixedI64 }
make_int_fixed! { u8 -> FixedU8 }
make_int_fixed! { u16 -> FixedU16 }
make_int_fixed! { u32 -> FixedU32 }
make_int_fixed! { u64 -> FixedU64 }
make_int_fixed! { u128 -> FixedU128 }
#[cfg(target_pointer_width = "16")]
make_int_fixed! { usize as u16 -> FixedU16 }
#[cfg(target_pointer_width = "32")]
make_int_fixed! { usize as u32 -> FixedU32 }
#[cfg(target_pointer_width = "64")]
make_int_fixed! { usize as u64 -> FixedU64 }
+306
View File
@@ -0,0 +1,306 @@
// Copyright © 2018–2025 Trevor Spiteri
// This library is free software: you can redistribute it and/or
// modify it under the terms of either
//
// * the Apache License, Version 2.0 or
// * the MIT License
//
// at your option.
//
// You should have recieved copies of the Apache License and the MIT
// License along with the library. If not, see
// <https://www.apache.org/licenses/LICENSE-2.0> and
// <https://opensource.org/licenses/MIT>.
use crate::int256;
use crate::int256::U256;
use core::num::NonZeroU128;
macro_rules! make_inv_lerp {
($i:ident, $u:ident, $ii:ident, $uu:ident, $uuf:expr) => {
pub const fn $i(v: $i, start: $i, end: $i, frac_bits: u32) -> ($i, bool) {
assert!(start != end, "empty range");
// 0x00 ≤ diff_abs ≤ 0xff
let (diff_abs, diff_neg) = if v >= start {
(v.wrapping_sub(start) as $u, false)
} else {
(start.wrapping_sub(v) as $u, true)
};
let wide_diff_abs = (diff_abs as $uu) << $u::BITS;
// 0x01 ≤ range_abs ≤ 0xff
let (range_abs, range_neg) = if end >= start {
(end.wrapping_sub(start) as $u, false)
} else {
(start.wrapping_sub(end) as $u, true)
};
let neg = diff_neg != range_neg;
let (wide_ret, overflow1) = if neg {
// ensure that difference is not 0
if diff_abs == 0 {
return (0, false);
}
// 0x0001 ≤ ceil_div ≤ 0xff00
let ceil_div = (wide_diff_abs - 1) / (range_abs as $uu) + 1;
// 0x0000 ≤ add ≤ 0x00ff
let add = $uuf >> frac_bits;
// If frac_bits is 0: add = 0x00ff; 0x0000 ≤ shifted ≤ 0x00ff
// If frac_bits is max: add = 0x0000; 0x0000 ≤ shifted ≤ 0xff00
let shifted = (ceil_div + add) >> ($u::BITS - frac_bits);
let wide_ret = shifted.wrapping_neg() as $ii;
(wide_ret, wide_ret > 0)
} else {
// 0x0000 ≤ floor_div ≤ 0xff00
let floor_div = wide_diff_abs / (range_abs as $uu);
let shifted = floor_div >> ($u::BITS - frac_bits);
let wide_ret = shifted as $ii;
(wide_ret, wide_ret < 0)
};
let ret = wide_ret as $i;
let overflow2 = wide_ret != ret as $ii;
(ret, overflow1 | overflow2)
}
pub const fn $u(v: $u, start: $u, end: $u, frac_bits: u32) -> ($u, bool) {
assert!(start != end, "empty range");
// 0x00 ≤ diff ≤ 0xff
let (diff_abs, diff_neg) = if v >= start {
(v - start, false)
} else {
(start - v, true)
};
let wide_diff_abs = (diff_abs as $uu) << $u::BITS;
// 0x01 ≤ range_abs ≤ 0xff
let (range_abs, range_neg) = if end >= start {
(end - start, false)
} else {
(start - end, true)
};
let neg = diff_neg != range_neg;
let (wide_ret, overflow1) = if neg {
// ensure that difference is not 0
if diff_abs == 0 {
return (0, false);
}
// 0x0001 ≤ ceil_div ≤ 0xff00
let ceil_div = (wide_diff_abs - 1) / (range_abs as $uu) + 1;
// 0x0000 ≤ add ≤ 0x00ff
let add = $uuf >> frac_bits;
// If frac_bits is 0: add = 0x00ff; 0x0000 ≤ shifted ≤ 0x00ff
// If frac_bits is max: add = 0x0000; 0x0000 ≤ shifted ≤ 0xff00
let shifted = (ceil_div + add) >> ($u::BITS - frac_bits);
let wide_ret = shifted.wrapping_neg();
(wide_ret, wide_ret > 0)
} else {
// 0x0000 ≤ floor_div ≤ 0xff00
let floor_div = wide_diff_abs / (range_abs as $uu);
let shifted = floor_div >> ($u::BITS - frac_bits);
(shifted, false)
};
let ret = wide_ret as $u;
let overflow2 = wide_ret != ret as $uu;
(ret, overflow1 | overflow2)
}
};
}
make_inv_lerp! { i8, u8, i16, u16, 0xff_u16 }
make_inv_lerp! { i16, u16, i32, u32, 0xffff_u32 }
make_inv_lerp! { i32, u32, i64, u64, 0xffff_ffff_u64 }
make_inv_lerp! { i64, u64, i128, u128, 0xffff_ffff_ffff_ffff_u128 }
pub const fn i128(v: i128, start: i128, end: i128, frac_bits: u32) -> (i128, bool) {
// 0x00 ≤ diff_abs ≤ 0xff
let (diff_abs, diff_neg) = if v >= start {
(v.wrapping_sub(start) as u128, false)
} else {
(start.wrapping_sub(v) as u128, true)
};
let wide_diff_abs = U256 {
lo: 0,
hi: diff_abs,
};
// 0x01 ≤ range_abs ≤ 0xff
let (range_abs, range_neg) = if end >= start {
(end.wrapping_sub(start) as u128, false)
} else {
(start.wrapping_sub(end) as u128, true)
};
let Some(range_abs) = NonZeroU128::new(range_abs) else {
panic!("empty range");
};
let neg = diff_neg != range_neg;
if neg {
// ensure that difference is not 0
if diff_abs == 0 {
return (0, false);
}
// 0x0001 ≤ ceil_div ≤ 0xff00
let (div, rem) = int256::div_rem_u256_u128(wide_diff_abs, range_abs);
let ceil_div = if rem > 0 {
int256::wrapping_add_u256_u128(div, 1)
} else {
div
};
// 0x0000 ≤ add ≤ 0x00ff
let add = if frac_bits == 128 {
0
} else {
u128::MAX >> frac_bits
};
// If frac_bits is 0: add = 0x00ff; 0x0000 ≤ shifted ≤ 0x00ff
// If frac_bits is max: add = 0x0000; 0x0000 ≤ shifted ≤ 0xff00
let sum = int256::wrapping_add_u256_u128(ceil_div, add);
let shifted = int256::shl_u256_max_128(sum, 128 - frac_bits);
let neg_shifted = int256::wrapping_neg_u256(shifted);
let ret = neg_shifted.lo as i128;
let overflow = if ret.is_negative() {
neg_shifted.hi != u128::MAX
} else if ret == 0 {
neg_shifted.hi != 0
} else {
true
};
(ret, overflow)
} else {
// 0x0000 ≤ floor_div ≤ 0xff00
let (floor_div, _) = int256::div_rem_u256_u128(wide_diff_abs, range_abs);
let shifted = int256::shl_u256_max_128(floor_div, 128 - frac_bits);
let ret = shifted.lo as i128;
let overflow = (ret < 0) | (shifted.hi != 0);
(ret, overflow)
}
}
pub const fn u128(v: u128, start: u128, end: u128, frac_bits: u32) -> (u128, bool) {
// 0x00 ≤ diff ≤ 0xff
let (diff_abs, diff_neg) = if v >= start {
(v - start, false)
} else {
(start - v, true)
};
let wide_diff_abs = U256 {
lo: 0,
hi: diff_abs,
};
// 0x01 ≤ range_abs ≤ 0xff
let (range_abs, range_neg) = if end >= start {
(end - start, false)
} else {
(start - end, true)
};
let Some(range_abs) = NonZeroU128::new(range_abs) else {
panic!("empty range");
};
let neg = diff_neg != range_neg;
if neg {
// ensure that difference is not 0
if diff_abs == 0 {
return (0, false);
}
// 0x0001 ≤ ceil_div ≤ 0xff00
let (div, rem) = int256::div_rem_u256_u128(wide_diff_abs, range_abs);
let ceil_div = if rem > 0 {
int256::wrapping_add_u256_u128(div, 1)
} else {
div
};
// 0x0000 ≤ add ≤ 0x00ff
let add = if frac_bits == 128 {
0
} else {
u128::MAX >> frac_bits
};
// If frac_bits is 0: add = 0x00ff; 0x0000 ≤ shifted ≤ 0x00ff
// If frac_bits is max: add = 0x0000; 0x0000 ≤ shifted ≤ 0xff00
let sum = int256::wrapping_add_u256_u128(ceil_div, add);
let shifted = int256::shl_u256_max_128(sum, 128 - frac_bits);
let neg_shifted = int256::wrapping_neg_u256(shifted);
let ret = neg_shifted.lo;
let overflow = (ret != 0) | (neg_shifted.hi != 0);
(ret, overflow)
} else {
// 0x0000 ≤ floor_div ≤ 0xff00
let (floor_div, _) = int256::div_rem_u256_u128(wide_diff_abs, range_abs);
let shifted = int256::shl_u256_max_128(floor_div, 128 - frac_bits);
let ret = shifted.lo;
let overflow = shifted.hi != 0;
(ret, overflow)
}
}
#[cfg(test)]
mod tests {
use crate::inv_lerp;
#[test]
fn inv_lerp_i8() {
assert_eq!(inv_lerp::i8(-128, -128, 127, 0), (0, false));
assert_eq!(inv_lerp::i8(127, 127, -128, 8), (0, false));
assert_eq!(inv_lerp::i8(-1, -128, 127, 6), (31, false));
assert_eq!(inv_lerp::i8(-90, -100, -110, 6), (-64, false));
assert_eq!(inv_lerp::i8(50, 0, 2, 2), (100, false));
assert_eq!(inv_lerp::i8(100, 0, 2, 2), (-56, true));
assert_eq!(inv_lerp::i8(-26, -1, 0, 2), (-100, false));
assert_eq!(inv_lerp::i8(50, 0, -1, 2), (56, true));
}
#[test]
fn inv_lerp_u8() {
assert_eq!(inv_lerp::u8(0, 0, 255, 0), (0, false));
assert_eq!(inv_lerp::u8(255, 255, 0, 8), (0, false));
assert_eq!(inv_lerp::u8(255, 0, 255, 0), (1, false));
assert_eq!(inv_lerp::u8(128, 255, 0, 7), (63, false));
assert_eq!(inv_lerp::u8(51, 52, 53, 6), (192, true));
assert_eq!(inv_lerp::u8(100, 0, 2, 2), (200, false));
assert_eq!(inv_lerp::u8(200, 0, 2, 2), (200 - 56, true));
}
#[test]
fn inv_lerp_i128() {
assert_eq!(inv_lerp::i128(-128, -128, 127, 0), (0, false));
assert_eq!(inv_lerp::i128(127, 127, -128, 128), (0, false));
assert_eq!(inv_lerp::i128(-1, -128, 127, 6), (31, false));
assert_eq!(inv_lerp::i128(-90, -100, -110, 126), ((-64) << 120, false));
assert_eq!(
inv_lerp::i128(i128::MAX / 2, 0, 2, 2),
(i128::MAX / 2 * 2, false)
);
assert_eq!(
inv_lerp::i128(i128::MAX, 0, 2, 2),
(i128::MAX.wrapping_mul(2), true)
);
assert_eq!(
inv_lerp::i128(i128::MIN / 4 - 1, -1, 0, 2),
(i128::MIN, false)
);
assert_eq!(
inv_lerp::i128(i128::MAX, 0, -1, 2),
(i128::MAX.wrapping_mul(-4), true)
);
}
#[test]
fn inv_lerp_u128() {
assert_eq!(inv_lerp::u128(0, 0, 255, 0), (0, false));
assert_eq!(inv_lerp::u128(255, 255, 0, 128), (0, false));
assert_eq!(inv_lerp::u128(255, 0, 255, 0), (1, false));
assert_eq!(
inv_lerp::u128(1 << 127, u128::MAX, 0, 127),
((1 << 126) - 1, false)
);
assert_eq!(inv_lerp::u128(51, 52, 53, 126), (192 << 120, true));
assert_eq!(
inv_lerp::u128(u128::MAX / 2, 0, 2, 2),
(u128::MAX / 2 * 2, false)
);
assert_eq!(
inv_lerp::u128(u128::MAX, 0, 2, 2),
(u128::MAX.wrapping_mul(2), true)
);
}
}
+224
View File
@@ -0,0 +1,224 @@
// Copyright © 2018–2025 Trevor Spiteri
// This library is free software: you can redistribute it and/or
// modify it under the terms of either
//
// * the Apache License, Version 2.0 or
// * the MIT License
//
// at your option.
//
// You should have recieved copies of the Apache License and the MIT
// License along with the library. If not, see
// <https://www.apache.org/licenses/LICENSE-2.0> and
// <https://opensource.org/licenses/MIT>.
use crate::int256;
macro_rules! make_lerp {
($i:ident, $u:ident, $ii:ident, $uu:ident, $uu1:expr) => {
pub const fn $i(r: $i, start: $i, end: $i, frac_bits: u32) -> ($i, bool) {
// 0x00 ≤ r_abs ≤ 0x80
let (r_abs, r_neg) = if r >= 0 {
(r as $u, false)
} else {
(r.wrapping_neg() as $u, true)
};
// 0x00 ≤ range_abs ≤ 0xff
let (range_abs, range_neg) = if end >= start {
(end.wrapping_sub(start) as $u, false)
} else {
(start.wrapping_sub(end) as $u, true)
};
// 0x0000 ≤ wide_abs ≤ 0x7f80
let wide_abs = (r_abs as $uu) * (range_abs as $uu);
let wide_neg = r_neg != range_neg;
let wide_uns = if wide_neg {
// 0x0000 ≤ add ≤ 0x00ff
let add = ($uu1 << frac_bits) - 1;
// If frac_bits is 0: add = 0x0000; 0x0000 ≤ shifted ≤ 0x7f80
// If frac_bits is max: add = 0x00ff; 0x0000 ≤ shifted ≤ 0x0080
let shifted = (wide_abs + add) >> frac_bits;
shifted.wrapping_neg()
} else {
wide_abs >> frac_bits
};
let wide = wide_uns as $ii;
let (wide_ret, overflow1) = wide.overflowing_add(start as $ii);
let ret = wide_ret as $i;
let overflow2 = wide_ret != ret as $ii;
(ret, overflow1 | overflow2)
}
pub const fn $u(r: $u, start: $u, end: $u, frac_bits: u32) -> ($u, bool) {
// 0x00 ≤ range_abs ≤ 0xff
let (range_abs, range_neg) = if end >= start {
(end - start, false)
} else {
(start - end, true)
};
// 0x0000 ≤ wide_abs ≤ 0xfe01
let wide_abs = (r as $uu) * (range_abs as $uu);
let (wide_ret, overflow1) = if range_neg {
// 0x0000 ≤ add ≤ 0x00ff
let add = ($uu1 << frac_bits) - 1;
// If frac_bits is 0: add = 0x0000; 0x0000 ≤ shifted ≤ 0xfe01
// If frac_bits is max: add = 0x00ff; 0x0000 ≤ shifted ≤ 0x00ff
let shifted = (wide_abs + add) >> frac_bits;
(start as $uu).overflowing_sub(shifted)
} else {
let shifted = wide_abs >> frac_bits;
(start as $uu).overflowing_add(shifted)
};
let ret = wide_ret as $u;
let overflow2 = wide_ret != ret as $uu;
(ret, overflow1 | overflow2)
}
};
}
make_lerp! { i8, u8, i16, u16, 1u16 }
make_lerp! { i16, u16, i32, u32, 1u32 }
make_lerp! { i32, u32, i64, u64, 1u64 }
make_lerp! { i64, u64, i128, u128, 1u128 }
pub const fn i128(r: i128, start: i128, end: i128, frac_bits: u32) -> (i128, bool) {
// 0x00 ≤ r_abs ≤ 0x80
let (r_abs, r_neg) = if r >= 0 {
(r as u128, false)
} else {
(r.wrapping_neg() as u128, true)
};
// 0x00 ≤ range_abs ≤ 0xff
let (range_abs, range_neg) = if end >= start {
(end.wrapping_sub(start) as u128, false)
} else {
(start.wrapping_sub(end) as u128, true)
};
// 0x0000 ≤ wide_abs ≤ 0x7f80
let wide_abs = int256::wide_mul_u128(r_abs, range_abs);
let wide_neg = r_neg != range_neg;
let wide_uns = if wide_neg {
// 0x0000 ≤ add ≤ 0x00ff
let add = if frac_bits == 128 {
u128::MAX
} else {
(1u128 << frac_bits) - 1
};
// If frac_bits is 0: add = 0x0000; 0x0000 ≤ shifted ≤ 0x7f80
// If frac_bits is max: add = 0x00ff; 0x0000 ≤ shifted ≤ 0x0080
let sum = int256::wrapping_add_u256_u128(wide_abs, add);
let shifted = int256::shl_u256_max_128(sum, frac_bits);
int256::wrapping_neg_u256(shifted)
} else {
int256::shl_u256_max_128(wide_abs, frac_bits)
};
let wide = int256::u256_wrapping_as_i256(wide_uns);
let (wide_ret, overflow1) = int256::overflowing_add_i256_i128(wide, start);
let ret = wide_ret.lo as i128;
let overflow2 = if ret < 0 {
wide_ret.hi != -1
} else {
wide_ret.hi != 0
};
(ret, overflow1 | overflow2)
}
pub const fn u128(r: u128, start: u128, end: u128, frac_bits: u32) -> (u128, bool) {
// 0x00 ≤ range_abs ≤ 0xff
let (range_abs, range_neg) = if end >= start {
(end - start, false)
} else {
(start - end, true)
};
// 0x0000 ≤ wide_abs ≤ 0xfe01
let wide_abs = int256::wide_mul_u128(r, range_abs);
if range_neg {
// 0x0000 ≤ add ≤ 0x00ff
let add = if frac_bits == 128 {
u128::MAX
} else {
(1u128 << frac_bits) - 1
};
// If frac_bits is 0: add = 0x0000; 0x0000 ≤ shifted ≤ 0xfe01
// If frac_bits is max: add = 0x00ff; 0x0000 ≤ shifted ≤ 0x00ff
let sum = int256::wrapping_add_u256_u128(wide_abs, add);
let shifted = int256::shl_u256_max_128(sum, frac_bits);
int256::overflowing_sub_u128_u256(start, shifted)
} else {
let shifted = int256::shl_u256_max_128(wide_abs, frac_bits);
int256::overflowing_add_u128_u256(start, shifted)
}
}
#[cfg(test)]
mod tests {
use crate::lerp;
#[test]
fn lerp_i8() {
assert_eq!(lerp::i8(0, -128, 127, 0), (-128, false));
assert_eq!(lerp::i8(0, 127, -128, 8), (127, false));
assert_eq!(lerp::i8(1, -128, 127, 1), (-1, false));
assert_eq!(lerp::i8(-64, -100, -110, 6), (-90, false));
assert_eq!(lerp::i8(2 << 2, 0, 50, 2), (100, false));
assert_eq!(lerp::i8(2 << 2, 0, 100, 2), (-56, true));
assert_eq!(lerp::i8((-1) << 2, 50, 0, 2), (100, false));
assert_eq!(lerp::i8((-1) << 2, 100, 0, 2), (-56, true));
}
#[test]
fn lerp_u8() {
assert_eq!(lerp::u8(0, 0, 255, 0), (0, false));
assert_eq!(lerp::u8(0, 255, 0, 8), (255, false));
assert_eq!(lerp::u8(1, 0, 255, 0), (255, false));
assert_eq!(lerp::u8(128, 255, 0, 7), (0, false));
assert_eq!(lerp::u8(2 << 2, 0, 100, 2), (200, false));
assert_eq!(lerp::u8(2 << 2, 0, 200, 2), (200 - 56, true));
}
#[test]
fn lerp_i128() {
assert_eq!(lerp::i128(0, -128, 127, 0), (-128, false));
assert_eq!(lerp::i128(0, 127, -128, 128), (127, false));
assert_eq!(lerp::i128(1, -128, 127, 1), (-1, false));
assert_eq!(lerp::i128(1 << 98, 127, -128, 100), (63, false));
assert_eq!(lerp::i128((-64) << 120, -100, -110, 126), (-90, false));
assert_eq!(
lerp::i128(2 << 2, 0, i128::MAX / 2, 2),
(i128::MAX / 2 * 2, false)
);
assert_eq!(
lerp::i128(2 << 2, 0, i128::MAX, 2),
(i128::MAX.wrapping_mul(2), true)
);
assert_eq!(
lerp::i128((-1) << 2, i128::MAX / 2, 0, 2),
(i128::MAX / 2 * 2, false)
);
assert_eq!(
lerp::i128((-1) << 2, i128::MAX, 0, 2),
(i128::MAX.wrapping_mul(2), true)
);
}
#[test]
fn lerp_u128() {
assert_eq!(lerp::u128(0, 0, 255, 0), (0, false));
assert_eq!(lerp::u128(0, 255, 0, 128), (255, false));
assert_eq!(lerp::u128(1, 0, 255, 0), (255, false));
assert_eq!(lerp::u128(128 << 120, 255, 0, 127), (0, false));
assert_eq!(
lerp::u128(2 << 2, 0, u128::MAX / 2, 2),
(u128::MAX / 2 * 2, false)
);
assert_eq!(
lerp::u128(2 << 2, 0, u128::MAX, 2),
(u128::MAX.wrapping_mul(2), true)
);
}
}
+1635
View File
File diff suppressed because it is too large. Load diff
+178
View File
@@ -0,0 +1,178 @@
// Copyright © 2018–2025 Trevor Spiteri
// This library is free software: you can redistribute it and/or
// modify it under the terms of either
//
// * the Apache License, Version 2.0 or
// * the MIT License
//
// at your option.
//
// You should have recieved copies of the Apache License and the MIT
// License along with the library. If not, see
// <https://www.apache.org/licenses/LICENSE-2.0> and
// <https://opensource.org/licenses/MIT>.
#[derive(Clone, Copy, Debug)]
pub struct Base(u32);
impl Base {
pub const fn new(base: u32) -> Option<Base> {
if base >= 2 { Some(Base(base)) } else { None }
}
pub const fn get(self) -> u32 {
self.0
}
}
macro_rules! impl_int_part {
($u:ident) => {
pub const fn $u(val: NonZero<$u>, base: Base) -> i32 {
const MAX_TABLE_SIZE: usize = ($u::BITS.ilog2() - 1) as usize;
let val = val.get();
let base = base.get();
let baseu = base as $u;
if baseu as u32 != base || val < baseu {
return 0;
}
// base^1, base^2, base^4, etc.
let mut base_powers: [$u; MAX_TABLE_SIZE] = [0; MAX_TABLE_SIZE];
let mut i = 0;
let mut partial_log = 1u32;
let mut partial_val = baseu;
loop {
let square = match partial_val.checked_mul(partial_val) {
Some(s) if val >= s => s,
_ => break,
};
base_powers[i] = partial_val;
i += 1;
partial_log *= 2;
partial_val = square;
}
let mut dlog = partial_log;
while i > 0 {
i -= 1;
dlog /= 2;
if let Some(mid) = partial_val.checked_mul(base_powers[i]) {
if val >= mid {
partial_val = mid;
partial_log += dlog;
}
}
}
return partial_log as i32;
}
};
}
pub mod int_part {
use crate::log::Base;
use core::num::NonZero;
impl_int_part! { u8 }
impl_int_part! { u16 }
impl_int_part! { u32 }
impl_int_part! { u64 }
impl_int_part! { u128 }
}
macro_rules! impl_frac_part {
($u:ident) => {
pub const fn $u(val: NonZero<$u>, base: Base) -> i32 {
const MAX_TABLE_SIZE: usize = ($u::BITS.ilog2() - 1) as usize;
let val = val.get();
let base = base.get();
let baseu = base as $u;
if baseu as u32 != base || val.checked_mul(baseu).is_none() {
return -1;
}
// base^1, base^2, base^4, etc.
let mut base_powers: [$u; MAX_TABLE_SIZE] = [0; MAX_TABLE_SIZE];
let mut i = 0;
let mut partial_log = 1u32;
let mut partial_val = baseu;
loop {
let square = match partial_val.checked_mul(partial_val) {
Some(s) if val.checked_mul(s).is_some() => s,
_ => break,
};
base_powers[i] = partial_val;
i += 1;
partial_log *= 2;
partial_val = square;
}
let mut dlog = partial_log;
while i > 0 {
i -= 1;
dlog /= 2;
if let Some(mid) = partial_val.checked_mul(base_powers[i]) {
if val.checked_mul(mid).is_some() {
partial_val = mid;
partial_log += dlog;
}
}
}
return -1 - partial_log as i32;
}
};
}
pub mod frac_part {
use crate::log::Base;
use core::num::NonZero;
impl_frac_part! { u8 }
impl_frac_part! { u16 }
impl_frac_part! { u32 }
impl_frac_part! { u64 }
impl_frac_part! { u128 }
}
#[cfg(test)]
mod tests {
use crate::log;
use crate::log::Base;
use core::num::NonZero;
// these tests require the maximum table sizes
#[test]
fn check_table_size_is_sufficient() {
let bin = Base::new(2).unwrap();
assert_eq!(log::int_part::u8(NonZero::<u8>::MAX, bin), 7);
assert_eq!(log::int_part::u16(NonZero::<u16>::MAX, bin), 15);
assert_eq!(log::int_part::u32(NonZero::<u32>::MAX, bin), 31);
assert_eq!(log::int_part::u64(NonZero::<u64>::MAX, bin), 63);
assert_eq!(log::int_part::u128(NonZero::<u128>::MAX, bin), 127);
assert_eq!(log::frac_part::u8(NonZero::<u8>::new(1).unwrap(), bin), -8);
assert_eq!(
log::frac_part::u16(NonZero::<u16>::new(1).unwrap(), bin),
-16
);
assert_eq!(
log::frac_part::u32(NonZero::<u32>::new(1).unwrap(), bin),
-32
);
assert_eq!(
log::frac_part::u64(NonZero::<u64>::new(1).unwrap(), bin),
-64
);
assert_eq!(
log::frac_part::u128(NonZero::<u128>::new(1).unwrap(), bin),
-128
);
}
}
+325
View File
@@ -0,0 +1,325 @@
// Copyright © 2018–2025 Trevor Spiteri
// This library is free software: you can redistribute it and/or
// modify it under the terms of either
//
// * the Apache License, Version 2.0 or
// * the MIT License
//
// at your option.
//
// You should have recieved copies of the Apache License and the MIT
// License along with the library. If not, see
// <https://www.apache.org/licenses/LICENSE-2.0> and
// <https://opensource.org/licenses/MIT>.
// the value must be positive for all public functions
pub mod frac_part {
use core::num::NonZero;
// MAX / 1000 (0) < val <= MAX (255)
// -3 <= log <= -1
#[inline]
pub const fn u8(val: NonZero<u8>) -> i32 {
let val = val.get();
if val > 25 {
-1
} else if val > 2 {
-2
} else {
-3
}
}
// MAX / 100_000 (0) < val <= MAX (65_535)
// -5 <= log <= -1
#[inline]
pub const fn u16(val: NonZero<u16>) -> i32 {
let val = val.get();
if val > 6553 {
-1
} else if val > 655 {
-2
} else if val > 65 {
-3
} else if val > 6 {
-4
} else {
-5
}
}
// 0 < val <= MAX
// -10 <= log <= -1
pub const fn u32(val: NonZero<u32>) -> i32 {
const MAX: u32 = u32::MAX;
let mut val = val.get();
if val <= MAX / 100_000_000 {
val *= 100_000_000;
// At this point, we have shifted out 8 digits, and we can only shift out 2 more.
// We can only check up to -2 more because -10 <= log <= -8.
if val > MAX / 10 {
-9
} else {
debug_assert!(val > MAX / 100);
-10
}
} else {
greater_equal_m8_u32(val)
}
}
// 0 < val <= MAX
// -20 <= log <= -1
pub const fn u64(val: NonZero<u64>) -> i32 {
const MAX: u64 = u64::MAX;
let mut val = val.get();
let mut log = 0;
if val <= MAX / 10_000_000_000_000_000 {
// After this, we can only check up to -4 more because -20 <= log <= -16.
// That is, we can skip the checks against MAX / 100_000_000 and MAX / 10_000.
val *= 10_000_000_000_000_000;
log += -16;
} else {
if val <= MAX / 100_000_000 {
val *= 100_000_000;
log += -8;
}
if val <= MAX / 10_000 {
val *= 10_000;
log += -4;
}
}
log + if val > MAX / 10 {
-1
} else if val > MAX / 100 {
-2
} else if val > MAX / 1000 {
-3
} else {
debug_assert!(val > MAX / 10_000);
-4
}
}
// 0 < val <= MAX
// -39 <= log <= -1
pub const fn u128(val: NonZero<u128>) -> i32 {
const MAX: u128 = u128::MAX;
let mut val = val.get();
let mut log = 0;
if val <= MAX / 100_000_000_000_000_000_000_000_000_000_000 {
val *= 100_000_000_000_000_000_000_000_000_000_000;
// At this point we have shifted out 32 digits, and we can only shift out 7 more.
// We can
// * use val >> 96 because we have shifted out 32 decimal digits (106 bits)
// * only check up to -8 more because -39 <= log <= -32
return -32 + greater_equal_m8_u32((val >> 96) as u32);
}
if val <= MAX / 10_000_000_000_000_000 {
val *= 10_000_000_000_000_000;
log += -16;
}
if val <= MAX / 100_000_000 {
val *= 100_000_000;
log += -8;
}
if log == -24 {
// At this point we have shifted out 24 digits, and we can only shift out 15 more.
// We can
// * use val >> 64 because we have shifted out 24 decimal digits (79 bits)
// * only check up to -8 more because -32 <= log <= -24
return -24 + greater_equal_m8_u64((val >> 64) as u64);
}
// We have *not* shifted out enough decimal digits, so we must *not* convert to u32 or u64.
if val <= MAX / 10_000 {
val *= 10_000;
log += -4;
}
log + if val > MAX / 10 {
-1
} else if val > MAX / 100 {
-2
} else if val > MAX / 1000 {
-3
} else {
debug_assert!(val > MAX / 10_000);
-4
}
}
// MAX / 100_000_000 < val <= MAX
// -8 <= log <= -1
const fn greater_equal_m8_u32(mut val: u32) -> i32 {
const MAX: u32 = u32::MAX;
debug_assert!(val > MAX / 100_000_000);
let mut log = 0;
if val <= MAX / 10_000 {
val *= 10_000;
log += -4;
}
log + if val > MAX / 10 {
-1
} else if val > MAX / 100 {
-2
} else if val > MAX / 1000 {
-3
} else {
debug_assert!(val > MAX / 10_000);
-4
}
}
// MAX / 100_000_000 < val <= MAX
// -8 <= log <= 1
const fn greater_equal_m8_u64(mut val: u64) -> i32 {
const MAX: u64 = u64::MAX;
debug_assert!(val > MAX / 100_000_000);
let mut log = 0;
if val <= MAX / 10_000 {
val *= 10_000;
log += -4;
}
log + if val > MAX / 10 {
-1
} else if val > MAX / 100 {
-2
} else if val > MAX / 1000 {
-3
} else {
debug_assert!(val > MAX / 10_000);
-4
}
}
}
// check log10() and log(10) in tandem
#[cfg(test)]
mod tests {
use crate::log;
use crate::log::Base;
use crate::log10;
use core::num::NonZero;
const DEC: Base = match Base::new(10) {
Some(s) => s,
None => unreachable!(),
};
macro_rules! check_loop {
($T:ident) => {
for i in 0..=$T::MAX.ilog10() as i32 {
let p = (10 as $T).pow(i as u32);
let nz = NonZero::<$T>::new(p).unwrap();
if i > 0 {
let nz_m1 = NonZero::<$T>::new(p - 1).unwrap();
assert_eq!((p - 1).ilog10() as i32, i - 1);
assert_eq!(log::int_part::$T(nz_m1, DEC), i - 1);
}
assert_eq!(p.ilog10() as i32, i);
assert_eq!(log::int_part::$T(nz, DEC), i);
let nz_p1 = NonZero::<$T>::new(p + 1).unwrap();
assert_eq!((p + 1).ilog10() as i32, i);
assert_eq!(log::int_part::$T(nz_p1, DEC), i);
}
for i in 0..-log10::frac_part::$T(NonZero::<$T>::new(1).unwrap()) {
let p = <$T>::MAX / (10 as $T).pow(i as u32);
let nz = NonZero::<$T>::new(p).unwrap();
if p > 1 {
let nz_m1 = NonZero::<$T>::new(p - 1).unwrap();
assert_eq!(log10::frac_part::$T(nz_m1), -1 - i);
assert_eq!(log::frac_part::$T(nz_m1, DEC), -1 - i);
}
assert_eq!(log10::frac_part::$T(nz), -1 - i);
assert_eq!(log::frac_part::$T(nz, DEC), -1 - i);
if i > 0 {
let nz_p1 = NonZero::<$T>::new(p + 1).unwrap();
assert_eq!(log10::frac_part::$T(nz_p1), -i);
assert_eq!(log::frac_part::$T(nz_p1, DEC), -i);
}
}
};
}
#[test]
fn log10_u8() {
let one = NonZero::<u8>::new(1).unwrap();
assert_eq!(1u8.ilog10(), 0);
assert_eq!(log::int_part::u8(one, DEC), 0);
assert_eq!(u8::MAX.ilog10(), 2);
assert_eq!(log::int_part::u8(NonZero::<u8>::MAX, DEC), 2);
assert_eq!(log10::frac_part::u8(one), -3);
assert_eq!(log::frac_part::u8(one, DEC), -3);
assert_eq!(log10::frac_part::u8(NonZero::<u8>::MAX), -1);
assert_eq!(log::frac_part::u8(NonZero::<u8>::MAX, DEC), -1);
check_loop! { u8 }
}
#[test]
fn log10_u16() {
let one = NonZero::<u16>::new(1).unwrap();
assert_eq!(1u16.ilog10(), 0);
assert_eq!(log::int_part::u16(one, DEC), 0);
assert_eq!(u16::MAX.ilog10(), 4);
assert_eq!(log::int_part::u16(NonZero::<u16>::MAX, DEC), 4);
assert_eq!(log10::frac_part::u16(one), -5);
assert_eq!(log::frac_part::u16(one, DEC), -5);
assert_eq!(log10::frac_part::u16(NonZero::<u16>::MAX), -1);
assert_eq!(log::frac_part::u16(NonZero::<u16>::MAX, DEC), -1);
check_loop! { u16 }
}
#[test]
fn log10_u32() {
let one = NonZero::<u32>::new(1).unwrap();
assert_eq!(1u32.ilog10(), 0);
assert_eq!(log::int_part::u32(NonZero::<u32>::new(1).unwrap(), DEC), 0);
assert_eq!(u32::MAX.ilog10(), 9);
assert_eq!(log::int_part::u32(NonZero::<u32>::MAX, DEC), 9);
assert_eq!(log10::frac_part::u32(one), -10);
assert_eq!(log::frac_part::u32(one, DEC), -10);
assert_eq!(log10::frac_part::u32(NonZero::<u32>::MAX), -1);
assert_eq!(log::frac_part::u32(NonZero::<u32>::MAX, DEC), -1);
check_loop! { u32 }
}
#[test]
fn log10_u64() {
let one = NonZero::<u64>::new(1).unwrap();
assert_eq!(1u64.ilog10(), 0);
assert_eq!(log::int_part::u64(one, DEC), 0);
assert_eq!(u64::MAX.ilog10(), 19);
assert_eq!(log::int_part::u64(NonZero::<u64>::MAX, DEC), 19);
assert_eq!(log10::frac_part::u64(one), -20);
assert_eq!(log::frac_part::u64(one, DEC), -20);
assert_eq!(log10::frac_part::u64(NonZero::<u64>::MAX), -1);
assert_eq!(log::frac_part::u64(NonZero::<u64>::MAX, DEC), -1);
check_loop! { u64 }
}
#[test]
fn log10_u128() {
let one = NonZero::<u128>::new(1).unwrap();
assert_eq!(1u128.ilog10(), 0);
assert_eq!(log::int_part::u128(one, DEC), 0);
assert_eq!(u128::MAX.ilog10(), 38);
assert_eq!(log::int_part::u128(NonZero::<u128>::MAX, DEC), 38);
assert_eq!(log10::frac_part::u128(one), -39);
assert_eq!(log::frac_part::u128(one, DEC), -39);
assert_eq!(log10::frac_part::u128(NonZero::<u128>::MAX), -1);
assert_eq!(log::frac_part::u128(NonZero::<u128>::MAX, DEC), -1);
check_loop! { u128 }
}
}
+70
View File
@@ -0,0 +1,70 @@
// Copyright © 2018–2025 Trevor Spiteri
// This library is free software: you can redistribute it and/or
// modify it under the terms of either
//
// * the Apache License, Version 2.0 or
// * the MIT License
//
// at your option.
//
// You should have recieved copies of the Apache License and the MIT
// License along with the library. If not, see
// <https://www.apache.org/licenses/LICENSE-2.0> and
// <https://opensource.org/licenses/MIT>.
macro_rules! if_signed {
(Signed; $($rem:tt)+) => {
$($rem)+
};
(Unsigned; $($rem:tt)+) => {};
}
macro_rules! if_unsigned {
(Signed; $($rem:tt)+) => {};
(Unsigned; $($rem:tt)+) => {
$($rem)+
};
}
macro_rules! if_signed_unsigned {
(Signed, $signed:expr, $unsigned:expr $(,)?) => {
$signed
};
(Unsigned, $signed:expr, $unsigned:expr $(,)?) => {
$unsigned
};
}
macro_rules! if_signed_else_empty_str {
(Signed; $($signed:tt)*) => {
concat!($($signed)*)
};
(Unsigned; $($signed:tt)*) => {
""
};
}
macro_rules! if_unsigned_else_empty_str {
(Signed; $($unsigned:tt)*) => {
""
};
(Unsigned; $($unsigned:tt)*) => {
concat!($($unsigned)*)
};
}
macro_rules! doc_comment {
($comment:expr; $($tt:tt)*) => {
#[doc = $comment]
$($tt)*
};
}
macro_rules! comment {
($($comment:expr),* $(,)?; $($tt:tt)*) => {
doc_comment! {
concat!($($comment),*);
$($tt)*
}
};
}
+413
View File
@@ -0,0 +1,413 @@
// Copyright © 2018–2025 Trevor Spiteri
// This library is free software: you can redistribute it and/or
// modify it under the terms of either
//
// * the Apache License, Version 2.0 or
// * the MIT License
//
// at your option.
//
// You should have recieved copies of the Apache License and the MIT
// License along with the library. If not, see
// <https://www.apache.org/licenses/LICENSE-2.0> and
// <https://opensource.org/licenses/MIT>.
macro_rules! fixed_const {
(
Self = $Self:ident,
Signedness = $Signedness:ident,
LeEqU = $LeEqU:ident,
[nm4 ..= n] = [$nm4:literal, $nm3:literal, $nm2:literal, $nm1:literal, $n:literal],
[LeEqUC0 ..= LeEqUC3] = [$LeEqUC0:ident, $LeEqUC1:ident, $LeEqUC2:ident, $LeEqUC3:ident],
) => {
impl<Frac: Unsigned> $Self<Frac> {
const fn from_const<SrcFrac: Unsigned>(src: FixedU128<SrcFrac>) -> $Self<Frac> {
let right = SrcFrac::U32 - Frac::U32;
let bits128 = if right == 128 {
0
} else {
src.to_bits() >> right
};
$Self::from_bits(bits128 as _)
}
}
comment! {
"This block contains constants in the range 0&nbsp;<&nbsp;<i>x</i>&nbsp;<&nbsp;0.5.
# Examples
```rust
use fixed::consts;
use fixed::types::extra::U", $n, ";
use fixed::", stringify!($Self), ";
type Fix = ", stringify!($Self), "<U", $n, ">;
assert_eq!(Fix::LOG10_2, Fix::from_num(consts::LOG10_2));
```
";
impl<Frac: $LeEqU> $Self<Frac> {
/// 1/τ = 0.159154…
pub const FRAC_1_TAU: $Self<Frac> = Self::from_const(consts::FRAC_1_TAU);
/// 2/τ = 0.318309…
pub const FRAC_2_TAU: $Self<Frac> = Self::from_const(consts::FRAC_2_TAU);
/// π/8 = 0.392699…
pub const FRAC_PI_8: $Self<Frac> = Self::from_const(consts::FRAC_PI_8);
/// 1/π = 0.318309…
pub const FRAC_1_PI: $Self<Frac> = Self::from_const(consts::FRAC_1_PI);
/// 1/√2π = 0.398942…
pub const FRAC_1_SQRT_2PI: $Self<Frac> = Self::from_const(consts::FRAC_1_SQRT_2PI);
/// log<sub>10</sub> 2 = 0.301029…
pub const LOG10_2: $Self<Frac> = Self::from_const(consts::LOG10_2);
/// log<sub>10</sub> e = 0.434294…
pub const LOG10_E: $Self<Frac> = Self::from_const(consts::LOG10_E);
}
}
comment! {
"This block contains constants in the range 0.5&nbsp;≤&nbsp;<i>x</i>&nbsp;<&nbsp;1",
if_signed_else_empty_str! {
$Signedness;
", and &minus;1.
These constants are not representable in signed fixed-point numbers with less
than 1 integer bit"
},
".
# Examples
```rust
use fixed::consts;
use fixed::types::extra::U",
if_signed_unsigned!($Signedness, $nm1, $n),
";
use fixed::", stringify!($Self), ";
type Fix = ", stringify!($Self), "<U",
if_signed_unsigned!($Signedness, $nm1, $n),
">;
assert_eq!(Fix::LN_2, Fix::from_num(consts::LN_2));
assert!(0.5 <= Fix::LN_2 && Fix::LN_2 < 1);
```
",
if_signed_else_empty_str! {
$Signedness;
"
The following example fails to compile, since the maximum
representable value with ", $n, " fractional bits and 0 integer
bits is <&nbsp;0.5.
```rust,compile_fail
use fixed::consts;
use fixed::types::extra::U", $n, ";
use fixed::", stringify!($Self), ";
type Fix = ", stringify!($Self), "<U", $n, ">;
let _ = Fix::LN_2;
```
"
};
impl<Frac: Unsigned> $Self<Frac>
where
Frac: IsLessOrEqual<$LeEqUC0, Output = True>,
{
if_signed! {
$Signedness;
comment! {
"Negative one.
# Examples
```rust
use fixed::types::extra::U", $nm1, ";
use fixed::", stringify!($Self), ";
type Fix = ", stringify!($Self), "<U", $nm1, ">;
assert_eq!(Fix::NEG_ONE, Fix::from_num(-1));
```
The following would fail as
<code>[", stringify!($Self), "]&lt;[U", $nm1, "]></code>
cannot represent 1, so there is no
<code>[", stringify!($Self), "]::&lt;[U", $nm1, "]>::[ONE]</code>.
[ONE]: ", stringify!($Self), "::ONE
```rust,compile_fail
use fixed::types::extra::U", $nm1, ";
use fixed::", stringify!($Self), ";
const _ERROR: ", stringify!($Self), "<U", $nm1, "> = ", stringify!($Self), "::ONE.unwrapped_neg();
```
";
pub const NEG_ONE: $Self<Frac> = Self::from_bits(-1 << Frac::U32);
}
}
/// τ/8 = 0.785398…
pub const FRAC_TAU_8: $Self<Frac> = Self::from_const(consts::FRAC_TAU_8);
/// τ/12 = 0.523598…
pub const FRAC_TAU_12: $Self<Frac> = Self::from_const(consts::FRAC_TAU_12);
/// 4/τ = 0.636619…
pub const FRAC_4_TAU: $Self<Frac> = Self::from_const(consts::FRAC_4_TAU);
/// π/4 = 0.785398…
pub const FRAC_PI_4: $Self<Frac> = Self::from_const(consts::FRAC_PI_4);
/// π/6 = 0.523598…
pub const FRAC_PI_6: $Self<Frac> = Self::from_const(consts::FRAC_PI_6);
/// 2/π = 0.636619…
pub const FRAC_2_PI: $Self<Frac> = Self::from_const(consts::FRAC_2_PI);
/// 1/√π = 0.564189…
pub const FRAC_1_SQRT_PI: $Self<Frac> = Self::from_const(consts::FRAC_1_SQRT_PI);
/// 1/√2 = 0.707106…
pub const FRAC_1_SQRT_2: $Self<Frac> = Self::from_const(consts::FRAC_1_SQRT_2);
/// 1/√3 = 0.577350…
pub const FRAC_1_SQRT_3: $Self<Frac> = Self::from_const(consts::FRAC_1_SQRT_3);
/// ln 2 = 0.693147…
pub const LN_2: $Self<Frac> = Self::from_const(consts::LN_2);
/// The golden ratio conjugate, Φ = 1/φ = 0.618033…
pub const FRAC_1_PHI: $Self<Frac> = Self::from_const(consts::FRAC_1_PHI);
/// The Euler-Mascheroni constant, γ = 0.577215…
pub const GAMMA: $Self<Frac> = Self::from_const(consts::GAMMA);
/// Catalan’s constant = 0.915965…
pub const CATALAN: $Self<Frac> = Self::from_const(consts::CATALAN);
}
}
comment! {
"This block contains constants in the range 1&nbsp;≤&nbsp;<i>x</i>&nbsp;<&nbsp;2.
These constants are not representable in ",
if_signed_unsigned!($Signedness, "signed", "unsigned"),
" fixed-point numbers with less than ",
if_signed_unsigned!($Signedness, "2 integer bits", "1 integer bit"),
".
# Examples
```rust
use fixed::consts;
use fixed::types::extra::U",
if_signed_unsigned!($Signedness, $nm2, $nm1),
";
use fixed::", stringify!($Self), ";
type Fix = ", stringify!($Self), "<U",
if_signed_unsigned!($Signedness, $nm2, $nm1),
">;
assert_eq!(Fix::LOG2_E, Fix::from_num(consts::LOG2_E));
assert!(1 <= Fix::LOG2_E && Fix::LOG2_E < 2);
```
The following example fails to compile, since the maximum
representable value with ",
if_signed_unsigned!($Signedness, $nm1, $n),
" fractional bits and ",
if_signed_unsigned!($Signedness, "1 integer bit", "0 integer bits"),
" is <&nbsp;1.
```rust,compile_fail
use fixed::consts;
use fixed::types::extra::U",
if_signed_unsigned!($Signedness, $nm1, $n),
";
use fixed::", stringify!($Self), ";
type Fix = ", stringify!($Self), "<U",
if_signed_unsigned!($Signedness, $nm1, $n),
">;
let _ = Fix::LOG2_E;
```
";
impl<Frac: Unsigned> $Self<Frac>
where
Frac: IsLessOrEqual<$LeEqUC1, Output = True>,
{
comment! {
"One.
# Examples
```rust
use fixed::types::extra::U4;
use fixed::", stringify!($Self), ";
type Fix = ", stringify!($Self), "<U4>;
assert_eq!(Fix::ONE, Fix::from_num(1));
```
";
pub const ONE: $Self<Frac> = Self::from_bits(1 << Frac::U32);
}
/// τ/4 = 1.57079…
pub const FRAC_TAU_4: $Self<Frac> = Self::from_const(consts::FRAC_TAU_4);
/// τ/6 = 1.04719…
pub const FRAC_TAU_6: $Self<Frac> = Self::from_const(consts::FRAC_TAU_6);
/// π/2 = 1.57079…
pub const FRAC_PI_2: $Self<Frac> = Self::from_const(consts::FRAC_PI_2);
/// π/3 = 1.04719…
pub const FRAC_PI_3: $Self<Frac> = Self::from_const(consts::FRAC_PI_3);
/// √π = 1.77245…
pub const SQRT_PI: $Self<Frac> = Self::from_const(consts::SQRT_PI);
/// 2/√π = 1.12837…
pub const FRAC_2_SQRT_PI: $Self<Frac> = Self::from_const(consts::FRAC_2_SQRT_PI);
/// √2 = 1.41421…
pub const SQRT_2: $Self<Frac> = Self::from_const(consts::SQRT_2);
/// √3 = 1.73205…
pub const SQRT_3: $Self<Frac> = Self::from_const(consts::SQRT_3);
/// √e = 1.64872…
pub const SQRT_E: $Self<Frac> = Self::from_const(consts::SQRT_E);
/// log<sub>2</sub> e = 1.44269…
pub const LOG2_E: $Self<Frac> = Self::from_const(consts::LOG2_E);
/// The golden ratio, φ = 1.61803…
pub const PHI: $Self<Frac> = Self::from_const(consts::PHI);
/// √φ = 1.27201…
pub const SQRT_PHI: $Self<Frac> = Self::from_const(consts::SQRT_PHI);
}
}
comment! {
"This block contains constants in the range 2&nbsp;≤&nbsp;<i>x</i>&nbsp;<&nbsp;4.
These constants are not representable in ",
if_signed_unsigned!($Signedness, "signed", "unsigned"),
" fixed-point numbers with less than ",
if_signed_unsigned!($Signedness, 3, 2),
" integer bits.
# Examples
```rust
use fixed::consts;
use fixed::types::extra::U",
if_signed_unsigned!($Signedness, $nm3, $nm2),
";
use fixed::", stringify!($Self), ";
type Fix = ", stringify!($Self), "<U",
if_signed_unsigned!($Signedness, $nm3, $nm2),
">;
assert_eq!(Fix::E, Fix::from_num(consts::E));
assert!(2 <= Fix::E && Fix::E < 4);
```
The following example fails to compile, since the maximum
representable value with ",
if_signed_unsigned!($Signedness, $nm2, $nm1),
" fractional bits and ",
if_signed_unsigned!($Signedness, "2 integer bits", "1 integer bit"),
" is <&nbsp;2.
```rust,compile_fail
use fixed::consts;
use fixed::types::extra::U",
if_signed_unsigned!($Signedness, $nm2, $nm1),
";
use fixed::", stringify!($Self), ";
type Fix = ", stringify!($Self), "<U",
if_signed_unsigned!($Signedness, $nm2, $nm1),
">;
let _ = Fix::E;
```
";
impl<Frac: Unsigned> $Self<Frac>
where
Frac: IsLessOrEqual<$LeEqUC2, Output = True>,
{
/// τ/2 = 3.14159…
pub const FRAC_TAU_2: $Self<Frac> = Self::from_const(consts::FRAC_TAU_2);
/// τ/3 = 2.09439…
pub const FRAC_TAU_3: $Self<Frac> = Self::from_const(consts::FRAC_TAU_3);
/// Archimedes’ constant, π = 3.14159…
pub const PI: $Self<Frac> = Self::from_const(consts::PI);
/// √2π = 2.50662…
pub const SQRT_2PI: $Self<Frac> = Self::from_const(consts::SQRT_2PI);
/// Euler’s number, e = 2.71828…
pub const E: $Self<Frac> = Self::from_const(consts::E);
/// log<sub>2</sub> 10 = 3.32192…
pub const LOG2_10: $Self<Frac> = Self::from_const(consts::LOG2_10);
/// ln 10 = 2.30258…
pub const LN_10: $Self<Frac> = Self::from_const(consts::LN_10);
}
}
comment! {
"This block contains constants in the range 4&nbsp;≤&nbsp;<i>x</i>&nbsp;<&nbsp;8.
These constants are not representable in ",
if_signed_unsigned!($Signedness, "signed", "unsigned"),
" fixed-point numbers with less than ",
if_signed_unsigned!($Signedness, 4, 3),
" integer bits.
# Examples
```rust
use fixed::consts;
use fixed::types::extra::U",
if_signed_unsigned!($Signedness, $nm4, $nm3),
";
use fixed::", stringify!($Self), ";
type Fix = ", stringify!($Self), "<U",
if_signed_unsigned!($Signedness, $nm4, $nm3),
">;
assert_eq!(Fix::TAU, Fix::from_num(consts::TAU));
assert!(4 <= Fix::TAU && Fix::TAU < 8);
```
The following example fails to compile, since the maximum
representable value with ",
if_signed_unsigned!($Signedness, $nm3, $nm2),
" fractional bits and ",
if_signed_unsigned!($Signedness, 3, 2),
" integer bits is <&nbsp;4.
```rust,compile_fail
use fixed::consts;
use fixed::types::extra::U",
if_signed_unsigned!($Signedness, $nm3, $nm2),
";
use fixed::", stringify!($Self), ";
type Fix = ", stringify!($Self), "<U",
if_signed_unsigned!($Signedness, $nm3, $nm2),
">;
let _ = Fix::TAU;
```
";
impl<Frac: Unsigned> $Self<Frac>
where
Frac: IsLessOrEqual<$LeEqUC3, Output = True>,
{
/// A turn, τ = 6.28318…
pub const TAU: $Self<Frac> = Self::from_const(consts::TAU);
}
}
};
}
+3411
View File
File diff suppressed because it is too large. Load diff
+2523
View File
File diff suppressed because it is too large. Load diff
+5626
View File
File diff suppressed because it is too large. Load diff
+1117
View File
File diff suppressed because it is too large. Load diff
+503
View File
@@ -0,0 +1,503 @@
// Copyright © 2018–2025 Trevor Spiteri
// This library is free software: you can redistribute it and/or modify it under
// the terms of either
//
// * the Apache License, Version 2.0 or
// * the MIT License
//
// at your option.
//
// You should have recieved copies of the Apache License and the MIT License
// along with the library. If not, see
// <https://www.apache.org/licenses/LICENSE-2.0> and
// <https://opensource.org/licenses/MIT>.
#![allow(deprecated)]
use crate::float_helper;
use crate::helpers::{FloatKind, FromFloatHelper, Private};
use crate::int_helper::IntFixed;
use crate::traits::{Fixed, FixedEquiv, FromFixed, ToFixed};
use crate::types::extra::U0;
use crate::{
F128, F128Bits, FixedI8, FixedI16, FixedI32, FixedI64, FixedI128, FixedU8, FixedU16, FixedU32,
FixedU64, FixedU128,
};
use bytemuck::TransparentWrapper;
use half::{bf16 as half_bf16, f16 as half_f16};
impl ToFixed for bool {
/// Converts a [`bool`] to a fixed-point number.
///
/// # Panics
///
/// When debug assertions are enabled, panics if the value does
/// not fit. When debug assertions are not enabled, the wrapped
/// value can be returned, but it is not considered a breaking
/// change if in the future it panics; if wrapping is required use
/// [`wrapping_to_fixed`] instead.
///
/// [`wrapping_to_fixed`]: ToFixed::wrapping_to_fixed
#[inline]
#[track_caller]
fn to_fixed<F: Fixed>(self) -> F {
ToFixed::to_fixed(u8::from(self))
}
/// Converts a [`bool`] to a fixed-point number if it fits, otherwise returns [`None`].
#[inline]
fn checked_to_fixed<F: Fixed>(self) -> Option<F> {
ToFixed::checked_to_fixed(u8::from(self))
}
/// Convert a [`bool`] to a fixed-point number, saturating if it does not fit.
#[inline]
fn saturating_to_fixed<F: Fixed>(self) -> F {
ToFixed::saturating_to_fixed(u8::from(self))
}
/// Converts a [`bool`] to a fixed-point number, wrapping if it does not fit.
#[inline]
fn wrapping_to_fixed<F: Fixed>(self) -> F {
ToFixed::wrapping_to_fixed(u8::from(self))
}
/// Converts a [`bool`] to a fixed-point number.
///
/// Returns a [tuple] of the fixed-point number and a [`bool`]
/// indicating whether an overflow has occurred. On overflow, the
/// wrapped value is returned.
#[inline]
fn overflowing_to_fixed<F: Fixed>(self) -> (F, bool) {
ToFixed::overflowing_to_fixed(u8::from(self))
}
/// Converts a [`bool`] to a fixed-point number, panicking if it
/// does not fit.
///
/// # Panics
///
/// Panics if the value does not fit, even when debug assertions
/// are not enabled.
#[inline]
#[track_caller]
fn unwrapped_to_fixed<F: Fixed>(self) -> F {
ToFixed::unwrapped_to_fixed(u8::from(self))
}
}
macro_rules! impl_int {
($Int:ident $(, $Equiv:ident)?) => {
impl FromFixed for $Int {
/// Converts a fixed-point number to an integer.
///
/// Any fractional bits are discarded, which rounds towards &minus;∞.
///
/// # Panics
///
/// When debug assertions are enabled, panics if the value
/// does not fit. When debug assertions are not enabled,
/// the wrapped value can be returned, but it is not
/// considered a breaking change if in the future it
/// panics; if wrapping is required use
/// [`wrapping_from_fixed`] instead.
///
/// [`wrapping_from_fixed`]: FromFixed::wrapping_from_fixed
#[inline]
#[track_caller]
fn from_fixed<F: Fixed>(src: F) -> Self {
IntFixed::<$Int>::int(FromFixed::from_fixed(src))
}
/// Converts a fixed-point number to an integer if it fits, otherwise returns [`None`].
///
/// Any fractional bits are discarded, which rounds towards &minus;∞.
#[inline]
fn checked_from_fixed<F: Fixed>(src: F) -> Option<Self> {
FromFixed::checked_from_fixed(src).map(IntFixed::<$Int>::int)
}
/// Converts a fixed-point number to an integer, saturating if it does not fit.
///
/// Any fractional bits are discarded, which rounds towards &minus;∞.
#[inline]
fn saturating_from_fixed<F: Fixed>(src: F) -> Self {
IntFixed::<$Int>::int(FromFixed::saturating_from_fixed(src))
}
/// Converts a fixed-point number to an integer, wrapping if it does not fit.
///
/// Any fractional bits are discarded, which rounds towards &minus;∞.
#[inline]
fn wrapping_from_fixed<F: Fixed>(src: F) -> Self {
IntFixed::<$Int>::int(FromFixed::wrapping_from_fixed(src))
}
/// Converts a fixed-point number to an integer.
///
/// Returns a [tuple] of the value and a [`bool`] indicating whether
/// an overflow has occurred. On overflow, the wrapped value is
/// returned.
///
/// Any fractional bits are discarded, which rounds towards &minus;∞.
#[inline]
fn overflowing_from_fixed<F: Fixed>(src: F) -> (Self, bool) {
let (fixed, overflow) = FromFixed::overflowing_from_fixed(src);
(IntFixed::<$Int>::int(fixed), overflow)
}
/// Converts a fixed-point number to an integer, panicking if it does not fit.
///
/// Any fractional bits are discarded, which rounds towards &minus;∞.
///
/// # Panics
///
/// Panics if the value
/// does not fit, even when debug assertions are not enabled.
#[inline]
#[track_caller]
fn unwrapped_from_fixed<F: Fixed>(src: F) -> Self {
IntFixed::<$Int>::int(FromFixed::unwrapped_from_fixed(src))
}
}
impl ToFixed for $Int {
/// Converts an integer to a fixed-point number.
///
/// # Panics
///
/// When debug assertions are enabled, panics if the value
/// does not fit. When debug assertions are not enabled,
/// the wrapped value can be returned, but it is not
/// considered a breaking change if in the future it
/// panics; if wrapping is required use
/// [`wrapping_to_fixed`] instead.
///
/// [`wrapping_to_fixed`]: ToFixed::wrapping_to_fixed
#[inline]
#[track_caller]
fn to_fixed<F: Fixed>(self) -> F {
ToFixed::to_fixed(IntFixed(self).fixed())
}
/// Converts an integer to a fixed-point number if it fits, otherwise returns [`None`].
#[inline]
fn checked_to_fixed<F: Fixed>(self) -> Option<F> {
ToFixed::checked_to_fixed(IntFixed(self).fixed())
}
/// Converts an integer to a fixed-point number, saturating if it does not fit.
#[inline]
fn saturating_to_fixed<F: Fixed>(self) -> F {
ToFixed::saturating_to_fixed(IntFixed(self).fixed())
}
/// Converts an integer to a fixed-point number, wrapping if it does not fit.
#[inline]
fn wrapping_to_fixed<F: Fixed>(self) -> F {
ToFixed::wrapping_to_fixed(IntFixed(self).fixed())
}
/// Converts an integer to a fixed-point number.
///
/// Returns a [tuple] of the fixed-point number and a [`bool`]
/// indicating whether an overflow has occurred. On overflow, the
/// wrapped value is returned.
#[inline]
fn overflowing_to_fixed<F: Fixed>(self) -> (F, bool) {
ToFixed::overflowing_to_fixed(IntFixed(self).fixed())
}
/// Converts an integer to a fixed-point number, panicking if it does not fit.
///
/// # Panics
///
/// Panics if the value does not fit, even when debug
/// assertions are not enabled.
#[inline]
#[track_caller]
fn unwrapped_to_fixed<F: Fixed>(self) -> F {
ToFixed::unwrapped_to_fixed(IntFixed(self).fixed())
}
}
$(
impl FixedEquiv for $Int {
type Equiv = $Equiv<U0>;
#[inline]
fn to_fixed_equiv(self) -> $Equiv<U0> {
$Equiv::from_bits(self)
}
#[inline]
fn as_fixed_equiv(&self) -> &$Equiv<U0> {
$Equiv::wrap_ref(self)
}
#[inline]
fn as_fixed_equiv_mut(&mut self) -> &mut $Equiv<U0> {
$Equiv::wrap_mut(self)
}
#[inline]
fn from_fixed_equiv(f: $Equiv<U0>) -> $Int {
f.to_bits()
}
#[inline]
fn ref_from_fixed_equiv(f: &$Equiv<U0>) -> &$Int {
&f.bits
}
#[inline]
fn mut_from_fixed_equiv(f: &mut $Equiv<U0>) -> &mut $Int {
&mut f.bits
}
}
)*
};
}
impl_int! { i8, FixedI8 }
impl_int! { i16, FixedI16 }
impl_int! { i32, FixedI32 }
impl_int! { i64, FixedI64 }
impl_int! { i128, FixedI128 }
impl_int! { isize }
impl_int! { u8, FixedU8 }
impl_int! { u16, FixedU16 }
impl_int! { u32, FixedU32 }
impl_int! { u64, FixedU64 }
impl_int! { u128, FixedU128 }
impl_int! { usize }
macro_rules! impl_float {
($Float:ident, $link:expr, $overflows_fmt:expr, $overflows_filt:expr) => {
impl FromFixed for $Float {
/// Converts a fixed-point number to a floating-point number.
///
/// Rounding is to the nearest, with ties rounded to even.
///
/// # Panics
///
/// When debug assertions are enabled, panics if the value
/// does not fit. When debug assertions are not enabled,
/// the wrapped value can be returned, but it is not
/// considered a breaking change if in the future it
/// panics; if wrapping is required use
/// [`wrapping_from_fixed`] instead.
///
/// [`wrapping_from_fixed`]: FromFixed::wrapping_from_fixed
#[inline]
#[track_caller]
fn from_fixed<F: Fixed>(src: F) -> Self {
let helper = src.to_float_helper(Private);
float_helper::$Float::from_to_float_helper(helper, F::FRAC_NBITS, F::INT_NBITS)
}
/// Converts a fixed-point number to a floating-point
/// number if it fits, otherwise returns [`None`].
///
/// Rounding is to the nearest, with ties rounded to even.
#[inline]
fn checked_from_fixed<F: Fixed>(src: F) -> Option<Self> {
Some(FromFixed::from_fixed(src))
}
/// Converts a fixed-point number to a floating-point
/// number, saturating if it does not fit.
///
/// Rounding is to the nearest, with ties rounded to even.
#[inline]
fn saturating_from_fixed<F: Fixed>(src: F) -> Self {
FromFixed::from_fixed(src)
}
/// Converts a fixed-point number to a floating-point
/// number, wrapping if it does not fit.
///
/// Rounding is to the nearest, with ties rounded to even.
#[inline]
fn wrapping_from_fixed<F: Fixed>(src: F) -> Self {
FromFixed::from_fixed(src)
}
/// Converts a fixed-point number to a floating-point number.
///
/// Returns a [tuple] of the value and a [`bool`]
/// indicating whether an overflow has occurred. On
/// overflow, the wrapped value is returned.
///
/// Rounding is to the nearest, with ties rounded to even.
#[inline]
fn overflowing_from_fixed<F: Fixed>(src: F) -> (Self, bool) {
(FromFixed::from_fixed(src), false)
}
/// Converts a fixed-point number to a floating-point
/// number, panicking if it does not fit.
///
/// Rounding is to the nearest, with ties rounded to even.
///
/// # Panics
///
/// Panics if the value does not fit, even when debug
/// assertions are not enabled.
#[inline]
#[track_caller]
fn unwrapped_from_fixed<F: Fixed>(src: F) -> Self {
FromFixed::from_fixed(src)
}
}
impl ToFixed for $Float {
comment! {
"Converts a floating-point number to a fixed-point number.
Rounding is to the nearest, with ties rounded to even.
# Panics
Panics if `self` is not [finite].
When debug assertions are enabled, also panics if the value does not
fit. When debug assertions are not enabled, the wrapped value can be
returned, but it is not considered a breaking change if in the future
it panics; if wrapping is required use [`wrapping_to_fixed`] instead.
[`wrapping_to_fixed`]: ToFixed::wrapping_to_fixed
[finite]: ", $link, "::is_finite
";
#[inline]
#[track_caller]
fn to_fixed<F: Fixed>(self) -> F {
let (wrapped, overflow) = ToFixed::overflowing_to_fixed(self);
debug_assert!(!overflow, $overflows_fmt, $overflows_filt(self));
wrapped
}
}
/// Converts a floating-point number to a fixed-point
/// number if it fits, otherwise returns [`None`].
///
/// Rounding is to the nearest, with ties rounded to even.
#[inline]
fn checked_to_fixed<F: Fixed>(self) -> Option<F> {
let kind = float_helper::$Float::to_float_kind(self, F::FRAC_NBITS, F::INT_NBITS);
match kind {
FloatKind::Finite { .. } => {
let helper = FromFloatHelper { kind };
match F::overflowing_from_float_helper(Private, helper) {
(_, true) => None,
(wrapped, false) => Some(wrapped),
}
}
_ => None,
}
}
comment! {
"Converts a floating-point number to a fixed-point
number, saturating if it does not fit.
Rounding is to the nearest, with ties rounded to even.
# Panics
Panics if `self` is [NaN].
[NaN]: ", $link, "::is_nan
";
#[inline]
#[track_caller]
fn saturating_to_fixed<F: Fixed>(self) -> F {
let kind =
float_helper::$Float::to_float_kind(self, F::FRAC_NBITS, F::INT_NBITS);
let helper = FromFloatHelper { kind };
F::saturating_from_float_helper(Private, helper)
}
}
comment! {
"Converts a floating-point number to a fixed-point
number, wrapping if it does not fit.
Rounding is to the nearest, with ties rounded to even.
# Panics
Panics if `self` is not [finite].
[finite]: ", $link, "::is_finite
";
#[inline]
#[track_caller]
fn wrapping_to_fixed<F: Fixed>(self) -> F {
let (wrapped, _) = ToFixed::overflowing_to_fixed(self);
wrapped
}
}
comment! {
"Converts a floating-point number to a fixed-point number.
Returns a [tuple] of the fixed-point number and a [`bool`] indicating
whether an overflow has occurred. On overflow, the wrapped value is
returned.
Rounding is to the nearest, with ties rounded to even.
# Panics
Panics if `self` is not [finite].
[finite]: ", $link, "::is_finite
";
#[inline]
#[track_caller]
fn overflowing_to_fixed<F: Fixed>(self) -> (F, bool) {
let kind =
float_helper::$Float::to_float_kind(self, F::FRAC_NBITS, F::INT_NBITS);
let helper = FromFloatHelper { kind };
F::overflowing_from_float_helper(Private, helper)
}
}
comment! {
"Converts a floating-point number to a fixed-point
number, panicking if it does not fit.
Rounding is to the nearest, with ties rounded to even.
# Panics
Panics if `self` is not [finite] or if the value does not fit, even
when debug assertions are not enabled.
[finite]: ", $link, "::is_finite
";
#[inline]
#[track_caller]
fn unwrapped_to_fixed<F: Fixed>(self) -> F {
match ToFixed::overflowing_to_fixed(self) {
(val, false) => val,
(_, true) => panic!("overflow"),
}
}
}
}
};
}
#[cfg(feature = "nightly-float")]
impl_float! { f16, "f16", "f16::from_bits(0x{:04X}) overflows", |x: f16| x.to_bits() }
impl_float! { half_f16, "half::f16", "{} overflows", |x| x }
impl_float! { half_bf16, "half::bf16", "{} overflows", |x| x }
impl_float! { f32, "f32", "{} overflows", |x| x }
impl_float! { f64, "f64", "{} overflows", |x| x }
#[cfg(feature = "nightly-float")]
impl_float! { f128, "f128", "f128::from_bits(0x{:032X}) overflows", |x: f128| x.to_bits() }
impl_float! { F128, "F128", "F128::from_bits(0x{:032X}) overflows", |x: F128| x.to_bits() }
impl_float! { F128Bits, "f64", "F128Bits({}) overflows", |x: F128Bits| x.0 }
+2257
View File
File diff suppressed because it is too large. Load diff
+171
View File
@@ -0,0 +1,171 @@
// Copyright © 2018–2025 Trevor Spiteri
// This library is free software: you can redistribute it and/or
// modify it under the terms of either
//
// * the Apache License, Version 2.0 or
// * the MIT License
//
// at your option.
//
// You should have recieved copies of the Apache License and the MIT
// License along with the library. If not, see
// <https://www.apache.org/licenses/LICENSE-2.0> and
// <https://opensource.org/licenses/MIT>.
use crate::types::extra::{LeEqU8, LeEqU16, LeEqU32, LeEqU64, LeEqU128};
use crate::{
FixedI8, FixedI16, FixedI32, FixedI64, FixedI128, FixedU8, FixedU16, FixedU32, FixedU64,
FixedU128, Unwrapped, Wrapping,
};
use serde::de::{Deserialize, Deserializer, Error as DeError};
use serde::ser::{Serialize, Serializer};
#[cfg(not(feature = "serde-str"))]
use {
core::fmt::{Formatter, Result as FmtResult},
serde::de::{MapAccess, SeqAccess, Visitor},
serde::ser::SerializeStruct,
};
macro_rules! serde_fixed {
($Fixed:ident($LeEqU:ident) is $TBits:ident name $Name:expr) => {
impl<Frac: $LeEqU> Serialize for $Fixed<Frac> {
#[cfg(not(feature = "serde-str"))]
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
let bits = self.to_bits();
let mut state = serializer.serialize_struct($Name, 1)?;
state.serialize_field("bits", &bits)?;
state.end()
}
#[cfg(feature = "serde-str")]
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
if serializer.is_human_readable() {
self.to_string().serialize(serializer)
} else {
self.to_bits().serialize(serializer)
}
}
}
impl<Frac: $LeEqU> Serialize for Wrapping<$Fixed<Frac>> {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
self.0.serialize(serializer)
}
}
impl<Frac: $LeEqU> Serialize for Unwrapped<$Fixed<Frac>> {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
self.0.serialize(serializer)
}
}
impl<'de, Frac: $LeEqU> Deserialize<'de> for $Fixed<Frac> {
#[cfg(not(feature = "serde-str"))]
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
struct FixedVisitor;
impl<'de> Visitor<'de> for FixedVisitor {
type Value = $TBits;
fn expecting(&self, formatter: &mut Formatter) -> FmtResult {
formatter.write_str("struct ")?;
formatter.write_str($Name)
}
fn visit_seq<V: SeqAccess<'de>>(self, mut seq: V) -> Result<$TBits, V::Error> {
let bits = seq
.next_element()?
.ok_or_else(|| DeError::invalid_length(0, &self))?;
Ok(bits)
}
fn visit_map<V: MapAccess<'de>>(self, mut map: V) -> Result<$TBits, V::Error> {
let mut bits = None;
while let Some(key) = map.next_key()? {
match key {
Field::Bits => {
if bits.is_some() {
return Err(DeError::duplicate_field("bits"));
}
bits = Some(map.next_value()?);
}
}
}
let bits = bits.ok_or_else(|| DeError::missing_field("bits"))?;
Ok(bits)
}
}
let bits = deserializer.deserialize_struct($Name, FIELDS, FixedVisitor)?;
Ok($Fixed::from_bits(bits))
}
#[cfg(feature = "serde-str")]
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
if deserializer.is_human_readable() {
String::deserialize(deserializer)?
.parse()
.map_err(|e| DeError::custom(format_args!("parse error: {e}")))
} else {
$TBits::deserialize(deserializer).map($Fixed::from_bits)
}
}
}
impl<'de, Frac: $LeEqU> Deserialize<'de> for Wrapping<$Fixed<Frac>> {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
$Fixed::deserialize(deserializer).map(Wrapping)
}
}
impl<'de, Frac: $LeEqU> Deserialize<'de> for Unwrapped<$Fixed<Frac>> {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
$Fixed::deserialize(deserializer).map(Unwrapped)
}
}
};
}
serde_fixed! { FixedI8(LeEqU8) is i8 name "FixedI8" }
serde_fixed! { FixedI16(LeEqU16) is i16 name "FixedI16" }
serde_fixed! { FixedI32(LeEqU32) is i32 name "FixedI32" }
serde_fixed! { FixedI64(LeEqU64) is i64 name "FixedI64" }
serde_fixed! { FixedI128(LeEqU128) is i128 name "FixedI128" }
serde_fixed! { FixedU8(LeEqU8) is u8 name "FixedU8" }
serde_fixed! { FixedU16(LeEqU16) is u16 name "FixedU16" }
serde_fixed! { FixedU32(LeEqU32) is u32 name "FixedU32" }
serde_fixed! { FixedU64(LeEqU64) is u64 name "FixedU64" }
serde_fixed! { FixedU128(LeEqU128) is u128 name "FixedU128" }
#[cfg(not(feature = "serde-str"))]
const FIELDS: &[&str] = &["bits"];
#[cfg(not(feature = "serde-str"))]
enum Field {
Bits,
}
#[cfg(not(feature = "serde-str"))]
impl<'de> Deserialize<'de> for Field {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Field, D::Error> {
struct FieldVisitor;
impl Visitor<'_> for FieldVisitor {
type Value = Field;
fn expecting(&self, formatter: &mut Formatter) -> FmtResult {
formatter.write_str("`bits`")
}
fn visit_str<E: DeError>(self, value: &str) -> Result<Field, E> {
match value {
"bits" => Ok(Field::Bits),
_ => Err(DeError::unknown_field(value, FIELDS)),
}
}
}
deserializer.deserialize_identifier(FieldVisitor)
}
}
+521
View File
@@ -0,0 +1,521 @@
// Copyright © 2018–2025 Trevor Spiteri
// This library is free software: you can redistribute it and/or
// modify it under the terms of either
//
// * the Apache License, Version 2.0 or
// * the MIT License
//
// at your option.
//
// You should have recieved copies of the Apache License and the MIT
// License along with the library. If not, see
// <https://www.apache.org/licenses/LICENSE-2.0> and
// <https://opensource.org/licenses/MIT>.
use core::num::NonZero;
// The mathematics below is based on the comments from FreeBSD's
// /usr/src/lib/msun/src/e_sqrt.c.
// q_i = sqrt(y) truncated to i bits after point.
// q_0 = 1
// y_i = 2^i (y - q_i^2)
// y_0 = y - 1
//
// If (q_i + 1>>(i+1))^2 <= y:
// q_(i+1) = q_i + 1>>(i+1)
// Else:
// q_(i+1) = q_i
//
// Equivalently:
//
// If q_i + 1>>(i+2) <= y_i:
// q_(i+1) = q_i + 1>>(i+1)
// y_(i+1) = 2 (y_i - q_i - 1>>(i+2))
// Else:
// q_i+1 = q_i
// y_i+1 = 2 y_i
//
// * Iterations do not include q_0, y_0 as they are initialization.
// * i goes from 1 to iter.
// * Both q and y are stored with 2 integer bits. q is in range [1, 2); y is
// in range [1, 4).
// * 1>>(i+2) needs special code when i + 2 > nbits - 2. Since maximum iter is
// nbits - 1, i + 2 can be nbits + 1 which is > nbits - 2 by 2.
//
// Some examples for u8.
//
// frac_nbits == 0:
// sip = 4 - leading / 2
// 4 significant int pairs: 0100 0000. -> 0000 1000. (y << 0, 3 iter, q >> 3)
// 3 significant int pairs: 0001 0000. -> 0000 0100. (y << 2, 2 iter, q >> 4)
// 2 significant int pairs: 0000 0100. -> 0000 0010. (y << 4, 1 iter, q >> 5)
// 1 significant int pairs: 0000 0001. -> 0000 0001. (y << 6, 0 iter, q >> 6)
// General: y << 8 - 2sip, -1 + sip iter, q >> 7 - sip
//
// frac_nbits == 1:
// sip = 4 - (leading + 1) / 2
// 4 significant int pairs: 100 0000.0 -> 000 1000.0 (y >> 1, 4 iter, q >> 2)
// 3 significant int pairs: 001 0000.0 -> 000 0100.0 (y << 1, 3 iter, q >> 3)
// 2 significant int pairs: 000 0100.0 -> 000 0010.0 (y << 3, 2 iter, q >> 4)
// 1 significant int pairs: 000 0001.0 -> 000 0001.0 (y << 5, 1 iter, q >> 5)
// 0 significant int pairs: 000 0000.1 -> 000 0000.1 (y << 7, 0 iter, q >> 6)
// General: y << 7 - 2sip, sip iter, q >> 6 - sip
//
// frac_nbits == 2:
// sip = 3 - leading / 2
// 3 significant int pairs: 01 0000.00 -> 00 0100.00 (y << 0, 4 iter, q >> 2)
// 2 significant int pairs: 00 0100.00 -> 00 0010.00 (y << 2, 3 iter, q >> 3)
// 1 significant int pairs: 00 0001.00 -> 00 0001.00 (y << 4, 2 iter, q >> 4)
// 0 significant int pairs: 00 0000.01 -> 00 0000.10 (y << 6, 1 iter, q >> 5)
// General: y << 6 - 2sip, 1 + sip iter, q >> 5 - sip
//
// frac_nbits = 3:
// sip = 3 - (leading + 1) / 2
// 3 significant int pairs: 1 0000.000 -> 0 0100.000 (y >> 1, 5 iter, q >> 1)
// 2 significant int pairs: 0 0100.000 -> 0 0010.000 (y << 1, 4 iter, q >> 2)
// 1 significant int pairs: 0 0001.000 -> 0 0001.000 (y << 3, 3 iter, q >> 3)
// 0 significant int pairs: 0 0000.010 -> 0 0000.100 (y << 5, 2 iter, q >> 4)
// -1 significant int pairs: 0 0000.001 -> 0 0000.010 (y << 7, 1 iter, q >> 5)
// General: y << 5 - 2sip, 2 + sip iter, q >> 4 - sip
//
// frac_nbits == 4:
// sip = 2 - leading / 2
// 2 significant int pairs: 0100.0000 -> 0010.0000 (y << 0, 5 iter, q >> 1)
// 1 significant int pairs: 0001.0000 -> 0001.0000 (y << 2, 4 iter, q >> 2)
// 0 significant int pairs: 0000.0100 -> 0000.1000 (y << 4, 3 iter, q >> 3)
// -1 significant int pairs: 0000.0001 -> 0000.0100 (y << 6, 2 iter, q >> 4)
// General: y << 4 - 2sip, 3 + sip iter, q >> 3 - sip
//
// frac_nbits = 5:
// sip = 2 - (leading + 1) / 2
// 2 significant int pairs: 100.0000 0 -> 010.0000 0 (y >> 1, 6 iter, q >> 0)
// 1 significant int pairs: 001.0000 0 -> 001.0000 0 (y << 1, 5 iter, q >> 1)
// 0 significant int pairs: 000.0100 0 -> 000.1000 0 (y << 3, 4 iter, q >> 2)
// -1 significant int pairs: 000.0001 0 -> 000.0100 0 (y << 5, 3 iter, q >> 3)
// -2 significant int pairs: 000.0000 1 -> 000.0010 1 (y << 7, 2 iter, q >> 4)
// General: y << 3 - 2sip, 4 + sip iter, q >> 2 - sip
//
// frac_nbits == 6:
// sip = 1 - leading / 2
// 1 significant int pairs: 01.0000 00 -> 01.0000 00 (y << 0, 6 iter, q >> 0)
// 0 significant int pairs: 00.0100 00 -> 00.1000 00 (y << 2, 5 iter, q >> 1)
// -1 significant int pairs: 00.0001 00 -> 00.0100 00 (y << 4, 4 iter, q >> 2)
// -2 significant int pairs: 00.0000 01 -> 00.0010 00 (y << 6, 3 iter, q >> 3)
// General: y << 2 - 2sip, 5 + sip iter, q >> 1 - sip
//
// frac_nbits == 7:
// sip = 1 - (leading + 1) / 2
// 1 significant int pairs: 1.0000 000 -> 1.0000 000 (y >> 1, 7 iter, q << 1)
// 0 significant int pairs: 0.0100 000 -> 0.1000 000 (y << 1, 6 iter, q >> 0)
// -1 significant int pairs: 0.0001 000 -> 0.0100 000 (y << 3, 5 iter, q >> 1)
// -2 significant int pairs: 0.0000 010 -> 0.0010 000 (y << 5, 4 iter, q >> 2)
// -3 significant int pairs: 0.0000 001 -> 0.0001 011 (y << 7, 3 iter, q >> 3)
// General: y << 1 - 2sip, 6 + sip iter, q >> -sip
//
// frac_nbits == 8:
// sip = 0 - leading / 2
// 0 significant int pairs: .0100 0000 -> .1000 0000 (y << 0, 7 iter, q << 1)
// -1 significant int pairs: .0001 0000 -> .0100 0000 (y << 2, 6 iter, q >> 0)
// -2 significant int pairs: .0000 0100 -> .0010 0000 (y << 4, 5 iter, q >> 1)
// -3 significant int pairs: .0000 0001 -> .0001 0000 (y << 6, 4 iter, q >> 2)
// General: y << -2sip, 7 + sip iter, q >> -1 - sip
//
// General:
// Even frac_nbits:
// sip = int_nbits / 2 - leading / 2
// Odd frac_nbits:
// sip = (int_nbits + 1) / 2 - (leading + 1) / 2
// Then:
// y << int_nbits - 2sip, frac_nbits - 1 + sip iter, q >> int_nbits - 1 - sip
macro_rules! impl_sqrt {
($u:ident) => {
pub const fn $u(val: NonZero<$u>, frac_nbits: u32) -> $u {
let int_nbits = $u::BITS - frac_nbits;
let odd_frac_nbits = frac_nbits % 2 != 0;
let leading = val.leading_zeros();
let sig_int_pairs = if odd_frac_nbits {
int_nbits.div_ceil(2) as i32 - leading.div_ceil(2) as i32
} else {
(int_nbits / 2) as i32 - (leading / 2) as i32
};
let mut i = 1;
let mut q_i = 1 << ($u::BITS - 2);
let mut next_bit = q_i >> 1;
let mut y_i = val.get();
let input_shl = int_nbits as i32 - sig_int_pairs * 2;
if input_shl < 0 {
// This can only happen when we have odd frac_nbits and the most
// significant bit is set. We would need to shift right by 1.
debug_assert!(input_shl == -1);
// Do one iteration here as this is a special case.
// In this special case, y is in the range [1, 2) instead of [1, 4),
// and q is in the range [1, √2) instead of [1, 2).
// Therefore, q_1 is always 0b1.0, and never 0b1.1.
// Since q_0 = q_1 = 1, y_1 = 2 × (y - q_1^2) = 2 × y - 2 × q_i.
// Since input_shl is -1, its effect is cancelled out by 2 × y,
// and we only need to subtract 2 × q_i from y_i.
y_i -= 2 * q_i;
next_bit >>= 1;
i += 1;
} else {
y_i <<= input_shl;
y_i -= q_i;
};
let iters = (frac_nbits as i32 - 1 + sig_int_pairs) as u32;
while i <= iters {
let d = next_bit >> 1;
if d == 0 {
if i == iters {
// Here result_shr must be 0, otherwise we wouldn't have
// room to potentially insert one extra bit.
debug_assert!(int_nbits as i32 - 1 - sig_int_pairs == 0);
// d == 0.5, so we really need q_i + 0.5 <= y_i,
// which can be obtained with q_i < y_i
if q_i < y_i {
q_i += 1;
}
return q_i;
}
debug_assert!(i == iters - 1);
// Here result_shr must be -1, otherwise we wouldn't have
// room to potentially insert two extra bits.
debug_assert!(int_nbits as i32 - 1 - sig_int_pairs == -1);
// d == 0.5, so we really need q_i + 0.5 <= y_i,
// which can be obtained with q_i < y_i
if q_i < y_i {
// We cannot subtract d == 0.5 from y_i immediately, so
// we subtract 1 from y_i before the multiplication by 2
// and then add 1 back. (There may be a potential overflow
// if we multiply y_i by 2 and then subtract 1.)
y_i -= q_i + 1;
y_i *= 2;
y_i += 1;
q_i += 1;
} else {
y_i *= 2;
}
// d == 0.25, so we really need q_i + 0.25 <= y_i,
// which can be obtained with q_i < y_i
if q_i < y_i {
// We cannot add next_bit == 0.5 to q_i immediately, so
// we add 1 to q_i after the left shift.
q_i = (q_i << 1) + 1;
} else {
q_i <<= 1;
}
return q_i;
}
if q_i + d <= y_i {
y_i -= q_i + d;
q_i += next_bit;
}
y_i *= 2;
next_bit = d;
i += 1;
}
let result_shr = int_nbits as i32 - 1 - sig_int_pairs;
q_i >> result_shr
}
};
}
impl_sqrt! { u8 }
impl_sqrt! { u16 }
impl_sqrt! { u32 }
impl_sqrt! { u64 }
impl_sqrt! { u128 }
#[cfg(test)]
mod tests {
use crate::types::extra::{
U0, U1, U3, U4, U5, U6, U7, U8, U9, U13, U14, U15, U16, U17, U29, U30, U31, U32, U33, U61,
U62, U63, U64, U65, U125, U126, U127, U128,
};
use crate::{
FixedI8, FixedI16, FixedI32, FixedI64, FixedI128, FixedU8, FixedU16, FixedU32, FixedU64,
FixedU128,
};
macro_rules! check_sqrt {
($val:expr) => {{
let sqrt = $val.sqrt();
assert!(sqrt * sqrt <= $val);
let delta = $val.wrapping_neg().wrapping_sub(!$val);
if let Some(sqrt_delta) = sqrt.checked_add(delta) {
if let Some(prod) = sqrt_delta.checked_mul(sqrt_delta) {
assert!(prod >= $val);
}
}
}};
}
#[test]
fn check_max_8() {
check_sqrt!(FixedU8::<U0>::MAX);
check_sqrt!(FixedU8::<U1>::MAX);
check_sqrt!(FixedU8::<U3>::MAX);
check_sqrt!(FixedU8::<U4>::MAX);
check_sqrt!(FixedU8::<U5>::MAX);
check_sqrt!(FixedU8::<U7>::MAX);
check_sqrt!(FixedU8::<U8>::MAX);
assert_eq!(FixedU8::<U8>::MAX.sqrt(), FixedU8::<U8>::MAX);
check_sqrt!(FixedI8::<U0>::MAX);
check_sqrt!(FixedI8::<U1>::MAX);
check_sqrt!(FixedI8::<U3>::MAX);
check_sqrt!(FixedI8::<U4>::MAX);
check_sqrt!(FixedI8::<U5>::MAX);
check_sqrt!(FixedI8::<U7>::MAX);
assert!(FixedI8::<U8>::MAX.checked_sqrt().is_none());
}
#[test]
fn check_max_16() {
check_sqrt!(FixedU16::<U0>::MAX);
check_sqrt!(FixedU16::<U1>::MAX);
check_sqrt!(FixedU16::<U7>::MAX);
check_sqrt!(FixedU16::<U8>::MAX);
check_sqrt!(FixedU16::<U9>::MAX);
check_sqrt!(FixedU16::<U15>::MAX);
check_sqrt!(FixedU16::<U16>::MAX);
assert_eq!(FixedU16::<U16>::MAX.sqrt(), FixedU16::<U16>::MAX);
check_sqrt!(FixedI16::<U0>::MAX);
check_sqrt!(FixedI16::<U1>::MAX);
check_sqrt!(FixedI16::<U7>::MAX);
check_sqrt!(FixedI16::<U8>::MAX);
check_sqrt!(FixedI16::<U9>::MAX);
check_sqrt!(FixedI16::<U15>::MAX);
assert!(FixedI16::<U16>::MAX.checked_sqrt().is_none());
}
#[test]
fn check_max_32() {
check_sqrt!(FixedU32::<U0>::MAX);
check_sqrt!(FixedU32::<U1>::MAX);
check_sqrt!(FixedU32::<U15>::MAX);
check_sqrt!(FixedU32::<U16>::MAX);
check_sqrt!(FixedU32::<U17>::MAX);
check_sqrt!(FixedU32::<U31>::MAX);
check_sqrt!(FixedU32::<U32>::MAX);
assert_eq!(FixedU32::<U32>::MAX.sqrt(), FixedU32::<U32>::MAX);
check_sqrt!(FixedI32::<U0>::MAX);
check_sqrt!(FixedI32::<U1>::MAX);
check_sqrt!(FixedI32::<U15>::MAX);
check_sqrt!(FixedI32::<U16>::MAX);
check_sqrt!(FixedI32::<U17>::MAX);
check_sqrt!(FixedI32::<U31>::MAX);
assert!(FixedI32::<U32>::MAX.checked_sqrt().is_none());
}
#[test]
fn check_max_64() {
check_sqrt!(FixedU64::<U0>::MAX);
check_sqrt!(FixedU64::<U1>::MAX);
check_sqrt!(FixedU64::<U31>::MAX);
check_sqrt!(FixedU64::<U32>::MAX);
check_sqrt!(FixedU64::<U33>::MAX);
check_sqrt!(FixedU64::<U63>::MAX);
check_sqrt!(FixedU64::<U64>::MAX);
assert_eq!(FixedU64::<U64>::MAX.sqrt(), FixedU64::<U64>::MAX);
check_sqrt!(FixedI64::<U0>::MAX);
check_sqrt!(FixedI64::<U1>::MAX);
check_sqrt!(FixedI64::<U31>::MAX);
check_sqrt!(FixedI64::<U32>::MAX);
check_sqrt!(FixedI64::<U33>::MAX);
check_sqrt!(FixedI64::<U63>::MAX);
assert!(FixedI64::<U64>::MAX.checked_sqrt().is_none());
}
#[test]
fn check_max_128() {
check_sqrt!(FixedU128::<U0>::MAX);
check_sqrt!(FixedU128::<U1>::MAX);
check_sqrt!(FixedU128::<U63>::MAX);
check_sqrt!(FixedU128::<U64>::MAX);
check_sqrt!(FixedU128::<U65>::MAX);
check_sqrt!(FixedU128::<U127>::MAX);
check_sqrt!(FixedU128::<U128>::MAX);
assert_eq!(FixedU128::<U128>::MAX.sqrt(), FixedU128::<U128>::MAX);
check_sqrt!(FixedI128::<U0>::MAX);
check_sqrt!(FixedI128::<U1>::MAX);
check_sqrt!(FixedI128::<U63>::MAX);
check_sqrt!(FixedI128::<U64>::MAX);
check_sqrt!(FixedI128::<U65>::MAX);
check_sqrt!(FixedI128::<U127>::MAX);
assert!(FixedI128::<U128>::MAX.checked_sqrt().is_none());
}
#[test]
fn check_two_8() {
assert_eq!(FixedU8::<U0>::from_num(2).sqrt(), FixedU8::<U0>::SQRT_2);
assert_eq!(FixedU8::<U1>::from_num(2).sqrt(), FixedU8::<U1>::SQRT_2);
assert_eq!(FixedU8::<U3>::from_num(2).sqrt(), FixedU8::<U3>::SQRT_2);
assert_eq!(FixedU8::<U4>::from_num(2).sqrt(), FixedU8::<U4>::SQRT_2);
assert_eq!(FixedU8::<U5>::from_num(2).sqrt(), FixedU8::<U5>::SQRT_2);
assert_eq!(FixedU8::<U6>::from_num(2).sqrt(), FixedU8::<U6>::SQRT_2);
assert!(
FixedU8::<U7>::MAX.sqrt() == FixedU8::<U7>::SQRT_2 - FixedU8::<U7>::DELTA
|| FixedU8::<U7>::MAX.sqrt() == FixedU8::<U7>::SQRT_2
);
assert_eq!(FixedI8::<U0>::from_num(2).sqrt(), FixedI8::<U0>::SQRT_2);
assert_eq!(FixedI8::<U1>::from_num(2).sqrt(), FixedI8::<U1>::SQRT_2);
assert_eq!(FixedI8::<U3>::from_num(2).sqrt(), FixedI8::<U3>::SQRT_2);
assert_eq!(FixedI8::<U4>::from_num(2).sqrt(), FixedI8::<U4>::SQRT_2);
assert_eq!(FixedI8::<U5>::from_num(2).sqrt(), FixedI8::<U5>::SQRT_2);
assert!(
FixedI8::<U6>::MAX.sqrt() == FixedI8::<U6>::SQRT_2 - FixedI8::<U6>::DELTA
|| FixedI8::<U6>::MAX.sqrt() == FixedI8::<U6>::SQRT_2
);
}
#[test]
fn check_two_16() {
assert_eq!(FixedU16::<U0>::from_num(2).sqrt(), FixedU16::<U0>::SQRT_2);
assert_eq!(FixedU16::<U1>::from_num(2).sqrt(), FixedU16::<U1>::SQRT_2);
assert_eq!(FixedU16::<U7>::from_num(2).sqrt(), FixedU16::<U7>::SQRT_2);
assert_eq!(FixedU16::<U8>::from_num(2).sqrt(), FixedU16::<U8>::SQRT_2);
assert_eq!(FixedU16::<U9>::from_num(2).sqrt(), FixedU16::<U9>::SQRT_2);
assert_eq!(FixedU16::<U13>::from_num(2).sqrt(), FixedU16::<U13>::SQRT_2);
assert_eq!(FixedU16::<U14>::from_num(2).sqrt(), FixedU16::<U14>::SQRT_2);
assert!(
FixedU16::<U15>::MAX.sqrt() == FixedU16::<U15>::SQRT_2 - FixedU16::<U15>::DELTA
|| FixedU16::<U15>::MAX.sqrt() == FixedU16::<U15>::SQRT_2
);
assert_eq!(FixedI16::<U0>::from_num(2).sqrt(), FixedI16::<U0>::SQRT_2);
assert_eq!(FixedI16::<U1>::from_num(2).sqrt(), FixedI16::<U1>::SQRT_2);
assert_eq!(FixedI16::<U7>::from_num(2).sqrt(), FixedI16::<U7>::SQRT_2);
assert_eq!(FixedI16::<U8>::from_num(2).sqrt(), FixedI16::<U8>::SQRT_2);
assert_eq!(FixedI16::<U9>::from_num(2).sqrt(), FixedI16::<U9>::SQRT_2);
assert_eq!(FixedI16::<U13>::from_num(2).sqrt(), FixedI16::<U13>::SQRT_2);
assert!(
FixedI16::<U14>::MAX.sqrt() == FixedI16::<U14>::SQRT_2 - FixedI16::<U14>::DELTA
|| FixedI16::<U14>::MAX.sqrt() == FixedI16::<U14>::SQRT_2
);
}
#[test]
fn check_two_32() {
assert_eq!(FixedU32::<U0>::from_num(2).sqrt(), FixedU32::<U0>::SQRT_2);
assert_eq!(FixedU32::<U1>::from_num(2).sqrt(), FixedU32::<U1>::SQRT_2);
assert_eq!(FixedU32::<U15>::from_num(2).sqrt(), FixedU32::<U15>::SQRT_2);
assert_eq!(FixedU32::<U16>::from_num(2).sqrt(), FixedU32::<U16>::SQRT_2);
assert_eq!(FixedU32::<U17>::from_num(2).sqrt(), FixedU32::<U17>::SQRT_2);
assert_eq!(FixedU32::<U29>::from_num(2).sqrt(), FixedU32::<U29>::SQRT_2);
assert_eq!(FixedU32::<U30>::from_num(2).sqrt(), FixedU32::<U30>::SQRT_2);
assert!(
FixedU32::<U31>::MAX.sqrt() == FixedU32::<U31>::SQRT_2 - FixedU32::<U31>::DELTA
|| FixedU32::<U31>::MAX.sqrt() == FixedU32::<U31>::SQRT_2
);
assert_eq!(FixedI32::<U0>::from_num(2).sqrt(), FixedI32::<U0>::SQRT_2);
assert_eq!(FixedI32::<U1>::from_num(2).sqrt(), FixedI32::<U1>::SQRT_2);
assert_eq!(FixedI32::<U15>::from_num(2).sqrt(), FixedI32::<U15>::SQRT_2);
assert_eq!(FixedI32::<U16>::from_num(2).sqrt(), FixedI32::<U16>::SQRT_2);
assert_eq!(FixedI32::<U17>::from_num(2).sqrt(), FixedI32::<U17>::SQRT_2);
assert_eq!(FixedI32::<U29>::from_num(2).sqrt(), FixedI32::<U29>::SQRT_2);
assert!(
FixedI32::<U30>::MAX.sqrt() == FixedI32::<U30>::SQRT_2 - FixedI32::<U30>::DELTA
|| FixedI32::<U30>::MAX.sqrt() == FixedI32::<U30>::SQRT_2
);
}
#[test]
fn check_two_64() {
assert_eq!(FixedU64::<U0>::from_num(2).sqrt(), FixedU64::<U0>::SQRT_2);
assert_eq!(FixedU64::<U1>::from_num(2).sqrt(), FixedU64::<U1>::SQRT_2);
assert_eq!(FixedU64::<U31>::from_num(2).sqrt(), FixedU64::<U31>::SQRT_2);
assert_eq!(FixedU64::<U32>::from_num(2).sqrt(), FixedU64::<U32>::SQRT_2);
assert_eq!(FixedU64::<U33>::from_num(2).sqrt(), FixedU64::<U33>::SQRT_2);
assert_eq!(FixedU64::<U61>::from_num(2).sqrt(), FixedU64::<U61>::SQRT_2);
assert_eq!(FixedU64::<U62>::from_num(2).sqrt(), FixedU64::<U62>::SQRT_2);
assert!(
FixedU64::<U63>::MAX.sqrt() == FixedU64::<U63>::SQRT_2 - FixedU64::<U63>::DELTA
|| FixedU64::<U63>::MAX.sqrt() == FixedU64::<U63>::SQRT_2
);
assert_eq!(FixedI64::<U0>::from_num(2).sqrt(), FixedI64::<U0>::SQRT_2);
assert_eq!(FixedI64::<U1>::from_num(2).sqrt(), FixedI64::<U1>::SQRT_2);
assert_eq!(FixedI64::<U31>::from_num(2).sqrt(), FixedI64::<U31>::SQRT_2);
assert_eq!(FixedI64::<U32>::from_num(2).sqrt(), FixedI64::<U32>::SQRT_2);
assert_eq!(FixedI64::<U33>::from_num(2).sqrt(), FixedI64::<U33>::SQRT_2);
assert_eq!(FixedI64::<U61>::from_num(2).sqrt(), FixedI64::<U61>::SQRT_2);
assert!(
FixedI64::<U62>::MAX.sqrt() == FixedI64::<U62>::SQRT_2 - FixedI64::<U62>::DELTA
|| FixedI64::<U62>::MAX.sqrt() == FixedI64::<U62>::SQRT_2
);
}
#[test]
fn check_two_128() {
assert_eq!(FixedU128::<U0>::from_num(2).sqrt(), FixedU128::<U0>::SQRT_2);
assert_eq!(FixedU128::<U1>::from_num(2).sqrt(), FixedU128::<U1>::SQRT_2);
assert_eq!(
FixedU128::<U63>::from_num(2).sqrt(),
FixedU128::<U63>::SQRT_2
);
assert_eq!(
FixedU128::<U64>::from_num(2).sqrt(),
FixedU128::<U64>::SQRT_2
);
assert_eq!(
FixedU128::<U65>::from_num(2).sqrt(),
FixedU128::<U65>::SQRT_2
);
assert_eq!(
FixedU128::<U125>::from_num(2).sqrt(),
FixedU128::<U125>::SQRT_2
);
assert_eq!(
FixedU128::<U126>::from_num(2).sqrt(),
FixedU128::<U126>::SQRT_2
);
assert!(
FixedU128::<U127>::MAX.sqrt() == FixedU128::<U127>::SQRT_2 - FixedU128::<U127>::DELTA
|| FixedU128::<U127>::MAX.sqrt() == FixedU128::<U127>::SQRT_2
);
assert_eq!(FixedI128::<U0>::from_num(2).sqrt(), FixedI128::<U0>::SQRT_2);
assert_eq!(FixedI128::<U1>::from_num(2).sqrt(), FixedI128::<U1>::SQRT_2);
assert_eq!(
FixedI128::<U63>::from_num(2).sqrt(),
FixedI128::<U63>::SQRT_2
);
assert_eq!(
FixedI128::<U64>::from_num(2).sqrt(),
FixedI128::<U64>::SQRT_2
);
assert_eq!(
FixedI128::<U65>::from_num(2).sqrt(),
FixedI128::<U65>::SQRT_2
);
assert_eq!(
FixedI128::<U125>::from_num(2).sqrt(),
FixedI128::<U125>::SQRT_2
);
assert!(
FixedI128::<U126>::MAX.sqrt() == FixedI128::<U126>::SQRT_2 - FixedI128::<U126>::DELTA
|| FixedI128::<U126>::MAX.sqrt() == FixedI128::<U126>::SQRT_2
);
}
}
+4926
View File
File diff suppressed because it is too large. Load diff
+322
View File
@@ -0,0 +1,322 @@
// Copyright © 2018–2025 Trevor Spiteri
// This library is free software: you can redistribute it and/or
// modify it under the terms of either
//
// * the Apache License, Version 2.0 or
// * the MIT License
//
// at your option.
//
// You should have recieved copies of the Apache License and the MIT
// License along with the library. If not, see
// <https://www.apache.org/licenses/LICENSE-2.0> and
// <https://opensource.org/licenses/MIT>.
use crate::traits::FixedEquiv;
#[cfg(feature = "arbitrary")]
use arbitrary::Arbitrary;
use az::{
Cast, CastFrom, CheckedCast, CheckedCastFrom, OverflowingCast, OverflowingCastFrom,
SaturatingCast, SaturatingCastFrom, UnwrappedCast, UnwrappedCastFrom, WrappingCast,
WrappingCastFrom,
};
#[cfg(feature = "borsh")]
use borsh::{BorshDeserialize, BorshSerialize};
use bytemuck::{Contiguous, Pod};
use core::fmt::{Binary, Debug, Display, LowerExp, LowerHex, Octal, UpperExp, UpperHex};
use core::hash::Hash;
use core::iter::{Product, Sum};
use core::num::ParseIntError;
use core::ops::{
Add, AddAssign, BitAnd, BitAndAssign, BitOr, BitOrAssign, BitXor, BitXorAssign, Div, DivAssign,
Mul, MulAssign, Not, Rem, RemAssign, Shl, ShlAssign, Shr, ShrAssign, Sub, SubAssign,
};
use core::str::FromStr;
#[cfg(feature = "num-traits")]
use num_traits::{
Num, NumAssignRef, NumRef,
cast::{AsPrimitive, FromPrimitive},
int::PrimInt,
ops::{
checked::{CheckedNeg, CheckedRem, CheckedShl, CheckedShr},
euclid::{CheckedEuclid, Euclid},
mul_add::{MulAdd, MulAddAssign},
overflowing::{OverflowingAdd, OverflowingMul, OverflowingSub},
saturating::{SaturatingAdd, SaturatingMul, SaturatingSub},
wrapping::{WrappingAdd, WrappingMul, WrappingNeg, WrappingShl, WrappingShr, WrappingSub},
},
};
#[cfg(feature = "serde")]
use serde::{de::Deserialize, ser::Serialize};
macro_rules! impl_bits {
($Bits:ident) => {
impl FixedBits for $Bits {
const MIN: $Bits = $Bits::MIN;
const MAX: $Bits = $Bits::MAX;
const IS_SIGNED: bool = $Bits::MIN != 0;
const BITS: u32 = $Bits::BITS;
}
impl Sealed for $Bits {}
impl FixedBitsCast<i8> for $Bits {}
impl FixedBitsCast<i16> for $Bits {}
impl FixedBitsCast<i32> for $Bits {}
impl FixedBitsCast<i64> for $Bits {}
impl FixedBitsCast<i128> for $Bits {}
impl FixedBitsCast<isize> for $Bits {}
impl FixedBitsCast<u8> for $Bits {}
impl FixedBitsCast<u16> for $Bits {}
impl FixedBitsCast<u32> for $Bits {}
impl FixedBitsCast<u64> for $Bits {}
impl FixedBitsCast<u128> for $Bits {}
impl FixedBitsCast<usize> for $Bits {}
impl FixedBitsOptionalArbitrary for $Bits {}
impl FixedBitsOptionalBorsh for $Bits {}
impl FixedBitsOptionalNum for $Bits {}
impl FixedBitsOptionalSerde for $Bits {}
};
}
/// This trait is implemented for <code>[Fixed]::[Bits]</code> for all
/// fixed-point numbers.
///
/// This provides some facilities to manipulate bits in generic functions.
///
/// This trait is sealed and cannot be implemented for more types; it is
/// implemented for [`i8`], [`i16`], [`i32`], [`i64`], [`i128`], [`u8`],
/// [`u16`], [`u32`], [`u64`], and [`u128`].
///
/// # Examples
///
/// ```rust
/// use az::OverflowingAs;
/// use fixed::traits::Fixed;
/// use fixed::types::*;
/// fn limited_positive_bits<F: Fixed>(fixed: F) -> Option<u32> {
/// let bits = fixed.to_bits();
/// match bits.overflowing_as::<u32>() {
/// (wrapped, false) => Some(wrapped),
/// (_, true) => None,
/// }
/// }
/// assert_eq!(limited_positive_bits(I16F16::from_bits(100)), Some(100));
/// assert_eq!(limited_positive_bits(I16F16::from_bits(-100)), None);
/// ```
///
/// [Bits]: crate::traits::Fixed::Bits
/// [Fixed]: crate::traits::Fixed
pub trait FixedBits
where
Self: Default + Hash + Ord,
Self: Contiguous + Pod,
Self: Debug + Display + LowerExp + UpperExp,
Self: Binary + Octal + LowerHex + UpperHex,
Self: FromStr<Err = ParseIntError>,
Self: Add<Output = Self> + AddAssign,
Self: Sub<Output = Self> + SubAssign,
Self: Mul<Output = Self> + MulAssign,
Self: Div<Output = Self> + DivAssign,
Self: Rem<Output = Self> + RemAssign,
Self: Not<Output = Self>,
Self: BitAnd<Output = Self> + BitAndAssign,
Self: BitOr<Output = Self> + BitOrAssign,
Self: BitXor<Output = Self> + BitXorAssign,
Self: Shl<u32, Output = Self> + ShlAssign<u32>,
Self: Shr<u32, Output = Self> + ShrAssign<u32>,
Self: Sum + Product,
Self: FixedEquiv,
Self: FixedBitsCast<i8> + FixedBitsCast<i16> + FixedBitsCast<i32>,
Self: FixedBitsCast<i64> + FixedBitsCast<i128> + FixedBitsCast<isize>,
Self: FixedBitsCast<u8> + FixedBitsCast<u16> + FixedBitsCast<u32>,
Self: FixedBitsCast<u64> + FixedBitsCast<u128> + FixedBitsCast<usize>,
Self: FixedBitsOptionalArbitrary,
Self: FixedBitsOptionalBorsh,
Self: FixedBitsOptionalNum,
Self: FixedBitsOptionalSerde,
Self: Sealed,
{
/// The smallest value that can be represented by this integer type.
const MIN: Self;
/// The largest value that can be represented by this integer type.
const MAX: Self;
/// [`true`] if this integer type is signed.
const IS_SIGNED: bool;
/// The size of this integer type in bits.
const BITS: u32;
}
/// This trait is used to provide supertraits to the [`FixedBits`] trait, and
/// should not be used directly.
pub trait FixedBitsCast<Prim>
where
Self: TryInto<Prim> + TryFrom<Prim>,
Self: Cast<Prim> + CastFrom<Prim>,
Self: CheckedCast<Prim> + CheckedCastFrom<Prim>,
Self: SaturatingCast<Prim> + SaturatingCastFrom<Prim>,
Self: WrappingCast<Prim> + WrappingCastFrom<Prim>,
Self: UnwrappedCast<Prim> + UnwrappedCastFrom<Prim>,
Self: OverflowingCast<Prim> + OverflowingCastFrom<Prim>,
Self: Sealed,
{
}
pub trait Sealed {}
impl_bits! { i8 }
impl_bits! { i16 }
impl_bits! { i32 }
impl_bits! { i64 }
impl_bits! { i128 }
impl_bits! { u8 }
impl_bits! { u16 }
impl_bits! { u32 }
impl_bits! { u64 }
impl_bits! { u128 }
#[cfg(not(feature = "arbitrary"))]
/// This trait is used to provide supertrait to the [`FixedBits`] trait
/// depending on the crates’s [optional features], and should not be used
/// directly.
///
/// If the `arbitrary` feature is enabled, [`Arbitrary`] is a supertrait of
/// [`FixedBits`].
///
/// [optional features]: crate#optional-features
pub trait FixedBitsOptionalArbitrary: Sealed {}
#[cfg(feature = "arbitrary")]
/// This trait is used to provide supertrait to the [`FixedBits`] trait
/// depending on the crates’s [optional features], and should not be used
/// directly.
///
/// If the `arbitrary` feature is enabled, [`Arbitrary`] is a supertrait of
/// [`FixedBits`].
///
/// [optional features]: crate#optional-features
pub trait FixedBitsOptionalArbitrary: Sealed
where
Self: for<'a> Arbitrary<'a>,
{
}
#[cfg(not(feature = "borsh"))]
/// This trait is used to provide supertraits to the [`FixedBits`] trait
/// depending on the crates’s [optional features], and should not be used
/// directly.
///
/// If the `borsh` experimental feature is enabled, [`BorshSerialize`] and
/// [`BorshDeserialize`] are supertraits of [`FixedBits`].
///
/// [optional features]: crate#optional-features
pub trait FixedBitsOptionalBorsh: Sealed {}
#[cfg(feature = "borsh")]
/// This trait is used to provide supertraits to the [`FixedBits`] trait
/// depending on the crates’s [optional features], and should not be used
/// directly.
///
/// If the `borsh` experimental feature is enabled, [`BorshSerialize`] and
/// [`BorshDeserialize`] are supertraits of [`FixedBits`].
///
/// [optional features]: crate#optional-features
pub trait FixedBitsOptionalBorsh: Sealed
where
Self: BorshSerialize + BorshDeserialize,
{
}
#[cfg(not(feature = "num-traits"))]
/// This trait is used to provide supertraits to the [`FixedBits`] trait
/// depending on the crates’s [optional features], and should not be used
/// directly.
///
/// If the [`num-traits` experimental feature][experimental features] is
/// enabled, the following are supertraits of [`FixedBits`]:
///
/// * [`PrimInt`], [`FromPrimitive`]
/// * <code>[AsPrimitive][`AsPrimitive`]&lt;T></code> where `T` can be [`i8`],
/// [`i16`], [`i32`], [`i64`], [`i128`], [`isize`], [`u8`], [`u16`],
/// [`u32`], [`u64`], [`u128`], [`usize`], [`f32`] or [`f64`]
/// * [`CheckedNeg`], [`CheckedRem`], [`CheckedShl`], [`CheckedShr`]
/// * [`SaturatingAdd`], [`SaturatingSub`], [`SaturatingMul`]
/// * [`WrappingAdd`], [`WrappingSub`], [`WrappingNeg`], [`WrappingMul`],
/// [`WrappingShl`], [`WrappingShr`]
/// * [`OverflowingAdd`], [`OverflowingSub`], [`OverflowingMul`]
/// * [`Euclid`], [`CheckedEuclid`]
/// * [`MulAdd`], [`MulAddAssign`]
///
/// [experimental features]: crate#experimental-optional-features
/// [optional features]: crate#optional-features
pub trait FixedBitsOptionalNum: Sealed {}
#[cfg(feature = "num-traits")]
/// This trait is used to provide supertraits to the [`FixedBits`] trait
/// depending on the crates’s [optional features], and should not be used
/// directly.
///
/// If the [`num-traits` experimental feature][experimental features] is
/// enabled, the following are supertraits of [`FixedBits`]:
///
/// * [`Num`], [`NumRef`], [`NumAssignRef`]
/// * [`PrimInt`], [`FromPrimitive`]
/// * <code>[AsPrimitive][`AsPrimitive`]&lt;T></code> where `T` can be [`i8`],
/// [`i16`], [`i32`], [`i64`], [`i128`], [`isize`], [`u8`], [`u16`],
/// [`u32`], [`u64`], [`u128`], [`usize`], [`f32`] or [`f64`]
/// * [`CheckedNeg`], [`CheckedRem`], [`CheckedShl`], [`CheckedShr`]
/// * [`SaturatingAdd`], [`SaturatingSub`], [`SaturatingMul`]
/// * [`WrappingAdd`], [`WrappingSub`], [`WrappingNeg`], [`WrappingMul`],
/// [`WrappingShl`], [`WrappingShr`]
/// * [`OverflowingAdd`], [`OverflowingSub`], [`OverflowingMul`]
/// * [`Euclid`], [`CheckedEuclid`]
/// * [`MulAdd`], [`MulAddAssign`]
///
/// [experimental features]: crate#experimental-optional-features
/// [optional features]: crate#optional-features
pub trait FixedBitsOptionalNum: Sealed
where
Self: Num<FromStrRadixErr = ParseIntError> + NumRef + NumAssignRef,
Self: PrimInt + FromPrimitive,
Self: AsPrimitive<i8> + AsPrimitive<i16> + AsPrimitive<i32>,
Self: AsPrimitive<i64> + AsPrimitive<i128> + AsPrimitive<isize>,
Self: AsPrimitive<u8> + AsPrimitive<u16> + AsPrimitive<u32>,
Self: AsPrimitive<u64> + AsPrimitive<u128> + AsPrimitive<usize>,
Self: AsPrimitive<f32> + AsPrimitive<f64>,
Self: CheckedNeg + CheckedRem + CheckedShl + CheckedShr,
Self: SaturatingAdd + SaturatingSub + SaturatingMul,
Self: WrappingAdd + WrappingSub + WrappingNeg + WrappingMul,
Self: WrappingShl + WrappingShr,
Self: OverflowingAdd + OverflowingSub + OverflowingMul,
Self: Euclid + CheckedEuclid,
Self: MulAdd + MulAddAssign,
{
}
#[cfg(not(feature = "serde"))]
/// This trait is used to provide supertraits to the [`FixedBits`] trait
/// depending on the crates’s [optional features], and should not be used
/// directly.
///
/// If the `serde` feature is enabled, [`Serialize`] and [`Deserialize`] are
/// supertraits of [`FixedBits`].
///
/// [optional features]: crate#optional-features
pub trait FixedBitsOptionalSerde: Sealed {}
#[cfg(feature = "serde")]
/// This trait is used to provide supertraits to the [`FixedBits`] trait
/// depending on the crates’s [optional features], and should not be used
/// directly.
///
/// If the `serde` feature is enabled, [`Serialize`] and [`Deserialize`] are
/// supertraits of [`FixedBits`].
///
/// [optional features]: crate#optional-features
pub trait FixedBitsOptionalSerde: Sealed
where
Self: Serialize + for<'de> Deserialize<'de>,
{
}
+49
View File
@@ -0,0 +1,49 @@
// Copyright © 2018–2025 Trevor Spiteri
// This library is free software: you can redistribute it and/or
// modify it under the terms of either
//
// * the Apache License, Version 2.0 or
// * the MIT License
//
// at your option.
//
// You should have recieved copies of the Apache License and the MIT
// License along with the library. If not, see
// <https://www.apache.org/licenses/LICENSE-2.0> and
// <https://opensource.org/licenses/MIT>.
/*!
Extra types that do not need to be handled directly.
These types are mostly reexports from the [*typenum* crate].
[*typenum* crate]: https://crates.io/crates/typenum
*/
pub use typenum::{
Diff, IsLessOrEqual, Sum, True, U0, U1, U2, U3, U4, U5, U6, U7, U8, U9, U10, U11, U12, U13,
U14, U15, U16, U17, U18, U19, U20, U21, U22, U23, U24, U25, U26, U27, U28, U29, U30, U31, U32,
U33, U34, U35, U36, U37, U38, U39, U40, U41, U42, U43, U44, U45, U46, U47, U48, U49, U50, U51,
U52, U53, U54, U55, U56, U57, U58, U59, U60, U61, U62, U63, U64, U65, U66, U67, U68, U69, U70,
U71, U72, U73, U74, U75, U76, U77, U78, U79, U80, U81, U82, U83, U84, U85, U86, U87, U88, U89,
U90, U91, U92, U93, U94, U95, U96, U97, U98, U99, U100, U101, U102, U103, U104, U105, U106,
U107, U108, U109, U110, U111, U112, U113, U114, U115, U116, U117, U118, U119, U120, U121, U122,
U123, U124, U125, U126, U127, U128, Unsigned,
};
/// Implemented for all [`Unsigned`] integers ≤ 8.
pub trait LeEqU8: Unsigned + IsLessOrEqual<U8, Output = True> {}
impl<T: Unsigned + IsLessOrEqual<U8, Output = True>> LeEqU8 for T {}
/// Implemented for all [`Unsigned`] integers ≤ 16.
pub trait LeEqU16: Unsigned + IsLessOrEqual<U16, Output = True> {}
impl<T: Unsigned + IsLessOrEqual<U16, Output = True>> LeEqU16 for T {}
/// Implemented for all [`Unsigned`] integers ≤ 32.
pub trait LeEqU32: Unsigned + IsLessOrEqual<U32, Output = True> {}
impl<T: Unsigned + IsLessOrEqual<U32, Output = True>> LeEqU32 for T {}
/// Implemented for all [`Unsigned`] integers ≤ 64.
pub trait LeEqU64: Unsigned + IsLessOrEqual<U64, Output = True> {}
impl<T: Unsigned + IsLessOrEqual<U64, Output = True>> LeEqU64 for T {}
/// Implemented for all [`Unsigned`] integers ≤ 128.
pub trait LeEqU128: Unsigned + IsLessOrEqual<U128, Output = True> {}
impl<T: Unsigned + IsLessOrEqual<U128, Output = True>> LeEqU128 for T {}
+1084
View File
File diff suppressed because it is too large. Load diff
+2833
View File
File diff suppressed because it is too large. Load diff
+2367
View File
File diff suppressed because it is too large. Load diff