use g_math::canonical::{gmath, evaluate, LazyExpr};
use g_math::fixed_point::prime_table::{is_prime, nth_prime, prime_count_up_to, PRIME_COUNT, MAX_PRIME};
fn gmath_safe(input: &'static str) -> LazyExpr {
if input.starts_with('-') {
let positive: &'static str = unsafe {
std::str::from_utf8_unchecked(std::slice::from_raw_parts(
input.as_ptr().add(1),
input.len() - 1,
))
};
-gmath(positive)
} else {
gmath(input)
}
}
#[test]
fn value_chaining_compound_interest_10_years() {
let mut balance = evaluate(&gmath("1000.00")).unwrap();
for _year in 1..=10 {
let expr = LazyExpr::from(balance) * gmath("1.05");
balance = evaluate(&expr).expect("compound interest step should not fail");
}
let s = format!("{}", balance);
assert!(
s.starts_with("1628.89"),
"1000*1.05^10 should match 1628.89..., got '{}'",
s
);
}
#[test]
fn value_chaining_compound_interest_30_years() {
let mut balance = evaluate(&gmath("1000.00")).unwrap();
let mut last_correct_year = 0;
let expected_prefixes: &[&str] = &[
"", "1050.0", "1102.5", "1157.625", "1215.50625", "1276.281562", "1340.09564062", "1407.10042265", "1477.45544378", "1551.32821597", "1628.89462677", "1710.33935811", "1795.85632602", "1885.64914232", "1979.93159943", "2078.92817941", "2182.87458838", "2292.01831780", "2406.61923369", "2526.95019537", "2653.29770514", ];
for year in 1..=30 {
let expr = LazyExpr::from(balance) * gmath("1.05");
balance = evaluate(&expr).expect("compound interest step should not fail");
let s = format!("{}", balance);
if year < expected_prefixes.len() {
if s.starts_with(expected_prefixes[year]) {
last_correct_year = year;
} else {
println!(
" year {}: MISMATCH (expected '{}...', got '{}')",
year, expected_prefixes[year], &s[..s.len().min(50)]
);
}
}
}
println!(" last correct year: {} of 30", last_correct_year);
#[cfg(table_format = "q16_16")]
let min_years = 3; #[cfg(not(table_format = "q16_16"))]
let min_years = 20;
assert!(
last_correct_year >= min_years,
"should maintain precision for {} checked years, lost at year {}",
min_years, last_correct_year + 1
);
}
#[cfg(not(any(table_format = "q16_16", table_format = "q256_256")))]
#[test]
fn value_chaining_compound_interest_100_years() {
let mut balance = evaluate(&gmath("1000.00")).unwrap();
for _year in 1..=100 {
let expr = LazyExpr::from(balance) * gmath("1.05");
balance = evaluate(&expr).expect("compound interest step should not fail");
}
let s = format!("{}", balance);
println!(" 1000 * 1.05^100 = {}", &s[..s.len().min(60)]);
assert!(
s.starts_with("131501.25"),
"1000*1.05^100 should match mpmath 131501.25..., got '{}'",
&s[..s.len().min(60)]
);
}
#[test]
#[cfg(not(any(table_format = "q16_16", table_format = "q32_32", table_format = "q256_256")))]
fn value_chaining_compound_interest_500_years() {
let mut balance = evaluate(&gmath("1000.00")).unwrap();
for _year in 1..=500 {
let expr = LazyExpr::from(balance) * gmath("1.05");
balance = evaluate(&expr).expect("compound interest step should not fail");
}
let s = format!("{}", balance);
println!(" 1000 * 1.05^500 = {}", &s[..s.len().min(60)]);
assert!(
s.starts_with("39323261827217.8"),
"1000*1.05^500 should match mpmath 39323261827217.8..., got '{}'",
&s[..s.len().min(60)]
);
}
#[test]
fn value_chaining_preserves_computation() {
let a = evaluate(&gmath("2.5")).unwrap();
let b = evaluate(&(LazyExpr::from(a) + gmath("3.5"))).unwrap();
let s = format!("{}", b);
assert!(
s.starts_with("6.0") || s.starts_with("6/1"),
"2.5 + 3.5 should be 6.0, got '{}'",
s
);
}
#[test]
fn value_chaining_transcendental_result() {
let e = evaluate(&gmath("1").exp()).unwrap();
let e_squared = evaluate(&(LazyExpr::from(e.clone()) * LazyExpr::from(e))).unwrap();
let s = format!("{}", e_squared);
assert!(
s.starts_with("7.389"),
"e * e should be ~7.389, got '{}'",
s
);
}
#[test]
fn display_binary_is_decimal() {
let val = evaluate(&gmath("42")).unwrap();
let s = format!("{}", val);
assert!(
s.starts_with("42."),
"integer 42 should display as '42.xxx', got '{}'",
s
);
assert!(
!s.contains("Binary"),
"Display should not contain 'Binary', got '{}'",
s
);
}
#[test]
fn display_precision_2() {
let val = evaluate(&gmath("3.14159")).unwrap();
let s = format!("{:.2}", val);
assert!(
s.starts_with("3.14") || s.starts_with("3.1"),
"{{:.2}} should give ~2 decimal places, got '{}'",
s
);
}
#[test]
fn display_precision_6() {
let val = evaluate(&gmath("3.14159")).unwrap();
let s = format!("{:.6}", val);
assert!(
s.starts_with("3.1415"),
"{{:.6}} should give decimal places starting with 3.1415, got '{}'",
s
);
}
#[test]
fn display_decimal_domain() {
let val = evaluate(&gmath("0.25")).unwrap();
let s = format!("{}", val);
assert!(
s.contains("0.25") || s.contains("25") || s.contains("1/4"),
"0.25 should display meaningfully, got '{}'",
s
);
}
#[test]
fn display_symbolic_domain() {
let val = evaluate(&gmath("1/3")).unwrap();
let s = format!("{}", val);
assert!(
s.contains("1/3") || s.contains("0.333"),
"1/3 should display as fraction or decimal, got '{}'",
s
);
}
#[test]
fn display_zero() {
let val = evaluate(&gmath("0")).unwrap();
let s = format!("{}", val);
assert!(
s.starts_with("0"),
"0 should display starting with '0', got '{}'",
s
);
}
#[test]
fn display_negative() {
let val = evaluate(&gmath_safe("-5")).unwrap();
let s = format!("{}", val);
assert!(
s.starts_with("-5.") || s.starts_with("-5/") || s == "-5",
"-5 should display with minus sign, got '{}'",
s
);
}
#[test]
fn identity_pythagorean() {
let test_values: &[&str] = &[
"0.1", "0.5", "1.0", "1.5", "2.0", "2.5", "3.0", "0.01", "0.99", "1.23",
];
for &x in test_values {
let sin_val = evaluate(&gmath(x).sin()).unwrap();
let cos_val = evaluate(&gmath(x).cos()).unwrap();
let sin_sq = evaluate(&(LazyExpr::from(sin_val.clone()) * LazyExpr::from(sin_val))).unwrap();
let cos_sq = evaluate(&(LazyExpr::from(cos_val.clone()) * LazyExpr::from(cos_val))).unwrap();
let sum = evaluate(&(LazyExpr::from(sin_sq) + LazyExpr::from(cos_sq))).unwrap();
let s = format!("{}", sum);
assert!(
s.starts_with("1.000") || s.starts_with("0.999"),
"sin^2({}) + cos^2({}) should be ~1.0, got '{}'",
x, x, s
);
}
}
#[test]
fn identity_exp_ln_roundtrip() {
let test_values: &[&str] = &[
"0.1", "0.5", "1.0", "2.0", "3.0", "5.0", "10.0", "0.01", "7.5", "42.0",
];
for &x in test_values {
let result = evaluate(&gmath(x).ln().exp());
assert!(result.is_ok(), "exp(ln({})) should succeed, got {:?}", x, result);
let s = format!("{}", result.unwrap());
let expected_prefix = x.split('.').next().unwrap();
assert!(
s.starts_with(expected_prefix),
"exp(ln({})) should recover ~{}, got '{}'",
x, x, s
);
}
}
#[test]
fn identity_ln_exp_roundtrip() {
let test_values: &[&str] = &[
"0.1", "0.5", "1.0", "2.0", "3.0", "5.0", "10.0", "0.01", "0.99", "7.5",
];
for &x in test_values {
let result = evaluate(&gmath(x).exp().ln());
assert!(result.is_ok(), "ln(exp({})) should succeed, got {:?}", x, result);
let s = format!("{}", result.unwrap());
let expected_prefix = x.split('.').next().unwrap();
assert!(
s.starts_with(expected_prefix),
"ln(exp({})) should recover ~{}, got '{}'",
x, x, s
);
}
}
#[test]
fn identity_sinh_definition() {
let test_values: &[&str] = &["0.5", "1.0", "2.0", "0.1", "3.0"];
for &x in test_values {
let sinh_direct = evaluate(&gmath(x).sinh()).unwrap();
let exp_pos = evaluate(&gmath(x).exp()).unwrap();
let exp_neg = evaluate(&gmath_safe(&leak_neg(x)).exp()).unwrap();
let diff = evaluate(&(LazyExpr::from(exp_pos) - LazyExpr::from(exp_neg))).unwrap();
let half = evaluate(&(LazyExpr::from(diff) / gmath("2"))).unwrap();
let s_direct = format!("{:.10}", sinh_direct);
let s_manual = format!("{:.10}", half);
let prefix_len = 8.min(s_direct.len()).min(s_manual.len());
assert_eq!(
&s_direct[..prefix_len],
&s_manual[..prefix_len],
"sinh({}) direct vs manual: '{}' vs '{}'",
x, s_direct, s_manual
);
}
}
fn leak_neg(s: &str) -> &'static str {
let neg = format!("-{}", s);
Box::leak(neg.into_boxed_str())
}
#[test]
fn deep_nesting_exp_ln_double() {
let test_values: &[&str] = &["1.0", "2.0", "5.0", "0.5", "10.0"];
for &x in test_values {
let result = evaluate(&gmath(x).ln().exp().ln().exp());
assert!(
result.is_ok(),
"exp(ln(exp(ln({})))) should succeed, got {:?}",
x, result
);
let s = format!("{}", result.unwrap());
let expected_prefix = x.split('.').next().unwrap();
assert!(
s.starts_with(expected_prefix),
"exp(ln(exp(ln({})))) should recover ~{}, got '{}'",
x, x, s
);
}
}
#[test]
fn deep_nesting_sin_asin() {
let test_values: &[&str] = &["0.1", "0.5", "0.9", "0.01", "0.99"];
for &x in test_values {
let result = evaluate(&gmath(x).asin().sin());
assert!(
result.is_ok(),
"sin(asin({})) should succeed, got {:?}",
x, result
);
let s = format!("{}", result.unwrap());
assert!(
s.starts_with("0."),
"sin(asin({})) should be ~{}, got '{}'",
x, x, s
);
}
}
#[test]
fn deep_nesting_sqrt_squared() {
let test_values: &[(&str, &[&str])] = &[
("2", &["2.000", "1.999"]),
("3", &["3.000", "2.999"]),
("5", &["5.000", "4.999"]),
("7", &["7.000", "6.999"]),
("10", &["10.00", "9.999"]),
("100", &["100.0", "99.99"]),
("0.5", &["0.500", "0.499"]),
("0.25",&["0.250", "0.249"]),
("42", &["42.00", "41.99"]),
];
for &(x, prefixes) in test_values {
let sqrt_val = evaluate(&gmath(x).sqrt()).unwrap();
let squared = evaluate(&(
LazyExpr::from(sqrt_val.clone()) * LazyExpr::from(sqrt_val)
)).unwrap();
let s = format!("{}", squared);
let ok = prefixes.iter().any(|p| s.starts_with(p));
assert!(
ok,
"sqrt({}) * sqrt({}) should recover ~{}, got '{}'",
x, x, x, s
);
}
}
#[test]
fn deep_nesting_cos_acos() {
let test_values: &[&str] = &["0.1", "0.5", "0.9", "0.01", "0.99"];
for &x in test_values {
let result = evaluate(&gmath(x).acos().cos());
assert!(
result.is_ok(),
"cos(acos({})) should succeed, got {:?}",
x, result
);
let s = format!("{}", result.unwrap());
assert!(
s.starts_with("0."),
"cos(acos({})) should be ~{}, got '{}'",
x, x, s
);
}
}
#[test]
fn deep_nesting_tan_atan() {
let test_values: &[&str] = &["0.1", "0.5", "1.0", "2.0", "10.0"];
for &x in test_values {
let result = evaluate(&gmath(x).atan().tan());
assert!(
result.is_ok(),
"tan(atan({})) should succeed, got {:?}",
x, result
);
let s = format!("{}", result.unwrap());
let expected_prefix = x.split('.').next().unwrap();
assert!(
s.starts_with(expected_prefix),
"tan(atan({})) should recover ~{}, got '{}'",
x, x, s
);
}
}
#[test]
fn prime_table_basic_primes() {
assert!(is_prime(2), "2 should be prime");
assert!(is_prime(3), "3 should be prime");
assert!(is_prime(5), "5 should be prime");
assert!(is_prime(7), "7 should be prime");
assert!(is_prime(11), "11 should be prime");
assert!(is_prime(97), "97 should be prime");
}
#[test]
fn prime_table_basic_composites() {
assert!(!is_prime(0), "0 should not be prime");
assert!(!is_prime(1), "1 should not be prime");
assert!(!is_prime(4), "4 should not be prime");
assert!(!is_prime(6), "6 should not be prime");
assert!(!is_prime(9), "9 should not be prime");
assert!(!is_prime(100), "100 should not be prime");
}
#[test]
fn prime_table_max_prime() {
assert!(MAX_PRIME > 9000, "MAX_PRIME should be > 9000, got {}", MAX_PRIME);
assert!(MAX_PRIME <= 10000, "MAX_PRIME should be <= 10000, got {}", MAX_PRIME);
assert!(is_prime(MAX_PRIME), "MAX_PRIME itself should be prime");
println!(" MAX_PRIME = {}", MAX_PRIME);
}
#[test]
fn prime_table_count() {
assert!(
PRIME_COUNT >= 1100 && PRIME_COUNT <= 1300,
"PRIME_COUNT should be in [1100, 1300], got {}",
PRIME_COUNT
);
assert_eq!(
PRIME_COUNT,
prime_count_up_to(MAX_PRIME),
"PRIME_COUNT should equal prime_count_up_to(MAX_PRIME)"
);
println!(" PRIME_COUNT = {}", PRIME_COUNT);
}
#[test]
fn prime_table_nth_prime() {
assert_eq!(nth_prime(0), Some(2), "0th prime should be 2");
assert_eq!(nth_prime(1), Some(3), "1st prime should be 3");
assert_eq!(nth_prime(2), Some(5), "2nd prime should be 5");
assert_eq!(nth_prime(3), Some(7), "3rd prime should be 7");
assert_eq!(nth_prime(4), Some(11), "4th prime should be 11");
}
#[test]
fn prime_table_nth_prime_last() {
let last = nth_prime(PRIME_COUNT - 1);
assert_eq!(last, Some(MAX_PRIME), "last prime (index {}) should be MAX_PRIME ({})", PRIME_COUNT - 1, MAX_PRIME);
}
#[test]
fn prime_table_nth_prime_out_of_bounds() {
assert_eq!(nth_prime(PRIME_COUNT), None, "index == PRIME_COUNT should return None");
assert_eq!(nth_prime(PRIME_COUNT + 1), None, "beyond PRIME_COUNT should return None");
assert_eq!(nth_prime(usize::MAX), None, "usize::MAX should return None");
}
#[test]
fn prime_count_up_to_100() {
let count = prime_count_up_to(100);
assert!(
count >= 20 && count <= 25,
"primes up to 100 should be ~25, got {}",
count
);
println!(" prime_count_up_to(100) = {}", count);
}
#[test]
fn prime_count_up_to_small_values() {
assert_eq!(prime_count_up_to(0), 0, "no primes up to 0");
assert_eq!(prime_count_up_to(1), 0, "no primes up to 1");
assert!(prime_count_up_to(2) >= 1, "at least 1 prime up to 2");
assert!(prime_count_up_to(5) >= 3, "at least 3 primes up to 5");
assert!(prime_count_up_to(10) >= 4, "at least 4 primes up to 10");
}
#[test]
fn prime_count_up_to_max() {
assert_eq!(
prime_count_up_to(MAX_PRIME),
PRIME_COUNT,
"prime_count_up_to(MAX_PRIME) should equal PRIME_COUNT"
);
}