use core::{
cmp::Ordering,
fmt::{Debug, Display},
hash::{Hash, Hasher},
ops::{AddAssign, SubAssign},
};
use num_traits::ops::wrapping::{WrappingAdd, WrappingSub};
use num_traits::{One, Zero};
#[cfg(feature = "float_nightly_experimental")]
use crate::UIntPlusOne;
mod private {
pub trait Sealed {}
}
#[doc(hidden)]
pub trait TotalFloat:
private::Sealed + Default + Copy + Clone + Debug + Send + Sync + 'static
{
const MIN: Self;
const MAX: Self;
const MAX_SIZE: Self::SafeLen;
type SafeLen: Send
+ Sync
+ Debug
+ Display
+ Hash
+ Copy
+ PartialEq
+ PartialOrd
+ num_traits::Zero
+ num_traits::One
+ AddAssign
+ SubAssign;
fn hash<H: Hasher>(x: Self, state: &mut H);
fn total_cmp(x: Self, y: Self) -> Ordering;
fn after(x: Self) -> Self;
fn before(x: Self) -> Self;
fn prim_safe_len(start: Self, end: Self) -> Self::SafeLen;
fn safe_len_to_f64_lossy(len: Self::SafeLen) -> f64;
fn f64_to_safe_len_lossy(f: f64) -> Self::SafeLen;
fn inclusive_end_from_start(a: Self, b: Self::SafeLen) -> Self;
fn start_from_inclusive_end(a: Self, b: Self::SafeLen) -> Self;
}
pub(super) trait TotalFloatImpl:
TotalFloat + Default + Copy + Clone + Debug + Send + Sync + 'static
{
type Bits: Copy + Eq + Hash + Send + Sync + Debug;
type Ordered: WrappingAdd + WrappingSub + One + PartialEq + Copy + Send + Sync + Debug + Display;
const MIN: Self;
const MAX: Self;
const MAX_SIZE: Self::SafeLen;
fn to_ordered(x: Self) -> Self::Ordered;
fn from_ordered(x: Self::Ordered) -> Self;
fn to_bits(x: Self) -> Self::Bits;
fn safe_len(start: Self::Ordered, end: Self::Ordered) -> Self::SafeLen;
fn safe_len_to_f64_lossy(len: Self::SafeLen) -> f64;
fn f64_to_safe_len_lossy(f: f64) -> Self::SafeLen;
fn safe_as_ordered(x: Self::SafeLen) -> Self::Ordered;
fn total_cmp(x: Self, y: Self) -> Ordering;
fn inclusive_end_from_start(a: Self, b: Self::SafeLen) -> Self {
#[cfg(debug_assertions)]
{
let max_len = <Self as TotalFloatImpl>::prim_safe_len(a, <Self as TotalFloatImpl>::MAX);
assert!(
Self::SafeLen::zero() < b && b <= max_len,
"b must be in range 1..=max_len (b = {b}, max_len = {max_len})"
);
}
Self::from_ordered(Self::to_ordered(a).wrapping_add(&Self::safe_as_ordered(b)))
}
fn start_from_inclusive_end(a: Self, b: Self::SafeLen) -> Self {
#[cfg(debug_assertions)]
{
let max_len = <Self as TotalFloatImpl>::prim_safe_len(<Self as TotalFloatImpl>::MIN, a);
assert!(
Self::SafeLen::zero() < b && b <= max_len,
"b must be in range 1..=max_len (b = {b}, max_len = {max_len})"
);
}
Self::from_ordered(Self::to_ordered(a).wrapping_sub(&Self::safe_as_ordered(b)))
}
fn prim_safe_len(start: Self, end: Self) -> Self::SafeLen {
Self::safe_len(Self::to_ordered(start), Self::to_ordered(end))
}
}
macro_rules! impl_ordered_transform {
($primitive:ty, $ordered:ty, $to_ordered:ident, $from_ordered:ident, $sign_shift:literal) => {
pub(super) const fn $to_ordered(x: $primitive) -> $ordered {
let mut bits = x.to_bits().cast_signed();
bits ^= ((bits >> $sign_shift).cast_unsigned() >> 1).cast_signed();
bits
}
pub(super) const fn $from_ordered(mut bits: $ordered) -> $primitive {
bits ^= ((bits >> $sign_shift).cast_unsigned() >> 1).cast_signed();
<$primitive>::from_bits(bits.cast_unsigned())
}
};
}
impl_ordered_transform!(f64, i64, to_ordered_64, from_ordered_64, 63);
impl_ordered_transform!(f32, i32, to_ordered_32, from_ordered_32, 31);
#[cfg(feature = "float_nightly_experimental")]
impl_ordered_transform!(f16, i16, to_ordered_16, from_ordered_16, 15);
#[cfg(feature = "float_nightly_experimental")]
impl_ordered_transform!(f128, i128, to_ordered_128, from_ordered_128, 127);
macro_rules! impl_total_ops {
($to_ordered:ident) => {
fn to_bits(x: Self) -> Self::Bits {
x.to_bits()
}
fn to_ordered(x: Self) -> Self::Ordered {
$to_ordered(x)
}
fn total_cmp(x: Self, y: Self) -> Ordering {
x.total_cmp(&y)
}
};
}
macro_rules! impl_total_ops_safelen {
() => {
fn safe_len(start: Self::Ordered, end: Self::Ordered) -> Self::SafeLen {
debug_assert!(start <= end, "start ≤ end required");
Self::SafeLen::from(end) - Self::SafeLen::from(start) + 1
}
#[allow(clippy::cast_precision_loss)]
#[allow(clippy::use_self, reason = "f64 is not really Self")]
#[allow(clippy::cast_lossless)]
fn safe_len_to_f64_lossy(len: Self::SafeLen) -> f64 {
len as f64
}
#[expect(clippy::cast_possible_truncation)]
fn f64_to_safe_len_lossy(f: f64) -> Self::SafeLen {
f as Self::SafeLen
}
#[expect(clippy::cast_possible_truncation)]
fn safe_as_ordered(x: Self::SafeLen) -> Self::Ordered {
debug_assert!(!x.is_zero(), "x must not be zero");
(x - 1) as Self::Ordered
}
};
}
impl private::Sealed for f64 {}
impl private::Sealed for f32 {}
#[cfg(feature = "float_nightly_experimental")]
impl private::Sealed for f16 {}
#[cfg(feature = "float_nightly_experimental")]
impl private::Sealed for f128 {}
macro_rules! impl_total_capability {
($primitive:ty, $safe_len:ty) => {
impl TotalFloat for $primitive {
type SafeLen = $safe_len;
const MIN: Self = <Self as TotalFloatImpl>::MIN;
const MAX: Self = <Self as TotalFloatImpl>::MAX;
const MAX_SIZE: Self::SafeLen = <Self as TotalFloatImpl>::MAX_SIZE;
fn hash<H: Hasher>(x: Self, state: &mut H) {
<Self as TotalFloatImpl>::to_bits(x).hash(state);
}
fn total_cmp(x: Self, y: Self) -> Ordering {
<Self as TotalFloatImpl>::total_cmp(x, y)
}
fn after(x: Self) -> Self {
<Self as TotalFloatImpl>::from_ordered(
<Self as TotalFloatImpl>::to_ordered(x)
.wrapping_add(<<Self as TotalFloatImpl>::Ordered as One>::one()),
)
}
fn before(x: Self) -> Self {
<Self as TotalFloatImpl>::from_ordered(
<Self as TotalFloatImpl>::to_ordered(x)
.wrapping_sub(<<Self as TotalFloatImpl>::Ordered as One>::one()),
)
}
fn prim_safe_len(start: Self, end: Self) -> Self::SafeLen {
<Self as TotalFloatImpl>::prim_safe_len(start, end)
}
fn safe_len_to_f64_lossy(len: Self::SafeLen) -> f64 {
<Self as TotalFloatImpl>::safe_len_to_f64_lossy(len)
}
fn f64_to_safe_len_lossy(f: f64) -> Self::SafeLen {
<Self as TotalFloatImpl>::f64_to_safe_len_lossy(f)
}
fn inclusive_end_from_start(a: Self, b: Self::SafeLen) -> Self {
<Self as TotalFloatImpl>::inclusive_end_from_start(a, b)
}
fn start_from_inclusive_end(a: Self, b: Self::SafeLen) -> Self {
<Self as TotalFloatImpl>::start_from_inclusive_end(a, b)
}
}
};
}
impl_total_capability!(f64, i128);
impl_total_capability!(f32, i64);
#[cfg(feature = "float_nightly_experimental")]
impl_total_capability!(f16, i32);
#[cfg(feature = "float_nightly_experimental")]
impl_total_capability!(f128, UIntPlusOne<u128>);
impl TotalFloatImpl for f64 {
type Bits = u64;
type Ordered = i64;
const MIN: Self = Self::from_bits(u64::MAX);
const MAX: Self = Self::from_bits(0x7fff_ffff_ffff_ffff);
const MAX_SIZE: Self::SafeLen = u64::MAX as Self::SafeLen + 1;
impl_total_ops!(to_ordered_64);
impl_total_ops_safelen!();
fn from_ordered(bits: Self::Ordered) -> Self {
from_ordered_64(bits)
}
}
impl TotalFloatImpl for f32 {
type Bits = u32;
type Ordered = i32;
const MIN: Self = Self::from_bits(u32::MAX);
const MAX: Self = Self::from_bits(0x7fff_ffff);
const MAX_SIZE: Self::SafeLen = u32::MAX as Self::SafeLen + 1;
impl_total_ops!(to_ordered_32);
impl_total_ops_safelen!();
fn from_ordered(bits: Self::Ordered) -> Self {
from_ordered_32(bits)
}
}
#[cfg(feature = "float_nightly_experimental")]
impl TotalFloatImpl for f16 {
type Bits = u16;
type Ordered = i16;
const MIN: Self = Self::from_bits(u16::MAX);
const MAX: Self = Self::from_bits(0x7fff);
const MAX_SIZE: Self::SafeLen = u16::MAX as Self::SafeLen + 1;
impl_total_ops!(to_ordered_16);
impl_total_ops_safelen!();
fn from_ordered(bits: Self::Ordered) -> Self {
from_ordered_16(bits)
}
}
#[cfg(feature = "float_nightly_experimental")]
impl TotalFloatImpl for f128 {
type Bits = u128;
type Ordered = i128;
const MIN: Self = Self::from_bits(u128::MAX);
const MAX: Self = Self::from_bits(0x7fff_ffff_ffff_ffff_ffff_ffff_ffff_ffff);
const MAX_SIZE: Self::SafeLen = UIntPlusOne::<u128>::MaxPlusOne;
impl_total_ops!(to_ordered_128);
fn from_ordered(bits: Self::Ordered) -> Self {
from_ordered_128(bits)
}
#[expect(clippy::cast_sign_loss)]
fn safe_len(start: Self::Ordered, end: Self::Ordered) -> Self::SafeLen {
debug_assert!(start <= end, "start ≤ end required");
let less1 = end.overflowing_sub(start).0 as u128;
let less1 = UIntPlusOne::UInt(less1);
less1 + UIntPlusOne::UInt(1)
}
#[allow(clippy::cast_precision_loss)]
fn safe_len_to_f64_lossy(len: Self::SafeLen) -> f64 {
match len {
UIntPlusOne::UInt(len) => len as f64,
UIntPlusOne::MaxPlusOne => UIntPlusOne::<u128>::max_plus_one_as_f64(),
}
}
#[expect(clippy::cast_possible_truncation)]
#[expect(clippy::cast_sign_loss)]
fn f64_to_safe_len_lossy(f: f64) -> Self::SafeLen {
if f >= UIntPlusOne::<u128>::max_plus_one_as_f64() {
UIntPlusOne::MaxPlusOne
} else {
UIntPlusOne::UInt(f as u128)
}
}
#[expect(clippy::cast_possible_wrap)]
fn safe_as_ordered(x: Self::SafeLen) -> Self::Ordered {
match x {
UIntPlusOne::UInt(x) => {
debug_assert!(x != 0, "x must not be zero");
(x - 1) as Self::Ordered
}
UIntPlusOne::MaxPlusOne => u128::MAX as Self::Ordered,
}
}
}