diff --git a/CHANGELOG.md b/CHANGELOG.md index 5afb47d7e64..56091b12084 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -36,7 +36,7 @@ - Add implementations for `HashSet`, `HashMap`, `BTreeSet`, `BTreeMap` (https://github.com/unicode-org/icu4x/pull/4268, https://github.com/unicode-org/icu4x/pull/4274, https://github.com/unicode-org/icu4x/pull/4295) - Improvements to `databake::test_bake!()` (https://github.com/unicode-org/icu4x/pull/4182) - `fixed_decimal` - - Experimental rounding increment support (https://github.com/unicode-org/icu4x/pull/4219) + - Experimental rounding increment support (https://github.com/unicode-org/icu4x/pull/4219, https://github.com/unicode-org/icu4x/pull/4246) - `litemap` - Implement `databake::Bake` on `LiteMap` (https://github.com/unicode-org/icu4x/pull/4275) - `tinystr` diff --git a/tools/ffi_coverage/src/allowlist.rs b/tools/ffi_coverage/src/allowlist.rs index 53a396adbd6..6d7a4b17680 100644 --- a/tools/ffi_coverage/src/allowlist.rs +++ b/tools/ffi_coverage/src/allowlist.rs @@ -317,6 +317,20 @@ lazy_static::lazy_static! { "fixed_decimal::FixedDecimal::expanded_to_increment", "fixed_decimal::FixedDecimal::trunc_to_increment", "fixed_decimal::FixedDecimal::trunced_to_increment", + "fixed_decimal::FixedDecimal::ceil_to_increment", + "fixed_decimal::FixedDecimal::ceiled_to_increment", + "fixed_decimal::FixedDecimal::floor_to_increment", + "fixed_decimal::FixedDecimal::floored_to_increment", + "fixed_decimal::FixedDecimal::half_ceil_to_increment", + "fixed_decimal::FixedDecimal::half_ceiled_to_increment", + "fixed_decimal::FixedDecimal::half_even_to_increment", + "fixed_decimal::FixedDecimal::half_evened_to_increment", + "fixed_decimal::FixedDecimal::half_expand_to_increment", + "fixed_decimal::FixedDecimal::half_expanded_to_increment", + "fixed_decimal::FixedDecimal::half_floor_to_increment", + "fixed_decimal::FixedDecimal::half_floored_to_increment", + "fixed_decimal::FixedDecimal::half_trunc_to_increment", + "fixed_decimal::FixedDecimal::half_trunced_to_increment", // Stuff that does not need to be exposed over FFI // Especially for stuff that are Rust specific like conversion traits diff --git a/utils/fixed_decimal/src/decimal.rs b/utils/fixed_decimal/src/decimal.rs index 12616bc92e0..fc791b0e6c4 100644 --- a/utils/fixed_decimal/src/decimal.rs +++ b/utils/fixed_decimal/src/decimal.rs @@ -349,6 +349,108 @@ impl FixedDecimal { } } + /// Returns the relative ordering of the digits after `magnitude` with respect to 5. + #[cfg(feature = "experimental")] + fn half_at_next_magnitude(&self, magnitude: i16) -> Ordering { + #[cfg(debug_assertions)] // Depends on having no trailing zeroes. + self.check_invariants(); + + match self.digit_at_next_position(magnitude).cmp(&5) { + // If the next digit is equal to 5, we can know if we're at exactly 5 + // by comparing the next magnitude with the last nonzero magnitude of + // the number. + Ordering::Equal => match (magnitude - 1).cmp(&self.nonzero_magnitude_end()) { + Ordering::Greater => { + // `magnitude - 1` has non-zero digits at its right, + // meaning the number overall must be greater than 5. + Ordering::Greater + } + Ordering::Equal => { + // `magnitude - 1` is the last digit of the number, + // meaning the number is exactly 5. + Ordering::Equal + } + Ordering::Less => { + debug_assert!(false, "digit `magnitude - 1` should not be zero"); + Ordering::Less + } + }, + // If the next digit is either greater or less than 5, + // we know that the digits cannot sum to exactly 5. + ord => ord, + } + } + + /// Returns the relative ordering of the digits from `magnitude` onwards + /// with respect to the half increment of `increment`. + #[cfg(feature = "experimental")] + fn half_increment_at_magnitude( + &self, + magnitude: i16, + increment: RoundingIncrement, + ) -> Ordering { + #[cfg(debug_assertions)] // Depends on having no trailing zeroes. + self.check_invariants(); + + match increment { + RoundingIncrement::MultiplesOf1 => self.half_at_next_magnitude(magnitude), + RoundingIncrement::MultiplesOf2 => { + let current_digit = self.digit_at(magnitude); + + // Equivalent to "if current_digit is odd". + if current_digit & 0x01 == 1 { + match magnitude.cmp(&self.nonzero_magnitude_end()) { + Ordering::Greater => { + // `magnitude` has non-zero digits at its right, + // meaning the number overall must be greater than + // the half increment. + Ordering::Greater + } + Ordering::Equal => { + // `magnitude` is the last digit of the number, + // meaning the number is exactly at a half increment. + Ordering::Equal + } + Ordering::Less => { + debug_assert!(false, "digit `magnitude` should not be zero"); + Ordering::Less + } + } + } else { + // Even numbers are always below the half increment. + Ordering::Less + } + } + RoundingIncrement::MultiplesOf5 => { + let current_digit = self.digit_at(magnitude); + match (current_digit % 5).cmp(&2) { + Ordering::Equal => { + // Need to know if the number exceeds 2.5. + self.half_at_next_magnitude(magnitude) + } + // If the next digit is either greater or less than the half increment, + // we know that the digits cannot sum to exactly it. + ord => ord, + } + } + RoundingIncrement::MultiplesOf25 => { + let current_digit = self.digit_at(magnitude); + let prev_digit = self.digit_at_previous_position(magnitude); + let number = prev_digit * 10 + current_digit; + + match (number % 25).cmp(&12) { + Ordering::Equal => { + // Need to know if the number exceeds 12.5. + self.half_at_next_magnitude(magnitude) + } + // If the next digit is either greater or less than the half increment, + // we know that the digits cannot sum to exactly it. + ord => ord, + } + } + } + } + /// Checks if this number is already rounded to the specified magnitude and /// increment. #[cfg(feature = "experimental")] @@ -1380,6 +1482,19 @@ impl FixedDecimal { /// assert_eq!("4", dec.to_string()); /// ``` pub fn half_trunc(&mut self, position: i16) { + #[cfg(feature = "experimental")] + { + self.half_trunc_to_increment(position, RoundingIncrement::MultiplesOf1); + } + + #[cfg(not(feature = "experimental"))] + { + self.half_trunc_internal(position); + } + } + + #[cfg(not(feature = "experimental"))] + fn half_trunc_internal(&mut self, position: i16) { let digit_after_position = self.digit_at_next_position(position); let should_expand = match digit_after_position.cmp(&5) { Ordering::Less => false, @@ -1398,6 +1513,51 @@ impl FixedDecimal { } } + /// Half Truncates the number on the right to a particular position and rounding increment, + /// deleting digits if necessary. + /// + ///
+ /// 🚧 This code is experimental; it may change at any time, in breaking or non-breaking ways, + /// including in SemVer minor releases. Use with caution. + /// #3929 + ///
+ /// + /// # Examples + /// + /// ``` + /// use fixed_decimal::{FixedDecimal, RoundingIncrement}; + /// # use std::str::FromStr; + /// + /// let mut dec = FixedDecimal::from_str("-3.5").unwrap(); + /// dec.half_trunc_to_increment(0, RoundingIncrement::MultiplesOf2); + /// assert_eq!("-4", dec.to_string()); + /// let mut dec = FixedDecimal::from_str("7.57").unwrap(); + /// dec.half_trunc_to_increment(-1, RoundingIncrement::MultiplesOf5); + /// assert_eq!("7.5", dec.to_string()); + /// let mut dec = FixedDecimal::from_str("5.45").unwrap(); + /// dec.half_trunc_to_increment(-2, RoundingIncrement::MultiplesOf25); + /// assert_eq!("5.50", dec.to_string()); + /// let mut dec = FixedDecimal::from_str("9.99").unwrap(); + /// dec.half_trunc_to_increment(-1, RoundingIncrement::MultiplesOf25); + /// assert_eq!("10.0", dec.to_string()); + /// let mut dec = FixedDecimal::from_str("9.99").unwrap(); + /// dec.half_trunc_to_increment(-2, RoundingIncrement::MultiplesOf2); + /// assert_eq!("9.98", dec.to_string()); + /// ``` + #[cfg(feature = "experimental")] + pub fn half_trunc_to_increment(&mut self, position: i16, increment: RoundingIncrement) { + // Only expand if the rounding position is strictly greater than the half increment. + // At the half increment, `half_trunc` always truncates. + let should_expand = + self.half_increment_at_magnitude(position, increment) == Ordering::Greater; + + if should_expand { + self.expand_to_increment(position, increment); + } else { + self.trunc_to_increment(position, increment); + } + } + /// Half Truncates the number on the right to a particular position, deleting /// digits if necessary. /// @@ -1425,6 +1585,62 @@ impl FixedDecimal { self } + /// Half Truncates the number on the right to a particular position and rounding increment, + /// deleting digits if necessary. + /// + ///
+ /// 🚧 This code is experimental; it may change at any time, in breaking or non-breaking ways, + /// including in SemVer minor releases. Use with caution. + /// #3929 + ///
+ /// + /// # Examples + /// + /// ``` + /// use fixed_decimal::{FixedDecimal, RoundingIncrement}; + /// # use std::str::FromStr; + /// + /// let mut dec = FixedDecimal::from_str("-3.5").unwrap(); + /// assert_eq!( + /// "-4", + /// dec.half_trunced_to_increment(0, RoundingIncrement::MultiplesOf2) + /// .to_string() + /// ); + /// let mut dec = FixedDecimal::from_str("7.57").unwrap(); + /// assert_eq!( + /// "7.5", + /// dec.half_trunced_to_increment(-1, RoundingIncrement::MultiplesOf5) + /// .to_string() + /// ); + /// let mut dec = FixedDecimal::from_str("5.45").unwrap(); + /// assert_eq!( + /// "5.50", + /// dec.half_trunced_to_increment(-2, RoundingIncrement::MultiplesOf25) + /// .to_string() + /// ); + /// let mut dec = FixedDecimal::from_str("9.99").unwrap(); + /// assert_eq!( + /// "10.0", + /// dec.half_trunced_to_increment(-1, RoundingIncrement::MultiplesOf25) + /// .to_string() + /// ); + /// let mut dec = FixedDecimal::from_str("9.99").unwrap(); + /// assert_eq!( + /// "9.98", + /// dec.half_trunced_to_increment(-2, RoundingIncrement::MultiplesOf2) + /// .to_string() + /// ); + /// ``` + #[cfg(feature = "experimental")] + pub fn half_trunced_to_increment( + mut self, + position: i16, + increment: RoundingIncrement, + ) -> Self { + self.half_trunc_to_increment(position, increment); + self + } + /// Take the expand of the number at a particular position. /// /// # Examples @@ -1884,6 +2100,19 @@ impl FixedDecimal { /// assert_eq!("2", dec.to_string()); /// ``` pub fn half_expand(&mut self, position: i16) { + #[cfg(feature = "experimental")] + { + self.half_expand_to_increment(position, RoundingIncrement::MultiplesOf1); + } + + #[cfg(not(feature = "experimental"))] + { + self.half_expand_internal(position); + } + } + + #[cfg(not(feature = "experimental"))] + pub fn half_expand_internal(&mut self, position: i16) { let digit_after_position = self.digit_at_next_position(position); if digit_after_position >= 5 { @@ -1893,6 +2122,49 @@ impl FixedDecimal { } } + /// Take the half expand of the number at a particular position and rounding increment. + /// + ///
+ /// 🚧 This code is experimental; it may change at any time, in breaking or non-breaking ways, + /// including in SemVer minor releases. Use with caution. + /// #3929 + ///
+ /// + /// # Examples + /// + /// ``` + /// use fixed_decimal::{FixedDecimal, RoundingIncrement}; + /// # use std::str::FromStr; + /// + /// let mut dec = FixedDecimal::from_str("-3.5").unwrap(); + /// dec.half_expand_to_increment(0, RoundingIncrement::MultiplesOf2); + /// assert_eq!("-4", dec.to_string()); + /// let mut dec = FixedDecimal::from_str("7.57").unwrap(); + /// dec.half_expand_to_increment(-1, RoundingIncrement::MultiplesOf5); + /// assert_eq!("7.5", dec.to_string()); + /// let mut dec = FixedDecimal::from_str("5.45").unwrap(); + /// dec.half_expand_to_increment(-2, RoundingIncrement::MultiplesOf25); + /// assert_eq!("5.50", dec.to_string()); + /// let mut dec = FixedDecimal::from_str("9.99").unwrap(); + /// dec.half_expand_to_increment(-1, RoundingIncrement::MultiplesOf25); + /// assert_eq!("10.0", dec.to_string()); + /// let mut dec = FixedDecimal::from_str("9.99").unwrap(); + /// dec.half_expand_to_increment(-2, RoundingIncrement::MultiplesOf2); + /// assert_eq!("10.00", dec.to_string()); + /// ``` + #[cfg(feature = "experimental")] + pub fn half_expand_to_increment(&mut self, position: i16, increment: RoundingIncrement) { + // Only truncate if the rounding position is strictly less than the half increment. + // At the half increment, `half_expand` always expands. + let should_trunc = self.half_increment_at_magnitude(position, increment) == Ordering::Less; + + if should_trunc { + self.trunc_to_increment(position, increment); + } else { + self.expand_to_increment(position, increment); + } + } + /// Take the half expand of the number at a particular position. /// /// # Examples @@ -1917,6 +2189,61 @@ impl FixedDecimal { self } + /// Take the half expand of the number at a particular position and rounding increment. + /// + ///
+ /// 🚧 This code is experimental; it may change at any time, in breaking or non-breaking ways, + /// including in SemVer minor releases. Use with caution. + /// #3929 + ///
+ /// + /// # Examples + /// + /// ``` + /// use fixed_decimal::{FixedDecimal, RoundingIncrement}; + /// # use std::str::FromStr; + /// + /// let mut dec = FixedDecimal::from_str("-3.5").unwrap(); + /// assert_eq!( + /// "-4", + /// dec.half_expanded_to_increment(0, RoundingIncrement::MultiplesOf2) + /// .to_string() + /// ); + /// let mut dec = FixedDecimal::from_str("7.57").unwrap(); + /// assert_eq!( + /// "7.5", + /// dec.half_expanded_to_increment(-1, RoundingIncrement::MultiplesOf5) + /// .to_string() + /// ); + /// let mut dec = FixedDecimal::from_str("5.45").unwrap(); + /// assert_eq!( + /// "5.50", + /// dec.half_expanded_to_increment(-2, RoundingIncrement::MultiplesOf25) + /// .to_string() + /// ); + /// let mut dec = FixedDecimal::from_str("9.99").unwrap(); + /// assert_eq!( + /// "10.0", + /// dec.half_expanded_to_increment(-1, RoundingIncrement::MultiplesOf25) + /// .to_string() + /// ); + /// let mut dec = FixedDecimal::from_str("9.99").unwrap(); + /// assert_eq!( + /// "10.00", + /// dec.half_expanded_to_increment(-2, RoundingIncrement::MultiplesOf2) + /// .to_string() + /// ); + /// ``` + #[cfg(feature = "experimental")] + pub fn half_expanded_to_increment( + mut self, + position: i16, + increment: RoundingIncrement, + ) -> Self { + self.half_expand_to_increment(position, increment); + self + } + /// Take the ceiling of the number at a particular position. /// /// # Examples @@ -1942,6 +2269,19 @@ impl FixedDecimal { /// assert_eq!("2", dec.to_string()); /// ``` pub fn ceil(&mut self, position: i16) { + #[cfg(feature = "experimental")] + { + self.ceil_to_increment(position, RoundingIncrement::MultiplesOf1); + } + + #[cfg(not(feature = "experimental"))] + { + self.ceil_internal(position); + } + } + + #[cfg(not(feature = "experimental"))] + fn ceil_internal(&mut self, position: i16) { if self.sign == Sign::Negative { self.trunc(position); return; @@ -1950,6 +2290,46 @@ impl FixedDecimal { self.expand(position); } + /// Take the ceiling of the number at a particular position and rounding increment. + /// + ///
+ /// 🚧 This code is experimental; it may change at any time, in breaking or non-breaking ways, + /// including in SemVer minor releases. Use with caution. + /// #3929 + ///
+ /// + /// # Examples + /// + /// ``` + /// use fixed_decimal::{FixedDecimal, RoundingIncrement}; + /// # use std::str::FromStr; + /// + /// let mut dec = FixedDecimal::from_str("-3.5").unwrap(); + /// dec.ceil_to_increment(0, RoundingIncrement::MultiplesOf2); + /// assert_eq!("-2", dec.to_string()); + /// let mut dec = FixedDecimal::from_str("7.57").unwrap(); + /// dec.ceil_to_increment(-1, RoundingIncrement::MultiplesOf5); + /// assert_eq!("8.0", dec.to_string()); + /// let mut dec = FixedDecimal::from_str("-5.45").unwrap(); + /// dec.ceil_to_increment(-2, RoundingIncrement::MultiplesOf25); + /// assert_eq!("-5.25", dec.to_string()); + /// let mut dec = FixedDecimal::from_str("9.99").unwrap(); + /// dec.ceil_to_increment(-1, RoundingIncrement::MultiplesOf25); + /// assert_eq!("10.0", dec.to_string()); + /// let mut dec = FixedDecimal::from_str("-9.99").unwrap(); + /// dec.ceil_to_increment(-2, RoundingIncrement::MultiplesOf2); + /// assert_eq!("-9.98", dec.to_string()); + /// ``` + #[cfg(feature = "experimental")] + pub fn ceil_to_increment(&mut self, position: i16, increment: RoundingIncrement) { + if self.sign == Sign::Negative { + self.trunc_to_increment(position, increment); + return; + } + + self.expand_to_increment(position, increment); + } + /// Take the ceiling of the number at a particular position. /// /// # Examples @@ -1974,21 +2354,72 @@ impl FixedDecimal { self } - /// Take the half ceiling of the number at a particular position. + /// Take the ceiling of the number at a particular position and rounding increment. + /// + ///
+ /// 🚧 This code is experimental; it may change at any time, in breaking or non-breaking ways, + /// including in SemVer minor releases. Use with caution. + /// #3929 + ///
/// /// # Examples /// /// ``` - /// use fixed_decimal::FixedDecimal; + /// use fixed_decimal::{FixedDecimal, RoundingIncrement}; /// # use std::str::FromStr; /// - /// let mut dec = FixedDecimal::from_str("-1.5").unwrap(); - /// dec.half_ceil(0); - /// assert_eq!("-1", dec.to_string()); - /// let mut dec = FixedDecimal::from_str("0.4").unwrap(); - /// dec.half_ceil(0); - /// assert_eq!("0", dec.to_string()); - /// let mut dec = FixedDecimal::from_str("0.5").unwrap(); + /// let mut dec = FixedDecimal::from_str("-3.5").unwrap(); + /// assert_eq!( + /// "-2", + /// dec.ceiled_to_increment(0, RoundingIncrement::MultiplesOf2) + /// .to_string() + /// ); + /// let mut dec = FixedDecimal::from_str("7.57").unwrap(); + /// assert_eq!( + /// "8.0", + /// dec.ceiled_to_increment(-1, RoundingIncrement::MultiplesOf5) + /// .to_string() + /// ); + /// let mut dec = FixedDecimal::from_str("-5.45").unwrap(); + /// assert_eq!( + /// "-5.25", + /// dec.ceiled_to_increment(-2, RoundingIncrement::MultiplesOf25) + /// .to_string() + /// ); + /// let mut dec = FixedDecimal::from_str("9.99").unwrap(); + /// assert_eq!( + /// "10.0", + /// dec.ceiled_to_increment(-1, RoundingIncrement::MultiplesOf25) + /// .to_string() + /// ); + /// let mut dec = FixedDecimal::from_str("-9.99").unwrap(); + /// assert_eq!( + /// "-9.98", + /// dec.ceiled_to_increment(-2, RoundingIncrement::MultiplesOf2) + /// .to_string() + /// ); + /// ``` + #[cfg(feature = "experimental")] + pub fn ceiled_to_increment(mut self, position: i16, increment: RoundingIncrement) -> Self { + self.ceil_to_increment(position, increment); + self + } + + /// Take the half ceiling of the number at a particular position. + /// + /// # Examples + /// + /// ``` + /// use fixed_decimal::FixedDecimal; + /// # use std::str::FromStr; + /// + /// let mut dec = FixedDecimal::from_str("-1.5").unwrap(); + /// dec.half_ceil(0); + /// assert_eq!("-1", dec.to_string()); + /// let mut dec = FixedDecimal::from_str("0.4").unwrap(); + /// dec.half_ceil(0); + /// assert_eq!("0", dec.to_string()); + /// let mut dec = FixedDecimal::from_str("0.5").unwrap(); /// dec.half_ceil(0); /// assert_eq!("1", dec.to_string()); /// let mut dec = FixedDecimal::from_str("0.6").unwrap(); @@ -1999,6 +2430,19 @@ impl FixedDecimal { /// assert_eq!("2", dec.to_string()); /// ``` pub fn half_ceil(&mut self, position: i16) { + #[cfg(feature = "experimental")] + { + self.half_ceil_to_increment(position, RoundingIncrement::MultiplesOf1); + } + + #[cfg(not(feature = "experimental"))] + { + self.half_ceil_internal(position); + } + } + + #[cfg(not(feature = "experimental"))] + fn half_ceil_internal(&mut self, position: i16) { if self.sign == Sign::Negative { self.half_trunc(position); return; @@ -2007,6 +2451,46 @@ impl FixedDecimal { self.half_expand(position); } + /// Take the half ceiling of the number at a particular position and rounding increment. + /// + ///
+ /// 🚧 This code is experimental; it may change at any time, in breaking or non-breaking ways, + /// including in SemVer minor releases. Use with caution. + /// #3929 + ///
+ /// + /// # Examples + /// + /// ``` + /// use fixed_decimal::{FixedDecimal, RoundingIncrement}; + /// # use std::str::FromStr; + /// + /// let mut dec = FixedDecimal::from_str("-3.5").unwrap(); + /// dec.half_ceil_to_increment(0, RoundingIncrement::MultiplesOf2); + /// assert_eq!("-4", dec.to_string()); + /// let mut dec = FixedDecimal::from_str("7.57").unwrap(); + /// dec.half_ceil_to_increment(-1, RoundingIncrement::MultiplesOf5); + /// assert_eq!("7.5", dec.to_string()); + /// let mut dec = FixedDecimal::from_str("-5.45").unwrap(); + /// dec.half_ceil_to_increment(-2, RoundingIncrement::MultiplesOf25); + /// assert_eq!("-5.50", dec.to_string()); + /// let mut dec = FixedDecimal::from_str("9.99").unwrap(); + /// dec.half_ceil_to_increment(-1, RoundingIncrement::MultiplesOf25); + /// assert_eq!("10.0", dec.to_string()); + /// let mut dec = FixedDecimal::from_str("-9.99").unwrap(); + /// dec.half_ceil_to_increment(-2, RoundingIncrement::MultiplesOf2); + /// assert_eq!("-9.98", dec.to_string()); + /// ``` + #[cfg(feature = "experimental")] + pub fn half_ceil_to_increment(&mut self, position: i16, increment: RoundingIncrement) { + if self.sign == Sign::Negative { + self.half_trunc_to_increment(position, increment); + return; + } + + self.half_expand_to_increment(position, increment); + } + /// Take the half ceiling of the number at a particular position. /// /// # Examples @@ -2031,6 +2515,57 @@ impl FixedDecimal { self } + /// Take the half ceiling of the number at a particular position and rounding increment. + /// + ///
+ /// 🚧 This code is experimental; it may change at any time, in breaking or non-breaking ways, + /// including in SemVer minor releases. Use with caution. + /// #3929 + ///
+ /// + /// # Examples + /// + /// ``` + /// use fixed_decimal::{FixedDecimal, RoundingIncrement}; + /// # use std::str::FromStr; + /// + /// let mut dec = FixedDecimal::from_str("-3.5").unwrap(); + /// assert_eq!( + /// "-4", + /// dec.half_ceiled_to_increment(0, RoundingIncrement::MultiplesOf2) + /// .to_string() + /// ); + /// let mut dec = FixedDecimal::from_str("7.57").unwrap(); + /// assert_eq!( + /// "7.5", + /// dec.half_ceiled_to_increment(-1, RoundingIncrement::MultiplesOf5) + /// .to_string() + /// ); + /// let mut dec = FixedDecimal::from_str("-5.45").unwrap(); + /// assert_eq!( + /// "-5.50", + /// dec.half_ceiled_to_increment(-2, RoundingIncrement::MultiplesOf25) + /// .to_string() + /// ); + /// let mut dec = FixedDecimal::from_str("9.99").unwrap(); + /// assert_eq!( + /// "10.0", + /// dec.half_ceiled_to_increment(-1, RoundingIncrement::MultiplesOf25) + /// .to_string() + /// ); + /// let mut dec = FixedDecimal::from_str("-9.99").unwrap(); + /// assert_eq!( + /// "-9.98", + /// dec.half_ceiled_to_increment(-2, RoundingIncrement::MultiplesOf2) + /// .to_string() + /// ); + /// ``` + #[cfg(feature = "experimental")] + pub fn half_ceiled_to_increment(mut self, position: i16, increment: RoundingIncrement) -> Self { + self.half_ceil_to_increment(position, increment); + self + } + /// Take the floor of the number at a particular position. /// /// # Examples @@ -2056,6 +2591,19 @@ impl FixedDecimal { /// assert_eq!("1", dec.to_string()); /// ``` pub fn floor(&mut self, position: i16) { + #[cfg(feature = "experimental")] + { + self.floor_to_increment(position, RoundingIncrement::MultiplesOf1); + } + + #[cfg(not(feature = "experimental"))] + { + self.floor_internal(position); + } + } + + #[cfg(not(feature = "experimental"))] + pub fn floor_internal(&mut self, position: i16) { if self.sign == Sign::Negative { self.expand(position); return; @@ -2064,6 +2612,46 @@ impl FixedDecimal { self.trunc(position); } + /// Take the floor of the number at a particular position and rounding increment. + /// + ///
+ /// 🚧 This code is experimental; it may change at any time, in breaking or non-breaking ways, + /// including in SemVer minor releases. Use with caution. + /// #3929 + ///
+ /// + /// # Examples + /// + /// ``` + /// use fixed_decimal::{FixedDecimal, RoundingIncrement}; + /// # use std::str::FromStr; + /// + /// let mut dec = FixedDecimal::from_str("-3.5").unwrap(); + /// dec.floor_to_increment(0, RoundingIncrement::MultiplesOf2); + /// assert_eq!("-4", dec.to_string()); + /// let mut dec = FixedDecimal::from_str("7.57").unwrap(); + /// dec.floor_to_increment(-1, RoundingIncrement::MultiplesOf5); + /// assert_eq!("7.5", dec.to_string()); + /// let mut dec = FixedDecimal::from_str("-5.45").unwrap(); + /// dec.floor_to_increment(-2, RoundingIncrement::MultiplesOf25); + /// assert_eq!("-5.50", dec.to_string()); + /// let mut dec = FixedDecimal::from_str("9.99").unwrap(); + /// dec.floor_to_increment(-1, RoundingIncrement::MultiplesOf25); + /// assert_eq!("7.5", dec.to_string()); + /// let mut dec = FixedDecimal::from_str("-9.99").unwrap(); + /// dec.floor_to_increment(-2, RoundingIncrement::MultiplesOf2); + /// assert_eq!("-10.00", dec.to_string()); + /// ``` + #[cfg(feature = "experimental")] + pub fn floor_to_increment(&mut self, position: i16, increment: RoundingIncrement) { + if self.sign == Sign::Negative { + self.expand_to_increment(position, increment); + return; + } + + self.trunc_to_increment(position, increment); + } + /// Take the floor of the number at a particular position. /// /// # Examples @@ -2088,6 +2676,57 @@ impl FixedDecimal { self } + /// Take the floor of the number at a particular position and rounding increment. + /// + ///
+ /// 🚧 This code is experimental; it may change at any time, in breaking or non-breaking ways, + /// including in SemVer minor releases. Use with caution. + /// #3929 + ///
+ /// + /// # Examples + /// + /// ``` + /// use fixed_decimal::{FixedDecimal, RoundingIncrement}; + /// # use std::str::FromStr; + /// + /// let mut dec = FixedDecimal::from_str("-3.5").unwrap(); + /// assert_eq!( + /// "-4", + /// dec.floored_to_increment(0, RoundingIncrement::MultiplesOf2) + /// .to_string() + /// ); + /// let mut dec = FixedDecimal::from_str("7.57").unwrap(); + /// assert_eq!( + /// "7.5", + /// dec.floored_to_increment(-1, RoundingIncrement::MultiplesOf5) + /// .to_string() + /// ); + /// let mut dec = FixedDecimal::from_str("-5.45").unwrap(); + /// assert_eq!( + /// "-5.50", + /// dec.floored_to_increment(-2, RoundingIncrement::MultiplesOf25) + /// .to_string() + /// ); + /// let mut dec = FixedDecimal::from_str("9.99").unwrap(); + /// assert_eq!( + /// "7.5", + /// dec.floored_to_increment(-1, RoundingIncrement::MultiplesOf25) + /// .to_string() + /// ); + /// let mut dec = FixedDecimal::from_str("-9.99").unwrap(); + /// assert_eq!( + /// "-10.00", + /// dec.floored_to_increment(-2, RoundingIncrement::MultiplesOf2) + /// .to_string() + /// ); + /// ``` + #[cfg(feature = "experimental")] + pub fn floored_to_increment(mut self, position: i16, increment: RoundingIncrement) -> Self { + self.floor_to_increment(position, increment); + self + } + /// Take the half floor of the number at a particular position. /// /// # Examples @@ -2113,6 +2752,19 @@ impl FixedDecimal { /// assert_eq!("1", dec.to_string()); /// ``` pub fn half_floor(&mut self, position: i16) { + #[cfg(feature = "experimental")] + { + self.half_floor_to_increment(position, RoundingIncrement::MultiplesOf1); + } + + #[cfg(not(feature = "experimental"))] + { + self.half_floor_internal(position); + } + } + + #[cfg(not(feature = "experimental"))] + fn half_floor_internal(&mut self, position: i16) { if self.sign == Sign::Negative { self.half_expand(position); return; @@ -2121,6 +2773,46 @@ impl FixedDecimal { self.half_trunc(position); } + /// Take the half floor of the number at a particular position and rounding increment. + /// + ///
+ /// 🚧 This code is experimental; it may change at any time, in breaking or non-breaking ways, + /// including in SemVer minor releases. Use with caution. + /// #3929 + ///
+ /// + /// # Examples + /// + /// ``` + /// use fixed_decimal::{FixedDecimal, RoundingIncrement}; + /// # use std::str::FromStr; + /// + /// let mut dec = FixedDecimal::from_str("-3.5").unwrap(); + /// dec.half_floor_to_increment(0, RoundingIncrement::MultiplesOf2); + /// assert_eq!("-4", dec.to_string()); + /// let mut dec = FixedDecimal::from_str("7.57").unwrap(); + /// dec.half_floor_to_increment(-1, RoundingIncrement::MultiplesOf5); + /// assert_eq!("7.5", dec.to_string()); + /// let mut dec = FixedDecimal::from_str("-5.45").unwrap(); + /// dec.half_floor_to_increment(-2, RoundingIncrement::MultiplesOf25); + /// assert_eq!("-5.50", dec.to_string()); + /// let mut dec = FixedDecimal::from_str("9.99").unwrap(); + /// dec.half_floor_to_increment(-1, RoundingIncrement::MultiplesOf25); + /// assert_eq!("10.0", dec.to_string()); + /// let mut dec = FixedDecimal::from_str("-9.99").unwrap(); + /// dec.half_floor_to_increment(-2, RoundingIncrement::MultiplesOf2); + /// assert_eq!("-10.00", dec.to_string()); + /// ``` + #[cfg(feature = "experimental")] + pub fn half_floor_to_increment(&mut self, position: i16, increment: RoundingIncrement) { + if self.sign == Sign::Negative { + self.half_expand_to_increment(position, increment); + return; + } + + self.half_trunc_to_increment(position, increment); + } + /// Take the half floor of the number at a particular position. /// /// # Examples @@ -2145,31 +2837,99 @@ impl FixedDecimal { self } - /// Take the half even of the number at a particular position. + /// Take the half floor of the number at a particular position and rounding increment. + /// + ///
+ /// 🚧 This code is experimental; it may change at any time, in breaking or non-breaking ways, + /// including in SemVer minor releases. Use with caution. + /// #3929 + ///
/// /// # Examples /// /// ``` - /// use fixed_decimal::FixedDecimal; + /// use fixed_decimal::{FixedDecimal, RoundingIncrement}; /// # use std::str::FromStr; /// - /// let mut dec = FixedDecimal::from_str("-1.5").unwrap(); - /// dec.half_even(0); - /// assert_eq!("-2", dec.to_string()); - /// let mut dec = FixedDecimal::from_str("0.4").unwrap(); - /// dec.half_even(0); - /// assert_eq!("0", dec.to_string()); - /// let mut dec = FixedDecimal::from_str("0.5").unwrap(); - /// dec.half_even(0); - /// assert_eq!("0", dec.to_string()); - /// let mut dec = FixedDecimal::from_str("0.6").unwrap(); - /// dec.half_even(0); - /// assert_eq!("1", dec.to_string()); - /// let mut dec = FixedDecimal::from_str("1.5").unwrap(); - /// dec.half_even(0); - /// assert_eq!("2", dec.to_string()); - /// ``` + /// let mut dec = FixedDecimal::from_str("-3.5").unwrap(); + /// assert_eq!( + /// "-4", + /// dec.half_floored_to_increment(0, RoundingIncrement::MultiplesOf2) + /// .to_string() + /// ); + /// let mut dec = FixedDecimal::from_str("7.57").unwrap(); + /// assert_eq!( + /// "7.5", + /// dec.half_floored_to_increment(-1, RoundingIncrement::MultiplesOf5) + /// .to_string() + /// ); + /// let mut dec = FixedDecimal::from_str("-5.45").unwrap(); + /// assert_eq!( + /// "-5.50", + /// dec.half_floored_to_increment(-2, RoundingIncrement::MultiplesOf25) + /// .to_string() + /// ); + /// let mut dec = FixedDecimal::from_str("9.99").unwrap(); + /// assert_eq!( + /// "10.0", + /// dec.half_floored_to_increment(-1, RoundingIncrement::MultiplesOf25) + /// .to_string() + /// ); + /// let mut dec = FixedDecimal::from_str("-9.99").unwrap(); + /// assert_eq!( + /// "-10.00", + /// dec.half_floored_to_increment(-2, RoundingIncrement::MultiplesOf2) + /// .to_string() + /// ); + /// ``` + #[cfg(feature = "experimental")] + pub fn half_floored_to_increment( + mut self, + position: i16, + increment: RoundingIncrement, + ) -> Self { + self.half_floor_to_increment(position, increment); + self + } + + /// Take the half even of the number at a particular position. + /// + /// # Examples + /// + /// ``` + /// use fixed_decimal::FixedDecimal; + /// # use std::str::FromStr; + /// + /// let mut dec = FixedDecimal::from_str("-1.5").unwrap(); + /// dec.half_even(0); + /// assert_eq!("-2", dec.to_string()); + /// let mut dec = FixedDecimal::from_str("0.4").unwrap(); + /// dec.half_even(0); + /// assert_eq!("0", dec.to_string()); + /// let mut dec = FixedDecimal::from_str("0.5").unwrap(); + /// dec.half_even(0); + /// assert_eq!("0", dec.to_string()); + /// let mut dec = FixedDecimal::from_str("0.6").unwrap(); + /// dec.half_even(0); + /// assert_eq!("1", dec.to_string()); + /// let mut dec = FixedDecimal::from_str("1.5").unwrap(); + /// dec.half_even(0); + /// assert_eq!("2", dec.to_string()); + /// ``` pub fn half_even(&mut self, position: i16) { + #[cfg(feature = "experimental")] + { + self.half_even_to_increment(position, RoundingIncrement::MultiplesOf1); + } + + #[cfg(not(feature = "experimental"))] + { + self.half_even_internal(position); + } + } + + #[cfg(not(feature = "experimental"))] + pub fn half_even_internal(&mut self, position: i16) { let digit_after_position = self.digit_at_next_position(position); let should_expand = match digit_after_position.cmp(&5) { Ordering::Less => false, @@ -2191,6 +2951,86 @@ impl FixedDecimal { } } + /// Take the half even of the number at a particular position and rounding increment. + /// + ///
+ /// 🚧 This code is experimental; it may change at any time, in breaking or non-breaking ways, + /// including in SemVer minor releases. Use with caution. + /// #3929 + ///
+ /// + /// # Examples + /// + /// ``` + /// use fixed_decimal::{FixedDecimal, RoundingIncrement}; + /// # use std::str::FromStr; + /// + /// let mut dec = FixedDecimal::from_str("-3.5").unwrap(); + /// dec.half_even_to_increment(0, RoundingIncrement::MultiplesOf2); + /// assert_eq!("-4", dec.to_string()); + /// let mut dec = FixedDecimal::from_str("7.57").unwrap(); + /// dec.half_even_to_increment(-1, RoundingIncrement::MultiplesOf5); + /// assert_eq!("7.5", dec.to_string()); + /// let mut dec = FixedDecimal::from_str("5.45").unwrap(); + /// dec.half_even_to_increment(-2, RoundingIncrement::MultiplesOf25); + /// assert_eq!("5.50", dec.to_string()); + /// let mut dec = FixedDecimal::from_str("9.99").unwrap(); + /// dec.half_even_to_increment(-1, RoundingIncrement::MultiplesOf25); + /// assert_eq!("10.0", dec.to_string()); + /// let mut dec = FixedDecimal::from_str("9.99").unwrap(); + /// dec.half_even_to_increment(-2, RoundingIncrement::MultiplesOf2); + /// assert_eq!("10.00", dec.to_string()); + /// ``` + #[cfg(feature = "experimental")] + pub fn half_even_to_increment(&mut self, position: i16, increment: RoundingIncrement) { + let should_expand = match self.half_increment_at_magnitude(position, increment) { + Ordering::Greater => true, + Ordering::Less => false, + Ordering::Equal => match increment { + RoundingIncrement::MultiplesOf1 => { + // Expand if odd, truncate if even. + self.digit_at(position) & 0x01 == 1 + } + RoundingIncrement::MultiplesOf2 => { + let current_digit = self.digit_at(position); + let previous_digit = self.digit_at_previous_position(position); + let full = previous_digit * 10 + current_digit; + + // This essentially expands to the "even" increments, + // or the increments that are in the even places on the + // rounding range: [0, 4, 8, 12, 16, 20, ...]. + // Equivalent to `(full / 2) is odd`. + // + // Examples: + // - 37 should truncate, since 37 % 20 = 17, which truncates to 16. + // 37 / 2 = 18, which is even. + // - 83 should expand, since 83 % 20 = 3, which expands to 4. + // 83 / 2 = 41, which is odd. + (full >> 1) & 0x01 == 1 + } + RoundingIncrement::MultiplesOf5 => { + // Expand 7.5 to 10 and truncate 2.5 to 0. + self.digit_at(position) == 7 + } + RoundingIncrement::MultiplesOf25 => { + let current_digit = self.digit_at(position); + let prev_digit = self.digit_at_previous_position(position); + let full_number = prev_digit * 10 + current_digit; + + // Expand `37.5` to 50 and `87.5` to 100. + // Truncate `12.5` to 0 and `62.5` to 50. + full_number == 37 || full_number == 87 + } + }, + }; + + if should_expand { + self.expand_to_increment(position, increment); + } else { + self.trunc_to_increment(position, increment); + } + } + /// Take the half even of the number at a particular position. /// /// # Examples @@ -2215,6 +3055,57 @@ impl FixedDecimal { self } + /// Take the half even of the number at a particular position and rounding increment. + /// + ///
+ /// 🚧 This code is experimental; it may change at any time, in breaking or non-breaking ways, + /// including in SemVer minor releases. Use with caution. + /// #3929 + ///
+ /// + /// # Examples + /// + /// ``` + /// use fixed_decimal::{FixedDecimal, RoundingIncrement}; + /// # use std::str::FromStr; + /// + /// let mut dec = FixedDecimal::from_str("-3.5").unwrap(); + /// assert_eq!( + /// "-4", + /// dec.half_evened_to_increment(0, RoundingIncrement::MultiplesOf2) + /// .to_string() + /// ); + /// let mut dec = FixedDecimal::from_str("7.57").unwrap(); + /// assert_eq!( + /// "7.5", + /// dec.half_evened_to_increment(-1, RoundingIncrement::MultiplesOf5) + /// .to_string() + /// ); + /// let mut dec = FixedDecimal::from_str("5.45").unwrap(); + /// assert_eq!( + /// "5.50", + /// dec.half_evened_to_increment(-2, RoundingIncrement::MultiplesOf25) + /// .to_string() + /// ); + /// let mut dec = FixedDecimal::from_str("9.99").unwrap(); + /// assert_eq!( + /// "10.0", + /// dec.half_evened_to_increment(-1, RoundingIncrement::MultiplesOf25) + /// .to_string() + /// ); + /// let mut dec = FixedDecimal::from_str("9.99").unwrap(); + /// assert_eq!( + /// "10.00", + /// dec.half_evened_to_increment(-2, RoundingIncrement::MultiplesOf2) + /// .to_string() + /// ); + /// ``` + #[cfg(feature = "experimental")] + pub fn half_evened_to_increment(mut self, position: i16, increment: RoundingIncrement) -> Self { + self.half_even_to_increment(position, increment); + self + } + /// Concatenate another `FixedDecimal` into the end of this `FixedDecimal`. /// /// All nonzero digits in `other` must have lower magnitude than nonzero digits in `self`. @@ -3993,267 +4884,715 @@ fn test_rounding() { assert_eq!("2.79", dec.to_string()); } -#[test] -fn test_concatenate() { - #[derive(Debug)] - struct TestCase { - pub input_1: &'static str, - pub input_2: &'static str, - pub expected: Option<&'static str>, - } - let cases = [ - TestCase { - input_1: "123", - input_2: "0.456", - expected: Some("123.456"), - }, - TestCase { - input_1: "0.456", - input_2: "123", - expected: None, - }, - TestCase { - input_1: "123", - input_2: "0.0456", - expected: Some("123.0456"), - }, - TestCase { - input_1: "0.0456", - input_2: "123", - expected: None, - }, - TestCase { - input_1: "100", - input_2: "0.456", - expected: Some("100.456"), - }, - TestCase { - input_1: "0.456", - input_2: "100", - expected: None, - }, - TestCase { - input_1: "100", - input_2: "0.001", - expected: Some("100.001"), - }, - TestCase { - input_1: "0.001", - input_2: "100", - expected: None, - }, - TestCase { - input_1: "123000", - input_2: "456", - expected: Some("123456"), - }, - TestCase { - input_1: "456", - input_2: "123000", - expected: None, - }, - TestCase { - input_1: "5", - input_2: "5", - expected: None, - }, - TestCase { - input_1: "120", - input_2: "25", - expected: None, - }, - TestCase { - input_1: "1.1", - input_2: "0.2", - expected: None, - }, - TestCase { - input_1: "0", - input_2: "222", - expected: Some("222"), - }, - TestCase { - input_1: "222", - input_2: "0", - expected: Some("222"), - }, - TestCase { - input_1: "0", - input_2: "0", - expected: Some("0"), - }, - TestCase { - input_1: "000", - input_2: "0", - expected: Some("000"), - }, - TestCase { - input_1: "0.00", - input_2: "0", - expected: Some("0.00"), - }, - ]; - for cas in &cases { - let fd1 = FixedDecimal::from_str(cas.input_1).unwrap(); - let fd2 = FixedDecimal::from_str(cas.input_2).unwrap(); - match fd1.concatenated_end(fd2) { - Ok(fd) => { - assert_eq!(cas.expected, Some(fd.to_string().as_str()), "{cas:?}"); - } - Err(_) => { - assert!(cas.expected.is_none(), "{cas:?}"); - } - } - } -} +#[test] +fn test_concatenate() { + #[derive(Debug)] + struct TestCase { + pub input_1: &'static str, + pub input_2: &'static str, + pub expected: Option<&'static str>, + } + let cases = [ + TestCase { + input_1: "123", + input_2: "0.456", + expected: Some("123.456"), + }, + TestCase { + input_1: "0.456", + input_2: "123", + expected: None, + }, + TestCase { + input_1: "123", + input_2: "0.0456", + expected: Some("123.0456"), + }, + TestCase { + input_1: "0.0456", + input_2: "123", + expected: None, + }, + TestCase { + input_1: "100", + input_2: "0.456", + expected: Some("100.456"), + }, + TestCase { + input_1: "0.456", + input_2: "100", + expected: None, + }, + TestCase { + input_1: "100", + input_2: "0.001", + expected: Some("100.001"), + }, + TestCase { + input_1: "0.001", + input_2: "100", + expected: None, + }, + TestCase { + input_1: "123000", + input_2: "456", + expected: Some("123456"), + }, + TestCase { + input_1: "456", + input_2: "123000", + expected: None, + }, + TestCase { + input_1: "5", + input_2: "5", + expected: None, + }, + TestCase { + input_1: "120", + input_2: "25", + expected: None, + }, + TestCase { + input_1: "1.1", + input_2: "0.2", + expected: None, + }, + TestCase { + input_1: "0", + input_2: "222", + expected: Some("222"), + }, + TestCase { + input_1: "222", + input_2: "0", + expected: Some("222"), + }, + TestCase { + input_1: "0", + input_2: "0", + expected: Some("0"), + }, + TestCase { + input_1: "000", + input_2: "0", + expected: Some("000"), + }, + TestCase { + input_1: "0.00", + input_2: "0", + expected: Some("0.00"), + }, + ]; + for cas in &cases { + let fd1 = FixedDecimal::from_str(cas.input_1).unwrap(); + let fd2 = FixedDecimal::from_str(cas.input_2).unwrap(); + match fd1.concatenated_end(fd2) { + Ok(fd) => { + assert_eq!(cas.expected, Some(fd.to_string().as_str()), "{cas:?}"); + } + Err(_) => { + assert!(cas.expected.is_none(), "{cas:?}"); + } + } + } +} + +#[test] +#[cfg(feature = "experimental")] +fn test_rounding_increment() { + // Test Truncate Right + let mut dec = FixedDecimal::from(4235970).multiplied_pow10(-3); + assert_eq!("4235.970", dec.to_string()); + + dec.trunc_to_increment(-2, RoundingIncrement::MultiplesOf2); + assert_eq!("4235.96", dec.to_string()); + + dec.trunc_to_increment(-1, RoundingIncrement::MultiplesOf5); + assert_eq!("4235.5", dec.to_string()); + + dec.trunc_to_increment(0, RoundingIncrement::MultiplesOf25); + assert_eq!("4225", dec.to_string()); + + dec.trunc_to_increment(5, RoundingIncrement::MultiplesOf5); + assert_eq!("00000", dec.to_string()); + + dec.trunc_to_increment(2, RoundingIncrement::MultiplesOf2); + assert_eq!("00000", dec.to_string()); + + let mut dec = FixedDecimal::from_str("-99.999").unwrap(); + dec.trunc_to_increment(-2, RoundingIncrement::MultiplesOf25); + assert_eq!("-99.75", dec.to_string()); + + let mut dec = FixedDecimal::from_str("1234.56").unwrap(); + dec.trunc_to_increment(-1, RoundingIncrement::MultiplesOf2); + assert_eq!("1234.4", dec.to_string()); + + let mut dec = FixedDecimal::from_str("0.009").unwrap(); + dec.trunc_to_increment(-1, RoundingIncrement::MultiplesOf5); + assert_eq!("0.0", dec.to_string()); + + let mut dec = FixedDecimal::from_str("0.60").unwrap(); + dec.trunc_to_increment(-2, RoundingIncrement::MultiplesOf25); + assert_eq!("0.50", dec.to_string()); + + let mut dec = FixedDecimal::from_str("0.40").unwrap(); + dec.trunc_to_increment(-2, RoundingIncrement::MultiplesOf25); + assert_eq!("0.25", dec.to_string()); + + let mut dec = FixedDecimal::from_str("0.7000000099").unwrap(); + dec.trunc_to_increment(-3, RoundingIncrement::MultiplesOf2); + assert_eq!("0.700", dec.to_string()); + + let mut dec = FixedDecimal::from_str("5").unwrap(); + dec.trunc_to_increment(0, RoundingIncrement::MultiplesOf25); + assert_eq!("0", dec.to_string()); + + let mut dec = FixedDecimal::from(7).multiplied_pow10(i16::MIN); + dec.trunc_to_increment(i16::MIN, RoundingIncrement::MultiplesOf2); + assert_eq!(FixedDecimal::from(6).multiplied_pow10(i16::MIN), dec); + + let mut dec = FixedDecimal::from(9).multiplied_pow10(i16::MIN); + dec.trunc_to_increment(i16::MIN, RoundingIncrement::MultiplesOf5); + assert_eq!(FixedDecimal::from(5).multiplied_pow10(i16::MIN), dec); + + let mut dec = FixedDecimal::from(70).multiplied_pow10(i16::MIN); + dec.trunc_to_increment(i16::MIN, RoundingIncrement::MultiplesOf25); + assert_eq!(FixedDecimal::from(50).multiplied_pow10(i16::MIN), dec); + + let mut dec = FixedDecimal::from(7).multiplied_pow10(i16::MAX); + dec.trunc_to_increment(i16::MAX, RoundingIncrement::MultiplesOf2); + assert_eq!(FixedDecimal::from(6).multiplied_pow10(i16::MAX), dec); + + let mut dec = FixedDecimal::from(7).multiplied_pow10(i16::MAX); + dec.trunc_to_increment(i16::MAX, RoundingIncrement::MultiplesOf5); + assert_eq!(FixedDecimal::from(5).multiplied_pow10(i16::MAX), dec); + + let mut dec = FixedDecimal::from(7).multiplied_pow10(i16::MAX); + dec.trunc_to_increment(i16::MAX, RoundingIncrement::MultiplesOf25); + assert_eq!(FixedDecimal::from(0).multiplied_pow10(i16::MAX), dec); + + // Test Expand + let mut dec = FixedDecimal::from(4235970).multiplied_pow10(-3); + assert_eq!("4235.970", dec.to_string()); + + dec.expand_to_increment(-2, RoundingIncrement::MultiplesOf2); + assert_eq!("4235.98", dec.to_string()); + + dec.expand_to_increment(-1, RoundingIncrement::MultiplesOf5); + assert_eq!("4236.0", dec.to_string()); + + dec.expand_to_increment(0, RoundingIncrement::MultiplesOf25); + assert_eq!("4250", dec.to_string()); + + dec.expand_to_increment(5, RoundingIncrement::MultiplesOf5); + assert_eq!("500000", dec.to_string()); + + dec.expand_to_increment(2, RoundingIncrement::MultiplesOf2); + assert_eq!("500000", dec.to_string()); + + let mut dec = FixedDecimal::from_str("-99.999").unwrap(); + dec.expand_to_increment(-2, RoundingIncrement::MultiplesOf25); + assert_eq!("-100.00", dec.to_string()); + + let mut dec = FixedDecimal::from_str("1234.56").unwrap(); + dec.expand_to_increment(-1, RoundingIncrement::MultiplesOf2); + assert_eq!("1234.6", dec.to_string()); + + let mut dec = FixedDecimal::from_str("0.009").unwrap(); + dec.expand_to_increment(-1, RoundingIncrement::MultiplesOf5); + assert_eq!("0.5", dec.to_string()); + + let mut dec = FixedDecimal::from_str("0.60").unwrap(); + dec.expand_to_increment(-2, RoundingIncrement::MultiplesOf25); + assert_eq!("0.75", dec.to_string()); + + let mut dec = FixedDecimal::from_str("0.40").unwrap(); + dec.expand_to_increment(-2, RoundingIncrement::MultiplesOf25); + assert_eq!("0.50", dec.to_string()); + + let mut dec = FixedDecimal::from_str("0.7000000099").unwrap(); + dec.expand_to_increment(-3, RoundingIncrement::MultiplesOf2); + assert_eq!("0.702", dec.to_string()); + + let mut dec = FixedDecimal::from_str("5").unwrap(); + dec.expand_to_increment(0, RoundingIncrement::MultiplesOf25); + assert_eq!("25", dec.to_string()); + + let mut dec = FixedDecimal::from(7).multiplied_pow10(i16::MIN); + dec.expand_to_increment(i16::MIN, RoundingIncrement::MultiplesOf2); + assert_eq!(FixedDecimal::from(8).multiplied_pow10(i16::MIN), dec); + + let mut dec = FixedDecimal::from(9).multiplied_pow10(i16::MIN); + dec.expand_to_increment(i16::MIN, RoundingIncrement::MultiplesOf5); + assert_eq!(FixedDecimal::from(10).multiplied_pow10(i16::MIN), dec); + + let mut dec = FixedDecimal::from(70).multiplied_pow10(i16::MIN); + dec.expand_to_increment(i16::MIN, RoundingIncrement::MultiplesOf25); + assert_eq!(FixedDecimal::from(75).multiplied_pow10(i16::MIN), dec); + + let mut dec = FixedDecimal::from(7).multiplied_pow10(i16::MAX); + dec.expand_to_increment(i16::MAX, RoundingIncrement::MultiplesOf2); + assert_eq!(FixedDecimal::from(8).multiplied_pow10(i16::MAX), dec); + + let mut dec = FixedDecimal::from(7).multiplied_pow10(i16::MAX); + dec.expand_to_increment(i16::MAX, RoundingIncrement::MultiplesOf5); + assert_eq!(FixedDecimal::from(0).multiplied_pow10(i16::MAX), dec); + + let mut dec = FixedDecimal::from(7).multiplied_pow10(i16::MAX); + dec.expand_to_increment(i16::MAX, RoundingIncrement::MultiplesOf25); + assert_eq!(FixedDecimal::from(0).multiplied_pow10(i16::MAX), dec); + + // Test Half Truncate Right + let mut dec = FixedDecimal::from(4235970).multiplied_pow10(-3); + assert_eq!("4235.970", dec.to_string()); + + dec.half_trunc_to_increment(-2, RoundingIncrement::MultiplesOf2); + assert_eq!("4235.96", dec.to_string()); + + dec.half_trunc_to_increment(-1, RoundingIncrement::MultiplesOf5); + assert_eq!("4236.0", dec.to_string()); + + dec.half_trunc_to_increment(0, RoundingIncrement::MultiplesOf25); + assert_eq!("4225", dec.to_string()); + + dec.half_trunc_to_increment(5, RoundingIncrement::MultiplesOf5); + assert_eq!("00000", dec.to_string()); + + dec.half_trunc_to_increment(2, RoundingIncrement::MultiplesOf2); + assert_eq!("00000", dec.to_string()); + + let mut dec = FixedDecimal::from_str("-99.999").unwrap(); + dec.half_trunc_to_increment(-2, RoundingIncrement::MultiplesOf25); + assert_eq!("-100.00", dec.to_string()); + + let mut dec = FixedDecimal::from_str("1234.56").unwrap(); + dec.half_trunc_to_increment(-1, RoundingIncrement::MultiplesOf2); + assert_eq!("1234.6", dec.to_string()); + + let mut dec = FixedDecimal::from_str("0.009").unwrap(); + dec.half_trunc_to_increment(-1, RoundingIncrement::MultiplesOf5); + assert_eq!("0.0", dec.to_string()); + + let mut dec = FixedDecimal::from_str("0.60").unwrap(); + dec.half_trunc_to_increment(-2, RoundingIncrement::MultiplesOf25); + assert_eq!("0.50", dec.to_string()); + + let mut dec = FixedDecimal::from_str("0.40").unwrap(); + dec.half_trunc_to_increment(-2, RoundingIncrement::MultiplesOf25); + assert_eq!("0.50", dec.to_string()); + + let mut dec = FixedDecimal::from_str("0.7000000099").unwrap(); + dec.half_trunc_to_increment(-3, RoundingIncrement::MultiplesOf2); + assert_eq!("0.700", dec.to_string()); + + let mut dec = FixedDecimal::from_str("5").unwrap(); + dec.half_trunc_to_increment(0, RoundingIncrement::MultiplesOf25); + assert_eq!("0", dec.to_string()); + + let mut dec = FixedDecimal::from(7).multiplied_pow10(i16::MIN); + dec.half_trunc_to_increment(i16::MIN, RoundingIncrement::MultiplesOf2); + assert_eq!(FixedDecimal::from(6).multiplied_pow10(i16::MIN), dec); + + let mut dec = FixedDecimal::from(9).multiplied_pow10(i16::MIN); + dec.half_trunc_to_increment(i16::MIN, RoundingIncrement::MultiplesOf5); + assert_eq!(FixedDecimal::from(10).multiplied_pow10(i16::MIN), dec); + + let mut dec = FixedDecimal::from(70).multiplied_pow10(i16::MIN); + dec.half_trunc_to_increment(i16::MIN, RoundingIncrement::MultiplesOf25); + assert_eq!(FixedDecimal::from(75).multiplied_pow10(i16::MIN), dec); + + let mut dec = FixedDecimal::from(7).multiplied_pow10(i16::MAX); + dec.half_trunc_to_increment(i16::MAX, RoundingIncrement::MultiplesOf2); + assert_eq!(FixedDecimal::from(6).multiplied_pow10(i16::MAX), dec); + + let mut dec = FixedDecimal::from(7).multiplied_pow10(i16::MAX); + dec.half_trunc_to_increment(i16::MAX, RoundingIncrement::MultiplesOf5); + assert_eq!(FixedDecimal::from(5).multiplied_pow10(i16::MAX), dec); + + let mut dec = FixedDecimal::from(7).multiplied_pow10(i16::MAX); + dec.half_trunc_to_increment(i16::MAX, RoundingIncrement::MultiplesOf25); + assert_eq!(FixedDecimal::from(0).multiplied_pow10(i16::MAX), dec); + + // Test Half Expand + let mut dec = FixedDecimal::from(4235970).multiplied_pow10(-3); + assert_eq!("4235.970", dec.to_string()); + + dec.half_expand_to_increment(-2, RoundingIncrement::MultiplesOf2); + assert_eq!("4235.98", dec.to_string()); + + dec.half_expand_to_increment(-1, RoundingIncrement::MultiplesOf5); + assert_eq!("4236.0", dec.to_string()); + + dec.half_expand_to_increment(0, RoundingIncrement::MultiplesOf25); + assert_eq!("4225", dec.to_string()); + + dec.half_expand_to_increment(5, RoundingIncrement::MultiplesOf5); + assert_eq!("00000", dec.to_string()); + + dec.half_expand_to_increment(2, RoundingIncrement::MultiplesOf2); + assert_eq!("00000", dec.to_string()); + + let mut dec = FixedDecimal::from_str("-99.999").unwrap(); + dec.half_expand_to_increment(-2, RoundingIncrement::MultiplesOf25); + assert_eq!("-100.00", dec.to_string()); + + let mut dec = FixedDecimal::from_str("1234.56").unwrap(); + dec.half_expand_to_increment(-1, RoundingIncrement::MultiplesOf2); + assert_eq!("1234.6", dec.to_string()); + + let mut dec = FixedDecimal::from_str("0.009").unwrap(); + dec.half_expand_to_increment(-1, RoundingIncrement::MultiplesOf5); + assert_eq!("0.0", dec.to_string()); + + let mut dec = FixedDecimal::from_str("0.60").unwrap(); + dec.half_expand_to_increment(-2, RoundingIncrement::MultiplesOf25); + assert_eq!("0.50", dec.to_string()); + + let mut dec = FixedDecimal::from_str("0.40").unwrap(); + dec.half_expand_to_increment(-2, RoundingIncrement::MultiplesOf25); + assert_eq!("0.50", dec.to_string()); + + let mut dec = FixedDecimal::from_str("0.7000000099").unwrap(); + dec.half_expand_to_increment(-3, RoundingIncrement::MultiplesOf2); + assert_eq!("0.700", dec.to_string()); + + let mut dec = FixedDecimal::from_str("5").unwrap(); + dec.half_expand_to_increment(0, RoundingIncrement::MultiplesOf25); + assert_eq!("0", dec.to_string()); + + let mut dec = FixedDecimal::from(7).multiplied_pow10(i16::MIN); + dec.half_expand_to_increment(i16::MIN, RoundingIncrement::MultiplesOf2); + assert_eq!(FixedDecimal::from(8).multiplied_pow10(i16::MIN), dec); + + let mut dec = FixedDecimal::from(9).multiplied_pow10(i16::MIN); + dec.half_expand_to_increment(i16::MIN, RoundingIncrement::MultiplesOf5); + assert_eq!(FixedDecimal::from(10).multiplied_pow10(i16::MIN), dec); + + let mut dec = FixedDecimal::from(70).multiplied_pow10(i16::MIN); + dec.half_expand_to_increment(i16::MIN, RoundingIncrement::MultiplesOf25); + assert_eq!(FixedDecimal::from(75).multiplied_pow10(i16::MIN), dec); + + let mut dec = FixedDecimal::from(7).multiplied_pow10(i16::MAX); + dec.half_expand_to_increment(i16::MAX, RoundingIncrement::MultiplesOf2); + assert_eq!(FixedDecimal::from(8).multiplied_pow10(i16::MAX), dec); + + // Test Ceil + let mut dec = FixedDecimal::from(4235970).multiplied_pow10(-3); + assert_eq!("4235.970", dec.to_string()); + + dec.ceil_to_increment(-2, RoundingIncrement::MultiplesOf2); + assert_eq!("4235.98", dec.to_string()); + + dec.ceil_to_increment(-1, RoundingIncrement::MultiplesOf5); + assert_eq!("4236.0", dec.to_string()); + + dec.ceil_to_increment(0, RoundingIncrement::MultiplesOf25); + assert_eq!("4250", dec.to_string()); + + dec.ceil_to_increment(5, RoundingIncrement::MultiplesOf5); + assert_eq!("500000", dec.to_string()); + + dec.ceil_to_increment(2, RoundingIncrement::MultiplesOf2); + assert_eq!("500000", dec.to_string()); + + let mut dec = FixedDecimal::from_str("-99.999").unwrap(); + dec.ceil_to_increment(-2, RoundingIncrement::MultiplesOf25); + assert_eq!("-99.75", dec.to_string()); + + let mut dec = FixedDecimal::from_str("1234.56").unwrap(); + dec.ceil_to_increment(-1, RoundingIncrement::MultiplesOf2); + assert_eq!("1234.6", dec.to_string()); + + let mut dec = FixedDecimal::from_str("0.009").unwrap(); + dec.ceil_to_increment(-1, RoundingIncrement::MultiplesOf5); + assert_eq!("0.5", dec.to_string()); + + let mut dec = FixedDecimal::from_str("0.60").unwrap(); + dec.ceil_to_increment(-2, RoundingIncrement::MultiplesOf25); + assert_eq!("0.75", dec.to_string()); + + let mut dec = FixedDecimal::from_str("0.40").unwrap(); + dec.ceil_to_increment(-2, RoundingIncrement::MultiplesOf25); + assert_eq!("0.50", dec.to_string()); + + let mut dec = FixedDecimal::from_str("0.7000000099").unwrap(); + dec.ceil_to_increment(-3, RoundingIncrement::MultiplesOf2); + assert_eq!("0.702", dec.to_string()); + + let mut dec = FixedDecimal::from_str("5").unwrap(); + dec.ceil_to_increment(0, RoundingIncrement::MultiplesOf25); + assert_eq!("25", dec.to_string()); + + let mut dec = FixedDecimal::from(7).multiplied_pow10(i16::MIN); + dec.ceil_to_increment(i16::MIN, RoundingIncrement::MultiplesOf2); + assert_eq!(FixedDecimal::from(8).multiplied_pow10(i16::MIN), dec); + + let mut dec = FixedDecimal::from(9).multiplied_pow10(i16::MIN); + dec.ceil_to_increment(i16::MIN, RoundingIncrement::MultiplesOf5); + assert_eq!(FixedDecimal::from(10).multiplied_pow10(i16::MIN), dec); + + let mut dec = FixedDecimal::from(70).multiplied_pow10(i16::MIN); + dec.ceil_to_increment(i16::MIN, RoundingIncrement::MultiplesOf25); + assert_eq!(FixedDecimal::from(75).multiplied_pow10(i16::MIN), dec); + + let mut dec = FixedDecimal::from(7).multiplied_pow10(i16::MAX); + dec.ceil_to_increment(i16::MAX, RoundingIncrement::MultiplesOf2); + assert_eq!(FixedDecimal::from(8).multiplied_pow10(i16::MAX), dec); + + // Test Half Ceil + let mut dec = FixedDecimal::from(4235970).multiplied_pow10(-3); + assert_eq!("4235.970", dec.to_string()); + + dec.half_ceil_to_increment(-2, RoundingIncrement::MultiplesOf2); + assert_eq!("4235.98", dec.to_string()); + + dec.half_ceil_to_increment(-1, RoundingIncrement::MultiplesOf5); + assert_eq!("4236.0", dec.to_string()); + + dec.half_ceil_to_increment(0, RoundingIncrement::MultiplesOf25); + assert_eq!("4225", dec.to_string()); + + dec.half_ceil_to_increment(5, RoundingIncrement::MultiplesOf5); + assert_eq!("00000", dec.to_string()); + + dec.half_ceil_to_increment(2, RoundingIncrement::MultiplesOf2); + assert_eq!("00000", dec.to_string()); + + let mut dec = FixedDecimal::from_str("-99.999").unwrap(); + dec.half_ceil_to_increment(-2, RoundingIncrement::MultiplesOf25); + assert_eq!("-100.00", dec.to_string()); + + let mut dec = FixedDecimal::from_str("1234.56").unwrap(); + dec.half_ceil_to_increment(-1, RoundingIncrement::MultiplesOf2); + assert_eq!("1234.6", dec.to_string()); + + let mut dec = FixedDecimal::from_str("0.009").unwrap(); + dec.half_ceil_to_increment(-1, RoundingIncrement::MultiplesOf5); + assert_eq!("0.0", dec.to_string()); + + let mut dec = FixedDecimal::from_str("0.60").unwrap(); + dec.half_ceil_to_increment(-2, RoundingIncrement::MultiplesOf25); + assert_eq!("0.50", dec.to_string()); + + let mut dec = FixedDecimal::from_str("0.40").unwrap(); + dec.half_ceil_to_increment(-2, RoundingIncrement::MultiplesOf25); + assert_eq!("0.50", dec.to_string()); + + let mut dec = FixedDecimal::from_str("0.7000000099").unwrap(); + dec.half_ceil_to_increment(-3, RoundingIncrement::MultiplesOf2); + assert_eq!("0.700", dec.to_string()); + + let mut dec = FixedDecimal::from_str("5").unwrap(); + dec.half_ceil_to_increment(0, RoundingIncrement::MultiplesOf25); + assert_eq!("0", dec.to_string()); + + let mut dec = FixedDecimal::from(7).multiplied_pow10(i16::MIN); + dec.half_ceil_to_increment(i16::MIN, RoundingIncrement::MultiplesOf2); + assert_eq!(FixedDecimal::from(8).multiplied_pow10(i16::MIN), dec); + + let mut dec = FixedDecimal::from(9).multiplied_pow10(i16::MIN); + dec.half_ceil_to_increment(i16::MIN, RoundingIncrement::MultiplesOf5); + assert_eq!(FixedDecimal::from(10).multiplied_pow10(i16::MIN), dec); + + let mut dec = FixedDecimal::from(70).multiplied_pow10(i16::MIN); + dec.half_ceil_to_increment(i16::MIN, RoundingIncrement::MultiplesOf25); + assert_eq!(FixedDecimal::from(75).multiplied_pow10(i16::MIN), dec); -#[test] -#[cfg(feature = "experimental")] -fn test_rounding_increment() { - // Test Truncate Right + let mut dec = FixedDecimal::from(7).multiplied_pow10(i16::MAX); + dec.half_ceil_to_increment(i16::MAX, RoundingIncrement::MultiplesOf2); + assert_eq!(FixedDecimal::from(8).multiplied_pow10(i16::MAX), dec); + + // Test Floor let mut dec = FixedDecimal::from(4235970).multiplied_pow10(-3); assert_eq!("4235.970", dec.to_string()); - dec.trunc_to_increment(-2, RoundingIncrement::MultiplesOf2); + dec.floor_to_increment(-2, RoundingIncrement::MultiplesOf2); assert_eq!("4235.96", dec.to_string()); - dec.trunc_to_increment(-1, RoundingIncrement::MultiplesOf5); + dec.floor_to_increment(-1, RoundingIncrement::MultiplesOf5); assert_eq!("4235.5", dec.to_string()); - dec.trunc_to_increment(0, RoundingIncrement::MultiplesOf25); + dec.floor_to_increment(0, RoundingIncrement::MultiplesOf25); assert_eq!("4225", dec.to_string()); - dec.trunc_to_increment(5, RoundingIncrement::MultiplesOf5); + dec.floor_to_increment(5, RoundingIncrement::MultiplesOf5); assert_eq!("00000", dec.to_string()); - dec.trunc_to_increment(2, RoundingIncrement::MultiplesOf2); + dec.floor_to_increment(2, RoundingIncrement::MultiplesOf2); assert_eq!("00000", dec.to_string()); let mut dec = FixedDecimal::from_str("-99.999").unwrap(); - dec.trunc_to_increment(-2, RoundingIncrement::MultiplesOf25); - assert_eq!("-99.75", dec.to_string()); + dec.floor_to_increment(-2, RoundingIncrement::MultiplesOf25); + assert_eq!("-100.00", dec.to_string()); let mut dec = FixedDecimal::from_str("1234.56").unwrap(); - dec.trunc_to_increment(-1, RoundingIncrement::MultiplesOf2); + dec.floor_to_increment(-1, RoundingIncrement::MultiplesOf2); assert_eq!("1234.4", dec.to_string()); let mut dec = FixedDecimal::from_str("0.009").unwrap(); - dec.trunc_to_increment(-1, RoundingIncrement::MultiplesOf5); + dec.floor_to_increment(-1, RoundingIncrement::MultiplesOf5); assert_eq!("0.0", dec.to_string()); let mut dec = FixedDecimal::from_str("0.60").unwrap(); - dec.trunc_to_increment(-2, RoundingIncrement::MultiplesOf25); + dec.floor_to_increment(-2, RoundingIncrement::MultiplesOf25); assert_eq!("0.50", dec.to_string()); let mut dec = FixedDecimal::from_str("0.40").unwrap(); - dec.trunc_to_increment(-2, RoundingIncrement::MultiplesOf25); + dec.floor_to_increment(-2, RoundingIncrement::MultiplesOf25); assert_eq!("0.25", dec.to_string()); let mut dec = FixedDecimal::from_str("0.7000000099").unwrap(); - dec.trunc_to_increment(-3, RoundingIncrement::MultiplesOf2); + dec.floor_to_increment(-3, RoundingIncrement::MultiplesOf2); assert_eq!("0.700", dec.to_string()); let mut dec = FixedDecimal::from_str("5").unwrap(); - dec.trunc_to_increment(0, RoundingIncrement::MultiplesOf25); + dec.floor_to_increment(0, RoundingIncrement::MultiplesOf25); assert_eq!("0", dec.to_string()); let mut dec = FixedDecimal::from(7).multiplied_pow10(i16::MIN); - dec.trunc_to_increment(i16::MIN, RoundingIncrement::MultiplesOf2); + dec.floor_to_increment(i16::MIN, RoundingIncrement::MultiplesOf2); assert_eq!(FixedDecimal::from(6).multiplied_pow10(i16::MIN), dec); let mut dec = FixedDecimal::from(9).multiplied_pow10(i16::MIN); - dec.trunc_to_increment(i16::MIN, RoundingIncrement::MultiplesOf5); + dec.floor_to_increment(i16::MIN, RoundingIncrement::MultiplesOf5); assert_eq!(FixedDecimal::from(5).multiplied_pow10(i16::MIN), dec); let mut dec = FixedDecimal::from(70).multiplied_pow10(i16::MIN); - dec.trunc_to_increment(i16::MIN, RoundingIncrement::MultiplesOf25); + dec.floor_to_increment(i16::MIN, RoundingIncrement::MultiplesOf25); assert_eq!(FixedDecimal::from(50).multiplied_pow10(i16::MIN), dec); let mut dec = FixedDecimal::from(7).multiplied_pow10(i16::MAX); - dec.trunc_to_increment(i16::MAX, RoundingIncrement::MultiplesOf2); + dec.floor_to_increment(i16::MAX, RoundingIncrement::MultiplesOf2); assert_eq!(FixedDecimal::from(6).multiplied_pow10(i16::MAX), dec); - let mut dec = FixedDecimal::from(7).multiplied_pow10(i16::MAX); - dec.trunc_to_increment(i16::MAX, RoundingIncrement::MultiplesOf5); - assert_eq!(FixedDecimal::from(5).multiplied_pow10(i16::MAX), dec); + // Test Half Floor + let mut dec = FixedDecimal::from(4235970).multiplied_pow10(-3); + assert_eq!("4235.970", dec.to_string()); + + dec.half_floor_to_increment(-2, RoundingIncrement::MultiplesOf2); + assert_eq!("4235.96", dec.to_string()); + + dec.half_floor_to_increment(-1, RoundingIncrement::MultiplesOf5); + assert_eq!("4236.0", dec.to_string()); + + dec.half_floor_to_increment(0, RoundingIncrement::MultiplesOf25); + assert_eq!("4225", dec.to_string()); + + dec.half_floor_to_increment(5, RoundingIncrement::MultiplesOf5); + assert_eq!("00000", dec.to_string()); + + dec.half_floor_to_increment(2, RoundingIncrement::MultiplesOf2); + assert_eq!("00000", dec.to_string()); + + let mut dec = FixedDecimal::from_str("-99.999").unwrap(); + dec.half_floor_to_increment(-2, RoundingIncrement::MultiplesOf25); + assert_eq!("-100.00", dec.to_string()); + + let mut dec = FixedDecimal::from_str("1234.56").unwrap(); + dec.half_floor_to_increment(-1, RoundingIncrement::MultiplesOf2); + assert_eq!("1234.6", dec.to_string()); + + let mut dec = FixedDecimal::from_str("0.009").unwrap(); + dec.half_floor_to_increment(-1, RoundingIncrement::MultiplesOf5); + assert_eq!("0.0", dec.to_string()); + + let mut dec = FixedDecimal::from_str("0.60").unwrap(); + dec.half_floor_to_increment(-2, RoundingIncrement::MultiplesOf25); + assert_eq!("0.50", dec.to_string()); + + let mut dec = FixedDecimal::from_str("0.40").unwrap(); + dec.half_floor_to_increment(-2, RoundingIncrement::MultiplesOf25); + assert_eq!("0.50", dec.to_string()); + + let mut dec = FixedDecimal::from_str("0.7000000099").unwrap(); + dec.half_floor_to_increment(-3, RoundingIncrement::MultiplesOf2); + assert_eq!("0.700", dec.to_string()); + + let mut dec = FixedDecimal::from_str("5").unwrap(); + dec.half_floor_to_increment(0, RoundingIncrement::MultiplesOf25); + assert_eq!("0", dec.to_string()); + + let mut dec = FixedDecimal::from(7).multiplied_pow10(i16::MIN); + dec.half_floor_to_increment(i16::MIN, RoundingIncrement::MultiplesOf2); + assert_eq!(FixedDecimal::from(6).multiplied_pow10(i16::MIN), dec); + + let mut dec = FixedDecimal::from(9).multiplied_pow10(i16::MIN); + dec.half_floor_to_increment(i16::MIN, RoundingIncrement::MultiplesOf5); + assert_eq!(FixedDecimal::from(10).multiplied_pow10(i16::MIN), dec); + + let mut dec = FixedDecimal::from(70).multiplied_pow10(i16::MIN); + dec.half_floor_to_increment(i16::MIN, RoundingIncrement::MultiplesOf25); + assert_eq!(FixedDecimal::from(75).multiplied_pow10(i16::MIN), dec); let mut dec = FixedDecimal::from(7).multiplied_pow10(i16::MAX); - dec.trunc_to_increment(i16::MAX, RoundingIncrement::MultiplesOf25); - assert_eq!(FixedDecimal::from(0).multiplied_pow10(i16::MAX), dec); + dec.half_floor_to_increment(i16::MAX, RoundingIncrement::MultiplesOf2); + assert_eq!(FixedDecimal::from(6).multiplied_pow10(i16::MAX), dec); - // Test Expand + // Test Half Even let mut dec = FixedDecimal::from(4235970).multiplied_pow10(-3); assert_eq!("4235.970", dec.to_string()); - dec.expand_to_increment(-2, RoundingIncrement::MultiplesOf2); - assert_eq!("4235.98", dec.to_string()); + dec.half_even_to_increment(-2, RoundingIncrement::MultiplesOf2); + assert_eq!("4235.96", dec.to_string()); - dec.expand_to_increment(-1, RoundingIncrement::MultiplesOf5); + dec.half_even_to_increment(-1, RoundingIncrement::MultiplesOf5); assert_eq!("4236.0", dec.to_string()); - dec.expand_to_increment(0, RoundingIncrement::MultiplesOf25); - assert_eq!("4250", dec.to_string()); + dec.half_even_to_increment(0, RoundingIncrement::MultiplesOf25); + assert_eq!("4225", dec.to_string()); - dec.expand_to_increment(5, RoundingIncrement::MultiplesOf5); - assert_eq!("500000", dec.to_string()); + dec.half_even_to_increment(5, RoundingIncrement::MultiplesOf5); + assert_eq!("00000", dec.to_string()); - dec.expand_to_increment(2, RoundingIncrement::MultiplesOf2); - assert_eq!("500000", dec.to_string()); + dec.half_even_to_increment(2, RoundingIncrement::MultiplesOf2); + assert_eq!("00000", dec.to_string()); let mut dec = FixedDecimal::from_str("-99.999").unwrap(); - dec.expand_to_increment(-2, RoundingIncrement::MultiplesOf25); + dec.half_even_to_increment(-2, RoundingIncrement::MultiplesOf25); assert_eq!("-100.00", dec.to_string()); let mut dec = FixedDecimal::from_str("1234.56").unwrap(); - dec.expand_to_increment(-1, RoundingIncrement::MultiplesOf2); + dec.half_even_to_increment(-1, RoundingIncrement::MultiplesOf2); assert_eq!("1234.6", dec.to_string()); let mut dec = FixedDecimal::from_str("0.009").unwrap(); - dec.expand_to_increment(-1, RoundingIncrement::MultiplesOf5); - assert_eq!("0.5", dec.to_string()); + dec.half_even_to_increment(-1, RoundingIncrement::MultiplesOf5); + assert_eq!("0.0", dec.to_string()); let mut dec = FixedDecimal::from_str("0.60").unwrap(); - dec.expand_to_increment(-2, RoundingIncrement::MultiplesOf25); - assert_eq!("0.75", dec.to_string()); + dec.half_even_to_increment(-2, RoundingIncrement::MultiplesOf25); + assert_eq!("0.50", dec.to_string()); let mut dec = FixedDecimal::from_str("0.40").unwrap(); - dec.expand_to_increment(-2, RoundingIncrement::MultiplesOf25); + dec.half_even_to_increment(-2, RoundingIncrement::MultiplesOf25); assert_eq!("0.50", dec.to_string()); let mut dec = FixedDecimal::from_str("0.7000000099").unwrap(); - dec.expand_to_increment(-3, RoundingIncrement::MultiplesOf2); - assert_eq!("0.702", dec.to_string()); + dec.half_even_to_increment(-3, RoundingIncrement::MultiplesOf2); + assert_eq!("0.700", dec.to_string()); let mut dec = FixedDecimal::from_str("5").unwrap(); - dec.expand_to_increment(0, RoundingIncrement::MultiplesOf25); - assert_eq!("25", dec.to_string()); + dec.half_even_to_increment(0, RoundingIncrement::MultiplesOf25); + assert_eq!("0", dec.to_string()); let mut dec = FixedDecimal::from(7).multiplied_pow10(i16::MIN); - dec.expand_to_increment(i16::MIN, RoundingIncrement::MultiplesOf2); + dec.half_even_to_increment(i16::MIN, RoundingIncrement::MultiplesOf2); assert_eq!(FixedDecimal::from(8).multiplied_pow10(i16::MIN), dec); let mut dec = FixedDecimal::from(9).multiplied_pow10(i16::MIN); - dec.expand_to_increment(i16::MIN, RoundingIncrement::MultiplesOf5); + dec.half_even_to_increment(i16::MIN, RoundingIncrement::MultiplesOf5); assert_eq!(FixedDecimal::from(10).multiplied_pow10(i16::MIN), dec); let mut dec = FixedDecimal::from(70).multiplied_pow10(i16::MIN); - dec.expand_to_increment(i16::MIN, RoundingIncrement::MultiplesOf25); + dec.half_even_to_increment(i16::MIN, RoundingIncrement::MultiplesOf25); assert_eq!(FixedDecimal::from(75).multiplied_pow10(i16::MIN), dec); let mut dec = FixedDecimal::from(7).multiplied_pow10(i16::MAX); - dec.expand_to_increment(i16::MAX, RoundingIncrement::MultiplesOf2); + dec.half_even_to_increment(i16::MAX, RoundingIncrement::MultiplesOf2); assert_eq!(FixedDecimal::from(8).multiplied_pow10(i16::MAX), dec); - let mut dec = FixedDecimal::from(7).multiplied_pow10(i16::MAX); - dec.expand_to_increment(i16::MAX, RoundingIncrement::MultiplesOf5); - assert_eq!(FixedDecimal::from(0).multiplied_pow10(i16::MAX), dec); - - let mut dec = FixedDecimal::from(7).multiplied_pow10(i16::MAX); - dec.expand_to_increment(i16::MAX, RoundingIncrement::MultiplesOf25); - assert_eq!(FixedDecimal::from(0).multiplied_pow10(i16::MAX), dec); - // Test specific cases - let mut dec = FixedDecimal::from_str("1.108").unwrap(); dec.expand_to_increment(-2, RoundingIncrement::MultiplesOf2); assert_eq!("1.12", dec.to_string()); @@ -4369,4 +5708,88 @@ fn test_rounding_increment() { let mut dec = FixedDecimal::from_str("0.50").unwrap(); dec.expand_to_increment(-2, RoundingIncrement::MultiplesOf25); assert_eq!("0.50", dec.to_string()); + + let mut dec = FixedDecimal::from_str("1.1025").unwrap(); + dec.half_trunc_to_increment(-3, RoundingIncrement::MultiplesOf5); + assert_eq!("1.100", dec.to_string()); + + let mut dec = FixedDecimal::from_str("1.10125").unwrap(); + dec.half_expand_to_increment(-4, RoundingIncrement::MultiplesOf25); + assert_eq!("1.1025", dec.to_string()); + + let mut dec = FixedDecimal::from_str("-1.25").unwrap(); + dec.half_ceil_to_increment(-1, RoundingIncrement::MultiplesOf5); + assert_eq!("-1.0", dec.to_string()); + + let mut dec = FixedDecimal::from_str("-1.251").unwrap(); + dec.half_ceil_to_increment(-1, RoundingIncrement::MultiplesOf5); + assert_eq!("-1.5", dec.to_string()); + + let mut dec = FixedDecimal::from_str("-1.125").unwrap(); + dec.half_floor_to_increment(-2, RoundingIncrement::MultiplesOf25); + assert_eq!("-1.25", dec.to_string()); + + let mut dec = FixedDecimal::from_str("2.71").unwrap(); + dec.half_even_to_increment(-2, RoundingIncrement::MultiplesOf2); + assert_eq!("2.72", dec.to_string()); + + let mut dec = FixedDecimal::from_str("2.73").unwrap(); + dec.half_even_to_increment(-2, RoundingIncrement::MultiplesOf2); + assert_eq!("2.72", dec.to_string()); + + let mut dec = FixedDecimal::from_str("2.75").unwrap(); + dec.half_even_to_increment(-2, RoundingIncrement::MultiplesOf2); + assert_eq!("2.76", dec.to_string()); + + let mut dec = FixedDecimal::from_str("2.77").unwrap(); + dec.half_even_to_increment(-2, RoundingIncrement::MultiplesOf2); + assert_eq!("2.76", dec.to_string()); + + let mut dec = FixedDecimal::from_str("2.79").unwrap(); + dec.half_even_to_increment(-2, RoundingIncrement::MultiplesOf2); + assert_eq!("2.80", dec.to_string()); + + let mut dec = FixedDecimal::from_str("2.41").unwrap(); + dec.half_even_to_increment(-2, RoundingIncrement::MultiplesOf2); + assert_eq!("2.40", dec.to_string()); + + let mut dec = FixedDecimal::from_str("2.43").unwrap(); + dec.half_even_to_increment(-2, RoundingIncrement::MultiplesOf2); + assert_eq!("2.44", dec.to_string()); + + let mut dec = FixedDecimal::from_str("2.45").unwrap(); + dec.half_even_to_increment(-2, RoundingIncrement::MultiplesOf2); + assert_eq!("2.44", dec.to_string()); + + let mut dec = FixedDecimal::from_str("2.47").unwrap(); + dec.half_even_to_increment(-2, RoundingIncrement::MultiplesOf2); + assert_eq!("2.48", dec.to_string()); + + let mut dec = FixedDecimal::from_str("2.49").unwrap(); + dec.half_even_to_increment(-2, RoundingIncrement::MultiplesOf2); + assert_eq!("2.48", dec.to_string()); + + let mut dec = FixedDecimal::from_str("2.725").unwrap(); + dec.half_even_to_increment(-2, RoundingIncrement::MultiplesOf5); + assert_eq!("2.70", dec.to_string()); + + let mut dec = FixedDecimal::from_str("2.775").unwrap(); + dec.half_even_to_increment(-2, RoundingIncrement::MultiplesOf5); + assert_eq!("2.80", dec.to_string()); + + let mut dec = FixedDecimal::from_str("2.875").unwrap(); + dec.half_even_to_increment(-2, RoundingIncrement::MultiplesOf25); + assert_eq!("3.00", dec.to_string()); + + let mut dec = FixedDecimal::from_str("2.375").unwrap(); + dec.half_even_to_increment(-2, RoundingIncrement::MultiplesOf25); + assert_eq!("2.50", dec.to_string()); + + let mut dec = FixedDecimal::from_str("2.125").unwrap(); + dec.half_even_to_increment(-2, RoundingIncrement::MultiplesOf25); + assert_eq!("2.00", dec.to_string()); + + let mut dec = FixedDecimal::from_str("2.625").unwrap(); + dec.half_even_to_increment(-2, RoundingIncrement::MultiplesOf25); + assert_eq!("2.50", dec.to_string()); } diff --git a/utils/fixed_decimal/tests/rounding.rs b/utils/fixed_decimal/tests/rounding.rs index a02bf5e2aba..6d403bdfa30 100644 --- a/utils/fixed_decimal/tests/rounding.rs +++ b/utils/fixed_decimal/tests/rounding.rs @@ -336,3 +336,90 @@ pub fn extra_rounding_mode_cases() { } } } + +#[test] +#[cfg(feature = "experimental")] +pub fn test_ecma402_table_with_increments() { + use fixed_decimal::RoundingIncrement; + + #[rustfmt::skip] // Don't split everything on its own line. Makes it look a lot nicer. + #[allow(clippy::type_complexity)] + let cases: [(_, _, [(_, fn(&mut FixedDecimal, i16, RoundingIncrement), _, _, _, _, _); 9]); 3] = [ + ("two", RoundingIncrement::MultiplesOf2, [ + ("ceil", FixedDecimal::ceil_to_increment, "-1.4", "0.4", "0.6", "0.6", "1.6"), + ("floor", FixedDecimal::floor_to_increment, "-1.6", "0.4", "0.4", "0.6", "1.4"), + ("expand", FixedDecimal::expand_to_increment, "-1.6", "0.4", "0.6", "0.6", "1.6"), + ("trunc", FixedDecimal::trunc_to_increment, "-1.4", "0.4", "0.4", "0.6", "1.4"), + ("half_ceil", FixedDecimal::half_ceil_to_increment, "-1.4", "0.4", "0.6", "0.6", "1.6"), + ("half_floor", FixedDecimal::half_floor_to_increment, "-1.6", "0.4", "0.4", "0.6", "1.4"), + ("half_expand", FixedDecimal::half_expand_to_increment, "-1.6", "0.4", "0.6", "0.6", "1.6"), + ("half_trunc", FixedDecimal::half_trunc_to_increment, "-1.4", "0.4", "0.4", "0.6", "1.4"), + ("half_even", FixedDecimal::half_even_to_increment, "-1.6", "0.4", "0.4", "0.6", "1.6"), + ]), + ("five", RoundingIncrement::MultiplesOf5, [ + ("ceil", FixedDecimal::ceil_to_increment, "-1.5", "0.5", "0.5", "1.0", "1.5"), + ("floor", FixedDecimal::floor_to_increment, "-1.5", "0.0", "0.5", "0.5", "1.5"), + ("expand", FixedDecimal::expand_to_increment, "-1.5", "0.5", "0.5", "1.0", "1.5"), + ("trunc", FixedDecimal::trunc_to_increment, "-1.5", "0.0", "0.5", "0.5", "1.5"), + ("half_ceil", FixedDecimal::half_ceil_to_increment, "-1.5", "0.5", "0.5", "0.5", "1.5"), + ("half_floor", FixedDecimal::half_floor_to_increment, "-1.5", "0.5", "0.5", "0.5", "1.5"), + ("half_expand", FixedDecimal::half_expand_to_increment, "-1.5", "0.5", "0.5", "0.5", "1.5"), + ("half_trunc", FixedDecimal::half_trunc_to_increment, "-1.5", "0.5", "0.5", "0.5", "1.5"), + ("half_even", FixedDecimal::half_even_to_increment, "-1.5", "0.5", "0.5", "0.5", "1.5"), + ]), + ("twenty-five", RoundingIncrement::MultiplesOf25, [ + ("ceil", FixedDecimal::ceil_to_increment, "-0.0", "2.5", "2.5", "2.5", "2.5"), + ("floor", FixedDecimal::floor_to_increment, "-2.5", "0.0", "0.0", "0.0", "0.0"), + ("expand", FixedDecimal::expand_to_increment, "-2.5", "2.5", "2.5", "2.5", "2.5"), + ("trunc", FixedDecimal::trunc_to_increment, "-0.0", "0.0", "0.0", "0.0", "0.0"), + ("half_ceil", FixedDecimal::half_ceil_to_increment, "-2.5", "0.0", "0.0", "0.0", "2.5"), + ("half_floor", FixedDecimal::half_floor_to_increment, "-2.5", "0.0", "0.0", "0.0", "2.5"), + ("half_expand", FixedDecimal::half_expand_to_increment, "-2.5", "0.0", "0.0", "0.0", "2.5"), + ("half_trunc", FixedDecimal::half_trunc_to_increment, "-2.5", "0.0", "0.0", "0.0", "2.5"), + ("half_even", FixedDecimal::half_even_to_increment, "-2.5", "0.0", "0.0", "0.0", "2.5"), + ]), + ]; + + for (increment_str, increment, cases) in cases { + for (rounding_mode, f, e1, e2, e3, e4, e5) in cases { + let mut fd1: FixedDecimal = "-1.5".parse().unwrap(); + let mut fd2: FixedDecimal = "0.4".parse().unwrap(); + let mut fd3: FixedDecimal = "0.5".parse().unwrap(); + let mut fd4: FixedDecimal = "0.6".parse().unwrap(); + let mut fd5: FixedDecimal = "1.5".parse().unwrap(); + // The original ECMA-402 table tests rounding at magnitude 0. + // However, testing rounding at magnitude -1 gives more + // interesting test cases for increments. + f(&mut fd1, -1, increment); + f(&mut fd2, -1, increment); + f(&mut fd3, -1, increment); + f(&mut fd4, -1, increment); + f(&mut fd5, -1, increment); + assert_eq!( + fd1.write_to_string(), + e1, + "-1.5 failed for {rounding_mode} with increments of {increment_str}" + ); + assert_eq!( + fd2.write_to_string(), + e2, + "0.4 failed for {rounding_mode} with increments of {increment_str}" + ); + assert_eq!( + fd3.write_to_string(), + e3, + "0.5 failed for {rounding_mode} with increments of {increment_str}" + ); + assert_eq!( + fd4.write_to_string(), + e4, + "0.6 failed for {rounding_mode} with increments of {increment_str}" + ); + assert_eq!( + fd5.write_to_string(), + e5, + "1.5 failed for {rounding_mode} with increments of {increment_str}" + ); + } + } +}