Skip to content

Commit

Permalink
Create pub constant Decimal::MAX_SCALE (#685)
Browse files Browse the repository at this point in the history
  • Loading branch information
Tony-Samuels authored Oct 15, 2024
1 parent fa88579 commit 46fb4c3
Show file tree
Hide file tree
Showing 15 changed files with 78 additions and 77 deletions.
8 changes: 4 additions & 4 deletions src/constants.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,13 @@ pub const SIGN_SHIFT: u32 = 31;
// to the byte boundary for simplicity.
pub const MAX_STR_BUFFER_SIZE: usize = 32;

// The maximum supported precision
pub const MAX_PRECISION: u8 = 28;
// The maximum supported [`Decimal::scale`] value
pub const MAX_SCALE: u8 = 28;
#[cfg(not(feature = "legacy-ops"))]
// u8 to i32 is infallible, therefore, this cast will never overflow
pub const MAX_PRECISION_I32: i32 = MAX_PRECISION as _;
pub const MAX_SCALE_I32: i32 = MAX_SCALE as _;
// u8 to u32 is infallible, therefore, this cast will never overflow
pub const MAX_PRECISION_U32: u32 = MAX_PRECISION as _;
pub const MAX_SCALE_U32: u32 = MAX_SCALE as _;
// 79,228,162,514,264,337,593,543,950,335
pub const MAX_I128_REPR: i128 = 0x0000_0000_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF;

Expand Down
49 changes: 26 additions & 23 deletions src/decimal.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use crate::constants::{
MAX_I128_REPR, MAX_PRECISION_U32, POWERS_10, SCALE_MASK, SCALE_SHIFT, SIGN_MASK, SIGN_SHIFT, U32_MASK, U8_MASK,
MAX_I128_REPR, MAX_SCALE_U32, POWERS_10, SCALE_MASK, SCALE_SHIFT, SIGN_MASK, SIGN_SHIFT, U32_MASK, U8_MASK,
UNSIGN_MASK,
};
use crate::ops;
Expand Down Expand Up @@ -272,6 +272,14 @@ impl Decimal {
/// assert_eq!(Decimal::ONE_THOUSAND, dec!(1000));
/// ```
pub const ONE_THOUSAND: Decimal = ONE_THOUSAND;
/// The maximum supported scale value.
///
/// Some operations, such as [`Self::rescale`] may accept larger scale values, but these
/// operations will result in a final value with a scale no larger than this.
///
/// Note that the maximum scale is _not_ the same as the maximum possible numeric precision in
/// base-10.
pub const MAX_SCALE: u32 = MAX_SCALE_U32;

/// A constant representing π as 3.1415926535897932384626433833
///
Expand Down Expand Up @@ -385,7 +393,7 @@ impl Decimal {
///
/// # Panics
///
/// This function panics if `scale` is > 28.
/// This function panics if `scale` is > [`Self::MAX_SCALE`].
///
/// # Example
///
Expand All @@ -403,7 +411,7 @@ impl Decimal {
}
}

/// Checked version of `Decimal::new`. Will return `Err` instead of panicking at run-time.
/// Checked version of [`Self::new`]. Will return an error instead of panicking at run-time.
///
/// # Example
///
Expand All @@ -414,7 +422,7 @@ impl Decimal {
/// assert!(max.is_err());
/// ```
pub const fn try_new(num: i64, scale: u32) -> crate::Result<Decimal> {
if scale > MAX_PRECISION_U32 {
if scale > Self::MAX_SCALE {
return Err(Error::ScaleExceedsMaximumPrecision(scale));
}
let flags: u32 = scale << SCALE_SHIFT;
Expand Down Expand Up @@ -444,7 +452,8 @@ impl Decimal {
///
/// # Panics
///
/// This function panics if `scale` is > 28 or if `num` exceeds the maximum supported 96 bits.
/// This function panics if `scale` is > [`Self::MAX_SCALE`] or if `num` exceeds the maximum
/// supported 96 bits.
///
/// # Example
///
Expand Down Expand Up @@ -474,7 +483,7 @@ impl Decimal {
/// assert!(max.is_err());
/// ```
pub const fn try_from_i128_with_scale(num: i128, scale: u32) -> crate::Result<Decimal> {
if scale > MAX_PRECISION_U32 {
if scale > Self::MAX_SCALE {
return Err(Error::ScaleExceedsMaximumPrecision(scale));
}
let mut neg = false;
Expand Down Expand Up @@ -504,14 +513,7 @@ impl Decimal {
/// * `mid` - The middle 32 bits of a 96-bit integer.
/// * `hi` - The high 32 bits of a 96-bit integer.
/// * `negative` - `true` to indicate a negative number.
/// * `scale` - A power of 10 ranging from 0 to 28.
///
/// # Caution: Undefined behavior
///
/// While a scale greater than 28 can be passed in, it will be automatically capped by this
/// function at the maximum precision. The library opts towards this functionality as opposed
/// to a panic to ensure that the function can be treated as constant. This may lead to
/// undefined behavior in downstream applications and should be treated with caution.
/// * `scale` - A power of 10 ranging from 0 to [`Self::MAX_SCALE`].
///
/// # Example
///
Expand All @@ -523,6 +525,7 @@ impl Decimal {
/// ```
#[must_use]
pub const fn from_parts(lo: u32, mid: u32, hi: u32, negative: bool, scale: u32) -> Decimal {
assert!(scale <= Self::MAX_SCALE, "Scale exceeds maximum supported scale");
Decimal {
lo,
mid,
Expand All @@ -533,7 +536,7 @@ impl Decimal {
} else {
negative
},
scale % (MAX_PRECISION_U32 + 1),
scale,
),
}
}
Expand Down Expand Up @@ -596,7 +599,7 @@ impl Decimal {
// we've parsed 1.2 as the base and 10 as the exponent. To represent this within a
// Decimal type we effectively store the mantissa as 12,000,000,000 and scale as
// zero.
if exp > MAX_PRECISION_U32 {
if exp > Self::MAX_SCALE {
return Err(Error::ScaleExceedsMaximumPrecision(exp));
}
let mut exp = exp as usize;
Expand Down Expand Up @@ -856,7 +859,7 @@ impl Decimal {
/// # }
/// ```
pub fn set_scale(&mut self, scale: u32) -> Result<(), Error> {
if scale > MAX_PRECISION_U32 {
if scale > Self::MAX_SCALE {
return Err(Error::ScaleExceedsMaximumPrecision(scale));
}
self.flags = (scale << SCALE_SHIFT) | (self.flags & SIGN_MASK);
Expand All @@ -870,7 +873,7 @@ impl Decimal {
/// cause the newly created `Decimal` to perform rounding using the `MidpointAwayFromZero` strategy.
///
/// Scales greater than the maximum precision that can be represented by `Decimal` will be
/// automatically rounded to either `Decimal::MAX_PRECISION` or the maximum precision that can
/// automatically rounded to either [`Self::MAX_SCALE`] or the maximum precision that can
/// be represented with the given mantissa.
///
/// # Arguments
Expand Down Expand Up @@ -967,7 +970,7 @@ impl Decimal {
hi: (bytes[12] as u32) | (bytes[13] as u32) << 8 | (bytes[14] as u32) << 16 | (bytes[15] as u32) << 24,
};
// Scale must be bound to maximum precision. Only two values can be greater than this
if raw.scale() > MAX_PRECISION_U32 {
if raw.scale() > Self::MAX_SCALE {
let mut bits = raw.mantissa_array3();
let remainder = match raw.scale() {
29 => ops::array::div_by_power::<1>(&mut bits),
Expand All @@ -981,7 +984,7 @@ impl Decimal {
raw.lo = bits[0];
raw.mid = bits[1];
raw.hi = bits[2];
raw.flags = flags(raw.is_sign_negative(), MAX_PRECISION_U32);
raw.flags = flags(raw.is_sign_negative(), Self::MAX_SCALE);
}
raw
}
Expand Down Expand Up @@ -2204,7 +2207,7 @@ fn base2_to_decimal(

// At this point, the mantissa has assimilated the exponent5, but
// exponent10 might not be suitable for assignment. exponent10 must be
// in the range [-MAX_PRECISION..0], so the mantissa must be scaled up or
// in the range [-MAX_SCALE..0], so the mantissa must be scaled up or
// down appropriately.
while exponent10 > 0 {
// In order to bring exponent10 down to 0, the mantissa should be
Expand All @@ -2218,10 +2221,10 @@ fn base2_to_decimal(
}
}

// In order to bring exponent up to -MAX_PRECISION, the mantissa should
// In order to bring exponent up to -MAX_SCALE, the mantissa should
// be divided by 10 to compensate. If the exponent10 is too small, this
// will cause the mantissa to underflow and become 0.
while exponent10 < -(MAX_PRECISION_U32 as i32) {
while exponent10 < -(Decimal::MAX_SCALE as i32) {
let rem10 = ops::array::div_by_u32(bits, 10);
exponent10 += 1;
if ops::array::is_all_zero(bits) {
Expand Down
5 changes: 3 additions & 2 deletions src/error.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::{constants::MAX_PRECISION_U32, Decimal};
use crate::Decimal;
use alloc::string::String;
use core::fmt;

Expand Down Expand Up @@ -57,7 +57,8 @@ impl fmt::Display for Error {
Self::ScaleExceedsMaximumPrecision(ref scale) => {
write!(
f,
"Scale exceeds the maximum precision allowed: {scale} > {MAX_PRECISION_U32}"
"Scale exceeds the maximum precision allowed: {scale} > {}",
Decimal::MAX_SCALE
)
}
Self::ConversionTo(ref type_name) => {
Expand Down
2 changes: 1 addition & 1 deletion src/fuzz.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ impl Arbitrary<'_> for crate::Decimal {
let mid = u32::arbitrary(u)?;
let hi = u32::arbitrary(u)?;
let negative = bool::arbitrary(u)?;
let scale = u32::arbitrary(u)?;
let scale = u32::arbitrary(u)? % (Self::MAX_SCALE + 1);
Ok(Decimal::from_parts(lo, mid, hi, negative, scale))
}
}
6 changes: 2 additions & 4 deletions src/ops/add.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
use crate::constants::{
MAX_I32_SCALE, MAX_PRECISION_U32, POWERS_10, SCALE_MASK, SCALE_SHIFT, SIGN_MASK, U32_MASK, U32_MAX,
};
use crate::constants::{MAX_I32_SCALE, POWERS_10, SCALE_MASK, SCALE_SHIFT, SIGN_MASK, U32_MASK, U32_MAX};
use crate::decimal::{CalculationResult, Decimal};
use crate::ops::common::{Buf24, Dec64};

Expand Down Expand Up @@ -263,7 +261,7 @@ fn unaligned_add(

rescale_factor -= MAX_I32_SCALE;

if tmp64 > U32_MAX || scale > MAX_PRECISION_U32 {
if tmp64 > U32_MAX || scale > Decimal::MAX_SCALE {
break;
} else {
high = tmp64 as u32;
Expand Down
4 changes: 2 additions & 2 deletions src/ops/array.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::constants::{MAX_PRECISION_U32, POWERS_10, U32_MASK};
use crate::constants::{MAX_SCALE_U32, POWERS_10, U32_MASK};

/// Rescales the given decimal to new scale.
/// e.g. with 1.23 and new scale 3 rescale the value to 1.230
Expand All @@ -15,7 +15,7 @@ fn rescale<const ROUND: bool>(value: &mut [u32; 3], value_scale: &mut u32, new_s
}

if is_all_zero(value) {
*value_scale = new_scale.min(MAX_PRECISION_U32);
*value_scale = new_scale.min(MAX_SCALE_U32);
return;
}

Expand Down
10 changes: 5 additions & 5 deletions src/ops/common.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::constants::{MAX_I32_SCALE, MAX_PRECISION_I32, POWERS_10};
use crate::constants::{MAX_I32_SCALE, MAX_SCALE_I32, POWERS_10};
use crate::Decimal;

#[derive(Debug)]
Expand Down Expand Up @@ -96,12 +96,12 @@ impl Buf12 {
return Some(x);
}

if scale > MAX_PRECISION_I32 - 9 {
if scale > MAX_SCALE_I32 - 9 {
// We can't scale by 10^9 without exceeding the max scale factor.
// Instead, we'll try to scale by the most that we can and see if that works.
// This is safe to do due to the check above. e.g. scale > 19 in the above, so it will
// evaluate to 9 or less below.
x = (MAX_PRECISION_I32 - scale) as usize;
x = (MAX_SCALE_I32 - scale) as usize;
if hi < POWER_OVERFLOW_VALUES[x - 1].data[2] {
if x as i32 + scale < 0 {
// We still overflow
Expand Down Expand Up @@ -350,8 +350,8 @@ impl Buf24 {
}

// Make sure we scale enough to bring it into a valid range
if rescale_target < scale - MAX_PRECISION_I32 {
rescale_target = scale - MAX_PRECISION_I32;
if rescale_target < scale - MAX_SCALE_I32 {
rescale_target = scale - MAX_SCALE_I32;
}

if rescale_target > 0 {
Expand Down
8 changes: 4 additions & 4 deletions src/ops/div.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::constants::{MAX_PRECISION_I32, POWERS_10};
use crate::constants::{MAX_SCALE_I32, POWERS_10};
use crate::decimal::{CalculationResult, Decimal};
use crate::ops::common::{Buf12, Buf16, Dec64};

Expand Down Expand Up @@ -260,7 +260,7 @@ pub(crate) fn div_impl(dividend: &Decimal, divisor: &Decimal) -> CalculationResu
// We have a remainder so we effectively want to try to adjust the quotient and add
// the remainder into the quotient. We do this below, however first of all we want
// to try to avoid overflowing so we do that check first.
let will_overflow = if scale == MAX_PRECISION_I32 {
let will_overflow = if scale == MAX_SCALE_I32 {
true
} else {
// Figure out how much we can scale by
Expand Down Expand Up @@ -376,7 +376,7 @@ pub(crate) fn div_impl(dividend: &Decimal, divisor: &Decimal) -> CalculationResu
// We have a remainder so we effectively want to try to adjust the quotient and add
// the remainder into the quotient. We do this below, however first of all we want
// to try to avoid overflowing so we do that check first.
let will_overflow = if scale == MAX_PRECISION_I32 {
let will_overflow = if scale == MAX_SCALE_I32 {
true
} else {
// Figure out how much we can scale by
Expand Down Expand Up @@ -467,7 +467,7 @@ pub(crate) fn div_impl(dividend: &Decimal, divisor: &Decimal) -> CalculationResu
// We have a remainder so we effectively want to try to adjust the quotient and add
// the remainder into the quotient. We do this below, however first of all we want
// to try to avoid overflowing so we do that check first.
let will_overflow = if scale == MAX_PRECISION_I32 {
let will_overflow = if scale == MAX_SCALE_I32 {
true
} else {
// Figure out how much we can scale by
Expand Down
20 changes: 10 additions & 10 deletions src/ops/legacy.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use crate::{
constants::{MAX_PRECISION_U32, POWERS_10, U32_MASK},
constants::{POWERS_10, U32_MASK},
decimal::{CalculationResult, Decimal},
ops::array::{
add_by_internal, cmp_internal, div_by_u32, is_all_zero, mul_by_u32, mul_part, rescale_internal, shl1_internal,
Expand Down Expand Up @@ -171,16 +171,16 @@ pub(crate) fn div_impl(d1: &Decimal, d2: &Decimal) -> CalculationResult {

// Check for underflow
let mut final_scale: u32 = quotient_scale as u32;
if final_scale > MAX_PRECISION_U32 {
if final_scale > Decimal::MAX_SCALE {
let mut remainder = 0;

// Division underflowed. We must remove some significant digits over using
// an invalid scale.
while final_scale > MAX_PRECISION_U32 && !is_all_zero(&quotient) {
while final_scale > Decimal::MAX_SCALE && !is_all_zero(&quotient) {
remainder = div_by_u32(&mut quotient, 10);
final_scale -= 1;
}
if final_scale > MAX_PRECISION_U32 {
if final_scale > Decimal::MAX_SCALE {
// Result underflowed so set to zero
final_scale = 0;
quotient_negative = false;
Expand Down Expand Up @@ -228,8 +228,8 @@ pub(crate) fn mul_impl(d1: &Decimal, d2: &Decimal) -> CalculationResult {
let mut u64_result = u64_to_array(u64::from(my[0]) * u64::from(ot[0]));

// If we're above max precision then this is a very small number
if final_scale > MAX_PRECISION_U32 {
final_scale -= MAX_PRECISION_U32;
if final_scale > Decimal::MAX_SCALE {
final_scale -= Decimal::MAX_SCALE;

// If the number is above 19 then this will equate to zero.
// This is because the max value in 64 bits is 1.84E19
Expand Down Expand Up @@ -258,7 +258,7 @@ pub(crate) fn mul_impl(d1: &Decimal, d2: &Decimal) -> CalculationResult {
u64_result[0] += 1;
}

final_scale = MAX_PRECISION_U32;
final_scale = Decimal::MAX_SCALE;
}
return CalculationResult::Ok(Decimal::from_parts(
u64_result[0],
Expand Down Expand Up @@ -350,17 +350,17 @@ pub(crate) fn mul_impl(d1: &Decimal, d2: &Decimal) -> CalculationResult {

// If we're still above max precision then we'll try again to
// reduce precision - we may be dealing with a limit of "0"
if final_scale > MAX_PRECISION_U32 {
if final_scale > Decimal::MAX_SCALE {
// We're in an underflow situation
// The easiest way to remove precision is to divide off the result
while final_scale > MAX_PRECISION_U32 && !is_all_zero(&product) {
while final_scale > Decimal::MAX_SCALE && !is_all_zero(&product) {
div_by_u32(&mut product, 10);
final_scale -= 1;
}
// If we're still at limit then we can't represent any
// significant decimal digits and will return an integer only
// Can also be invoked while representing 0.
if final_scale > MAX_PRECISION_U32 {
if final_scale > Decimal::MAX_SCALE {
final_scale = 0;
}
} else if !(product[3] == 0 && product[4] == 0 && product[5] == 0) {
Expand Down
Loading

0 comments on commit 46fb4c3

Please sign in to comment.