Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Create pub constant Decimal::MAX_SCALE #685

Merged
merged 2 commits into from
Oct 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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