Skip to content

Commit

Permalink
Merge pull request #1581 from radixdlt/feature/dec-macro-float-literals
Browse files Browse the repository at this point in the history
Feature/dec macro float literals
  • Loading branch information
iamyulong authored Sep 20, 2023
2 parents bc7f774 + 59be1f7 commit cc142e7
Show file tree
Hide file tree
Showing 40 changed files with 386 additions and 149 deletions.
Binary file modified assets/faucet.wasm
Binary file not shown.
Binary file modified assets/flash_loan.wasm
Binary file not shown.
Binary file modified assets/genesis_helper.wasm
Binary file not shown.
Binary file modified assets/global_n_owned.wasm
Binary file not shown.
Binary file modified assets/kv_store.wasm
Binary file not shown.
Binary file modified assets/max_transaction.wasm
Binary file not shown.
Binary file modified assets/metadata.wasm
Binary file not shown.
Binary file modified assets/radiswap.wasm
Binary file not shown.
3 changes: 2 additions & 1 deletion radix-engine-common/src/math/bnum_integer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -515,9 +515,10 @@ macro_rules! error {
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum [<Parse $t Error>] {
NegativeToUnsigned,
Overflow,
InvalidLength,
InvalidDigit,
Empty,
Overflow,
}

#[cfg(not(feature = "alloc"))]
Expand Down
15 changes: 11 additions & 4 deletions radix-engine-common/src/math/bnum_integer/convert.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ impl_to_primitive! { U448, BUint::<7>, (u8, u16, u32, u64, u128, usize, i8, i16,
impl_to_primitive! { U512, BUint::<8>, (u8, u16, u32, u64, u128, usize, i8, i16, i32, i64, i128, isize) }
impl_to_primitive! { U768, BUint::<12>, (u8, u16, u32, u64, u128, usize, i8, i16, i32, i64, i128, isize) }

macro_rules! impl_from_builtin{
macro_rules! impl_from_builtin {
($t:ident, $wrapped:ty, ($($o:ident),*)) => {
paste! {
$(
Expand All @@ -73,7 +73,7 @@ macro_rules! impl_from_builtin{
};
}

macro_rules! impl_try_from_builtin{
macro_rules! impl_try_from_builtin {
($t:ident, $wrapped:ty, ($($o:ident),*)) => {
paste! {
$(
Expand All @@ -92,7 +92,7 @@ macro_rules! impl_try_from_builtin{
};
}

macro_rules! impl_to_builtin{
macro_rules! impl_to_builtin {
($t:ident, $wrapped:ty, ($($o:ident),*)) => {
paste! {
$(
Expand Down Expand Up @@ -219,7 +219,14 @@ macro_rules! impl_from_string {
fn from_str(val: &str) -> Result<Self, Self::Err> {
match <$wrapped>::from_str(val) {
Ok(val) => Ok(Self(val)),
Err(_) => Err([<Parse $t Error>]::InvalidDigit),
Err(err) => Err(match err.kind() {
core::num::IntErrorKind::Empty => [<Parse $t Error>]::Empty,
core::num::IntErrorKind::InvalidDigit => [<Parse $t Error>]::InvalidDigit,
core::num::IntErrorKind::PosOverflow => [<Parse $t Error>]::Overflow,
core::num::IntErrorKind::NegOverflow => [<Parse $t Error>]::Overflow,
core::num::IntErrorKind::Zero => unreachable!("Zero is only issued for non-zero type"),
_ => [<Parse $t Error>]::InvalidDigit, // Enum is non-exhaustive, sensible fallback is InvalidDigit
})
}
}
}
Expand Down
96 changes: 80 additions & 16 deletions radix-engine-common/src/math/decimal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -740,36 +740,75 @@ impl FromStr for Decimal {
type Err = ParseDecimalError;

fn from_str(s: &str) -> Result<Self, Self::Err> {
let tens = I192::TEN;
let v: Vec<&str> = s.split('.').collect();

let mut int = match I192::from_str(v[0]) {
if v.len() > 2 {
return Err(ParseDecimalError::MoreThanOneDecimalPoint);
}

let integer_part = match I192::from_str(v[0]) {
Ok(val) => val,
Err(_) => return Err(ParseDecimalError::InvalidDigit),
Err(err) => match err {
ParseI192Error::NegativeToUnsigned => {
unreachable!("NegativeToUnsigned is only for parsing unsigned types, not I192")
}
ParseI192Error::Overflow => return Err(ParseDecimalError::Overflow),
ParseI192Error::InvalidLength => {
unreachable!("InvalidLength is only for parsing &[u8], not &str")
}
ParseI192Error::InvalidDigit => return Err(ParseDecimalError::InvalidDigit),
// We have decided to be restrictive to force people to type "0.123" instead of ".123"
// for clarity, and to align with how rust's float literal works
ParseI192Error::Empty => return Err(ParseDecimalError::EmptyIntegralPart),
},
};

int *= tens.pow(Self::SCALE);
let mut subunits = integer_part
.checked_mul(Self::ONE.0)
.ok_or(ParseDecimalError::Overflow)?;

if v.len() == 2 {
let scale = if let Some(scale) = Self::SCALE.checked_sub(v[1].len() as u32) {
Ok(scale)
} else {
Err(Self::Err::UnsupportedDecimalPlace)
Err(Self::Err::MoreThanEighteenDecimalPlaces)
}?;

let frac = match I192::from_str(v[1]) {
let fractional_part = match I192::from_str(v[1]) {
Ok(val) => val,
Err(_) => return Err(ParseDecimalError::InvalidDigit),
Err(err) => match err {
ParseI192Error::NegativeToUnsigned => {
unreachable!(
"NegativeToUnsigned is only for parsing unsigned types, no I192"
)
}
ParseI192Error::Overflow => return Err(ParseDecimalError::Overflow),
ParseI192Error::InvalidLength => {
unreachable!("InvalidLength is only for parsing &[u8], not &str")
}
ParseI192Error::InvalidDigit => return Err(ParseDecimalError::InvalidDigit),
ParseI192Error::Empty => return Err(ParseDecimalError::EmptyFractionalPart),
},
};

// The product of these must be less than Self::SCALE
let fractional_subunits = fractional_part
.checked_mul(I192::TEN.pow(scale))
.expect("No overflow possible");

// if input is -0. then from_str returns 0 and we loose '-' sign.
// Therefore check for '-' in input directly
if int.is_negative() || v[0].starts_with('-') {
int -= frac * tens.pow(scale);
if integer_part.is_negative() || v[0].starts_with('-') {
subunits = subunits
.checked_sub(fractional_subunits)
.ok_or(ParseDecimalError::Overflow)?;
} else {
int += frac * tens.pow(scale);
subunits = subunits
.checked_add(fractional_subunits)
.ok_or(ParseDecimalError::Overflow)?;
}
}
Ok(Self(int))
Ok(Self(subunits))
}
}

Expand Down Expand Up @@ -810,12 +849,13 @@ impl fmt::Debug for Decimal {
/// Represents an error when parsing Decimal from another type.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ParseDecimalError {
InvalidDecimal(String),
InvalidChar(char),
InvalidDigit,
UnsupportedDecimalPlace,
InvalidLength(usize),
Overflow,
EmptyIntegralPart,
EmptyFractionalPart,
MoreThanEighteenDecimalPlaces,
MoreThanOneDecimalPoint,
InvalidLength(usize),
}

#[cfg(not(feature = "alloc"))]
Expand Down Expand Up @@ -904,6 +944,10 @@ mod tests {
Decimal::from_str("0.000000000000000001").unwrap(),
Decimal(1i128.into()),
);
assert_eq!(
Decimal::from_str("0.0000000000000000001"),
Err(ParseDecimalError::MoreThanEighteenDecimalPlaces),
);
assert_eq!(
Decimal::from_str("0.123456789123456789").unwrap(),
Decimal(123456789123456789i128.into()),
Expand All @@ -921,11 +965,31 @@ mod tests {
.unwrap(),
Decimal::MAX,
);
assert_eq!(
Decimal::from_str("3138550867693340381917894711603833208051.177722232017256448"),
Err(ParseDecimalError::Overflow),
);
assert_eq!(
Decimal::from_str("3138550867693340381917894711603833208052.177722232017256447"),
Err(ParseDecimalError::Overflow),
);
assert_eq!(
Decimal::from_str("-3138550867693340381917894711603833208051.177722232017256448")
.unwrap(),
Decimal::MIN,
);
assert_eq!(
Decimal::from_str("-3138550867693340381917894711603833208051.177722232017256449"),
Err(ParseDecimalError::Overflow),
);
assert_eq!(
Decimal::from_str(".000000000000000231"),
Err(ParseDecimalError::EmptyIntegralPart),
);
assert_eq!(
Decimal::from_str("231."),
Err(ParseDecimalError::EmptyFractionalPart),
);

assert_eq!(test_dec!("0"), Decimal::ZERO);
assert_eq!(test_dec!("1"), Decimal::ONE);
Expand Down Expand Up @@ -1886,7 +1950,7 @@ mod tests {
// Assert
assert!(matches!(
decimal,
Err(ParseDecimalError::UnsupportedDecimalPlace)
Err(ParseDecimalError::MoreThanEighteenDecimalPlaces)
))
}

Expand Down
103 changes: 87 additions & 16 deletions radix-engine-common/src/math/precise_decimal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -778,37 +778,79 @@ impl FromStr for PreciseDecimal {
type Err = ParsePreciseDecimalError;

fn from_str(s: &str) -> Result<Self, Self::Err> {
let tens = I256::TEN;
let v: Vec<&str> = s.split('.').collect();

let mut int = match I256::from_str(v[0]) {
if v.len() > 2 {
return Err(ParsePreciseDecimalError::MoreThanOneDecimalPoint);
}

let integer_part = match I256::from_str(v[0]) {
Ok(val) => val,
Err(_) => return Err(ParsePreciseDecimalError::InvalidDigit),
Err(err) => match err {
ParseI256Error::NegativeToUnsigned => {
unreachable!("NegativeToUnsigned is only for parsing unsigned types, not I256")
}
ParseI256Error::Overflow => return Err(ParsePreciseDecimalError::Overflow),
ParseI256Error::InvalidLength => {
unreachable!("InvalidLength is only for parsing &[u8], not &str")
}
ParseI256Error::InvalidDigit => return Err(ParsePreciseDecimalError::InvalidDigit),
// We have decided to be restrictive to force people to type "0.123" instead of ".123"
// for clarity, and to align with how rust's float literal works
ParseI256Error::Empty => return Err(ParsePreciseDecimalError::EmptyIntegralPart),
},
};

int *= tens.pow(Self::SCALE);
let mut subunits = integer_part
.checked_mul(Self::ONE.0)
.ok_or(ParsePreciseDecimalError::Overflow)?;

if v.len() == 2 {
let scale = if let Some(scale) = Self::SCALE.checked_sub(v[1].len() as u32) {
Ok(scale)
} else {
Err(Self::Err::UnsupportedDecimalPlace)
Err(Self::Err::MoreThanThirtySixDecimalPlaces)
}?;

let frac = match I256::from_str(v[1]) {
let fractional_part = match I256::from_str(v[1]) {
Ok(val) => val,
Err(_) => return Err(ParsePreciseDecimalError::InvalidDigit),
Err(err) => match err {
ParseI256Error::NegativeToUnsigned => {
unreachable!(
"NegativeToUnsigned is only for parsing unsigned types, not I256"
)
}
ParseI256Error::Overflow => return Err(ParsePreciseDecimalError::Overflow),
ParseI256Error::InvalidLength => {
unreachable!("InvalidLength is only for parsing &[u8], not &str")
}
ParseI256Error::InvalidDigit => {
return Err(ParsePreciseDecimalError::InvalidDigit)
}
ParseI256Error::Empty => {
return Err(ParsePreciseDecimalError::EmptyFractionalPart)
}
},
};

// The product of these must be less than Self::SCALE
let fractional_subunits = fractional_part
.checked_mul(I256::TEN.pow(scale))
.expect("No overflow possible");

// if input is -0. then from_str returns 0 and we loose '-' sign.
// Therefore check for '-' in input directly
if int.is_negative() || v[0].starts_with('-') {
int -= frac * tens.pow(scale);
if integer_part.is_negative() || v[0].starts_with('-') {
subunits = subunits
.checked_sub(fractional_subunits)
.ok_or(ParsePreciseDecimalError::Overflow)?;
} else {
int += frac * tens.pow(scale);
subunits = subunits
.checked_add(fractional_subunits)
.ok_or(ParsePreciseDecimalError::Overflow)?;
}
}
Ok(Self(int))
Ok(Self(subunits))
}
}

Expand Down Expand Up @@ -849,12 +891,13 @@ impl fmt::Debug for PreciseDecimal {
/// Represents an error when parsing PreciseDecimal from another type.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ParsePreciseDecimalError {
InvalidDecimal(String),
InvalidChar(char),
InvalidDigit,
UnsupportedDecimalPlace,
InvalidLength(usize),
Overflow,
EmptyIntegralPart,
EmptyFractionalPart,
MoreThanThirtySixDecimalPlaces,
MoreThanOneDecimalPoint,
InvalidLength(usize),
}

#[cfg(not(feature = "alloc"))]
Expand Down Expand Up @@ -983,6 +1026,10 @@ mod tests {
PreciseDecimal::from_str("0.000000000000000001").unwrap(),
PreciseDecimal(I256::from(10).pow(18)),
);
assert_eq!(
PreciseDecimal::from_str("0.0000000000000000000000000000000000001"),
Err(ParsePreciseDecimalError::MoreThanThirtySixDecimalPlaces),
);
assert_eq!(
PreciseDecimal::from_str("0.123456789123456789").unwrap(),
PreciseDecimal(I256::from(123456789123456789i128) * I256::from(10i8).pow(18)),
Expand All @@ -1004,13 +1051,37 @@ mod tests {
.unwrap(),
PreciseDecimal::MAX,
);
assert_eq!(
PreciseDecimal::from_str(
"57896044618658097711785492504343953926634.992332820282019728792003956564819968"
),
Err(ParsePreciseDecimalError::Overflow),
);
assert_eq!(
PreciseDecimal::from_str("157896044618658097711785492504343953926634"),
Err(ParsePreciseDecimalError::Overflow),
);
assert_eq!(
PreciseDecimal::from_str(
"-57896044618658097711785492504343953926634.992332820282019728792003956564819968"
)
.unwrap(),
PreciseDecimal::MIN,
);
assert_eq!(
PreciseDecimal::from_str(
"-57896044618658097711785492504343953926634.992332820282019728792003956564819969"
),
Err(ParsePreciseDecimalError::Overflow),
);
assert_eq!(
PreciseDecimal::from_str(".000000000000000231"),
Err(ParsePreciseDecimalError::EmptyIntegralPart),
);
assert_eq!(
PreciseDecimal::from_str("231."),
Err(ParsePreciseDecimalError::EmptyFractionalPart),
);

assert_eq!(test_pdec!("0"), PreciseDecimal::ZERO);
assert_eq!(test_pdec!("1"), PreciseDecimal::ONE);
Expand Down Expand Up @@ -2076,7 +2147,7 @@ mod tests {
// Assert
assert!(matches!(
decimal,
Err(ParsePreciseDecimalError::UnsupportedDecimalPlace)
Err(ParsePreciseDecimalError::MoreThanThirtySixDecimalPlaces)
))
}

Expand Down
Loading

0 comments on commit cc142e7

Please sign in to comment.