Skip to content

Commit

Permalink
Truncate Decimal192 fractional part when it has more than 18 decimals (
Browse files Browse the repository at this point in the history
…#160)

* Update Decimal192 to work with more than 18 decimals

* Fix rust doc tests. Fix kotlin tests

* Improve test coverage

* Add test

* Update f32 to Decimal192 parsing to not throw

* Fix doctests

* Version bump
  • Loading branch information
sergiupuhalschi-rdx authored Jun 10, 2024
1 parent f6f1ef4 commit b1fb6d6
Show file tree
Hide file tree
Showing 5 changed files with 80 additions and 60 deletions.
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "sargon"
version = "1.0.9"
version = "1.0.10"
edition = "2021"
build = "build.rs"

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -147,9 +147,7 @@ class Decimal192Test : SampleTestable<Decimal192> {
Float.MAX_VALUE.toBigDecimal().toPlainString(),
Float.MAX_VALUE.toDecimal192().plainString()
)
assertThrows<CommonException.DecimalOverflow> {
Float.MIN_VALUE.toDecimal192()
}
assertEquals(0.toDecimal192().string, Float.MIN_VALUE.toDecimal192().string)
}

@Test
Expand All @@ -163,7 +161,7 @@ class Decimal192Test : SampleTestable<Decimal192> {
assertNull(Double.MAX_VALUE.toDecimal192OrNull())
assertNotNull(10.0.toDecimal192OrNull())

assertNull(Float.MIN_VALUE.toDecimal192OrNull())
assertNotNull(Float.MIN_VALUE.toDecimal192OrNull())
assertNotNull(3.14f.toDecimal192OrNull())

assertNull(Float.MIN_VALUE.toString().toDecimal192OrNull())
Expand Down
99 changes: 67 additions & 32 deletions src/core/types/decimal192.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use crate::prelude::*;
use delegate::delegate;
use enum_iterator::reverse_all;
use radix_common::math::ParseDecimalError;

uniffi::custom_type!(ScryptoDecimal192, String);

Expand Down Expand Up @@ -87,13 +88,37 @@ impl Decimal {
}
}

impl Decimal192 {
/// Will lose precision if the fractional part has more than 18 digits!
fn from_str_by_truncating(s: impl AsRef<str>) -> Result<Self> {
let str_value = s.as_ref();
let parts: Vec<&str> = str_value.split('.').collect();
let fractional_part = parts[1];
let processed_s = format!("{}.{}", parts[0], &fractional_part[..18]);

processed_s
.parse::<ScryptoDecimal192>()
.map(Self::from_native)
.map_err(|_| CommonError::DecimalError)
}

pub fn from_str_value(s: impl AsRef<str>) -> Result<Self> {
let str_value = s.as_ref();
match str_value.parse::<ScryptoDecimal192>() {
Ok(decimal) => Ok(Self::from_native(decimal)),
Err(ParseDecimalError::MoreThanEighteenDecimalPlaces) => {
Self::from_str_by_truncating(str_value)
}
Err(_) => Err(CommonError::DecimalError),
}
}
}

impl FromStr for Decimal192 {
type Err = crate::CommonError;

fn from_str(s: &str) -> Result<Self> {
s.parse::<ScryptoDecimal192>()
.map(Self::from_native)
.map_err(|_| CommonError::DecimalError)
Self::from_str_value(s)
}
}

Expand All @@ -111,32 +136,23 @@ forward_from_for_num!(u64);
forward_from_for_num!(i32);
forward_from_for_num!(i64);

impl TryFrom<f32> for Decimal {
type Error = crate::CommonError;

impl From<f32> for Decimal {
/// Creates a new `Decimal192` from a f32 float. Will
/// fail if the f32 cannot be losslessly represented
/// by the underlying Decimal from Scrypto.
/// lose precision if the f32 cannot be losslessly
/// represented by the underlying Decimal from Scrypto
///
/// ```
/// extern crate sargon;
/// use sargon::prelude::*;
///
/// assert!(Decimal::try_from(208050.17).is_ok());
/// assert!(Decimal::from(208050.17).to_string() == "208050.17");
///
/// assert!(Decimal::from(f32::MIN_POSITIVE).to_string() == "0");
///
/// assert_eq!(
/// Decimal::try_from(f32::MIN_POSITIVE),
/// Err(CommonError::DecimalOverflow { bad_value: f32::MIN_POSITIVE.to_string() })
/// );
/// ```
fn try_from(value: f32) -> Result<Self, Self::Error> {
fn from(value: f32) -> Self {
let str_value = value.to_string();

str_value
.parse::<Self>()
.map_err(|_| CommonError::DecimalOverflow {
bad_value: str_value,
})
Self::from_str_value(str_value).unwrap()
}
}

Expand All @@ -153,19 +169,11 @@ impl TryFrom<f64> for Decimal {
///
/// assert!(Decimal::try_from(208050.17).is_ok());
///
/// assert_eq!(
/// Decimal::try_from(f64::MIN_POSITIVE),
/// Err(CommonError::DecimalOverflow { bad_value: f64::MIN_POSITIVE.to_string() })
/// );
/// assert!(Decimal::try_from(f64::MIN_POSITIVE).is_ok());
/// ```
fn try_from(value: f64) -> Result<Self, Self::Error> {
let str_value = value.to_string();

str_value
.parse::<Self>()
.map_err(|_| CommonError::DecimalOverflow {
bad_value: str_value,
})
Self::from_str_value(str_value)
}
}

Expand Down Expand Up @@ -1219,25 +1227,47 @@ mod test_decimal {
};
test(0.1, "0.1");
test(f32::MAX as f64, "340282346638528860000000000000000000000");
test(f32::MIN as f64, "-340282346638528860000000000000000000000");
test(123456789.87654321, "123456789.87654321");
test(4.012_345_678_901_235, "4.012345678901235"); // precision lost
test(4.012_345_678_901_235, "4.012345678901235"); // Over 18 decimals is OK (precision lost)
test(4.012_345_678_901_234_567_890, "4.012345678901235"); // Over 18 decimals is OK (precision lost)
}

#[test]
fn from_f32_more_than_18_decimals_is_ok() {
let test = |f: f32, s: &str| {
let sut = Decimal192::try_from(f).unwrap();
let sut = Decimal192::from(f);
assert_eq!(sut.to_string(), s);
};

test(0.1, "0.1");
test(f32::MAX, "340282350000000000000000000000000000000");
test(f32::MIN, "-340282350000000000000000000000000000000");
test(f32::MIN_POSITIVE, "0");
test(123456789.87654321, "123456790");
test(4.012_346, "4.012346"); // precision lost
test(4.012_346, "4.012346"); // Over 18 decimals is OK (precision lost)
}

#[test]
fn from_str_more_than_18_decimals_is_ok() {
assert_eq!(
"4.012345678901234567890"
.parse::<Decimal192>()
.unwrap()
.to_string(),
"4.012345678901234567"
); // Over 18 decimals is OK (precision lost)
}

#[test]
fn from_more_than_one_decimal_point_with_more_than_18_decimals_string() {
assert_eq!(
"4.0123456789012345678.123".parse::<Decimal192>(),
Err(CommonError::DecimalError)
);
}

#[test]
fn from_negative_string() {
let sut: SUT = "-3.2".parse().unwrap();
Expand Down Expand Up @@ -1308,6 +1338,11 @@ mod test_decimal {

fail("1,000,000.23", &swedish);
test("1,000,000.23", &us, "1000000.23");
test(
"4.012 345 678 901 234 567 890",
&us,
"4.012345678901234567890",
);
}

#[test]
Expand Down
31 changes: 9 additions & 22 deletions src/core/types/decimal192_uniffi_fn.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,16 +75,13 @@ pub fn decimal_formatted_plain(
/// extern crate sargon;
/// use sargon::prelude::*;
///
/// assert!(new_decimal_from_f32(208050.17).is_ok());
/// assert!(new_decimal_from_f32(208050.17).to_string() == "208050.17");
///
/// assert_eq!(
/// new_decimal_from_f32(f32::MIN_POSITIVE),
/// Err(CommonError::DecimalOverflow { bad_value: f32::MIN_POSITIVE.to_string() })
/// );
/// assert!(new_decimal_from_f32(f32::MIN_POSITIVE).to_string() == "0");
/// ```
#[uniffi::export]
pub fn new_decimal_from_f32(value: f32) -> Result<Decimal192> {
value.try_into()
pub fn new_decimal_from_f32(value: f32) -> Decimal192 {
value.into()
}

/// Creates a new `Decimal192` from a f64 float. Will
Expand All @@ -97,10 +94,7 @@ pub fn new_decimal_from_f32(value: f32) -> Result<Decimal192> {
///
/// assert!(new_decimal_from_f64(208050.17).is_ok());
///
/// assert_eq!(
/// new_decimal_from_f64(f64::MIN_POSITIVE),
/// Err(CommonError::DecimalOverflow { bad_value: f64::MIN_POSITIVE.to_string() })
/// );
/// assert!(new_decimal_from_f64(f64::MIN_POSITIVE).is_ok());
/// ```
#[uniffi::export]
pub fn new_decimal_from_f64(value: f64) -> Result<Decimal192> {
Expand Down Expand Up @@ -480,17 +474,12 @@ mod uniffi_tests {
let f: f32 = 208050.17;
assert_eq!(f.to_string(), "208050.17");
let sut = new_decimal_from_f32(f);
assert_eq!(sut.unwrap().to_string(), "208050.17");
assert_eq!(sut.to_string(), "208050.17");
assert_eq!(
SUT::try_from(f32::MAX).unwrap().to_string(),
"340282350000000000000000000000000000000"
);
assert_eq!(
SUT::try_from(f32::MIN_POSITIVE),
Err(CommonError::DecimalOverflow {
bad_value: f32::MIN_POSITIVE.to_string()
})
);
assert_eq!(SUT::try_from(f32::MIN_POSITIVE).unwrap().to_string(), "0");
}

#[test]
Expand All @@ -504,10 +493,8 @@ mod uniffi_tests {
"340282346638528860000000000000000000000"
);
assert_eq!(
SUT::try_from(f32::MIN_POSITIVE as f64),
Err(CommonError::DecimalOverflow {
bad_value: (f32::MIN_POSITIVE as f64).to_string()
})
SUT::try_from(f32::MIN_POSITIVE as f64).unwrap().to_string(),
"0"
);
}

Expand Down

0 comments on commit b1fb6d6

Please sign in to comment.