325 lines
12 KiB
Rust
325 lines
12 KiB
Rust
// tests/boundary_tests.rs
|
|
use rstest::*;
|
|
use steel_decimal::*;
|
|
use rust_decimal::Decimal;
|
|
use std::str::FromStr;
|
|
|
|
// Test extreme decimal values
|
|
#[rstest]
|
|
#[case("79228162514264337593543950335")] // Max decimal value
|
|
#[case("-79228162514264337593543950335")] // Min decimal value
|
|
#[case("0.0000000000000000000000000001")] // Smallest positive decimal (28 decimal places)
|
|
#[case("-0.0000000000000000000000000001")] // Smallest negative decimal
|
|
#[case("999999999999999999999999999.9999")] // Near maximum with precision
|
|
fn test_extreme_decimal_values(#[case] extreme_value: &str) {
|
|
// These should not panic, but may return errors for unsupported ranges
|
|
let add_result = decimal_add(extreme_value.to_string(), "1".to_string());
|
|
let sub_result = decimal_sub(extreme_value.to_string(), "1".to_string());
|
|
let abs_result = decimal_abs(extreme_value.to_string());
|
|
let conversion_result = to_decimal(extreme_value.to_string());
|
|
|
|
// At minimum, conversion should work for valid decimals
|
|
if let Ok(_) = Decimal::from_str(extreme_value) {
|
|
assert!(conversion_result.is_ok(), "Valid decimal should convert: {}", extreme_value);
|
|
}
|
|
}
|
|
|
|
// Test maximum precision scenarios
|
|
#[rstest]
|
|
#[case(0)]
|
|
#[case(28)] // Maximum precision
|
|
fn test_precision_boundaries(#[case] precision: u32) {
|
|
let test_value = "123.456789012345678901234567890123456789";
|
|
|
|
if precision <= 28 {
|
|
let result = decimal_format(test_value.to_string(), precision);
|
|
assert!(result.is_ok(), "Precision {} should be valid", precision);
|
|
|
|
if let Ok(formatted) = result {
|
|
if precision == 0 {
|
|
assert!(!formatted.contains('.'), "Precision 0 should not have decimal point");
|
|
} else {
|
|
let decimal_places = formatted.split('.').nth(1).map(|s| s.len()).unwrap_or(0);
|
|
assert!(decimal_places <= precision as usize,
|
|
"Result should have at most {} decimal places, got {}",
|
|
precision, decimal_places);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Test precision setting boundaries
|
|
#[rstest]
|
|
#[case(29)] // One over maximum
|
|
#[case(100)] // Way over maximum
|
|
#[case(u32::MAX)] // Maximum u32
|
|
fn test_invalid_precision_values(#[case] invalid_precision: u32) {
|
|
let result = set_precision(invalid_precision);
|
|
assert!(result.contains("Error"), "Should reject precision {}", invalid_precision);
|
|
|
|
// Verify precision wasn't actually set
|
|
let current = get_precision();
|
|
assert_ne!(current, invalid_precision.to_string());
|
|
}
|
|
|
|
// Test very long input strings
|
|
#[rstest]
|
|
fn test_very_long_inputs() {
|
|
// Create very long but valid decimal string
|
|
let long_integer = "1".repeat(1000);
|
|
let long_decimal = format!("{}.{}", "1".repeat(500), "2".repeat(28)); // Respect max precision
|
|
let very_long_decimal = format!("{}.{}", "9".repeat(2000), "1".repeat(28));
|
|
|
|
// These might fail due to decimal limits, but shouldn't panic
|
|
let _ = to_decimal(long_integer);
|
|
let _ = to_decimal(long_decimal);
|
|
let _ = to_decimal(very_long_decimal);
|
|
|
|
// Operations on long strings
|
|
let _ = decimal_add("1".repeat(100), "2".repeat(100));
|
|
let _ = decimal_mul("1".repeat(50), "3".repeat(50));
|
|
}
|
|
|
|
// Test scientific notation boundaries
|
|
#[rstest]
|
|
#[case("1e308")] // Near f64 max
|
|
#[case("1e-324")] // Near f64 min
|
|
#[case("1e1000")] // Way beyond f64
|
|
#[case("1e-1000")] // Way beyond f64
|
|
#[case("1.5e100")]
|
|
#[case("9.999e99")]
|
|
#[case("1.23456789e-50")]
|
|
fn test_extreme_scientific_notation(#[case] sci_notation: &str) {
|
|
let result = to_decimal(sci_notation.to_string());
|
|
|
|
// Should either succeed or fail gracefully
|
|
match result {
|
|
Ok(converted) => {
|
|
// If successful, should be a valid decimal
|
|
assert!(Decimal::from_str(&converted).is_ok(),
|
|
"Converted result should be valid decimal: {}", converted);
|
|
}
|
|
Err(_) => {
|
|
// Failure is acceptable for extreme values
|
|
}
|
|
}
|
|
}
|
|
|
|
// Test edge cases in arithmetic operations
|
|
#[rstest]
|
|
fn test_arithmetic_edge_cases() {
|
|
let max_decimal = "79228162514264337593543950335";
|
|
let min_decimal = "-79228162514264337593543950335";
|
|
let tiny_decimal = "0.0000000000000000000000000001";
|
|
|
|
// Addition near overflow
|
|
let _result = decimal_add(max_decimal.to_string(), "1".to_string());
|
|
// May overflow, but shouldn't panic
|
|
|
|
// Subtraction near underflow
|
|
let _result = decimal_sub(min_decimal.to_string(), "1".to_string());
|
|
// May underflow, but shouldn't panic
|
|
|
|
// Multiplication that could overflow
|
|
let _result = decimal_mul(max_decimal.to_string(), "2".to_string());
|
|
// May overflow, but shouldn't panic
|
|
|
|
// Division by very small number
|
|
let _result = decimal_div("1".to_string(), tiny_decimal.to_string());
|
|
// May be very large, but shouldn't panic
|
|
|
|
// All operations should complete without panicking
|
|
}
|
|
|
|
// Test malformed but potentially parseable inputs
|
|
#[rstest]
|
|
#[case("1.2.3")] // Multiple decimal points
|
|
#[case("1..2")] // Double decimal point
|
|
#[case(".123")] // Leading decimal point
|
|
#[case("123.")] // Trailing decimal point
|
|
#[case("1.23e")] // Incomplete scientific notation
|
|
#[case("1.23e+")] // Incomplete positive exponent
|
|
#[case("1.23e-")] // Incomplete negative exponent
|
|
#[case("e5")] // Missing mantissa
|
|
#[case("1e1e1")] // Multiple exponents
|
|
#[case("++1")] // Multiple signs
|
|
#[case("--1")] // Multiple negative signs
|
|
#[case("1.23.45e6")] // Decimal in mantissa and base
|
|
fn test_malformed_decimal_inputs(#[case] malformed: &str) {
|
|
// These should all fail gracefully, not panic
|
|
let result = to_decimal(malformed.to_string());
|
|
assert!(result.is_err(), "Malformed input should be rejected: {}", malformed);
|
|
|
|
// Test in arithmetic operations too
|
|
let _ = decimal_add(malformed.to_string(), "1".to_string());
|
|
let _ = decimal_sub("1".to_string(), malformed.to_string());
|
|
let _ = decimal_mul(malformed.to_string(), "2".to_string());
|
|
let _ = decimal_abs(malformed.to_string());
|
|
}
|
|
|
|
// Test edge cases in comparison operations
|
|
#[rstest]
|
|
fn test_comparison_edge_cases() {
|
|
// Test comparisons at boundaries
|
|
let results = [
|
|
decimal_eq("0".to_string(), "-0".to_string()),
|
|
decimal_eq("0.0".to_string(), "0.00".to_string()),
|
|
decimal_gt("0.0000000000000000000000000001".to_string(), "0".to_string()),
|
|
decimal_lt("-0.0000000000000000000000000001".to_string(), "0".to_string()),
|
|
];
|
|
|
|
for result in results {
|
|
assert!(result.is_ok(), "Comparison should not fail");
|
|
}
|
|
|
|
// Test with extreme values
|
|
let max_val = "79228162514264337593543950335";
|
|
let min_val = "-79228162514264337593543950335";
|
|
|
|
assert!(decimal_gt(max_val.to_string(), min_val.to_string()).unwrap_or(false));
|
|
assert!(decimal_lt(min_val.to_string(), max_val.to_string()).unwrap_or(false));
|
|
}
|
|
|
|
// Test trigonometric functions at boundaries
|
|
#[rstest]
|
|
#[case("0")] // sin(0) = 0, cos(0) = 1
|
|
#[case("1.5707963267948966")] // π/2
|
|
#[case("3.1415926535897932")] // π
|
|
#[case("6.2831853071795865")] // 2π
|
|
#[case("100")] // Large angle
|
|
#[case("-100")] // Large negative angle
|
|
fn test_trig_function_boundaries(#[case] angle: &str) {
|
|
let sin_result = decimal_sin(angle.to_string());
|
|
let cos_result = decimal_cos(angle.to_string());
|
|
let tan_result = decimal_tan(angle.to_string());
|
|
|
|
// These should all complete without panicking
|
|
// Results may be imprecise for large angles, but should be finite
|
|
if let Ok(sin_val) = sin_result {
|
|
let sin_decimal = Decimal::from_str(&sin_val).unwrap();
|
|
assert!(sin_decimal.abs() <= Decimal::from(2), "Sin should be bounded: {}", sin_val);
|
|
}
|
|
|
|
if let Ok(cos_val) = cos_result {
|
|
let cos_decimal = Decimal::from_str(&cos_val).unwrap();
|
|
assert!(cos_decimal.abs() <= Decimal::from(2), "Cos should be bounded: {}", cos_val);
|
|
}
|
|
}
|
|
|
|
// Test logarithmic functions at boundaries
|
|
#[rstest]
|
|
#[case("1")] // ln(1) = 0
|
|
#[case("2.718281828459045")] // ln(e) = 1
|
|
#[case("0.0000000000000000000000000001")] // Very small positive
|
|
#[case("79228162514264337593543950335")] // Very large
|
|
fn test_log_function_boundaries(#[case] value: &str) {
|
|
let ln_result = decimal_ln(value.to_string());
|
|
let log10_result = decimal_log10(value.to_string());
|
|
|
|
// Should not panic, may return errors for invalid domains
|
|
if Decimal::from_str(value).unwrap() > Decimal::ZERO {
|
|
// Positive values should potentially work
|
|
match ln_result {
|
|
Ok(_) => {}, // Success is fine
|
|
Err(_) => {}, // Failure is also acceptable for extreme values
|
|
}
|
|
} else {
|
|
// Zero or negative should fail
|
|
assert!(ln_result.is_err(), "ln of non-positive should fail");
|
|
}
|
|
}
|
|
|
|
// Test square root at boundaries
|
|
#[rstest]
|
|
#[case("0")] // sqrt(0) = 0
|
|
#[case("1")] // sqrt(1) = 1
|
|
#[case("4")] // sqrt(4) = 2
|
|
#[case("0.0000000000000000000000000001")] // Very small
|
|
#[case("79228162514264337593543950335")] // Very large
|
|
fn test_sqrt_boundaries(#[case] value: &str) {
|
|
let result = decimal_sqrt(value.to_string());
|
|
|
|
if Decimal::from_str(value).unwrap() >= Decimal::ZERO {
|
|
match result {
|
|
Ok(sqrt_val) => {
|
|
let sqrt_decimal = Decimal::from_str(&sqrt_val).unwrap();
|
|
assert!(sqrt_decimal >= Decimal::ZERO, "Square root should be non-negative");
|
|
}
|
|
Err(_) => {
|
|
// May fail for very large values
|
|
}
|
|
}
|
|
} else {
|
|
assert!(result.is_err(), "Square root of negative should fail");
|
|
}
|
|
}
|
|
|
|
// Test power function boundaries
|
|
#[rstest]
|
|
#[case("2", "0")] // 2^0 = 1
|
|
#[case("2", "1")] // 2^1 = 2
|
|
#[case("2", "10")] // 2^10 = 1024
|
|
#[case("0", "5")] // 0^5 = 0
|
|
#[case("1", "1000")] // 1^1000 = 1
|
|
#[case("2", "100")] // Large exponent
|
|
#[case("10", "20")] // Another large case
|
|
fn test_pow_boundaries(#[case] base: &str, #[case] exponent: &str) {
|
|
let result = decimal_pow(base.to_string(), exponent.to_string());
|
|
|
|
// Should not panic, may overflow for large exponents
|
|
match &result {
|
|
Ok(_) => {}, // Success is fine
|
|
Err(_) => {}, // Overflow/underflow acceptable for extreme cases
|
|
}
|
|
|
|
// Special cases that should always work
|
|
if base == "1" {
|
|
// 1^anything = 1
|
|
if let Ok(ref val) = result {
|
|
assert_eq!(val, "1");
|
|
}
|
|
}
|
|
|
|
if exponent == "0" && base != "0" {
|
|
// anything^0 = 1 (except 0^0 which is undefined)
|
|
if let Ok(ref val) = result {
|
|
assert_eq!(val, "1");
|
|
}
|
|
}
|
|
}
|
|
|
|
// Test financial functions with boundary values
|
|
#[rstest]
|
|
fn test_financial_boundaries() {
|
|
// Test percentage calculations
|
|
let percentage_tests = [
|
|
("0", "50"), // 0% of 50
|
|
("100", "0"), // 100% of 0
|
|
("100", "100"), // 100% of 100
|
|
("1000000", "0.001"), // Large amount, tiny percentage
|
|
("0.001", "1000000"), // Tiny amount, huge percentage
|
|
];
|
|
|
|
for (amount, percentage) in percentage_tests {
|
|
let result = decimal_percentage(amount.to_string(), percentage.to_string());
|
|
assert!(result.is_ok(), "Percentage calculation should work: {}% of {}", percentage, amount);
|
|
}
|
|
|
|
// Test compound interest edge cases
|
|
let compound_tests = [
|
|
("1000", "0", "10"), // 0% interest
|
|
("1000", "0.05", "0"), // 0 time periods
|
|
("0", "0.05", "10"), // 0 principal
|
|
("1", "2", "10"), // 200% interest (extreme but valid)
|
|
];
|
|
|
|
for (principal, rate, time) in compound_tests {
|
|
let result = decimal_compound(principal.to_string(), rate.to_string(), time.to_string());
|
|
// Some extreme cases may overflow, but shouldn't panic
|
|
match result {
|
|
Ok(_) => {},
|
|
Err(_) => {}, // Acceptable for extreme cases
|
|
}
|
|
}
|
|
}
|