Skip to content

Commit d581c9d

Browse files
committed
Merge branch 'master' of github.com:paupino/rust-decimal into f/fix-invalid-decimal
2 parents cb0dbe7 + 80e9f08 commit d581c9d

File tree

4 files changed

+90
-51
lines changed

4 files changed

+90
-51
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();

src/str.rs

+51-8
Original file line numberDiff line numberDiff line change
@@ -355,8 +355,17 @@ fn handle_full_128<const POINT: bool, const NEG: bool, const ROUND: bool>(
355355
let next = *next;
356356
if POINT && scale >= 28 {
357357
if ROUND {
358-
// This is the call site
359-
maybe_round(data, next, scale, POINT, NEG)
358+
// If it is an underscore at the rounding position we require slightly different handling to look ahead another digit
359+
if next == b'_' {
360+
if let Some((next, bytes)) = bytes.split_first() {
361+
handle_full_128::<POINT, NEG, ROUND>(data, bytes, scale, *next)
362+
} else {
363+
handle_data::<NEG, true>(data, scale)
364+
}
365+
} else {
366+
// Otherwise, we round as usual
367+
maybe_round(data, next, scale, POINT, NEG)
368+
}
360369
} else {
361370
Err(Error::Underflow)
362371
}
@@ -392,7 +401,7 @@ fn handle_full_128<const POINT: bool, const NEG: bool, const ROUND: bool>(
392401
fn maybe_round(mut data: u128, next_byte: u8, mut scale: u8, point: bool, negative: bool) -> Result<Decimal, Error> {
393402
let digit = match next_byte {
394403
b'0'..=b'9' => u32::from(next_byte - b'0'),
395-
b'_' => 0, // this should be an invalid string?
404+
b'_' => 0, // This is perhaps an error case, but keep this here for compatibility
396405
b'.' if !point => 0,
397406
b => return tail_invalid_digit(b),
398407
};
@@ -708,7 +717,6 @@ mod test {
708717
use crate::Decimal;
709718
use arrayvec::ArrayString;
710719
use core::{fmt::Write, str::FromStr};
711-
use futures::StreamExt;
712720

713721
#[test]
714722
fn display_does_not_overflow_max_capacity() {
@@ -937,7 +945,6 @@ mod test {
937945
);
938946
}
939947

940-
#[ignore]
941948
#[test]
942949
fn from_str_mantissa_overflow_4() {
943950
// Same test as above, however with underscores. This causes issues.
@@ -968,7 +975,14 @@ mod test {
968975
#[test]
969976
fn character_at_rounding_position() {
970977
let tests = [
971-
// 6 is at the rounding position, so we round up
978+
// digit is at the rounding position
979+
(
980+
"1.000_000_000_000_000_000_000_000_000_04",
981+
Ok(Decimal::from_i128_with_scale(
982+
1_000_000_000_000_000_000_000_000_000_0,
983+
28,
984+
)),
985+
),
972986
(
973987
"1.000_000_000_000_000_000_000_000_000_06",
974988
Ok(Decimal::from_i128_with_scale(
@@ -977,6 +991,13 @@ mod test {
977991
)),
978992
),
979993
// Decimal point is at the rounding position
994+
(
995+
"1_000_000_000_000_000_000_000_000_000_0.4",
996+
Ok(Decimal::from_i128_with_scale(
997+
1_000_000_000_000_000_000_000_000_000_0,
998+
0,
999+
)),
1000+
),
9801001
(
9811002
"1_000_000_000_000_000_000_000_000_000_0.6",
9821003
Ok(Decimal::from_i128_with_scale(
@@ -985,17 +1006,39 @@ mod test {
9851006
)),
9861007
),
9871008
// Placeholder is at the rounding position
1009+
(
1010+
"1.000_000_000_000_000_000_000_000_000_0_4",
1011+
Ok(Decimal::from_i128_with_scale(
1012+
1_000_000_000_000_000_000_000_000_000_0,
1013+
28,
1014+
)),
1015+
),
9881016
(
9891017
"1.000_000_000_000_000_000_000_000_000_0_6",
9901018
Ok(Decimal::from_i128_with_scale(
9911019
1_000_000_000_000_000_000_000_000_000_1,
9921020
28,
9931021
)),
9941022
),
1023+
// Multiple placeholders at rounding position
1024+
(
1025+
"1.000_000_000_000_000_000_000_000_000_0__4",
1026+
Ok(Decimal::from_i128_with_scale(
1027+
1_000_000_000_000_000_000_000_000_000_0,
1028+
28,
1029+
)),
1030+
),
1031+
(
1032+
"1.000_000_000_000_000_000_000_000_000_0__6",
1033+
Ok(Decimal::from_i128_with_scale(
1034+
1_000_000_000_000_000_000_000_000_000_1,
1035+
28,
1036+
)),
1037+
),
9951038
];
9961039

997-
for (index, (input, expected)) in tests.iter().enumerate() {
998-
assert_eq!(parse_str_radix_10(input), *expected, "Test Index {}", index);
1040+
for (input, expected) in tests.iter() {
1041+
assert_eq!(parse_str_radix_10(input), *expected, "Test input {}", input);
9991042
}
10001043
}
10011044

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)