Skip to content

Commit 80e9f08

Browse files
schungxpaupino
andauthored
Reimplement pow function for integer exponent. (#638)
* Reimplement checked_powu. * Add pow tests. * Add short-cuts to powu. * Fix x.powu(0) * Refine powu implementation. * Exclude a failing test that was caused by the deprecated legacy-ops feature --------- Co-authored-by: Paul Mason <[email protected]>
1 parent 10ee2ee commit 80e9f08

File tree

3 files changed

+39
-43
lines changed

3 files changed

+39
-43
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -22,3 +22,4 @@ Cargo.lock
2222
artifacts
2323
corpus
2424
target
25+
.vscode/settings.json

src/maths.rs

+29-22
Original file line numberDiff line numberDiff line change
@@ -227,33 +227,40 @@ impl MathematicalOps for Decimal {
227227
}
228228

229229
fn checked_powu(&self, exp: u64) -> Option<Decimal> {
230+
if exp == 0 {
231+
return Some(Decimal::ONE);
232+
}
233+
if self.is_zero() {
234+
return Some(Decimal::ZERO);
235+
}
236+
if self.is_one() {
237+
return Some(Decimal::ONE);
238+
}
239+
230240
match exp {
231-
0 => Some(Decimal::ONE),
241+
0 => unreachable!(),
232242
1 => Some(*self),
233243
2 => self.checked_mul(*self),
244+
// Do the exponentiation by multiplying squares:
245+
// y = Sum (for each 1 bit in binary representation) of (2 ^ bit)
246+
// x ^ y = Sum (for each 1 bit in y) of (x ^ (2 ^ bit))
247+
// See: https://en.wikipedia.org/wiki/Exponentiation_by_squaring
234248
_ => {
235-
// Get the squared value
236-
let squared = match self.checked_mul(*self) {
237-
Some(s) => s,
238-
None => return None,
239-
};
240-
// Square self once and make an infinite sized iterator of the square.
241-
let iter = core::iter::repeat(squared);
242-
243-
// We then take half of the exponent to create a finite iterator and then multiply those together.
244249
let mut product = Decimal::ONE;
245-
for x in iter.take((exp >> 1) as usize) {
246-
match product.checked_mul(x) {
247-
Some(r) => product = r,
248-
None => return None,
249-
};
250-
}
251-
252-
// If the exponent is odd we still need to multiply once more
253-
if exp & 0x1 > 0 {
254-
match self.checked_mul(product) {
255-
Some(p) => product = p,
256-
None => return None,
250+
let mut mask = exp;
251+
let mut power = *self;
252+
253+
// Run through just enough 1 bits
254+
for n in 0..(64 - exp.leading_zeros()) {
255+
if n > 0 {
256+
power = power.checked_mul(power)?;
257+
mask >>= 1;
258+
}
259+
if mask & 0x01 > 0 {
260+
match product.checked_mul(power) {
261+
Some(r) => product = r,
262+
None => return None,
263+
};
257264
}
258265
}
259266
product.normalize_assign();

tests/decimal_tests.rs

+9-21
Original file line numberDiff line numberDiff line change
@@ -3693,6 +3693,8 @@ mod maths {
36933693
("0.1", 0_u64, "1"),
36943694
("342.4", 1_u64, "342.4"),
36953695
("2.0", 16_u64, "65536"),
3696+
("0.99999999999999", 1477289400_u64, "0.9999852272151186611602884841"),
3697+
("0.99999999999999", 0x8000_8000_0000_0000, "0"),
36963698
];
36973699
for &(x, y, expected) in test_cases {
36983700
let x = Decimal::from_str(x).unwrap();
@@ -3829,6 +3831,7 @@ mod maths {
38293831
"0.1234567890123456789012345678",
38303832
either!("0.0003533642875741443321850682", "0.0003305188683169079961720764"),
38313833
),
3834+
("0.99999999999999", "1477289400", "0.9999852272151186611602884841"),
38323835
];
38333836
for &(x, y, expected) in test_cases {
38343837
let x = Decimal::from_str(x).unwrap();
@@ -3965,43 +3968,28 @@ mod maths {
39653968
}
39663969

39673970
#[test]
3971+
#[cfg(not(feature = "legacy-ops"))]
39683972
fn test_norm_cdf() {
39693973
let test_cases = &[
39703974
(
39713975
Decimal::from_str("-0.4").unwrap(),
3972-
either!(
3973-
Decimal::from_str("0.3445781286821245037094401704").unwrap(),
3974-
Decimal::from_str("0.3445781286821245037094401728").unwrap()
3975-
),
3976+
Decimal::from_str("0.3445781286821245037094401704").unwrap(),
39763977
),
39773978
(
39783979
Decimal::from_str("-0.1").unwrap(),
3979-
either!(
3980-
Decimal::from_str("0.4601722899186706579921922696").unwrap(),
3981-
Decimal::from_str("0.4601722899186706579921922711").unwrap()
3982-
),
3980+
Decimal::from_str("0.4601722899186706579921922696").unwrap(),
39833981
),
39843982
(
39853983
Decimal::from_str("0.1").unwrap(),
3986-
Decimal::from_str(either!(
3987-
"0.5398277100813293420078077304",
3988-
"0.5398277100813293420078077290"
3989-
))
3990-
.unwrap(),
3984+
Decimal::from_str("0.5398277100813293420078077304").unwrap(),
39913985
),
39923986
(
39933987
Decimal::from_str("0.4").unwrap(),
3994-
either!(
3995-
Decimal::from_str("0.6554218713178754962905598296").unwrap(),
3996-
Decimal::from_str("0.6554218713178754962905598272").unwrap()
3997-
),
3988+
Decimal::from_str("0.6554218713178754962905598296").unwrap(),
39983989
),
39993990
(
40003991
Decimal::from_str("2.0").unwrap(),
4001-
either!(
4002-
Decimal::from_str("0.9772497381095865280953380673").unwrap(),
4003-
Decimal::from_str("0.9772497381095865280953380672").unwrap()
4004-
),
3992+
Decimal::from_str("0.9772497381095865280953380673").unwrap(),
40053993
),
40063994
];
40073995
for case in test_cases {

0 commit comments

Comments
 (0)