+ //! This is based on a copy of [`rust_decimal::str`], with adaptations to handle german decimals.
+
+ use rust_decimal::Decimal;
+ use rust_decimal::Error;
+
+ // Determines potential overflow for 128 bit operations
+ const OVERFLOW_U96: u128 = 1u128 << 96;
+ const WILL_OVERFLOW_U64: u64 = u64::MAX / 10 - u8::MAX as u64;
+ const BYTES_TO_OVERFLOW_U64: usize = 18; // We can probably get away with less
+
+ #[inline]
+ pub fn parse(value: &str) -> Result<Decimal, Error> {
+ let bytes = value.as_bytes();
+ if bytes.len() < BYTES_TO_OVERFLOW_U64 {
+ parse_str_radix_10_dispatch::<false>(bytes)
+ } else {
+ parse_str_radix_10_dispatch::<true>(bytes)
+ }
+ }
+
+ #[inline]
+ fn parse_str_radix_10_dispatch<const BIG: bool>(bytes: &[u8]) -> Result<Decimal, Error> {
+ match bytes {
+ [b, rest @ ..] => byte_dispatch_u64::<false, false, false, BIG, true>(rest, 0, 0, *b),
+ [] => tail_error("invalid decimal: empty"),
+ }
+ }
+
+ #[inline]
+ fn overflow_64(val: u64) -> bool {
+ val >= WILL_OVERFLOW_U64
+ }
+
+ #[inline]
+ pub fn overflow_128(val: u128) -> bool {
+ val >= OVERFLOW_U96
+ }
+
+ /// Dispatch the next byte:
+ ///
+ /// * SAW_DECIMAL_SEPARATOR - a decimal point has been seen
+ /// * NEGATIVE - we've encountered a `-` and the number is negative
+ /// * SAW_DIGIT - a digit has been encountered (when HAS is false it's invalid)
+ /// * BIG - a number that uses 96 bits instead of only 64 bits
+ /// * FIRST - true if it is the first byte in the string
+ #[inline]
+ fn dispatch_next<
+ const SAW_DECIMAL_SEPARATOR: bool,
+ const NEGATIVE: bool,
+ const SAW_DIGIT: bool,
+ const BIG: bool,
+ >(
+ bytes: &[u8],
+ data64: u64,
+ scale: u8,
+ ) -> Result<Decimal, Error> {
+ if let Some((next, bytes)) = bytes.split_first() {
+ byte_dispatch_u64::<SAW_DECIMAL_SEPARATOR, NEGATIVE, SAW_DIGIT, BIG, false>(
+ bytes, data64, scale, *next,
+ )
+ } else {
+ handle_data::<NEGATIVE, SAW_DIGIT>(data64 as u128, scale)
+ }
+ }
+
+ #[inline(never)]
+ fn non_digit_dispatch_u64<
+ const SAW_DECIMAL_SEPARATOR: bool,
+ const NEG: bool,
+ const NON_EMPTY: bool,
+ const BIG: bool,
+ const FIRST: bool,
+ >(
+ bytes: &[u8],
+ data64: u64,
+ scale: u8,
+ b: u8,
+ ) -> Result<Decimal, Error> {
+ match b {
+ b'-' if FIRST && !NON_EMPTY => {
+ dispatch_next::<false, true, false, BIG>(bytes, data64, scale)
+ }
+ b'+' if FIRST && !NON_EMPTY => {
+ dispatch_next::<false, false, false, BIG>(bytes, data64, scale)
+ }
+ b'.' if !SAW_DECIMAL_SEPARATOR && NON_EMPTY => {
+ handle_separator::<SAW_DECIMAL_SEPARATOR, NEG, BIG>(bytes, data64, scale)
+ }
+ b => tail_invalid_digit(b),
+ }
+ }
+
+ #[inline]
+ fn byte_dispatch_u64<
+ const SAW_DECIMAL_SEPARATOR: bool,
+ const NEGATIVE: bool,
+ const NON_EMPTY: bool,
+ const BIG: bool,
+ const FIRST: bool,
+ >(
+ bytes: &[u8],
+ data64: u64,
+ scale: u8,
+ b: u8,
+ ) -> Result<Decimal, Error> {
+ match b {
+ b'0'..=b'9' => {
+ handle_digit_64::<SAW_DECIMAL_SEPARATOR, NEGATIVE, BIG>(bytes, data64, scale, b - b'0')
+ }
+ b',' if !SAW_DECIMAL_SEPARATOR => {
+ handle_point::<NEGATIVE, NON_EMPTY, BIG>(bytes, data64, scale)
+ }
+ b => non_digit_dispatch_u64::<SAW_DECIMAL_SEPARATOR, NEGATIVE, NON_EMPTY, BIG, FIRST>(
+ bytes, data64, scale, b,
+ ),
+ }
+ }
+
+ #[inline(never)]
+ fn handle_digit_64<const SAW_DECIMAL_SEPARATOR: bool, const NEGATIVE: bool, const BIG: bool>(
+ bytes: &[u8],
+ data64: u64,
+ scale: u8,
+ digit: u8,
+ ) -> Result<Decimal, Error> {
+ // we have already validated that we cannot overflow
+ let data64 = data64 * 10 + digit as u64;
+ let scale = if SAW_DECIMAL_SEPARATOR { scale + 1 } else { 0 };
+
+ if let Some((next, bytes)) = bytes.split_first() {
+ let next = *next;
+ if SAW_DECIMAL_SEPARATOR && BIG && scale >= 28 {
+ Err(Error::Underflow)
+ } else if BIG && overflow_64(data64) {
+ handle_full_128::<SAW_DECIMAL_SEPARATOR, NEGATIVE>(data64 as u128, bytes, scale, next)
+ } else {
+ byte_dispatch_u64::<SAW_DECIMAL_SEPARATOR, NEGATIVE, true, BIG, false>(
+ bytes, data64, scale, next,
+ )
+ }
+ } else {
+ let data: u128 = data64 as u128;
+
+ handle_data::<NEGATIVE, true>(data, scale)
+ }
+ }
+
+ #[inline(never)]
+ fn handle_point<const NEG: bool, const NON_EMPTY: bool, const BIG: bool>(
+ bytes: &[u8],
+ data64: u64,
+ scale: u8,
+ ) -> Result<Decimal, Error> {
+ dispatch_next::<true, NEG, NON_EMPTY, BIG>(bytes, data64, scale)
+ }
+
+ #[inline(never)]
+ fn handle_separator<const SAW_DECIMAL_SEPARATOR: bool, const NEG: bool, const BIG: bool>(
+ bytes: &[u8],
+ data64: u64,
+ scale: u8,
+ ) -> Result<Decimal, Error> {
+ dispatch_next::<SAW_DECIMAL_SEPARATOR, NEG, true, BIG>(bytes, data64, scale)
+ }
+
+ #[cold]
+ fn tail_error(from: &'static str) -> Result<Decimal, Error> {
+ Err(from.into())
+ }
+
+ #[inline(never)]
+ #[cold]
+ fn tail_invalid_digit(digit: u8) -> Result<Decimal, Error> {
+ match digit {
+ b',' => tail_error("invalid decimal: two decimal points"),
+ // b'_' => tail_error("Invalid decimal: must start lead with a number"),
+ _ => tail_error("invalid decimal: unknown character"),
+ }
+ }
+
+ #[inline(never)]
+ #[cold]
+ fn handle_full_128<const SAW_DECIMAL_SEPARATOR: bool, const NEG: bool>(
+ mut data: u128,
+ bytes: &[u8],
+ scale: u8,
+ next_byte: u8,
+ ) -> Result<Decimal, Error> {
+ let b = next_byte;
+ match b {
+ b'0'..=b'9' => {
+ let digit = u32::from(b - b'0');
+
+ // If the data is going to overflow then we should go into recovery mode
+ let next = (data * 10) + digit as u128;
+ if overflow_128(next) {
+ if !SAW_DECIMAL_SEPARATOR {
+ tail_error("invalid decimal: overflow from too many digits")
+ } else {
+ Err(Error::Underflow)
+ }
+ } else {
+ data = next;
+ let scale = scale + SAW_DECIMAL_SEPARATOR as u8;
+ if let Some((next, bytes)) = bytes.split_first() {
+ let next = *next;
+ if SAW_DECIMAL_SEPARATOR && scale >= 28 {
+ Err(Error::Underflow)
+ } else {
+ handle_full_128::<SAW_DECIMAL_SEPARATOR, NEG>(data, bytes, scale, next)
+ }
+ } else {
+ handle_data::<NEG, true>(data, scale)
+ }
+ }
+ }
+ b',' if !SAW_DECIMAL_SEPARATOR => {
+ // This call won't tail?
+ if let Some((next, bytes)) = bytes.split_first() {
+ handle_full_128::<true, NEG>(data, bytes, scale, *next)
+ } else {
+ handle_data::<NEG, true>(data, scale)
+ }
+ }
+ b'.' => {
+ if let Some((next, bytes)) = bytes.split_first() {
+ handle_full_128::<SAW_DECIMAL_SEPARATOR, NEG>(data, bytes, scale, *next)
+ } else {
+ handle_data::<NEG, true>(data, scale)
+ }
+ }
+ b => tail_invalid_digit(b),
+ }
+ }
+
+ #[inline(never)]
+ fn tail_empty() -> Result<Decimal, Error> {
+ tail_error("invalid decimal: no digits found")
+ }
+
+ #[inline]
+ fn handle_data<const NEG: bool, const HAS: bool>(data: u128, scale: u8) -> Result<Decimal, Error> {
+ debug_assert_eq!(data >> 96, 0);
+
+ if !HAS {
+ tail_empty()
+ } else {
+ Ok(Decimal::from_parts(
+ data as u32,
+ (data >> 32) as u32,
+ (data >> 64) as u32,
+ NEG,
+ scale as u32,
+ ))
+ }
+ }