use crate::core_type::I128;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum DecimalConvertError {
Overflow,
NotFinite,
}
impl core::fmt::Display for DecimalConvertError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Self::Overflow => f.write_str("decimal conversion overflow"),
Self::NotFinite => f.write_str("decimal conversion from non-finite float"),
}
}
}
#[cfg(feature = "std")]
impl std::error::Error for DecimalConvertError {}
impl<const SCALE: u32> From<i8> for I128<SCALE> {
#[inline]
fn from(value: i8) -> Self {
Self((value as i128) * Self::multiplier())
}
}
impl<const SCALE: u32> From<i16> for I128<SCALE> {
#[inline]
fn from(value: i16) -> Self {
Self((value as i128) * Self::multiplier())
}
}
impl<const SCALE: u32> From<i32> for I128<SCALE> {
#[inline]
fn from(value: i32) -> Self {
Self((value as i128) * Self::multiplier())
}
}
impl<const SCALE: u32> From<i64> for I128<SCALE> {
#[inline]
fn from(value: i64) -> Self {
Self((value as i128) * Self::multiplier())
}
}
impl<const SCALE: u32> From<u8> for I128<SCALE> {
#[inline]
fn from(value: u8) -> Self {
Self((value as i128) * Self::multiplier())
}
}
impl<const SCALE: u32> From<u16> for I128<SCALE> {
#[inline]
fn from(value: u16) -> Self {
Self((value as i128) * Self::multiplier())
}
}
impl<const SCALE: u32> From<u32> for I128<SCALE> {
#[inline]
fn from(value: u32) -> Self {
Self((value as i128) * Self::multiplier())
}
}
impl<const SCALE: u32> From<u64> for I128<SCALE> {
#[inline]
fn from(value: u64) -> Self {
Self((value as i128) * Self::multiplier())
}
}
impl<const SCALE: u32> TryFrom<i128> for I128<SCALE> {
type Error = DecimalConvertError;
#[inline]
fn try_from(value: i128) -> Result<Self, Self::Error> {
value
.checked_mul(Self::multiplier())
.map(Self)
.ok_or(DecimalConvertError::Overflow)
}
}
impl<const SCALE: u32> TryFrom<u128> for I128<SCALE> {
type Error = DecimalConvertError;
#[inline]
fn try_from(value: u128) -> Result<Self, Self::Error> {
let as_i128: i128 = i128::try_from(value).map_err(|_| DecimalConvertError::Overflow)?;
as_i128
.checked_mul(Self::multiplier())
.map(Self)
.ok_or(DecimalConvertError::Overflow)
}
}
impl<const SCALE: u32> TryFrom<f32> for I128<SCALE> {
type Error = DecimalConvertError;
#[inline]
fn try_from(value: f32) -> Result<Self, Self::Error> {
Self::try_from(value as f64)
}
}
impl<const SCALE: u32> TryFrom<f64> for I128<SCALE> {
type Error = DecimalConvertError;
#[inline]
fn try_from(value: f64) -> Result<Self, Self::Error> {
if !value.is_finite() {
return Err(DecimalConvertError::NotFinite);
}
let scaled = value * (Self::multiplier() as f64);
const I128_MAX_F64: f64 = i128::MAX as f64;
const I128_MIN_F64: f64 = i128::MIN as f64;
if !(I128_MIN_F64..I128_MAX_F64).contains(&scaled) {
return Err(DecimalConvertError::Overflow);
}
Ok(Self(scaled as i128))
}
}
impl<const SCALE: u32> I128<SCALE> {
#[inline]
pub fn from_int(value: i64) -> Self {
Self::from(value)
}
#[inline]
pub fn from_i32(value: i32) -> Self {
Self::from(value)
}
pub fn from_f64_lossy(value: f64) -> Self {
if value.is_nan() {
return Self::ZERO;
}
if value.is_infinite() {
return if value > 0.0 { Self::MAX } else { Self::MIN };
}
let scaled = value * (Self::multiplier() as f64);
const I128_MAX_F64: f64 = i128::MAX as f64;
const I128_MIN_F64: f64 = i128::MIN as f64;
if scaled >= I128_MAX_F64 {
return Self::MAX;
}
if scaled < I128_MIN_F64 {
return Self::MIN;
}
Self(scaled as i128)
}
#[inline]
pub fn to_int_lossy(self) -> i64 {
let int_part: i128 = self.0 / Self::multiplier();
if int_part > i64::MAX as i128 {
i64::MAX
} else if int_part < i64::MIN as i128 {
i64::MIN
} else {
int_part as i64
}
}
#[inline]
pub fn to_f64_lossy(self) -> f64 {
(self.0 as f64) / (Self::multiplier() as f64)
}
#[inline]
pub fn to_f32_lossy(self) -> f32 {
self.to_f64_lossy() as f32
}
}
#[cfg(test)]
mod tests {
use super::DecimalConvertError;
use crate::core_type::{I128, I128s12};
#[test]
fn from_int_zero_is_zero() {
assert_eq!(I128s12::from_int(0), I128s12::ZERO);
}
#[test]
fn from_i32_zero_is_zero() {
assert_eq!(I128s12::from_i32(0), I128s12::ZERO);
}
#[test]
fn from_int_one_is_one() {
assert_eq!(I128s12::from_int(1), I128s12::ONE);
}
#[test]
fn from_i32_one_is_one() {
assert_eq!(I128s12::from_i32(1), I128s12::ONE);
}
#[test]
fn from_int_negative() {
assert_eq!(I128s12::from_int(-1), -I128s12::ONE);
assert_eq!(I128s12::from_int(-42).to_bits(), -42_000_000_000_000_i128);
}
#[test]
fn from_i8_scales_correctly() {
assert_eq!(I128s12::from(0_i8).to_bits(), 0);
assert_eq!(I128s12::from(1_i8).to_bits(), 1_000_000_000_000);
assert_eq!(I128s12::from(-1_i8).to_bits(), -1_000_000_000_000);
assert_eq!(I128s12::from(i8::MAX).to_bits(), 127_000_000_000_000);
assert_eq!(I128s12::from(i8::MIN).to_bits(), -128_000_000_000_000);
}
#[test]
fn from_i16_scales_correctly() {
assert_eq!(I128s12::from(0_i16).to_bits(), 0);
assert_eq!(I128s12::from(1_i16).to_bits(), 1_000_000_000_000);
assert_eq!(I128s12::from(i16::MAX).to_bits(), 32_767_000_000_000_000);
assert_eq!(I128s12::from(i16::MIN).to_bits(), -32_768_000_000_000_000);
}
#[test]
fn from_i32_scales_correctly() {
assert_eq!(I128s12::from(0_i32).to_bits(), 0);
assert_eq!(I128s12::from(i32::MAX).to_bits(), (i32::MAX as i128) * 1_000_000_000_000);
assert_eq!(I128s12::from(i32::MIN).to_bits(), (i32::MIN as i128) * 1_000_000_000_000);
}
#[test]
fn from_i64_scales_correctly() {
assert_eq!(I128s12::from(0_i64).to_bits(), 0);
assert_eq!(I128s12::from(i64::MAX).to_bits(), (i64::MAX as i128) * 1_000_000_000_000);
assert_eq!(I128s12::from(i64::MIN).to_bits(), (i64::MIN as i128) * 1_000_000_000_000);
}
#[test]
fn from_u8_scales_correctly() {
assert_eq!(I128s12::from(0_u8).to_bits(), 0);
assert_eq!(I128s12::from(u8::MAX).to_bits(), 255_000_000_000_000);
}
#[test]
fn from_u16_scales_correctly() {
assert_eq!(I128s12::from(0_u16).to_bits(), 0);
assert_eq!(I128s12::from(u16::MAX).to_bits(), 65_535_000_000_000_000);
}
#[test]
fn from_u32_scales_correctly() {
assert_eq!(I128s12::from(0_u32).to_bits(), 0);
assert_eq!(I128s12::from(u32::MAX).to_bits(), (u32::MAX as i128) * 1_000_000_000_000);
}
#[test]
fn from_u64_at_boundary_is_lossless() {
let v = I128s12::from(u64::MAX);
assert_eq!(v.to_bits(), (u64::MAX as i128) * 1_000_000_000_000);
}
#[test]
fn integer_round_trip_via_lossy_to_int() {
for v in [0_i32, 1, -1, 42, -42, i32::MAX, i32::MIN] {
assert_eq!(I128s12::from(v).to_int_lossy(), v as i64);
}
for v in [0_i64, 1, -1, 1_000_000_000, -1_000_000_000] {
assert_eq!(I128s12::from(v).to_int_lossy(), v);
}
}
#[test]
fn from_f64_lossy_zero_is_zero() {
assert_eq!(I128s12::from_f64_lossy(0.0), I128s12::ZERO);
}
#[test]
fn zero_to_int_lossy_is_zero() {
assert_eq!(I128s12::ZERO.to_int_lossy(), 0);
}
#[test]
fn zero_to_f64_lossy_is_zero() {
assert_eq!(I128s12::ZERO.to_f64_lossy(), 0.0);
}
#[test]
fn zero_to_f32_lossy_is_zero() {
assert_eq!(I128s12::ZERO.to_f32_lossy(), 0.0);
}
#[test]
fn from_f64_lossy_one_is_one() {
let v = I128s12::from_f64_lossy(1.0);
assert_eq!(v, I128s12::ONE);
}
#[test]
fn from_f64_lossy_negative() {
let v = I128s12::from_f64_lossy(-1.0);
assert_eq!(v, -I128s12::ONE);
}
#[test]
fn from_f64_to_f64_round_trip_within_1_lsb() {
let lsb = 1.0 / (I128s12::multiplier() as f64);
let cases = [
0.0_f64,
1.0,
-1.0,
0.5,
-0.5,
1.5,
-1.5,
1.234567890123_f64,
-1.234567890123_f64,
1e6,
-1e6,
1e10,
-1e10,
1.1,
2.2,
3.3,
];
for x in cases {
let v = I128s12::from_f64_lossy(x);
let back = v.to_f64_lossy();
let err = (back - x).abs();
assert!(
err <= lsb * 2.0, "round-trip exceeded 2 LSB for x = {x}: back = {back}, err = {err}, lsb = {lsb}"
);
}
}
#[test]
fn to_f32_lossy_matches_f64_path() {
let cases = [
I128s12::ZERO,
I128s12::ONE,
-I128s12::ONE,
I128s12::from_bits(1_500_000_000_000),
I128s12::from_bits(-7_321_654_987_000),
];
for v in cases {
let via_f64 = v.to_f64_lossy() as f32;
assert_eq!(v.to_f32_lossy(), via_f64);
}
}
#[test]
fn from_f64_lossy_infinity_saturates_max() {
assert_eq!(I128s12::from_f64_lossy(f64::INFINITY), I128s12::MAX);
}
#[test]
fn from_f64_lossy_neg_infinity_saturates_min() {
assert_eq!(I128s12::from_f64_lossy(f64::NEG_INFINITY), I128s12::MIN);
}
#[test]
fn from_f64_lossy_nan_is_zero() {
assert_eq!(I128s12::from_f64_lossy(f64::NAN), I128s12::ZERO);
}
#[test]
fn from_f64_lossy_finite_out_of_range_saturates() {
assert_eq!(I128s12::from_f64_lossy(1e30), I128s12::MAX);
assert_eq!(I128s12::from_f64_lossy(-1e30), I128s12::MIN);
}
#[test]
fn to_int_lossy_truncates_toward_zero() {
assert_eq!(I128s12::from_bits(2_500_000_000_000).to_int_lossy(), 2);
assert_eq!(I128s12::from_bits(-2_500_000_000_000).to_int_lossy(), -2);
assert_eq!(I128s12::from_bits(999_999_999_999).to_int_lossy(), 0);
assert_eq!(I128s12::from_bits(-999_999_999_999).to_int_lossy(), 0);
}
#[test]
fn to_int_lossy_saturates() {
assert_eq!(I128s12::MAX.to_int_lossy(), i64::MAX);
assert_eq!(I128s12::MIN.to_int_lossy(), i64::MIN);
}
#[test]
fn try_from_i128_zero_succeeds() {
let v: I128s12 = 0_i128.try_into().expect("zero fits");
assert_eq!(v, I128s12::ZERO);
}
#[test]
fn try_from_i128_in_range_succeeds() {
let v: I128s12 = 1_000_000_i128.try_into().expect("in-range fits");
assert_eq!(v.to_bits(), 1_000_000 * 1_000_000_000_000);
}
#[test]
fn try_from_i128_overflow_returns_err() {
let result: Result<I128s12, _> = i128::MAX.try_into();
assert_eq!(result, Err(DecimalConvertError::Overflow));
let result_neg: Result<I128s12, _> = i128::MIN.try_into();
assert_eq!(result_neg, Err(DecimalConvertError::Overflow));
}
#[test]
fn try_from_u128_zero_succeeds() {
let v: I128s12 = 0_u128.try_into().expect("zero fits");
assert_eq!(v, I128s12::ZERO);
}
#[test]
fn try_from_u128_in_range_succeeds() {
let v: I128s12 = 42_u128.try_into().expect("in-range fits");
assert_eq!(v.to_bits(), 42 * 1_000_000_000_000);
}
#[test]
fn try_from_u128_above_i128_max_returns_err() {
let above: u128 = (i128::MAX as u128) + 1;
let result: Result<I128s12, _> = above.try_into();
assert_eq!(result, Err(DecimalConvertError::Overflow));
}
#[test]
fn try_from_u128_max_returns_err() {
let result: Result<I128s12, _> = u128::MAX.try_into();
assert_eq!(result, Err(DecimalConvertError::Overflow));
}
#[test]
fn try_from_f64_zero_succeeds() {
let v: I128s12 = 0.0_f64.try_into().expect("zero fits");
assert_eq!(v, I128s12::ZERO);
}
#[test]
fn try_from_f64_one_succeeds() {
let v: I128s12 = 1.0_f64.try_into().expect("one fits");
assert_eq!(v, I128s12::ONE);
}
#[test]
fn try_from_f64_nan_returns_err() {
let result: Result<I128s12, _> = f64::NAN.try_into();
assert_eq!(result, Err(DecimalConvertError::NotFinite));
}
#[test]
fn try_from_f64_pos_infinity_returns_err() {
let result: Result<I128s12, _> = f64::INFINITY.try_into();
assert_eq!(result, Err(DecimalConvertError::NotFinite));
}
#[test]
fn try_from_f64_neg_infinity_returns_err() {
let result: Result<I128s12, _> = f64::NEG_INFINITY.try_into();
assert_eq!(result, Err(DecimalConvertError::NotFinite));
}
#[test]
fn try_from_f64_out_of_range_returns_err() {
let result: Result<I128s12, _> = 1e30_f64.try_into();
assert_eq!(result, Err(DecimalConvertError::Overflow));
let result_neg: Result<I128s12, _> = (-1e30_f64).try_into();
assert_eq!(result_neg, Err(DecimalConvertError::Overflow));
}
#[test]
fn try_from_f32_zero_succeeds() {
let v: I128s12 = 0.0_f32.try_into().expect("zero fits");
assert_eq!(v, I128s12::ZERO);
}
#[test]
fn try_from_f32_nan_returns_err() {
let result: Result<I128s12, _> = f32::NAN.try_into();
assert_eq!(result, Err(DecimalConvertError::NotFinite));
}
#[test]
fn try_from_f32_infinity_returns_err() {
let result: Result<I128s12, _> = f32::INFINITY.try_into();
assert_eq!(result, Err(DecimalConvertError::NotFinite));
}
#[test]
fn try_from_f32_neg_infinity_returns_err() {
let result: Result<I128s12, _> = f32::NEG_INFINITY.try_into();
assert_eq!(result, Err(DecimalConvertError::NotFinite));
}
#[cfg(feature = "alloc")]
#[test]
fn convert_error_display() {
extern crate alloc;
use alloc::string::ToString;
assert_eq!(
DecimalConvertError::Overflow.to_string(),
"decimal conversion overflow"
);
assert_eq!(
DecimalConvertError::NotFinite.to_string(),
"decimal conversion from non-finite float"
);
}
#[test]
fn convert_error_traits_compile() {
fn assert_traits<T: core::fmt::Debug + Copy + Eq + core::hash::Hash>() {}
assert_traits::<DecimalConvertError>();
}
#[test]
fn from_int_works_at_scale_6() {
type D6 = I128<6>;
let v: D6 = D6::from(1_000_i64);
assert_eq!(v.to_bits(), 1_000_000_000); assert_eq!(v.to_int_lossy(), 1_000);
}
#[test]
fn from_int_works_at_scale_0() {
type D0 = I128<0>;
let v: D0 = D0::from(42_i64);
assert_eq!(v.to_bits(), 42);
assert_eq!(v.to_int_lossy(), 42);
}
}