use super::*;
macro_rules! impl_safe_convert_decimal_to_int {
($($dst:ty),*) => {
$(
impl SafeConvert<$dst> for Decimal {
fn checked_convert(self) -> Option<$dst> {
narrow_i256(self.trunc())
}
fn saturating_convert(self) -> $dst {
if let Some(value) = narrow_i256(self.trunc()) {
value
} else if self.is_negative() {
<$dst>::MIN
} else {
<$dst>::MAX
}
}
fn wrapping_convert(self) -> $dst {
self.saturating_convert()
}
}
)*
};
}
macro_rules! impl_safe_convert_decimal_to_float {
($($dst:ty => $to:ident),*) => {
$(
impl SafeConvert<$dst> for Decimal {
fn checked_convert(self) -> Option<$dst> {
let value = self.$to();
value.is_finite().then_some(value)
}
fn saturating_convert(self) -> $dst {
let value = self.$to();
if value.is_finite() {
value
} else if value.is_sign_negative() {
<$dst>::MIN
} else {
<$dst>::MAX
}
}
fn wrapping_convert(self) -> $dst {
self.saturating_convert()
}
}
)*
};
}
impl_safe_convert_decimal_to_int!(i8, i16, i32, i64, i128, u8, u16, u32, u64, u128);
impl_safe_convert_decimal_to_float!(f32 => to_f32, f64 => to_f64);
#[cfg(test)]
pub mod tests {
mod i8 {
use super::*;
use crate::value::{decimal::Decimal, number::safe::convert::SafeConvert};
#[test]
fn test_checked_convert() {
let x = Decimal::from(127i64);
let y: Option<i8> = x.checked_convert();
assert_eq!(y, Some(127i8));
}
#[test]
fn test_checked_convert_overflow() {
let x = Decimal::from(128i64);
let y: Option<i8> = x.checked_convert();
assert_eq!(y, None);
}
#[test]
fn test_saturating_convert() {
let x = Decimal::from(200i64);
let y: i8 = x.saturating_convert();
assert_eq!(y, i8::MAX);
}
#[test]
fn test_wrapping_convert() {
let x = Decimal::from(-129i64);
let y: i8 = x.wrapping_convert();
assert_eq!(y, i8::MIN);
}
}
mod i32 {
use super::*;
use crate::value::{decimal::Decimal, number::safe::convert::SafeConvert};
#[test]
fn test_checked_convert() {
let x = Decimal::from(2147483647i64);
let y: Option<i32> = x.checked_convert();
assert_eq!(y, Some(2147483647i32));
}
#[test]
fn test_saturating_convert() {
let x = Decimal::from(-2147483648i64);
let y: i32 = x.saturating_convert();
assert_eq!(y, -2147483648i32);
}
}
mod u8 {
use super::*;
use crate::value::{decimal::Decimal, number::safe::convert::SafeConvert};
#[test]
fn test_checked_convert() {
let x = Decimal::from(255i64);
let y: Option<u8> = x.checked_convert();
assert_eq!(y, Some(255u8));
}
#[test]
fn test_checked_convert_overflow() {
let x = Decimal::from(256i64);
let y: Option<u8> = x.checked_convert();
assert_eq!(y, None);
}
#[test]
fn test_checked_convert_negative() {
let x = Decimal::from(-1i64);
let y: Option<u8> = x.checked_convert();
assert_eq!(y, None);
}
#[test]
fn test_saturating_convert() {
let x = Decimal::from(1000i64);
let y: u8 = x.saturating_convert();
assert_eq!(y, u8::MAX);
}
}
mod u32 {
use super::*;
use crate::value::{decimal::Decimal, number::safe::convert::SafeConvert};
#[test]
fn test_checked_convert() {
let x = Decimal::from(4294967295i64);
let y: Option<u32> = x.checked_convert();
assert_eq!(y, Some(4294967295u32));
}
#[test]
fn test_saturating_convert() {
let x = Decimal::from(-100i64);
let y: u32 = x.saturating_convert();
assert_eq!(y, 0u32);
}
}
mod f32 {
use std::str::FromStr;
use super::*;
use crate::value::{decimal::Decimal, number::safe::convert::SafeConvert};
#[test]
fn test_checked_convert() {
let x = Decimal::from(42i64);
let y: Option<f32> = x.checked_convert();
assert_eq!(y, Some(42.0f32));
}
#[test]
fn test_saturating_convert() {
let x = Decimal::from(-1000i64);
let y: f32 = x.saturating_convert();
assert_eq!(y, -1000.0f32);
}
#[test]
fn checked_convert_f32_max_exact_literal_roundtrips() {
let dec = Decimal::from_str("3.4028234663852886e38").unwrap();
let out: Option<f32> = dec.checked_convert();
assert_eq!(out, Some(f32::MAX));
}
#[test]
fn checked_convert_neg_f32_max_exact_literal_roundtrips() {
let dec = Decimal::from_str("-3.4028234663852886e38").unwrap();
let out: Option<f32> = dec.checked_convert();
assert_eq!(out, Some(f32::MIN));
}
#[test]
fn checked_convert_rounds_value_just_above_f32_max_to_max() {
let dec = Decimal::from_str("3.4028235e38").unwrap();
let out: Option<f32> = dec.checked_convert();
assert_eq!(out, Some(f32::MAX));
}
#[test]
fn checked_convert_rejects_value_above_f32_max() {
let dec = Decimal::from_str("1e40").unwrap();
let out: Option<f32> = dec.checked_convert();
assert_eq!(out, None);
}
#[test]
fn saturating_convert_above_f32_max_returns_max() {
let dec = Decimal::from_str("1e40").unwrap();
let out: f32 = dec.saturating_convert();
assert_eq!(out, f32::MAX);
}
#[test]
fn saturating_convert_below_neg_f32_max_returns_min() {
let dec = Decimal::from_str("-1e40").unwrap();
let out: f32 = dec.saturating_convert();
assert_eq!(out, f32::MIN);
}
#[test]
fn checked_convert_f32_min_positive_roundtrips() {
let dec = Decimal::from_str("1.17549435e-38").unwrap();
let out: Option<f32> = dec.checked_convert();
assert_eq!(out, Some(f32::MIN_POSITIVE));
}
}
mod f64 {
use std::str::FromStr;
use super::*;
use crate::value::{decimal::Decimal, number::safe::convert::SafeConvert};
#[test]
fn test_checked_convert() {
let x = Decimal::from(42i64);
let y: Option<f64> = x.checked_convert();
assert_eq!(y, Some(42.0f64));
}
#[test]
fn test_saturating_convert() {
let x = Decimal::from(-1000i64);
let y: f64 = x.saturating_convert();
assert_eq!(y, -1000.0f64);
}
#[test]
fn checked_convert_widest_decimal_literal_roundtrips() {
let dec = Decimal::from_str(&"9".repeat(76)).unwrap();
let out: Option<f64> = dec.checked_convert();
assert_eq!(out, Some(1e76));
}
#[test]
fn checked_convert_neg_widest_decimal_literal_roundtrips() {
let dec = Decimal::from_str(&format!("-{}", "9".repeat(76))).unwrap();
let out: Option<f64> = dec.checked_convert();
assert_eq!(out, Some(-1e76));
}
#[test]
fn saturating_convert_of_the_widest_decimals_stays_finite() {
let max = Decimal::from_str(&"9".repeat(76)).unwrap();
let out: f64 = max.clone().saturating_convert();
assert_eq!(out, 1e76);
let out: f64 = max.negate().saturating_convert();
assert_eq!(out, -1e76);
}
#[test]
fn checked_convert_smallest_positive_decimal_roundtrips() {
let dec = Decimal::from_str(&format!("0.{}1", "0".repeat(75))).unwrap();
let out: Option<f64> = dec.checked_convert();
assert_eq!(out, Some(1e-76));
}
#[test]
fn checked_convert_rounds_at_seventeen_digits_like_the_literal() {
let text = "0.12345678901234567890123456789";
let dec = Decimal::from_str(text).unwrap();
let out: Option<f64> = dec.checked_convert();
assert_eq!(out, Some(text.parse::<f64>().unwrap()));
}
}
mod self_conversion {
use crate::value::{decimal::Decimal, number::safe::convert::SafeConvert};
#[test]
fn test_checked_convert() {
let x = Decimal::from(42i64);
let y: Option<Decimal> = x.clone().checked_convert();
assert_eq!(y, Some(x));
}
#[test]
fn test_saturating_convert() {
let x = Decimal::from(-100i64);
let y: Decimal = x.clone().saturating_convert();
assert_eq!(y, x);
}
#[test]
fn test_wrapping_convert() {
let x = Decimal::from(999i64);
let y: Decimal = x.clone().wrapping_convert();
assert_eq!(y, x);
}
}
mod range {
use std::str::FromStr;
use crate::value::{decimal::Decimal, number::safe::convert::SafeConvert};
#[test]
fn integer_targets_truncate_toward_zero() {
let dec = Decimal::from_str("-1.9").unwrap();
assert_eq!(SafeConvert::<i8>::checked_convert(dec.clone()), Some(-1));
}
#[test]
fn wide_decimals_refuse_narrow_targets_and_clamp_with_their_sign() {
let max = Decimal::from_str(&"9".repeat(76)).unwrap();
assert_eq!(SafeConvert::<i128>::checked_convert(max.clone()), None);
assert_eq!(SafeConvert::<u128>::checked_convert(max.clone()), None);
assert_eq!(SafeConvert::<u128>::saturating_convert(max.clone()), u128::MAX);
assert_eq!(SafeConvert::<i64>::saturating_convert(max.negate()), i64::MIN);
let u128_max = Decimal::from_str(&u128::MAX.to_string()).unwrap();
assert_eq!(SafeConvert::<u128>::checked_convert(u128_max), Some(u128::MAX));
}
}
}