diff --git a/packages/std/src/math/signed_decimal.rs b/packages/std/src/math/signed_decimal.rs index 1d658ef9eb..05be984cc8 100644 --- a/packages/std/src/math/signed_decimal.rs +++ b/packages/std/src/math/signed_decimal.rs @@ -523,7 +523,7 @@ impl FromStr for SignedDecimal { type Err = StdError; /// Converts the decimal string to a SignedDecimal - /// Possible inputs: "1.23", "1", "000012", "1.123000000" + /// Possible inputs: "1.23", "1", "000012", "1.123000000", "-1.12300" /// Disallowed: "", ".23" /// /// This never performs any kind of rounding. @@ -607,7 +607,7 @@ impl fmt::Display for SignedDecimal { impl fmt::Debug for SignedDecimal { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "Decimal({self})") + write!(f, "SignedDecimal({self})") } } @@ -913,6 +913,10 @@ mod tests { SignedDecimal::from_atomics(4321i128, 20).unwrap(), SignedDecimal::from_str("0.000000000000000043").unwrap() ); + assert_eq!( + SignedDecimal::from_atomics(-4321i128, 20).unwrap(), + SignedDecimal::from_str("-0.000000000000000043").unwrap() + ); assert_eq!( SignedDecimal::from_atomics(6789i128, 20).unwrap(), SignedDecimal::from_str("0.000000000000000067").unwrap() @@ -953,6 +957,13 @@ mod tests { max ); + // Can be used with min value + let min = SignedDecimal::MIN; + assert_eq!( + SignedDecimal::from_atomics(min.atomics(), min.decimal_places()).unwrap(), + min + ); + // Overflow is only possible with digits < 18 let result = SignedDecimal::from_atomics(i128::MAX, 17); assert_eq!(result.unwrap_err(), SignedDecimalRangeExceeded); @@ -1012,6 +1023,16 @@ mod tests { SignedDecimal::permille(125) ); + // -0.125 + assert_eq!( + SignedDecimal::from_ratio(-1i64, 8i64), + SignedDecimal::permille(-125) + ); + assert_eq!( + SignedDecimal::from_ratio(125i64, -1000i64), + SignedDecimal::permille(-125) + ); + // 1/3 (result floored) assert_eq!( SignedDecimal::from_ratio(1i64, 3i64), @@ -1124,7 +1145,7 @@ mod tests { SignedDecimal::percent(4200) ); - // SignedDecimals + // Positive decimals assert_eq!( SignedDecimal::from_str("1.0").unwrap(), SignedDecimal::percent(100) @@ -1158,6 +1179,19 @@ mod tests { SignedDecimal::from_str("00.04").unwrap(), SignedDecimal::percent(4) ); + // Negative decimals + assert_eq!( + SignedDecimal::from_str("-00.04").unwrap(), + SignedDecimal::percent(-4) + ); + assert_eq!( + SignedDecimal::from_str("-00.40").unwrap(), + SignedDecimal::percent(-40) + ); + assert_eq!( + SignedDecimal::from_str("-04.00").unwrap(), + SignedDecimal::percent(-400) + ); // Can handle DECIMAL_PLACES fractional digits assert_eq!( @@ -1174,6 +1208,11 @@ mod tests { SignedDecimal::from_str("170141183460469231731.687303715884105727").unwrap(), SignedDecimal::MAX ); + // Works for documented min value + assert_eq!( + SignedDecimal::from_str("-170141183460469231731.687303715884105728").unwrap(), + SignedDecimal::MIN + ); assert_eq!( SignedDecimal::from_str("-1").unwrap(), SignedDecimal::negative_one() @@ -1191,6 +1230,11 @@ mod tests { StdError::GenericErr { msg, .. } => assert_eq!(msg, "Error parsing whole"), e => panic!("Unexpected error: {e:?}"), } + + match SignedDecimal::from_str("-").unwrap_err() { + StdError::GenericErr { msg, .. } => assert_eq!(msg, "Error parsing whole"), + e => panic!("Unexpected error: {e:?}"), + } } #[test] @@ -1214,6 +1258,11 @@ mod tests { StdError::GenericErr { msg, .. } => assert_eq!(msg, "Error parsing fractional"), e => panic!("Unexpected error: {e:?}"), } + + match SignedDecimal::from_str("1.-2").unwrap_err() { + StdError::GenericErr { msg, .. } => assert_eq!(msg, "Error parsing fractional"), + e => panic!("Unexpected error: {e:?}"), + } } #[test] @@ -1254,6 +1303,10 @@ mod tests { StdError::GenericErr { msg, .. } => assert_eq!(msg, "Value too big"), e => panic!("Unexpected error: {e:?}"), } + match SignedDecimal::from_str("-170141183460469231732").unwrap_err() { + StdError::GenericErr { msg, .. } => assert_eq!(msg, "Value too big"), + e => panic!("Unexpected error: {e:?}"), + } // SignedDecimal match SignedDecimal::from_str("170141183460469231732.0").unwrap_err() { @@ -1264,6 +1317,10 @@ mod tests { StdError::GenericErr { msg, .. } => assert_eq!(msg, "Value too big"), e => panic!("Unexpected error: {e:?}"), } + match SignedDecimal::from_str("-170141183460469231731.687303715884105729").unwrap_err() { + StdError::GenericErr { msg, .. } => assert_eq!(msg, "Value too big"), + e => panic!("Unexpected error: {e:?}"), + } } #[test] @@ -1273,12 +1330,18 @@ mod tests { let half = SignedDecimal::percent(50); let two = SignedDecimal::percent(200); let max = SignedDecimal::MAX; + let neg_half = SignedDecimal::percent(-50); + let neg_two = SignedDecimal::percent(-200); + let min = SignedDecimal::MIN; assert_eq!(zero.atomics(), Int128::new(0)); assert_eq!(one.atomics(), Int128::new(1000000000000000000)); assert_eq!(half.atomics(), Int128::new(500000000000000000)); assert_eq!(two.atomics(), Int128::new(2000000000000000000)); assert_eq!(max.atomics(), Int128::MAX); + assert_eq!(neg_half.atomics(), Int128::new(-500000000000000000)); + assert_eq!(neg_two.atomics(), Int128::new(-2000000000000000000)); + assert_eq!(min.atomics(), Int128::MIN); } #[test] @@ -1288,12 +1351,14 @@ mod tests { let half = SignedDecimal::percent(50); let two = SignedDecimal::percent(200); let max = SignedDecimal::MAX; + let neg_one = SignedDecimal::negative_one(); assert_eq!(zero.decimal_places(), 18); assert_eq!(one.decimal_places(), 18); assert_eq!(half.decimal_places(), 18); assert_eq!(two.decimal_places(), 18); assert_eq!(max.decimal_places(), 18); + assert_eq!(neg_one.decimal_places(), 18); } #[test] @@ -1304,7 +1369,7 @@ mod tests { assert!(!SignedDecimal::one().is_zero()); assert!(!SignedDecimal::percent(123).is_zero()); - assert!(!SignedDecimal::permille(1234).is_zero()); + assert!(!SignedDecimal::permille(-1234).is_zero()); } #[test] @@ -1315,6 +1380,12 @@ mod tests { // d == 1 assert_eq!(SignedDecimal::one().inv(), Some(SignedDecimal::one())); + // d == -1 + assert_eq!( + SignedDecimal::negative_one().inv(), + Some(SignedDecimal::negative_one()) + ); + // d > 1 exact assert_eq!( SignedDecimal::from_str("2").unwrap().inv(), @@ -1360,6 +1431,17 @@ mod tests { SignedDecimal::from_str("0.0005").unwrap().inv(), Some(SignedDecimal::from_str("2000").unwrap()) ); + + // d < 0 + assert_eq!( + SignedDecimal::from_str("-0.5").unwrap().inv(), + Some(SignedDecimal::from_str("-2").unwrap()) + ); + // d < 0 rounded + assert_eq!( + SignedDecimal::from_str("-3").unwrap().inv(), + Some(SignedDecimal::from_str("-0.333333333333333333").unwrap()) + ); } #[test] @@ -1383,6 +1465,19 @@ mod tests { SignedDecimal::zero() + SignedDecimal::zero(), SignedDecimal::zero() ); + // negative numbers + assert_eq!( + SignedDecimal::percent(-5) + SignedDecimal::percent(-4), + SignedDecimal::percent(-9) + ); + assert_eq!( + SignedDecimal::percent(-5) + SignedDecimal::percent(4), + SignedDecimal::percent(-1) + ); + assert_eq!( + SignedDecimal::percent(5) + SignedDecimal::percent(-4), + SignedDecimal::percent(1) + ); // works for refs let a = SignedDecimal::percent(15); @@ -1440,6 +1535,20 @@ mod tests { SignedDecimal::zero() ); + // negative numbers + assert_eq!( + SignedDecimal::percent(-5) - SignedDecimal::percent(-4), + SignedDecimal::percent(-1) + ); + assert_eq!( + SignedDecimal::percent(-5) - SignedDecimal::percent(4), + SignedDecimal::percent(-9) + ); + assert_eq!( + SignedDecimal::percent(500) - SignedDecimal::percent(-4), + SignedDecimal::percent(504) + ); + // works for refs let a = SignedDecimal::percent(13); let b = SignedDecimal::percent(6); @@ -1502,6 +1611,11 @@ mod tests { SignedDecimal::percent(1000) ); assert_eq!(SignedDecimal::MAX * one, SignedDecimal::MAX); + assert_eq!(SignedDecimal::percent(-1) * one, SignedDecimal::percent(-1)); + assert_eq!( + one * SignedDecimal::percent(-10), + SignedDecimal::percent(-10) + ); // double assert_eq!(two * SignedDecimal::percent(0), SignedDecimal::percent(0)); @@ -1526,6 +1640,11 @@ mod tests { SignedDecimal::percent(1000) * two, SignedDecimal::percent(2000) ); + assert_eq!(SignedDecimal::percent(-1) * two, SignedDecimal::percent(-2)); + assert_eq!( + two * SignedDecimal::new(Int128::MIN / Int128::new(2)), + SignedDecimal::MIN + ); // half assert_eq!(half * SignedDecimal::percent(0), SignedDecimal::percent(0)); @@ -1571,6 +1690,10 @@ mod tests { assert_eq!(dec("1000000000000") * a, dec("123127726548762.582")); assert_eq!(dec("1000000000000000") * a, dec("123127726548762582")); assert_eq!(dec("1000000000000000000") * a, dec("123127726548762582000")); + assert_eq!( + dec("-1000000000000000000") * a, + dec("-123127726548762582000") + ); // Move right let max = SignedDecimal::MAX; @@ -1654,6 +1777,8 @@ mod tests { SignedDecimal::percent(200), ), (SignedDecimal::permille(6), SignedDecimal::permille(13)), + (SignedDecimal::permille(-6), SignedDecimal::permille(0)), + (SignedDecimal::MAX, SignedDecimal::negative_one()), ]; // The regular core::ops::Mul is our source of truth for these tests. @@ -1709,6 +1834,14 @@ mod tests { SignedDecimal::percent(1000) / one, SignedDecimal::percent(1000) ); + assert_eq!( + one / SignedDecimal::percent(-1), + SignedDecimal::percent(-10_000) + ); + assert_eq!( + one / SignedDecimal::percent(-10), + SignedDecimal::percent(-1_000) + ); // double assert_eq!( @@ -1738,6 +1871,14 @@ mod tests { SignedDecimal::percent(1000) / two, SignedDecimal::percent(500) ); + assert_eq!( + two / SignedDecimal::percent(-1), + SignedDecimal::percent(-20_000) + ); + assert_eq!( + SignedDecimal::percent(-10000) / two, + SignedDecimal::percent(-5000) + ); // half assert_eq!( @@ -1791,6 +1932,18 @@ mod tests { assert_eq!(dec("1000000000000") / a, dec("0.000008121647560868")); assert_eq!(dec("1000000000000000") / a, dec("0.008121647560868164")); assert_eq!(dec("1000000000000000000") / a, dec("8.121647560868164773")); + // negative + let a = dec("-123127726548762582"); + assert_eq!(a / dec("1"), dec("-123127726548762582")); + assert_eq!(a / dec("10"), dec("-12312772654876258.2")); + assert_eq!(a / dec("100"), dec("-1231277265487625.82")); + assert_eq!(a / dec("1000"), dec("-123127726548762.582")); + assert_eq!(a / dec("1000000"), dec("-123127726548.762582")); + assert_eq!(a / dec("1000000000"), dec("-123127726.548762582")); + assert_eq!(a / dec("1000000000000"), dec("-123127.726548762582")); + assert_eq!(a / dec("1000000000000000"), dec("-123.127726548762582")); + assert_eq!(a / dec("1000000000000000000"), dec("-0.123127726548762582")); + assert_eq!(dec("1") / a, dec("-0.000000000000000008")); // Move left let a = dec("0.123127726548762582"); @@ -1803,6 +1956,14 @@ mod tests { assert_eq!(a / dec("0.000000000001"), dec("123127726548.762582")); assert_eq!(a / dec("0.000000000000001"), dec("123127726548762.582")); assert_eq!(a / dec("0.000000000000000001"), dec("123127726548762582")); + // negative + let a = dec("-0.123127726548762582"); + assert_eq!(a / dec("1.0"), dec("-0.123127726548762582")); + assert_eq!(a / dec("0.1"), dec("-1.23127726548762582")); + assert_eq!(a / dec("0.01"), dec("-12.3127726548762582")); + assert_eq!(a / dec("0.001"), dec("-123.127726548762582")); + assert_eq!(a / dec("0.000001"), dec("-123127.726548762582")); + assert_eq!(a / dec("0.000000001"), dec("-123127726.548762582")); assert_eq!( SignedDecimal::percent(15) / SignedDecimal::percent(60), @@ -1851,6 +2012,11 @@ mod tests { let right = Int128::new(3); assert_eq!(left / right, SignedDecimal::percent(50)); + // negative + let left = SignedDecimal::percent(-150); // -1.5 + let right = Int128::new(3); + assert_eq!(left / right, SignedDecimal::percent(-50)); + // 0/a let left = SignedDecimal::zero(); let right = Int128::new(300); @@ -1909,6 +2075,18 @@ mod tests { ); } + for exp in 1..10 { + assert_eq!( + SignedDecimal::negative_one().checked_pow(exp).unwrap(), + // alternates between 1 and -1 + if exp % 2 == 0 { + SignedDecimal::one() + } else { + SignedDecimal::negative_one() + } + ) + } + for num in &[ SignedDecimal::percent(50), SignedDecimal::percent(99), @@ -1966,6 +2144,24 @@ mod tests { SignedDecimal::percent(10).checked_pow(18).unwrap(), SignedDecimal(1i128.into()) ); + + let decimals = [ + SignedDecimal::percent(-50), + SignedDecimal::percent(-99), + SignedDecimal::percent(-200), + ]; + let exponents = [1, 2, 3, 4, 5, 8, 10]; + + for d in decimals { + for e in exponents { + // use multiplication as source of truth + let mut mul = Ok(d); + for _ in 1..e { + mul = mul.and_then(|mul| mul.checked_mul(d)); + } + assert_eq!(mul, d.checked_pow(e)); + } + } } #[test] @@ -1986,12 +2182,17 @@ mod tests { assert_eq!(SignedDecimal::zero().to_string(), "0"); assert_eq!(SignedDecimal::one().to_string(), "1"); assert_eq!(SignedDecimal::percent(500).to_string(), "5"); + assert_eq!(SignedDecimal::percent(-500).to_string(), "-5"); // SignedDecimals assert_eq!(SignedDecimal::percent(125).to_string(), "1.25"); assert_eq!(SignedDecimal::percent(42638).to_string(), "426.38"); assert_eq!(SignedDecimal::percent(3).to_string(), "0.03"); assert_eq!(SignedDecimal::permille(987).to_string(), "0.987"); + assert_eq!(SignedDecimal::percent(-125).to_string(), "-1.25"); + assert_eq!(SignedDecimal::percent(-42638).to_string(), "-426.38"); + assert_eq!(SignedDecimal::percent(-3).to_string(), "-0.03"); + assert_eq!(SignedDecimal::permille(-987).to_string(), "-0.987"); assert_eq!( SignedDecimal(Int128::from(1i128)).to_string(), @@ -2061,6 +2262,18 @@ mod tests { SignedDecimal(Int128::from(100000000000000000i128)).to_string(), "0.1" ); + assert_eq!( + SignedDecimal(Int128::from(-1i128)).to_string(), + "-0.000000000000000001" + ); + assert_eq!( + SignedDecimal(Int128::from(-100000000000000i128)).to_string(), + "-0.0001" + ); + assert_eq!( + SignedDecimal(Int128::from(-100000000000000000i128)).to_string(), + "-0.1" + ); } #[test] @@ -2069,14 +2282,15 @@ mod tests { SignedDecimal::zero(), SignedDecimal(Int128::from(2i128)), SignedDecimal(Int128::from(2i128)), + SignedDecimal(Int128::from(-2i128)), ]; assert_eq!( items.iter().sum::(), - SignedDecimal(Int128::from(4i128)) + SignedDecimal(Int128::from(2i128)) ); assert_eq!( items.into_iter().sum::(), - SignedDecimal(Int128::from(4i128)) + SignedDecimal(Int128::from(2i128)) ); let empty: Vec = vec![]; @@ -2094,6 +2308,12 @@ mod tests { to_vec(&SignedDecimal::percent(8765)).unwrap(), br#""87.65""# ); + assert_eq!( + to_vec(&SignedDecimal::percent(-87654)).unwrap(), + br#""-876.54""# + ); + assert_eq!(to_vec(&SignedDecimal::negative_one()).unwrap(), br#""-1""#); + assert_eq!(to_vec(&-SignedDecimal::percent(8)).unwrap(), br#""-0.08""#); } #[test] @@ -2131,6 +2351,24 @@ mod tests { from_slice::(br#""87.65""#).unwrap(), SignedDecimal::percent(8765) ); + + // negative numbers + assert_eq!( + from_slice::(br#""-0""#).unwrap(), + SignedDecimal::zero() + ); + assert_eq!( + from_slice::(br#""-1""#).unwrap(), + SignedDecimal::negative_one() + ); + assert_eq!( + from_slice::(br#""-001""#).unwrap(), + SignedDecimal::negative_one() + ); + assert_eq!( + from_slice::(br#""-0.08""#).unwrap(), + SignedDecimal::percent(-8) + ); } #[test] @@ -2146,6 +2384,12 @@ mod tests { let expected = Decimal::percent(400); assert_eq!(a.abs_diff(b), expected); assert_eq!(b.abs_diff(a), expected); + + let a = SignedDecimal::percent(-200); + let b = SignedDecimal::percent(-240); + let expected = Decimal::percent(40); + assert_eq!(a.abs_diff(b), expected); + assert_eq!(b.abs_diff(a), expected); } #[test] @@ -2163,6 +2407,12 @@ mod tests { SignedDecimal::percent(325) ); + // -20.25 % 5 = -25 + assert_eq!( + SignedDecimal::percent(-2025) % SignedDecimal::percent(500), + SignedDecimal::percent(-25) + ); + let a = SignedDecimal::percent(318); let b = SignedDecimal::percent(317); let expected = SignedDecimal::percent(1); @@ -2182,6 +2432,11 @@ mod tests { let b = SignedDecimal::percent(1270); a %= &b; assert_eq!(a, SignedDecimal::percent(452)); // 42.62 % 12.7 = 4.52 + + let mut a = SignedDecimal::percent(-4262); + let b = SignedDecimal::percent(1270); + a %= &b; + assert_eq!(a, SignedDecimal::percent(-452)); // -42.62 % 12.7 = -4.52 } #[test] @@ -2203,6 +2458,10 @@ mod tests { SignedDecimal::MAX.checked_add(SignedDecimal::percent(1)), Err(OverflowError { .. }) )); + assert!(matches!( + SignedDecimal::MIN.checked_add(SignedDecimal::percent(-1)), + Err(OverflowError { .. }) + )); // checked sub assert_eq!( @@ -2221,6 +2480,10 @@ mod tests { SignedDecimal::MIN.checked_sub(SignedDecimal::percent(1)), Err(OverflowError { .. }) )); + assert!(matches!( + SignedDecimal::MAX.checked_sub(SignedDecimal::percent(-1)), + Err(OverflowError { .. }) + )); // checked div assert_eq!( @@ -2243,6 +2506,18 @@ mod tests { SignedDecimal::MAX.checked_div(SignedDecimal::percent(1)), Err(CheckedFromRatioError::Overflow {}) )); + assert_eq!( + SignedDecimal::percent(-88) + .checked_div(SignedDecimal::percent(20)) + .unwrap(), + SignedDecimal::percent(-440) + ); + assert_eq!( + SignedDecimal::percent(-88) + .checked_div(SignedDecimal::percent(-20)) + .unwrap(), + SignedDecimal::percent(440) + ); // checked rem assert_eq!( @@ -2257,6 +2532,18 @@ mod tests { .unwrap(), SignedDecimal::percent(325) ); + assert_eq!( + SignedDecimal::percent(-1525) + .checked_rem(SignedDecimal::percent(400)) + .unwrap(), + SignedDecimal::percent(-325) + ); + assert_eq!( + SignedDecimal::percent(-1525) + .checked_rem(SignedDecimal::percent(-400)) + .unwrap(), + SignedDecimal::percent(-325) + ); assert!(matches!( SignedDecimal::MAX.checked_rem(SignedDecimal::zero()), Err(DivideByZeroError { .. }) @@ -2269,6 +2556,14 @@ mod tests { SignedDecimal::percent(200).pow(2), SignedDecimal::percent(400) ); + assert_eq!( + SignedDecimal::percent(-200).pow(2), + SignedDecimal::percent(400) + ); + assert_eq!( + SignedDecimal::percent(-200).pow(3), + SignedDecimal::percent(-800) + ); assert_eq!( SignedDecimal::percent(200).pow(10), SignedDecimal::percent(102400) @@ -2287,14 +2582,34 @@ mod tests { SignedDecimal::percent(200).saturating_add(SignedDecimal::percent(200)), SignedDecimal::percent(400) ); + assert_eq!( + SignedDecimal::percent(-200).saturating_add(SignedDecimal::percent(200)), + SignedDecimal::zero() + ); + assert_eq!( + SignedDecimal::percent(-200).saturating_add(SignedDecimal::percent(-200)), + SignedDecimal::percent(-400) + ); assert_eq!( SignedDecimal::MAX.saturating_add(SignedDecimal::percent(200)), SignedDecimal::MAX ); + assert_eq!( + SignedDecimal::MIN.saturating_add(SignedDecimal::percent(-1)), + SignedDecimal::MIN + ); assert_eq!( SignedDecimal::percent(200).saturating_sub(SignedDecimal::percent(100)), SignedDecimal::percent(100) ); + assert_eq!( + SignedDecimal::percent(-200).saturating_sub(SignedDecimal::percent(100)), + SignedDecimal::percent(-300) + ); + assert_eq!( + SignedDecimal::percent(-200).saturating_sub(SignedDecimal::percent(-100)), + SignedDecimal::percent(-100) + ); assert_eq!( SignedDecimal::zero().saturating_sub(SignedDecimal::percent(200)), SignedDecimal::from_str("-2").unwrap() @@ -2303,10 +2618,22 @@ mod tests { SignedDecimal::MIN.saturating_sub(SignedDecimal::percent(200)), SignedDecimal::MIN ); + assert_eq!( + SignedDecimal::MAX.saturating_sub(SignedDecimal::percent(-200)), + SignedDecimal::MAX + ); assert_eq!( SignedDecimal::percent(200).saturating_mul(SignedDecimal::percent(50)), SignedDecimal::percent(100) ); + assert_eq!( + SignedDecimal::percent(-200).saturating_mul(SignedDecimal::percent(50)), + SignedDecimal::percent(-100) + ); + assert_eq!( + SignedDecimal::percent(-200).saturating_mul(SignedDecimal::percent(-50)), + SignedDecimal::percent(100) + ); assert_eq!( SignedDecimal::MAX.saturating_mul(SignedDecimal::percent(200)), SignedDecimal::MAX @@ -2438,12 +2765,12 @@ mod tests { #[test] fn signed_decimal_implements_debug() { let decimal = SignedDecimal::from_str("123.45").unwrap(); - assert_eq!(format!("{decimal:?}"), "Decimal(123.45)"); + assert_eq!(format!("{decimal:?}"), "SignedDecimal(123.45)"); let test_cases = ["5", "5.01", "42", "0", "2"]; for s in test_cases { let decimal = SignedDecimal::from_str(s).unwrap(); - let expected = format!("Decimal({s})"); + let expected = format!("SignedDecimal({s})"); assert_eq!(format!("{decimal:?}"), expected); } }