// 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 } } }