use crate::ffi::decimal as ffi;
use std::convert::{TryFrom, TryInto};
use std::mem::size_of;
use once_cell::sync::Lazy;
use serde::{Deserialize, Serialize};
#[derive(Debug, Copy, Clone)]
pub struct Decimal {
pub(crate) inner: DecimalImpl,
}
impl Decimal {
#[inline(always)]
pub unsafe fn from_raw(inner: ffi::decNumber) -> Self {
let inner = std::mem::transmute(inner);
Self { inner }
}
unsafe fn from_inner_unchecked(inner: DecimalImpl) -> Self {
Self { inner }
}
#[inline(always)]
pub fn zero() -> Self {
unsafe { Self::from_inner_unchecked(DecimalImpl::zero()) }
}
#[inline(always)]
pub fn precision(&self) -> i32 {
let digits = self.inner.digits() as i32;
let exponent = self.inner.exponent();
if exponent <= 0 {
digits.max(-exponent)
} else {
digits + exponent
}
}
#[inline(always)]
pub fn scale(&self) -> i32 {
if self.inner.exponent() < 0 {
-self.inner.exponent()
} else {
0
}
}
#[inline(always)]
pub fn is_int(&self) -> bool {
let (_, exponent, _, lsu) = self.inner.to_raw_parts();
if exponent >= 0 {
return true;
}
let mut count = -exponent as usize;
let mut uit = 0;
while count >= ffi::DECDPUN {
if lsu[uit] != 0 {
return false;
};
count -= ffi::DECDPUN;
uit += 1;
}
if count == 0 {
return true; } else {
const POWERS: [u16; ffi::DECDPUN] = [1, 10, 100];
let rem = lsu[uit] % POWERS[count]; if rem != 0 {
return false;
}
}
true
}
#[inline(always)]
pub fn trim(mut self) -> Self {
self.inner.trim();
self
}
}
impl Decimal {
fn round_to_with_mode(mut self, scale: u8, mode: dec::Rounding) -> Option<Self> {
if scale > ffi::DECIMAL_MAX_DIGITS as _ {
return None;
}
if scale >= self.scale() as _ {
return Some(self);
}
let ndig = (self.precision() - self.scale() + scale as i32).max(1);
let mut ctx: Context = unsafe { &*CONTEXT }.clone();
ctx.set_precision(ndig as _).unwrap();
ctx.set_max_exponent(ndig as _).unwrap();
ctx.set_min_exponent(if scale != 0 { -1 } else { 0 })
.unwrap();
ctx.set_rounding(mode);
ctx.plus(&mut self.inner);
check_status(ctx.status()).ok()?;
Self::try_from(self.inner).ok()
}
#[inline(always)]
pub fn round(self) -> Self {
self.round_to_with_mode(0, dec::Rounding::HalfUp).unwrap()
}
#[inline(always)]
pub fn floor(self) -> Self {
self.round_to_with_mode(0, dec::Rounding::Down).unwrap()
}
#[inline(always)]
pub fn round_to(self, scale: u8) -> Option<Self> {
self.round_to_with_mode(scale, dec::Rounding::HalfUp)
}
#[inline(always)]
pub fn floor_to(self, scale: u8) -> Option<Self> {
self.round_to_with_mode(scale, dec::Rounding::Down)
}
#[inline(always)]
pub fn rescale(mut self, scale: u8) -> Option<Self> {
if scale <= self.scale() as _ {
return self.round_to(scale);
}
if scale as u32 > ffi::DECIMAL_MAX_DIGITS {
return None;
}
let delta = scale as i32 + self.inner.exponent();
if self.inner.digits() + delta as u32 > ffi::DECIMAL_MAX_DIGITS {
return None;
}
with_context(|ctx| ctx.rescale(&mut self.inner, &Self::from(-(scale as i32)).inner))?;
Self::try_from(self.inner).ok()
}
#[inline(always)]
pub fn abs(mut self) -> Self {
with_context(|ctx| ctx.abs(&mut self.inner)).expect("abs is a safe operation");
unsafe { Self::from_inner_unchecked(self.inner) }
}
#[inline(always)]
pub fn log10(mut self) -> Option<Self> {
with_context(|ctx| ctx.log10(&mut self.inner))?;
Self::try_from(self.inner).ok()
}
#[inline(always)]
pub fn ln(mut self) -> Option<Self> {
with_context(|ctx| ctx.ln(&mut self.inner))?;
Self::try_from(self.inner).ok()
}
#[inline(always)]
pub fn exp(mut self) -> Option<Self> {
with_context(|ctx| ctx.exp(&mut self.inner))?;
Self::try_from(self.inner).ok()
}
#[inline(always)]
pub fn sqrt(mut self) -> Option<Self> {
with_context(|ctx| ctx.sqrt(&mut self.inner))?;
Self::try_from(self.inner).ok()
}
#[inline(always)]
pub fn pow(mut self, pow: impl Into<Self>) -> Option<Self> {
with_context(|ctx| ctx.pow(&mut self.inner, &pow.into().inner))?;
Self::try_from(self.inner).ok()
}
#[inline(always)]
pub fn to_i64(self) -> Option<i64> {
std::convert::TryInto::try_into(self).ok()
}
#[inline(always)]
pub fn to_u64(self) -> Option<u64> {
std::convert::TryInto::try_into(self).ok()
}
}
type DecimalImpl = dec::Decimal<{ ffi::DECNUMUNITS as _ }>;
#[derive(Debug, Copy, Clone, PartialEq, Eq, thiserror::Error)]
pub enum ToDecimalError {
#[error("Infinite decimals are not supported")]
Infinite,
#[error("NaN decimals are not supported")]
Nan,
}
impl TryFrom<DecimalImpl> for Decimal {
type Error = ToDecimalError;
#[inline(always)]
fn try_from(inner: DecimalImpl) -> Result<Self, Self::Error> {
if inner.is_finite() {
Ok(Self { inner })
} else if inner.is_nan() {
Err(ToDecimalError::Nan)
} else if inner.is_infinite() {
Err(ToDecimalError::Infinite)
} else {
unreachable!()
}
}
}
type Context = dec::Context<DecimalImpl>;
static mut CONTEXT: Lazy<Context> = Lazy::new(|| {
let mut ctx = Context::default();
ctx.set_rounding(dec::Rounding::HalfUp);
ctx.set_precision(ffi::DECIMAL_MAX_DIGITS as _).unwrap();
ctx.set_clamp(false);
ctx.set_max_exponent((ffi::DECIMAL_MAX_DIGITS - 1) as _)
.unwrap();
ctx.set_min_exponent(-1).unwrap();
ctx
});
#[inline(always)]
fn with_context<F, T>(f: F) -> Option<T>
where
F: FnOnce(&mut Context) -> T,
{
let ctx = unsafe { &mut CONTEXT };
let res = f(ctx);
let status = ctx.status();
ctx.set_status(Default::default());
check_status(status).map(|()| res).ok()
}
const _: () = {
if size_of::<dec::Status>() != size_of::<u32>()
|| size_of::<dec::Status>() != size_of::<Status>()
{
panic!("unsupported layout")
}
};
#[derive(Clone, Copy)]
pub struct Status {
inner: u32,
}
impl From<dec::Status> for Status {
fn from(s: dec::Status) -> Self {
unsafe { std::mem::transmute(s) }
}
}
impl From<Status> for dec::Status {
fn from(s: Status) -> Self {
unsafe { std::mem::transmute(s) }
}
}
impl std::fmt::Debug for Status {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let status = dec::Status::from(*self);
let mut s = f.debug_struct("Status");
if status.conversion_syntax() {
s.field("conversion_syntax", &true);
}
if status.division_by_zero() {
s.field("division_by_zero", &true);
}
if status.division_impossible() {
s.field("division_impossible", &true);
}
if status.division_undefined() {
s.field("division_undefined", &true);
}
if status.insufficient_storage() {
s.field("insufficient_storage", &true);
}
if status.inexact() {
s.field("inexact", &true);
}
if status.invalid_context() {
s.field("invalid_context", &true);
}
if status.invalid_operation() {
s.field("invalid_operation", &true);
}
if status.overflow() {
s.field("overflow", &true);
}
if status.clamped() {
s.field("clamped", &true);
}
if status.rounded() {
s.field("rounded", &true);
}
if status.subnormal() {
s.field("subnormal", &true);
}
if status.underflow() {
s.field("underflow", &true);
}
s.finish()
}
}
#[track_caller]
fn check_status(status: dec::Status) -> Result<(), Status> {
let mut ignore = dec::Status::default();
ignore.set_inexact();
ignore.set_rounded();
ignore.set_underflow();
ignore.set_subnormal();
ignore.set_clamped();
let ignore = Status::from(ignore).inner;
let mut status = Status::from(status);
status.inner &= !ignore;
(status.inner == 0).then(|| ()).ok_or(status)
}
impl std::cmp::PartialOrd for Decimal {
#[inline(always)]
#[track_caller]
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
with_context(|ctx| ctx.partial_cmp(&self.inner, &other.inner)).flatten()
}
}
impl std::cmp::Ord for Decimal {
#[inline(always)]
#[track_caller]
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.partial_cmp(other)
.expect("special numbers aren't supported")
}
}
impl std::cmp::PartialEq for Decimal {
#[inline(always)]
#[track_caller]
fn eq(&self, other: &Self) -> bool {
self.cmp(other) == std::cmp::Ordering::Equal
}
}
impl std::cmp::Eq for Decimal {}
macro_rules! impl_cmp_int {
($($t:ty)+) => {
$(
impl std::cmp::PartialEq<$t> for Decimal {
#[inline(always)]
fn eq(&self, &other: &$t) -> bool {
self.is_int() && self == &Self::from(other)
}
}
)+
}
}
impl_cmp_int! {i8 i16 i32 i64 isize u8 u16 u32 u64 usize}
impl std::hash::Hash for Decimal {
#[inline(always)]
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
let (digits, exponent, bits, lsu) = self.trim().inner.to_raw_parts();
digits.hash(state);
exponent.hash(state);
bits.hash(state);
for u in &lsu[0..digits as usize / ffi::DECDPUN] {
u.hash(state);
}
}
}
macro_rules! impl_bin_op {
($m:ident, $trait:ident, $op:ident, $ass_trait:ident, $ass_op:ident) => {
impl Decimal {
#[inline(always)]
#[track_caller]
pub fn $m(mut self, rhs: impl Into<Self>) -> Option<Self> {
with_context(|ctx| ctx.$op(&mut self.inner, &rhs.into().inner))?;
Self::try_from(self.inner).ok()
}
}
impl<T: Into<Decimal>> std::ops::$trait<T> for Decimal {
type Output = Self;
#[inline(always)]
#[track_caller]
fn $op(self, rhs: T) -> Self {
self.$m(rhs).expect("overflow")
}
}
impl<T: Into<Decimal>> std::ops::$ass_trait<T> for Decimal {
#[inline(always)]
#[track_caller]
fn $ass_op(&mut self, rhs: T) {
*self = self.$m(rhs).expect("overlow")
}
}
};
}
impl_bin_op! {checked_add, Add, add, AddAssign, add_assign}
impl_bin_op! {checked_sub, Sub, sub, SubAssign, sub_assign}
impl_bin_op! {checked_mul, Mul, mul, MulAssign, mul_assign}
impl_bin_op! {checked_div, Div, div, DivAssign, div_assign}
impl_bin_op! {checked_rem, Rem, rem, RemAssign, rem_assign}
impl std::ops::Neg for Decimal {
type Output = Self;
#[inline(always)]
fn neg(self) -> Self {
unsafe { Self::from_inner_unchecked(self.inner.neg()) }
}
}
impl std::fmt::Display for Decimal {
#[inline(always)]
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.inner.fmt(f)
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub struct DecimalFromStrError;
impl std::str::FromStr for Decimal {
type Err = DecimalFromStrError;
#[inline(always)]
fn from_str(s: &str) -> Result<Self, Self::Err> {
with_context(|ctx| ctx.parse(s).ok())
.flatten()
.and_then(|d| Self::try_from(d).ok())
.ok_or(DecimalFromStrError)
}
}
impl std::convert::TryFrom<&str> for Decimal {
type Error = <Decimal as std::str::FromStr>::Err;
#[inline(always)]
fn try_from(s: &str) -> Result<Self, Self::Error> {
s.parse()
}
}
impl std::convert::TryFrom<&std::ffi::CStr> for Decimal {
type Error = DecimalFromStrError;
#[inline(always)]
fn try_from(s: &std::ffi::CStr) -> Result<Self, Self::Error> {
s.to_str()
.map_err(|_| DecimalFromStrError)
.and_then(str::parse)
}
}
static CTID_DECIMAL: Lazy<u32> = Lazy::new(|| {
use tlua::AsLua;
let lua = crate::global_lua();
let ctid_decimal = unsafe { tlua::ffi::luaL_ctypeid(lua.as_lua(), crate::c_ptr!("decimal_t")) };
debug_assert!(ctid_decimal != 0);
ctid_decimal
});
impl<L> tlua::LuaRead<L> for Decimal
where
L: tlua::AsLua,
{
fn lua_read_at_position(lua: L, index: std::num::NonZeroI32) -> Result<Self, L> {
let raw_lua = lua.as_lua();
let index = index.get();
unsafe {
if tlua::ffi::lua_type(raw_lua, index) != tlua::ffi::LUA_TCDATA {
return Err(lua);
}
let mut ctypeid = std::mem::MaybeUninit::uninit();
let cdata = tlua::ffi::luaL_checkcdata(raw_lua, index, ctypeid.as_mut_ptr());
if ctypeid.assume_init() != *CTID_DECIMAL {
return Err(lua);
}
Ok(Self::from_raw(*cdata.cast::<ffi::decNumber>()))
}
}
}
#[inline(always)]
fn push_decimal<L: tlua::AsLua>(lua: L, d: ffi::decNumber) -> tlua::PushGuard<L> {
unsafe {
let dec = tlua::ffi::luaL_pushcdata(lua.as_lua(), *CTID_DECIMAL);
std::ptr::write(dec.cast::<ffi::decNumber>(), d);
tlua::PushGuard::new(lua, 1)
}
}
impl<L: tlua::AsLua> tlua::Push<L> for Decimal {
type Err = tlua::Void;
fn push_to_lua(&self, lua: L) -> Result<tlua::PushGuard<L>, (Self::Err, L)> {
let (digits, exponent, bits, lsu) = self.inner.to_raw_parts();
let digits = digits as i32;
Ok(push_decimal(
lua,
ffi::decNumber {
digits,
exponent,
bits,
lsu,
},
))
}
}
impl<L: tlua::AsLua> tlua::PushOne<L> for Decimal {}
impl<L: tlua::AsLua> tlua::PushInto<L> for Decimal {
type Err = tlua::Void;
fn push_into_lua(self, lua: L) -> Result<tlua::PushGuard<L>, (Self::Err, L)> {
let (digits, exponent, bits, lsu) = self.inner.to_raw_parts();
let digits = digits as i32;
Ok(push_decimal(
lua,
ffi::decNumber {
digits,
exponent,
bits,
lsu,
},
))
}
}
impl<L: tlua::AsLua> tlua::PushOneInto<L> for Decimal {}
macro_rules! impl_from_int {
($($t:ty)+ => $f:expr) => {
$(
impl From<$t> for Decimal {
#[inline(always)]
fn from(num: $t) -> Self {
unsafe {
Self::from_inner_unchecked($f(num))
}
}
}
)+
}
}
impl_from_int! {i8 i16 i32 u8 u16 u32 => DecimalImpl::from}
impl_from_int! {i64 isize => |num| CONTEXT.from_i64(num as _)}
impl_from_int! {u64 usize => |num| CONTEXT.from_u64(num as _)}
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
pub enum DecimalFromfloatError<T> {
OutOfRange(T),
Infinite,
Nan,
}
impl<T: std::fmt::Display> std::fmt::Display for DecimalFromfloatError<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::OutOfRange(num) => {
write!(
f,
"float `{}` cannot be represented using {} digits",
num,
ffi::DECIMAL_MAX_DIGITS,
)
}
Self::Infinite => f.write_str("float is infinite"),
Self::Nan => f.write_str("float is NaN"),
}
}
}
impl<T> std::error::Error for DecimalFromfloatError<T>
where
T: std::fmt::Debug + std::fmt::Display,
{
fn description(&self) -> &'static str {
match self {
Self::OutOfRange(_) => "float is out of range",
Self::Infinite => "float is infinite",
Self::Nan => "float is NaN",
}
}
}
macro_rules! impl_tryfrom_float {
($($t:ty => $f:ident)+) => {
$(
impl From<$t> for DecimalFromfloatError<$t> {
#[inline(always)]
fn from(num: $t) -> Self {
match num.classify() {
std::num::FpCategory::Infinite => DecimalFromfloatError::Infinite,
std::num::FpCategory::Nan => DecimalFromfloatError::Nan,
std::num::FpCategory::Normal => DecimalFromfloatError::OutOfRange(num),
std::num::FpCategory::Zero => {
unreachable!("conversion cannot fail if number is zero")
}
std::num::FpCategory::Subnormal => {
unreachable!("subnormal floats are usually converted to zero")
}
}
}
}
impl std::convert::TryFrom<$t> for Decimal {
type Error = DecimalFromfloatError<$t>;
#[inline(always)]
fn try_from(num: $t) -> Result<Self, Self::Error> {
with_context(|ctx| ctx.$f(num))
.and_then(|inner| Self::try_from(inner).ok())
.ok_or_else(|| DecimalFromfloatError::from(num))
}
}
)+
}
}
impl_tryfrom_float! {
f32 => from_f32
f64 => from_f64
}
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
pub enum DecimalToIntError {
OutOfRange,
NonInteger,
}
#[allow(deprecated)]
impl std::fmt::Display for DecimalToIntError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(std::error::Error::description(self))
}
}
impl std::error::Error for DecimalToIntError {
fn description(&self) -> &'static str {
match self {
Self::OutOfRange => "decimal is out of range",
Self::NonInteger => "decimal is not an integer",
}
}
}
macro_rules! impl_try_into_int {
($($t:ty => $f:ident)+) => {
$(
impl std::convert::TryFrom<Decimal> for $t {
type Error = DecimalToIntError;
fn try_from(dec: Decimal) -> Result<Self, Self::Error> {
with_context(|ctx| ctx.$f(dec.inner).ok())
.flatten()
.ok_or_else(||
if dec.is_int() {
DecimalToIntError::OutOfRange
} else {
DecimalToIntError::NonInteger
}
)
}
}
)+
}
}
impl_try_into_int! {
i64 => try_into_i64
isize => try_into_isize
u64 => try_into_u64
usize => try_into_usize
}
impl serde::Serialize for Decimal {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
#[derive(Serialize)]
struct _ExtStruct((std::os::raw::c_char, serde_bytes::ByteBuf));
let data = {
let mut data = vec![];
let (bcd, scale) = self.inner.clone().to_packed_bcd().unwrap();
rmp::encode::write_i32(&mut data, scale).unwrap();
data.extend(bcd);
data
};
_ExtStruct((ffi::MP_DECIMAL, serde_bytes::ByteBuf::from(data))).serialize(serializer)
}
}
impl<'de> serde::Deserialize<'de> for Decimal {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
use serde::de::Error;
#[derive(Deserialize)]
struct _ExtStruct((std::os::raw::c_char, serde_bytes::ByteBuf));
match serde::Deserialize::deserialize(deserializer)? {
_ExtStruct((ffi::MP_DECIMAL, bytes)) => {
let mut data = bytes.as_slice();
let scale = rmp::decode::read_int(&mut data).unwrap();
let bcd = data;
DecimalImpl::from_packed_bcd(bcd, scale)
.map_err(|e| Error::custom(format!("Failed to unpack decimal: {e}")))?
.try_into()
.map_err(|e| Error::custom(format!("Failed to unpack decimal: {e}")))
}
_ExtStruct((kind, _)) => Err(serde::de::Error::custom(format!(
"Expected Decimal, found msgpack ext #{}",
kind
))),
}
}
}
#[macro_export]
macro_rules! decimal {
($($num:tt)+) => {
{
let r_str = ::std::concat![$(::std::stringify!($num)),+];
let dec: $crate::decimal::Decimal = ::std::convert::TryFrom::try_from(r_str)
.expect(
::std::concat![
"failed to convert '",
$(::std::stringify!($num)),+,
"' to decimal",
]
);
dec
}
}
}
#[cfg(test)]
mod test {
use super::Decimal;
use once_cell::sync::Lazy;
use std::convert::TryFrom;
use std::sync::Mutex;
static DECIMALS_ARENT_THREAD_SAFE: Lazy<Mutex<()>> = Lazy::new(Default::default);
#[test]
fn from_num() {
let _lock = DECIMALS_ARENT_THREAD_SAFE.lock().unwrap();
assert_eq!(Decimal::from(0i8), Decimal::zero());
assert_eq!(Decimal::from(42i8).to_string(), "42");
assert_eq!(Decimal::from(i8::MAX).to_string(), "127");
assert_eq!(Decimal::from(i8::MIN).to_string(), "-128");
assert_eq!(Decimal::from(0i16), Decimal::zero());
assert_eq!(Decimal::from(42i16).to_string(), "42");
assert_eq!(Decimal::from(i16::MAX).to_string(), "32767");
assert_eq!(Decimal::from(i16::MIN).to_string(), "-32768");
assert_eq!(Decimal::from(0i32), Decimal::zero());
assert_eq!(Decimal::from(42i32).to_string(), "42");
assert_eq!(Decimal::from(i32::MAX).to_string(), "2147483647");
assert_eq!(Decimal::from(i32::MIN).to_string(), "-2147483648");
assert_eq!(Decimal::from(0i64), Decimal::zero());
assert_eq!(Decimal::from(42i64).to_string(), "42");
assert_eq!(Decimal::from(i64::MAX).to_string(), "9223372036854775807");
assert_eq!(Decimal::from(i64::MIN).to_string(), "-9223372036854775808");
assert_eq!(Decimal::from(0isize), Decimal::zero());
assert_eq!(Decimal::from(42isize).to_string(), "42");
assert_eq!(Decimal::from(isize::MAX).to_string(), "9223372036854775807");
assert_eq!(
Decimal::from(isize::MIN).to_string(),
"-9223372036854775808"
);
assert_eq!(Decimal::from(0u8), Decimal::zero());
assert_eq!(Decimal::from(42u8).to_string(), "42");
assert_eq!(Decimal::from(u8::MAX).to_string(), "255");
assert_eq!(Decimal::from(0u16), Decimal::zero());
assert_eq!(Decimal::from(42u16).to_string(), "42");
assert_eq!(Decimal::from(u16::MAX).to_string(), "65535");
assert_eq!(Decimal::from(0u32), Decimal::zero());
assert_eq!(Decimal::from(42u32).to_string(), "42");
assert_eq!(Decimal::from(u32::MAX).to_string(), "4294967295");
assert_eq!(Decimal::from(0u64), Decimal::zero());
assert_eq!(Decimal::from(42u64).to_string(), "42");
assert_eq!(Decimal::from(u64::MAX).to_string(), "18446744073709551615");
assert_eq!(Decimal::from(0usize), Decimal::zero());
assert_eq!(Decimal::from(42usize).to_string(), "42");
assert_eq!(
Decimal::from(usize::MAX).to_string(),
"18446744073709551615"
);
assert_eq!(Decimal::try_from(0f32).unwrap(), Decimal::zero());
assert_eq!(Decimal::try_from(-8.11f32).unwrap().to_string(), "-8.11");
assert_eq!(
Decimal::try_from(f32::INFINITY).unwrap_err().to_string(),
"float is infinite"
);
assert_eq!(
Decimal::try_from(f32::NEG_INFINITY)
.unwrap_err()
.to_string(),
"float is infinite"
);
assert_eq!(
Decimal::try_from(f32::NAN).unwrap_err().to_string(),
"float is NaN"
);
assert_eq!(
Decimal::try_from(f32::EPSILON).unwrap().to_string(),
"1.1920929E-7"
);
assert_eq!(Decimal::try_from(f32::MIN).unwrap_err().to_string(),
"float `-340282350000000000000000000000000000000` cannot be represented using 38 digits"
);
assert_eq!(
Decimal::try_from(f32::MAX).unwrap_err().to_string(),
"float `340282350000000000000000000000000000000` cannot be represented using 38 digits"
);
assert_eq!(Decimal::try_from(1.0e-40_f32).unwrap(), Decimal::zero());
assert_eq!(
Decimal::try_from(1e37_f32).unwrap().to_string(),
"10000000000000000000000000000000000000"
);
assert_eq!(
Decimal::try_from(1e38_f32).unwrap_err().to_string(),
"float `100000000000000000000000000000000000000` cannot be represented using 38 digits"
);
assert_eq!(Decimal::try_from(0f64).unwrap(), Decimal::zero());
assert_eq!(Decimal::try_from(-8.11f64).unwrap().to_string(), "-8.11");
assert_eq!(
Decimal::try_from(f64::INFINITY).unwrap_err().to_string(),
"float is infinite"
);
assert_eq!(
Decimal::try_from(f64::NEG_INFINITY)
.unwrap_err()
.to_string(),
"float is infinite"
);
assert_eq!(
Decimal::try_from(f64::NAN).unwrap_err().to_string(),
"float is NaN"
);
assert_eq!(
Decimal::try_from(f64::EPSILON).unwrap().to_string(),
"2.220446049250313E-16"
);
assert_eq!(Decimal::try_from(f64::MIN).unwrap_err().to_string(),
"float `-179769313486231570000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000` cannot be represented using 38 digits"
);
assert_eq!(Decimal::try_from(f64::MAX).unwrap_err().to_string(),
"float `179769313486231570000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000` cannot be represented using 38 digits"
);
assert_eq!(Decimal::try_from(1.0e-40_f64).unwrap(), Decimal::zero());
assert_eq!(
Decimal::try_from(1e38_f64).unwrap_err().to_string(),
"float `100000000000000000000000000000000000000` cannot be represented using 38 digits"
);
}
#[test]
pub fn to_num() {
let _lock = DECIMALS_ARENT_THREAD_SAFE.lock().unwrap();
assert_eq!(i64::try_from(decimal!(420)).unwrap(), 420);
assert_eq!(
i64::try_from(decimal!(9223372036854775807)).unwrap(),
i64::MAX
);
assert_eq!(
i64::try_from(decimal!(9223372036854775808))
.unwrap_err()
.to_string(),
"decimal is out of range"
);
assert_eq!(
i64::try_from(decimal!(-9223372036854775808)).unwrap(),
i64::MIN
);
assert_eq!(
i64::try_from(decimal!(-9223372036854775809))
.unwrap_err()
.to_string(),
"decimal is out of range"
);
assert_eq!(
i64::try_from(decimal!(3.14)).unwrap_err().to_string(),
"decimal is not an integer"
);
assert_eq!(isize::try_from(decimal!(420)).unwrap(), 420);
assert_eq!(
isize::try_from(decimal!(9223372036854775807)).unwrap(),
isize::MAX
);
assert_eq!(
isize::try_from(decimal!(9223372036854775808))
.unwrap_err()
.to_string(),
"decimal is out of range"
);
assert_eq!(
isize::try_from(decimal!(-9223372036854775808)).unwrap(),
isize::MIN
);
assert_eq!(
isize::try_from(decimal!(-9223372036854775809))
.unwrap_err()
.to_string(),
"decimal is out of range"
);
assert_eq!(
isize::try_from(decimal!(3.14)).unwrap_err().to_string(),
"decimal is not an integer"
);
assert_eq!(u64::try_from(decimal!(420)).unwrap(), 420);
assert_eq!(
u64::try_from(decimal!(18446744073709551615)).unwrap(),
u64::MAX
);
assert_eq!(
u64::try_from(decimal!(18446744073709551616))
.unwrap_err()
.to_string(),
"decimal is out of range"
);
assert_eq!(
u64::try_from(decimal!(-1)).unwrap_err().to_string(),
"decimal is out of range"
);
assert_eq!(
u64::try_from(decimal!(3.14)).unwrap_err().to_string(),
"decimal is not an integer"
);
assert_eq!(usize::try_from(decimal!(420)).unwrap(), 420);
assert_eq!(
usize::try_from(decimal!(18446744073709551615)).unwrap(),
usize::MAX
);
assert_eq!(
usize::try_from(decimal!(18446744073709551616))
.unwrap_err()
.to_string(),
"decimal is out of range"
);
assert_eq!(
usize::try_from(decimal!(-1)).unwrap_err().to_string(),
"decimal is out of range"
);
assert_eq!(
usize::try_from(decimal!(3.14)).unwrap_err().to_string(),
"decimal is not an integer"
);
}
#[test]
pub fn cmp() {
let _lock = DECIMALS_ARENT_THREAD_SAFE.lock().unwrap();
assert!(decimal!(.1) < decimal!(.2));
assert!(decimal!(.1) <= decimal!(.2));
assert!(decimal!(.2) > decimal!(.1));
assert!(decimal!(.2) >= decimal!(.1));
assert_eq!(decimal!(0), 0);
assert_eq!(decimal!(1), 1);
assert_eq!(decimal!(-3), -3);
assert_ne!(decimal!(-8.11), -8);
}
#[test]
pub fn hash() {
let _lock = DECIMALS_ARENT_THREAD_SAFE.lock().unwrap();
fn to_hash<T: std::hash::Hash>(t: &T) -> u64 {
let mut s = std::collections::hash_map::DefaultHasher::new();
t.hash(&mut s);
std::hash::Hasher::finish(&s)
}
assert_eq!(to_hash(&decimal!(1)), to_hash(&decimal!(1.000)));
assert_eq!(to_hash(&decimal!(1.00)), to_hash(&decimal!(1.00)));
assert_eq!(to_hash(&decimal!(1)), to_hash(&decimal!(1)));
assert_eq!(to_hash(&decimal!(1.000000)), to_hash(&decimal!(1.0)));
assert_eq!(to_hash(&decimal!(1.000000000000000)), to_hash(&decimal!(1)));
assert_ne!(
to_hash(&decimal!(1.000000000000000)),
to_hash(&decimal!(1.000000000000001))
);
assert_ne!(
to_hash(&decimal!(1.000000000000000)),
to_hash(&decimal!(0.999999999999999))
);
assert_eq!(
to_hash(&decimal!(99999999999999999999999999999999999999)),
to_hash(&decimal!(99999999999999999999999999999999999999))
);
assert_ne!(
to_hash(&decimal!(9999999999999999999999999999999999999.0)),
to_hash(&decimal!(9999999999999999999999999999999999998.9))
);
assert_eq!(to_hash(&decimal!(0)), to_hash(&decimal!(0.000)));
assert_eq!(
to_hash(&decimal!(-99999999999999999999999999999999999999)),
to_hash(&decimal!(-99999999999999999999999999999999999999))
);
assert_eq!(to_hash(&decimal!(-1)), to_hash(&decimal!(-1.000)));
assert_ne!(
to_hash(&decimal!(-1.000)),
to_hash(&decimal!(-0.9999999999999999999999999999999999999))
);
}
#[test]
#[allow(clippy::bool_assert_comparison)]
pub fn ops() {
let _lock = DECIMALS_ARENT_THREAD_SAFE.lock().unwrap();
let a = decimal!(.1);
let b = decimal!(.2);
let c = decimal!(.3);
assert_eq!(a + b, c);
assert_eq!(c - b, a);
assert_eq!(c - a, b);
assert_eq!(b * c, decimal!(.06));
assert_eq!(c / b, decimal!(1.5));
let mut x = decimal!(.5);
x += 1;
assert_eq!(x, decimal!(1.5));
x -= 2;
assert_eq!(x, decimal!(-.5));
x *= 3;
assert_eq!(x, decimal!(-1.5));
x /= 4;
assert_eq!(x, decimal!(-.375));
x %= 5;
assert_eq!(x, decimal!(-.375));
x += 12;
assert_eq!(x, decimal!(11.625));
assert_eq!(x % 5, decimal!(1.625));
let x: Decimal = decimal!(99999999999999999999999999999999999999);
let y: Decimal = 1.into();
assert_eq!(x.checked_add(y), None::<Decimal>);
let x: Decimal = decimal!(10000000000000000000000000000000000000);
let y: Decimal = 10.into();
assert_eq!(x.checked_mul(y), None::<Decimal>);
let x = decimal!(-8.11);
let y = x.abs();
assert_eq!(y, -x);
assert_eq!(y, decimal!(8.11));
let x = decimal!(1.000);
assert_eq!(x.to_string(), "1.000");
assert_eq!(x.precision(), 4);
assert_eq!(x.scale(), 3);
let x = x.trim();
assert_eq!(x.to_string(), "1");
assert_eq!(x.precision(), 1);
assert_eq!(x.scale(), 0);
let x = x.rescale(3).unwrap();
assert_eq!(x.to_string(), "1.000");
assert_eq!(x.precision(), 4);
assert_eq!(x.scale(), 3);
assert_eq!(decimal!(-1).log10(), None);
assert_eq!(decimal!(0).log10(), None);
assert_eq!(decimal!(100).log10(), Some(decimal!(2)));
assert_eq!(decimal!(.01).log10(), Some(decimal!(-2)));
let e = decimal!(1).exp().unwrap();
assert_eq!(e, decimal!(2.7182818284590452353602874713526624978));
assert_eq!(decimal!(1000).exp(), None::<Decimal>);
assert_eq!(e.precision(), 38);
assert_eq!(e.scale(), 37);
assert_eq!(e.is_int(), false);
assert_eq!(e.round().precision(), 1);
assert_eq!(e.round().scale(), 0);
assert_eq!(e.round().is_int(), true);
assert_eq!(Decimal::from(usize::MAX).precision(), 20);
assert_eq!(e.round_to(4), Some(decimal!(2.7183)));
assert_eq!(e.floor_to(4), Some(decimal!(2.7182)));
assert_eq!(e.round_to(40), None::<Decimal>);
assert_eq!(e.floor_to(40), None::<Decimal>);
assert_eq!(decimal!(-1).ln(), None);
assert_eq!(decimal!(0).ln(), None);
assert_eq!(decimal!(1).ln(), Some(decimal!(0)));
assert_eq!(e.ln(), Some(decimal!(1)));
assert_eq!(decimal!(4).sqrt(), Some(decimal!(2)));
assert_eq!(decimal!(-1).sqrt(), None::<Decimal>);
assert_eq!(decimal!(2).pow(64), Some(decimal!(18446744073709551616)));
assert_eq!(decimal!(2).pow(-2), Some(decimal!(.25)));
assert_eq!(decimal!(10).pow(39), None::<Decimal>);
}
}