Files
komp_ac/steel_decimal/tests/boundary_tests.rs
2025-07-07 20:29:51 +02:00

350 lines
13 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 - should return error, not panic
let add_result = decimal_add(max_decimal.to_string(), "1".to_string());
match add_result {
Ok(_) => {}, // Unlikely but possible
Err(e) => assert!(e.contains("overflow"), "Expected overflow error, got: {}", e),
}
// Subtraction near underflow - should return error, not panic
let sub_result = decimal_sub(min_decimal.to_string(), "1".to_string());
match sub_result {
Ok(_) => {}, // Unlikely but possible
Err(e) => assert!(e.contains("overflow"), "Expected overflow error, got: {}", e),
}
// Multiplication that could overflow - should return error, not panic
let mul_result = decimal_mul(max_decimal.to_string(), "2".to_string());
match mul_result {
Ok(_) => {}, // Unlikely but possible
Err(e) => assert!(e.contains("overflow"), "Expected overflow error, got: {}", e),
}
// Division by very small number - might be very large but shouldn't panic
let div_result = decimal_div("1".to_string(), tiny_decimal.to_string());
match div_result {
Ok(_) => {}, // Should work
Err(e) => assert!(e.contains("overflow"), "Expected overflow error if any, got: {}", e),
}
// All operations should complete without panicking - if we get here, that's success!
}
// Test malformed but potentially parseable inputs
#[rstest]
#[case("1.2.3")] // Multiple decimal points
#[case("1..2")] // Double 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());
}
#[rstest]
#[case(".123")] // Leading decimal point - VALID in rust_decimal
#[case("123.")] // Trailing decimal point - VALID in rust_decimal
#[case("0.123")] // Standard format
#[case("123.0")] // Standard format with trailing zero
fn test_edge_case_valid_formats(#[case] valid_input: &str) {
// These should be accepted since rust_decimal accepts them
let result = to_decimal(valid_input.to_string());
assert!(result.is_ok(), "Valid rust_decimal format should be accepted: {}", valid_input);
// Should also work in arithmetic operations
let add_result = decimal_add(valid_input.to_string(), "1".to_string());
assert!(add_result.is_ok(), "Arithmetic should work with valid format: {}", valid_input);
}
// 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
}
}
}