use std::convert::TryFrom;
use proptest::prelude::*;
use uninum::Number;
fn stress_config() -> ProptestConfig {
ProptestConfig::with_cases(10000)
}
fn extreme_number_strategy() -> impl Strategy<Value = Number> {
prop_oneof![
Just(Number::from(u64::MAX)),
Just(Number::from(u64::MAX - 1)),
Just(Number::from(u64::MAX / 2)),
(u64::MAX - 1000..u64::MAX).prop_map(Number::from),
Just(Number::from(i64::MAX)),
Just(Number::from(i64::MIN)),
Just(Number::from(i64::MAX - 1)),
Just(Number::from(i64::MIN + 1)),
(i64::MIN..i64::MIN + 1000).prop_map(Number::from),
(i64::MAX - 1000..i64::MAX).prop_map(Number::from),
Just(Number::from(f64::MAX)),
Just(Number::from(f64::MIN)),
Just(Number::from(f64::MIN_POSITIVE)),
Just(Number::from(f64::EPSILON)),
Just(Number::from(-f64::EPSILON)),
(1e100f64..f64::MAX / 2.0).prop_map(Number::from),
(f64::MIN / 2.0..-1e100f64).prop_map(Number::from),
]
}
proptest! {
#![proptest_config(stress_config())]
#[test]
#[ignore]
fn prop_exhaustive_arithmetic_overflow(
a in any::<i64>(),
b in any::<i64>()
) {
let na = Number::from(a);
let nb = Number::from(b);
let result = na.clone() + nb.clone();
match a.checked_add(b) {
Some(expected) => assert_eq!(result, Number::from(expected)),
None => {
#[cfg(feature = "decimal")]
{
assert!(
result.try_get_decimal().is_some() || result.try_get_f64().is_some(),
"Expected promotion to Decimal or F64 on overflow, got {result:?}"
);
}
#[cfg(not(feature = "decimal"))]
{
assert!(result.try_get_f64().is_some());
}
}
}
let result = na.clone() * nb.clone();
match a.checked_mul(b) {
Some(expected) => assert_eq!(result, Number::from(expected)),
None => {
#[cfg(feature = "decimal")]
{
assert!(
result.try_get_decimal().is_some() || result.try_get_f64().is_some(),
"Expected promotion to Decimal or F64 on overflow, got {result:?}"
);
}
#[cfg(not(feature = "decimal"))]
{
assert!(result.try_get_f64().is_some());
}
}
}
}
#[test]
#[ignore]
fn prop_extreme_value_operations(
val in extreme_number_strategy(),
op in 0..4u8
) {
match op {
0 => {
let neg = -val.clone();
let double_neg = -neg;
if !val.is_nan() {
assert_eq!(val, double_neg);
}
}
1 => {
let squared = val.clone() * val.clone();
assert!(!squared.is_negative() || squared.is_nan());
}
2 => {
let small = Number::from(0.0001);
let _result = val / small;
}
3 => {
let zero = Number::from(0);
let _cmp = val.partial_cmp(&zero);
}
_ => unreachable!()
}
}
#[test]
#[ignore]
fn prop_massive_string_parsing(
digits in 100..1000usize,
has_decimal in any::<bool>(),
is_negative in any::<bool>()
) {
let mut s = String::new();
if is_negative {
s.push('-');
}
for i in 0..digits {
s.push_str(&(i % 10).to_string());
if has_decimal && i == digits / 2 {
s.push('.');
}
}
let result = Number::try_from(s.as_str());
prop_assert!(result.is_ok(), "Failed to parse: {}", s);
}
#[test]
#[ignore]
fn prop_extreme_conversions(val in any::<u64>()) {
let n = Number::from(val);
if let Some(v) = n.try_get_u64() {
assert_eq!(v, val);
} else {
panic!("Expected U64 variant");
}
let f = if let Some(v) = n.try_get_u64() {
v as f64
} else {
unreachable!();
};
let back = Number::from(f);
if val < (1u64 << 53) {
if let Some(v) = back.try_get_u64() {
assert_eq!(v, val);
} else if back.try_get_f64().is_some() {
} else {
panic!("Unexpected conversion result");
}
}
}
#[test]
#[ignore]
fn prop_bitwise_exhaustive(
a in any::<u64>(),
b in any::<u64>(),
shift in 0..128u8
) {
let na = Number::from(a);
let nb = Number::from(b);
if let (Some(a_u64), Some(b_u64)) = (na.try_get_u64(), nb.try_get_u64()) {
assert_eq!(a_u64 & b_u64, a & b);
assert_eq!(a_u64 | b_u64, a | b);
assert_eq!(a_u64 ^ b_u64, a ^ b);
assert_eq!(!a_u64, !a);
if shift < 64 {
if let Some(expected) = a.checked_shl(shift as u32) {
assert_eq!(a_u64.checked_shl(shift as u32), Some(expected));
}
if let Some(expected) = a.checked_shr(shift as u32) {
assert_eq!(a_u64.checked_shr(shift as u32), Some(expected));
}
}
}
}
#[test]
#[ignore]
fn prop_decimal_precision_stress(
integer in -999_999_999i64..=999_999_999,
decimal in 0u64..1_000_000_000,
) {
#[cfg(not(feature = "decimal"))]
{
let _ = (integer, decimal);
}
#[cfg(feature = "decimal")]
{
let float_str = format!("{integer}.{decimal:09}");
let parsed = Number::try_from(float_str.as_str()).unwrap();
assert!(parsed.try_get_decimal().is_some());
let back_str = parsed.to_string();
let reparsed = Number::try_from(back_str.as_str()).unwrap();
assert_eq!(parsed, reparsed);
}
}
#[test]
#[ignore]
fn prop_mixed_type_arithmetic_exhaustive(
a_type in 0..5u8,
b_type in 0..5u8,
val_a in 0..1000i64,
val_b in 0..1000i64,
) {
let a = match a_type {
0 => Number::from(val_a as u64),
1 => Number::from(val_a),
2 => Number::from(val_a as f64),
3 => Number::from(val_a as u32),
4 => Number::from(val_a as i32),
_ => unreachable!()
};
let b = match b_type {
0 => Number::from(val_b as u64),
1 => Number::from(val_b),
2 => Number::from(val_b as f64),
3 => Number::from(val_b as u32),
4 => Number::from(val_b as i32),
_ => unreachable!()
};
let _add = &a + &b;
let _sub = &a - &b;
let _mul = &a * &b;
if !b.is_zero() {
let _div = &a / &b;
let _rem = &a % &b;
}
let _lt = a < b;
let _eq = a == b;
let _gt = a > b;
}
#[test]
#[ignore]
fn prop_extreme_pow_operations(
base in -100f64..100f64,
exp in -100f64..100f64,
) {
let n_base = Number::from(base);
let n_exp = Number::from(exp);
let result = n_base.pow(&n_exp);
if let Some(f) = result.try_get_f64() {
assert!(f.is_finite() || f.is_infinite() || f.is_nan());
}
}
}