diff --git a/CHANGELOG.md b/CHANGELOG.md index b399114..4552256 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # Version History +# 1.37.0 + +### Added + +* Built in `dec!` macro with extended features. You no longer need external `rust_decimal_macros`. + If you do want to keep it, `use rust_decimal_macros::dec;`, not `use rust_decimal_macros::*;`, + which can now be ambiguous. + # 1.36.0 ### Fixed diff --git a/README.md b/README.md index 5d2d8e9..d58a910 100644 --- a/README.md +++ b/README.md @@ -42,11 +42,10 @@ rust_decimal_macros = "1.36" ## Usage Decimal numbers can be created in a few distinct ways. The easiest and most efficient method of creating a Decimal is to -use the procedural macro that can be enabled using the `macros` feature: +use the macro: ```rust -// Import the `rust_decimal_macros` crate and use the macro directly from there. -use rust_decimal_macros::dec; +use rust_decimal::dec; let number = dec!(-1.23) + dec!(3.45); assert_eq!(number, dec!(2.22)); @@ -93,7 +92,6 @@ Once you have instantiated your `Decimal` number you can perform calculations wi ```rust use rust_decimal::prelude::*; -use rust_decimal_macros::dec; let amount = dec!(25.12); let tax_percentage = dec!(0.085); diff --git a/examples/rkyv-remote.rs b/examples/rkyv-remote.rs index 89f4dcb..8d5abf3 100644 --- a/examples/rkyv-remote.rs +++ b/examples/rkyv-remote.rs @@ -1,8 +1,7 @@ extern crate rkyv_0_8 as rkyv; use rkyv::{rancor::Error, Archive, Deserialize, Serialize}; -use rust_decimal::Decimal; -use rust_decimal_macros::dec; +use rust_decimal::{dec, Decimal}; /// The type containing a [`Decimal`] that will be de/serialized. #[derive(Archive, Serialize, Deserialize, Debug, PartialEq, Eq)] diff --git a/examples/serde-json-scenarios/src/main.rs b/examples/serde-json-scenarios/src/main.rs index a173e77..168950a 100644 --- a/examples/serde-json-scenarios/src/main.rs +++ b/examples/serde-json-scenarios/src/main.rs @@ -1,5 +1,4 @@ use rust_decimal::prelude::*; -use rust_decimal_macros::dec; type ExampleResult = Result<(), Box>; diff --git a/src/decimal.rs b/src/decimal.rs index e828801..fd6436f 100644 --- a/src/decimal.rs +++ b/src/decimal.rs @@ -484,25 +484,26 @@ impl Decimal { /// ``` pub const fn try_from_i128_with_scale(num: i128, scale: u32) -> crate::Result { if scale > Self::MAX_SCALE { - return Err(Error::ScaleExceedsMaximumPrecision(scale)); - } - let mut neg = false; - let mut wrapped = num; - if num > MAX_I128_REPR { - return Err(Error::ExceedsMaximumPossibleValue); + Err(Error::ScaleExceedsMaximumPrecision(scale)) + } else if num > MAX_I128_REPR { + Err(Error::ExceedsMaximumPossibleValue) } else if num < -MAX_I128_REPR { - return Err(Error::LessThanMinimumPossibleValue); - } else if num < 0 { - neg = true; - wrapped = -num; + Err(Error::LessThanMinimumPossibleValue) + } else { + Ok(Self::from_i128_with_scale_unchecked(num, scale)) } - let flags: u32 = flags(neg, scale); - Ok(Decimal { + } + + #[inline] + pub(crate) const fn from_i128_with_scale_unchecked(num: i128, scale: u32) -> Decimal { + let flags = flags(num < 0, scale); + let num = num.unsigned_abs(); + Decimal { flags, - lo: (wrapped as u64 & U32_MASK) as u32, - mid: ((wrapped as u64 >> 32) & U32_MASK) as u32, - hi: ((wrapped as u128 >> 64) as u64 & U32_MASK) as u32, - }) + lo: (num as u64 & U32_MASK) as u32, + mid: ((num as u64 >> 32) & U32_MASK) as u32, + hi: ((num >> 64) as u64 & U32_MASK) as u32, + } } /// Returns a `Decimal` using the instances constituent parts. diff --git a/src/lib.rs b/src/lib.rs index 45282f2..e8ee683 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -9,7 +9,7 @@ mod constants; mod decimal; mod error; mod ops; -mod str; +pub mod str; // We purposely place this here for documentation ordering mod arithmetic_impls; @@ -64,7 +64,7 @@ pub use maths::MathematicalOps; pub mod prelude { #[cfg(feature = "maths")] pub use crate::maths::MathematicalOps; - pub use crate::{Decimal, RoundingStrategy}; + pub use crate::{dec, Decimal, RoundingStrategy}; pub use core::str::FromStr; pub use num_traits::{FromPrimitive, One, Signed, ToPrimitive, Zero}; // #[cfg(feature = "macros")] diff --git a/src/str.rs b/src/str.rs index 97ce733..56c8627 100644 --- a/src/str.rs +++ b/src/str.rs @@ -711,6 +711,299 @@ pub(crate) fn parse_str_radix_n(str: &str, radix: u32) -> Result Ok(Decimal::from_parts(data[0], data[1], data[2], negative, scale)) } +/// Transform a literal number directly to a `Decimal` at compile time. Any Rust number format works. +/// +/// - `dec!(1)`, `dec!(-1)`, `dec!(1_999)`, `dec!(- 1_999)` +/// - `dec!(0b1)`, `dec!(-0b1_1111)`, `dec!(0o1)`, `dec!(-0o1_777)`, `dec!(0x1)`, `dec!(-0x1_Ffff)` +/// - `dec!(1.)`, `dec!(-1.111_009)`, `dec!(1e6)`, `dec!(-1.2e+6)`, `dec!(12e-6)`, `dec!(-1.2e-6)` +/// +/// ### Option `radix:` +/// +/// You can give it integers (not float-like) in any radix from 2 to 36 inclusive, using the letters too: +/// `dec!(radix: 2, 100) == 4`, `dec!(radix: 3, -1_222) == -53`, `dec!(radix: 36, z1) == 1261`, +/// `dec!(radix: 36, -1_xyz) == -90683` +/// +/// ### Option `exp:` +/// +/// This is the same as the `e` 10’s exponent in float syntax (except as a Rust expression it doesn’t accept +/// a unary `+`.) You need this for other radices. Currently it must be between -28 and +28 inclusive: +/// `dec!(radix: 2, exp: 5, 10) == 200_000`, `dec!(exp: -3, radix: 8, -1_777) == dec!(-1.023)` +/// +/// ### Inner attribute `#![run_time]` +/// +/// Normally this macro is performed at compile time. Alas that only allows limited error messages. If you +/// put this attribute before any other parameters, evaluation is deferred. This gives richer error messages, +/// useful if you don’t understand why your input is not being accepted. +/// +/// Furthermore the expressions you pass to the options, which must normally be `const`, become dynamic: +/// `dec!(#![run_time] radix: my_radix, exp: my_exp, 10)` +#[macro_export] +macro_rules! dec { + (#![run_time] $($rest:tt)+) => { + $crate::dec_inner!([run_time, , ] $($rest)+) + }; + ($($rest:tt)+) => { + $crate::dec_inner!([ , , ] $($rest)+) + }; +} + +#[macro_export] +#[doc(hidden)] +macro_rules! dec_inner { + // TT muncher of options + ([$($run_time:ident)?, , $($exp:expr)?] radix: $radix:expr, $($rest:tt)+) => { + $crate::dec_inner!([$($run_time)?, $radix, $($exp)?] $($rest)+) + }; + ([$($run_time:ident)?, $($radix:expr)?, ] exp: $exp:expr, $($rest:tt)+) => { + $crate::dec_inner!([$($run_time)?, $($radix)?, $exp] $($rest)+) + }; + ([$($run_time:ident)?, $($radix:expr)?, ] $($rest:tt)+) => { + $crate::dec_inner!([$($run_time)?, $($radix)?, 0] $($rest)+) + }; + + // TT munching finished + ([$($run_time:ident)?, , $exp:expr] $($rest:tt)+) => { + $crate::dec_inner!($($run_time)? parse_dec(stringify!($($rest)+), $exp)) + }; + ([$($run_time:ident)?, $radix:expr, $exp:expr] $($rest:tt)+) => { + $crate::dec_inner!($($run_time)? parse_radix_dec($radix, stringify!($($rest)+), $exp)) + }; + + // Intermediate step under run_time + (run_time $fn:ident $args:tt) => { + $crate::dec_inner!(@ @ $fn $args; + Err(InvalidRadix(radix)) => panic!("invalid radix {radix} -- radix must be in the range 2 to 36 inclusive"), + Err(InvalidExp(exp)) => panic!("invalid exp {exp} -- exp must be in the range -28 to 28 inclusive"), + Err(Unparseable(src)) => + // We know these bytes are valid, so just use `unwrap()`. + panic!("cannot parse decimal, unexpected \"{}\"", core::str::from_utf8(src).unwrap()) + ) + }; + // Intermediate step for compile-time + ($fn:ident $args:tt) => { + $crate::dec_inner!(@ const @ $fn $args; + Err(InvalidRadix(_)) => panic!("invalid radix -- radix must be in the range 2 to 36 inclusive"), + Err(InvalidExp(_)) => panic!("invalid exp -- exp must be in the range -28 to 28 inclusive"), + Err(Unparseable([b'.', ..])) => panic!("cannot parse decimal, unexpected '.'"), + Err(Unparseable([b'+', ..])) => panic!("cannot parse decimal, unexpected '+'"), + Err(Unparseable([b'-', ..])) => panic!("cannot parse decimal, unexpected '-'"), + Err(Unparseable([b'e' | b'E', ..])) => panic!("cannot parse decimal, unexpected 'e' or 'E'"), + Err(Unparseable([b' ' | b'\t' | b'\n', ..])) => panic!("cannot parse decimal, unexpected white space"), + _ => panic!("cannot parse decimal, unexpected character") + ) + }; + // The actual created code + (@ $($const:ident)? @ $fn:ident $args:tt; $($panic:tt)+) => { + $($const)? { + use $crate::str::ParseError::*; + match $crate::str::$fn$args { + Ok(dec) => dec, + // Putting the panics into the macro expansion reports the right file & line. + Err(Empty) => panic!("number is empty, must have an integer part"), + Err(FractionEmpty) => panic!("consider adding a `0` after the period"), + Err(ExceedsMaximumPossibleValue) => panic!("number too big"), + Err(LessThanMinimumPossibleValue) => panic!("number too small"), + Err(Underflow) => panic!("too many fractional digits"), + $($panic)+ + } + } + }; +} + +// workaround for `Result<…String…>` not being droppable in `const {}` +type ParseResult<'src> = Result>; +#[doc(hidden)] +pub enum ParseError<'src> { + Empty, + ExceedsMaximumPossibleValue, + FractionEmpty, + InvalidExp(i32), + InvalidRadix(u32), + LessThanMinimumPossibleValue, + Underflow, + Unparseable(&'src [u8]), +} + +impl From> for Error { + fn from(error: ParseError) -> Self { + use ParseError::*; + match error { + Empty => Self::ConversionTo("number is empty, must have an integer part".into()), + ExceedsMaximumPossibleValue => Self::ExceedsMaximumPossibleValue, + FractionEmpty => Self::ConversionTo("consider adding a `0` after the period".into()), + InvalidExp(exp) if exp < 0 => Self::ScaleExceedsMaximumPrecision(exp.unsigned_abs()), + InvalidExp(exp) => + Self::ConversionTo(format!("invalid exp {exp} -- exp must be in the range -28 to 28 inclusive")), + InvalidRadix(radix) => Self::ConversionTo(format!("invalid radix {radix} -- radix must be in the range 2 to 36 inclusive")), + LessThanMinimumPossibleValue => Self::LessThanMinimumPossibleValue, + Underflow => Self::Underflow, + Unparseable(src) => + // We know these bytes are valid, so just use `unwrap()`. + Self::ConversionTo(format!("cannot parse decimal, unexpected \"{}\"", core::str::from_utf8(src).unwrap())), + } + } +} +// dec!() entrypoint without radix +pub const fn parse_dec(src: &str, exp: i32) -> ParseResult { + const fn skip_us(radix: u32, is_positive: bool, mut src: &[u8], exp: i32) -> ParseResult { + while let [b'_', rest @ ..] = src { + src = rest + } + parse_bytes(radix, is_positive, src, exp) + } + + let (is_positive, src) = parse_sign(src); + match src { + [b'0', b'b', src @ ..] => skip_us(2, is_positive, src, exp), + [b'0', b'o', src @ ..] => skip_us(8, is_positive, src, exp), + [b'0', b'x', src @ ..] => skip_us(16, is_positive, src, exp), + src => parse_10(is_positive, src, exp), + } +} + +// dec!() entrypoint with radix +pub const fn parse_radix_dec(radix: u32, src: &str, exp: i32) -> ParseResult { + if 2 <= radix && radix <= 36 { + let (is_positive, src) = parse_sign(src); + parse_bytes(radix, is_positive, src, exp) + } else { + Err(ParseError::InvalidRadix(radix)) + } +} + +const fn parse_sign(src: &str) -> (bool, &[u8]) { + let mut src = src.as_bytes(); + if let [b'-', signed @ ..] = src { + src = signed; + while let [b' ' | b'\t' | b'\n', rest @ ..] = src { + src = rest; + } + (false, src) + } else { + (true, src) + } +} + +const fn parse_bytes(radix: u32, is_positive: bool, src: &[u8], exp: i32) -> ParseResult { + match parse_bytes_inner(radix, src, 0) { + (.., Some(rest)) => Err(ParseError::Unparseable(rest)), + (_, 0, _) => Err(ParseError::Empty), + (num, ..) => to_decimal(is_positive, num, exp), + } +} + +// translate bi-directional exp to rest of lib’s scale down-only and create Decimal +const fn to_decimal<'src>(is_positive: bool, mut num: i128, mut exp: i32) -> ParseResult<'src> { + // Why is scale unsigned? :-( + use ParseError::*; + + const POWERS_10: [i128; 29] = { + let mut powers_10 = [1; 29]; + // no iter in const + let mut i = 1; + while i < 29 { + powers_10[i] = 10 * powers_10[i - 1]; + i += 1; + } + powers_10 + }; + + if exp >= 0 { + if exp > 28 { + return Err(InvalidExp(exp)); + } + if let Some(shifted) = num.checked_mul(POWERS_10[exp as usize]) { + num = shifted + } + exp = 0; + } else if exp < -28 { + return Err(InvalidExp(exp)); + } + if num > crate::constants::MAX_I128_REPR { + return Err(if is_positive { ExceedsMaximumPossibleValue } else { LessThanMinimumPossibleValue }); + } + Ok(Decimal::from_i128_with_scale_unchecked( + if is_positive { num } else { -num }, + exp.unsigned_abs() + )) +} + +// parse normal (radix 10) numbers with optional float-like .fraction and 10’s exponent +const fn parse_10(is_positive: bool, src: &[u8], mut exp: i32) -> ParseResult { + // parse 1st part (upto optional . or e) + let (mut num, len, mut more) = parse_bytes_inner(10, src, 0); + // Numbers can’t be empty (before optional . or e) + if len == 0 { + return Err(ParseError::Empty); + } + + // parse optional fraction + if let Some([b'.', rest @ ..]) = more { + let (whole_num, scale, _more) = parse_bytes_inner(10, rest, num); + more = _more; + // May only be empty if no exp + if scale == 0 && more.is_some() { + return Err(ParseError::FractionEmpty); + } + num = whole_num; + if num > crate::constants::MAX_I128_REPR { + return Err(ParseError::Underflow); + } + exp -= scale as i32 + } + + // parse optional 10’s exponent + if let Some([b'e' | b'E', rest @ ..]) = more { + let (rest, exp_is_positive) = if let [sign @ b'-' | sign @ b'+', signed @ ..] = rest { + (signed, *sign == b'+') + } else { + (rest, true) + }; + // if this gives Some more, we’ll return that below + let (e_part, _, _more) = parse_bytes_inner(10, rest, 0); + more = _more; + // dummy value, more than MAX not storable + if e_part > i32::MAX as i128 { + return Err(ParseError::InvalidExp(i32::MAX)); + } + if exp_is_positive { + exp += e_part as i32 + } else { + exp -= e_part as i32 + } + } + + if let Some(rest) = more { + Err(ParseError::Unparseable(rest)) + } else { + to_decimal(is_positive, num, exp) + } +} + +// Can’t use `from_str_radix`, as that neither groks '_', nor allows to continue after '.' or 'e'. +// For multi-step (see test) return: number parsed, digits count, offending rest +// num saturates at i128::MAX, which is currently not a valid Decimal +const fn parse_bytes_inner( + radix: u32, + src: &[u8], + mut num: i128, +) -> (i128, u8, Option<&[u8]>) { + let mut count = 0; + let mut next = src; + while let [byte, rest @ ..] = next { + if let Some(digit) = (*byte as char).to_digit(radix) { + count += 1; + num = num + .saturating_mul(radix as i128) + .saturating_add(digit as i128); + } else if *byte != b'_' || count == 0 { + return (num, count, Some(next)); + } + next = rest; + } + (num, count, None) +} + #[cfg(test)] mod test { use super::*; @@ -1087,4 +1380,157 @@ mod test { Err(Error::from("Invalid decimal: overflow from mantissa after rounding")) ); } + + #[test] + pub fn parse_bytes_inner_full() { + let test = |radix, src: &str, result| + assert_eq!(parse_bytes_inner(radix, src.as_bytes(), 0).0, result, "{radix}, {src}"); + + test(2, "111", 0b111); + test(2, "111", 0b111); + test(8, "177", 0o177); + test(10, "199", 199); + test(16, "1ff", 0x1ff); + test(36, "1_zzz", i128::from_str_radix("1zzz", 36).unwrap()); + + test(16, "7fff_ffff_ffff_ffff_ffff_ffff_ffff_fffE", i128::MAX - 1); + test(16, "7fff_ffff_ffff_ffff_ffff_ffff_ffff_fffF", i128::MAX); + // must saturate at MAX + test(16, "Ffff_ffff_ffff_ffff_ffff_ffff_ffff_ffff", i128::MAX); + } + + #[test] + pub fn parse_bytes_inner_partial() { + // Can only pass matcher to a macro, as normal variable would be a (useless) binding. + macro_rules! test { + ($radix:expr, $src:expr, $num:expr; $result:tt) => { + assert!(matches!(parse_bytes_inner($radix, $src.as_bytes(), $num), $result), + "{}, {}, {}", $radix, $src, $num); + } + } + // Assemble floaty: number parsed, digits count, offending rest + test!(10, "01_234.567_8", 0; + (1234, 5, Some(b".567_8"))); + // … and feed received 1234 back in, to get whole number & -exp + test!(10, "567_8", 1234; + (12345678, 4, None)); + } + + // Convert ParseResult to Decimal Result by impl From + fn parse_dec(src: &str, exp: i32) -> Result { + Ok(super::parse_dec(src, exp)?) + } + + fn parse_radix_dec(radix: u32, src: &str, exp: i32) -> Result { + Ok(super::parse_radix_dec(radix, src, exp)?) + } + + #[test] + // cases that don’t have their own Error symbol + pub fn parse_dec_string() { + use Error::*; + let test = |src, exp, result: &str| + if let Err(err) = parse_dec(src, exp) { + assert_eq!(err, ConversionTo(result.into()), "{src}, {exp}") + } else { + panic!("no Err {src}, {exp}") + }; + test("", 0, "number is empty, must have an integer part"); + test(".1", 0, "number is empty, must have an integer part"); + test("1.e2", 0, "consider adding a `0` after the period"); + test("1abc", 0, "cannot parse decimal, unexpected \"abc\""); + test("1 e1", 0, "cannot parse decimal, unexpected \" e1\""); + test("1e 1", 0, "cannot parse decimal, unexpected \" 1\""); + test("1e+ 1", 0, "cannot parse decimal, unexpected \" 1\""); + test("1e- 1", 0, "cannot parse decimal, unexpected \" 1\""); + test("1e +1", 0, "cannot parse decimal, unexpected \" +1\""); + test("1e -1", 0, "cannot parse decimal, unexpected \" -1\""); + test("1e-1", 50, "invalid exp 49 -- exp must be in the range -28 to 28 inclusive"); + test("1e60", -1, "invalid exp 59 -- exp must be in the range -28 to 28 inclusive"); + test("1e+80", 9, "invalid exp 89 -- exp must be in the range -28 to 28 inclusive"); + test("1", 99, "invalid exp 99 -- exp must be in the range -28 to 28 inclusive"); + } + + #[test] + pub fn parse_dec_other() { + use Error::*; + let test = |src, exp, result| + if let Err(err) = parse_dec(src, exp) { + assert_eq!(err, result, "{src}, {exp}") + } else { + panic!("no Err {src}, {exp}") + }; + test("1e1", -50, ScaleExceedsMaximumPrecision(49)); + test("1e-80", 1, ScaleExceedsMaximumPrecision(79)); + test("1", -99, ScaleExceedsMaximumPrecision(99)); + test("100", 28, ExceedsMaximumPossibleValue); + test("-100", 28, LessThanMinimumPossibleValue); + test("100_000_000_000_000_000_000_000_000_000", -1, ExceedsMaximumPossibleValue); + test("-100_000_000_000_000_000_000_000_000_000", -1, LessThanMinimumPossibleValue); + test("9.000_000_000_000_000_000_000_000_000_001", 0, Underflow); + } + + #[test] + pub fn parse_radix_dec_any() { + use Error::*; + let test = |radix, src, exp, result| + if let Err(err) = parse_radix_dec(radix, src, exp) { + assert_eq!(err, result, "{src}, {exp}") + } else { + panic!("no Err {src}, {exp}") + }; + test(1, "", 0, ConversionTo("invalid radix 1 -- radix must be in the range 2 to 36 inclusive".into())); + test(37, "", 0, ConversionTo("invalid radix 37 -- radix must be in the range 2 to 36 inclusive".into())); + test(4, "12_3456", 0, ConversionTo("cannot parse decimal, unexpected \"456\"".into())); + } + + #[test] + // dec!() macro and old parser give same result + pub fn dec_exact() { + macro_rules! test { + ($src:literal) => { + assert_eq!(dec!($src), parse_str_radix_10_exact(stringify!($src)).unwrap(), + stringify!($src)); + } + } + test!(1_000); + test!(-1_000); + test!(0.000_001); + test!(-0.000_001); + test!(79_228_162_514_264_337_593_543_950_335); + test!(-79_228_162_514_264_337_593_543_950_335); + test!(79.228_162_514_264_337_593_543_950_335); + test!(-79.228_162_514_264_337_593_543_950_335); + test!(7.922_816_251_426_433_759_354_395_033_5); + test!(-7.922_816_251_426_433_759_354_395_033_5); + } + + #[test] + // dec!() macro and old parser give same result + pub fn dec_scientific() { + macro_rules! test { + ($src:literal) => { + assert_eq!(dec!($src), Decimal::from_scientific(stringify!($src)).unwrap(), + stringify!($src)); + } + } + test!(1e1); + test!(-1e1); + test!(1e+1); + test!(-1e+1); + test!(1e-1); + test!(-1e-1); + + test!(1.1e1); + test!(-1.1e1); + test!(1.1e+1); + test!(-1.1e+1); + test!(1.1e-1); + test!(-1.1e-1); + + test!(7.922_816_251_426_433_759_354_395_033_5e28); + test!(-7.922_816_251_426_433_759_354_395_033_5e28); + test!(79_228_162_514_264_337_593_543_950_335e-28); + test!(-79_228_162_514_264_337_593_543_950_335e-28); + } }