use fermat_core::{Decimal, RoundingMode, MAX_SCALE};
use proptest::prelude::*;
fn arb_mantissa() -> impl Strategy<Value = i128> {
(-1_000_000_000_000_000_000i128..=1_000_000_000_000_000_000i128)
}
fn arb_scale() -> impl Strategy<Value = u8> {
0u8..=MAX_SCALE
}
fn arb_decimal() -> impl Strategy<Value = Decimal> {
(arb_mantissa(), arb_scale()).prop_map(|(m, s)| Decimal::new(m, s).unwrap())
}
fn arb_decimal_same_scale(scale: u8) -> impl Strategy<Value = Decimal> {
arb_mantissa().prop_map(move |m| Decimal::new(m, scale).unwrap())
}
fn arb_nonzero_decimal() -> impl Strategy<Value = Decimal> {
(arb_mantissa(), arb_scale())
.prop_map(|(m, s)| Decimal::new(m, s).unwrap())
.prop_filter("non-zero", |d| !d.is_zero())
}
proptest! {
#[test]
fn add_commutative(
s in arb_scale(),
a in arb_decimal_same_scale(0),
b in arb_decimal_same_scale(0),
) {
let _ = s; if let (Ok(ab), Ok(ba)) = (a.checked_add(b), b.checked_add(a)) {
prop_assert_eq!(ab, ba);
}
}
#[test]
fn add_zero_identity(a in arb_decimal()) {
if let Ok(result) = a.checked_add(Decimal::ZERO) {
prop_assert_eq!(result.mantissa(), a.mantissa());
}
}
#[test]
fn add_self_negation(a in arb_decimal()) {
if let (Ok(neg_a), Ok(result)) = (a.checked_neg(), a.checked_add(a.checked_neg().unwrap())) {
let _ = neg_a;
prop_assert_eq!(result.mantissa(), 0);
}
}
#[test]
fn sub_self_is_zero(a in arb_decimal()) {
if let Ok(result) = a.checked_sub(a) {
prop_assert_eq!(result.mantissa(), 0);
}
}
}
proptest! {
#[test]
fn mul_commutative(
m_a in arb_mantissa(),
m_b in arb_mantissa(),
s in 0u8..=14u8, ) {
let a = Decimal::new(m_a, s).unwrap();
let b = Decimal::new(m_b, s).unwrap();
if let (Ok(ab), Ok(ba)) = (a.checked_mul(b), b.checked_mul(a)) {
prop_assert_eq!(ab, ba);
}
}
#[test]
fn mul_one_identity(a in arb_decimal_same_scale(0)) {
if let Ok(result) = a.checked_mul(Decimal::ONE) {
prop_assert_eq!(result, a);
}
}
#[test]
fn mul_zero(a in arb_decimal_same_scale(0)) {
if let Ok(result) = a.checked_mul(Decimal::ZERO) {
prop_assert_eq!(result.mantissa(), 0);
}
}
}
proptest! {
#[test]
fn mul_div_roundtrip(
m_a in -1_000_000i128..=1_000_000i128,
m_b in 1i128..=1_000_000i128, ) {
let a = Decimal::new(m_a, 0).unwrap();
let b = Decimal::new(m_b, 0).unwrap();
if let Ok(result) = a.checked_mul_div(b, b) {
prop_assert_eq!(result.to_i128_truncated(), a.to_i128_truncated());
}
}
}
proptest! {
#[test]
fn double_neg(a in arb_decimal()) {
if let (Ok(neg_a), ) = (a.checked_neg(), ) {
if let Ok(neg_neg_a) = neg_a.checked_neg() {
prop_assert_eq!(neg_neg_a, a);
}
}
}
#[test]
fn abs_non_negative(a in arb_decimal()) {
if let Ok(abs_a) = a.checked_abs() {
prop_assert!(!abs_a.is_negative());
}
}
#[test]
fn abs_neg_eq_abs(a in arb_decimal()) {
if let (Ok(neg_a), Ok(abs_a)) = (a.checked_neg(), a.checked_abs()) {
if let Ok(abs_neg_a) = neg_a.checked_abs() {
prop_assert_eq!(abs_neg_a, abs_a);
}
}
}
}
proptest! {
#[test]
fn ord_reflexive(a in arb_decimal()) {
prop_assert_eq!(a.cmp(&a), core::cmp::Ordering::Equal);
}
#[test]
fn ord_antisymmetric(a in arb_decimal(), b in arb_decimal()) {
if a < b {
prop_assert!(!(b < a));
}
}
}
proptest! {
#[test]
fn round_produces_correct_scale(
m in arb_mantissa(),
s in 1u8..=MAX_SCALE,
dp in 0u8..=MAX_SCALE,
) {
let a = Decimal::new(m, s).unwrap();
if dp <= s {
if let Ok(rounded) = a.round(dp, RoundingMode::HalfEven) {
prop_assert_eq!(rounded.scale(), dp);
}
}
}
#[test]
fn rescale_up_correct_scale(
m in arb_mantissa(),
s in 0u8..=14u8,
extra in 0u8..=14u8,
) {
let a = Decimal::new(m, s).unwrap();
let new_scale = s + extra;
if new_scale <= MAX_SCALE {
if let Ok(rescaled) = a.rescale_up(new_scale) {
prop_assert_eq!(rescaled.scale(), new_scale);
}
}
}
}