use super::*;
use crate::fixed_point::universal::fasc::lazy_expr::gmath;
use crate::fixed_point::universal::tier_types::{CompactShadow, ShadowConstantId};
#[test]
fn test_literal_parsing() {
let mut eval = StackEvaluator::new(DeploymentProfile::default());
let decimal = eval.parse_literal("123.456").unwrap();
assert!(matches!(decimal, StackValue::Decimal(3, _, _)));
let integer = eval.parse_literal("42").unwrap();
assert!(matches!(integer, StackValue::Binary(_, _, _)));
let hex = eval.parse_literal("0xFF").unwrap();
assert!(matches!(hex, StackValue::Binary(_, _, _)));
let bin = eval.parse_literal("0b1010").unwrap();
assert!(matches!(bin, StackValue::Binary(_, _, _)));
let fraction = eval.parse_literal("1/3").unwrap();
assert!(matches!(fraction, StackValue::Symbolic(_)));
let repeating = eval.parse_literal("0.333...").unwrap();
assert!(matches!(repeating, StackValue::Symbolic(_)));
let pi = eval.parse_literal("pi").unwrap();
assert!(matches!(pi, StackValue::Symbolic(_)));
}
#[test]
fn test_simple_arithmetic() {
let expr = gmath("10") + gmath("20");
let result = evaluate(&expr).unwrap();
assert!(matches!(result, StackValue::Binary(_, _, _)),
"Expected Binary result, got {:?}", result);
}
#[test]
fn test_exp_lazy_evaluation() {
let expr = gmath("1.5").exp();
assert_eq!(expr.operation_count(), 1);
assert_eq!(expr.depth(), 2);
}
#[cfg(table_format = "q64_64")]
#[test]
fn test_exp_basic_computation() {
let expr = gmath("0").exp();
let result = evaluate(&expr).unwrap();
match result {
StackValue::Binary(tier, val, _) => {
assert_eq!(tier, 3, "Expected tier 3 (Q64.64)");
let expected_one = 1i128 << 64; assert_eq!(val, expected_one, "exp(0) should equal 1.0");
}
_ => panic!("Expected Binary result, got {:?}", result),
}
}
#[cfg(table_format = "q64_64")]
#[test]
fn test_exp_positive_value() {
let expr = gmath("1").exp();
let result = evaluate(&expr).unwrap();
match result {
StackValue::Binary(tier, val, _) => {
assert_eq!(tier, 3, "Expected tier 3 (Q64.64)");
let e_q64 = 2.718281828 * (1u128 << 64) as f64;
let expected_approx = e_q64 as i128;
let diff = (val - expected_approx).abs();
let tolerance = 1i128 << 54;
assert!(diff < tolerance,
"exp(1) = {} is not close to e ≈ {}. Diff: {}", val, expected_approx, diff);
}
_ => panic!("Expected Binary result, got {:?}", result),
}
}
#[cfg(table_format = "q64_64")]
#[test]
fn test_exp_half() {
let expr = gmath("0.5").exp();
let result = evaluate(&expr).unwrap();
let s = format!("{}", result);
assert!(s.starts_with("1.648") || s.starts_with("1.649"),
"exp(0.5) ≈ 1.6487, got: {}", s);
}
#[cfg(table_format = "q64_64")]
#[test]
fn test_exp_negative_value() {
let expr = gmath("-1").exp();
let result = evaluate(&expr).unwrap();
match result {
StackValue::Binary(_, val, _) => {
let exp_neg_one_q64 = 0.367879441 * (1u128 << 64) as f64;
let expected_approx = exp_neg_one_q64 as i128;
let diff = (val - expected_approx).abs();
let tolerance = 1i128 << 54;
assert!(diff < tolerance,
"exp(-1) = {} is not close to expected ≈ {}. Diff: {}", val, expected_approx, diff);
}
_ => panic!("Expected Binary result, got {:?}", result),
}
}
#[cfg(table_format = "q64_64")]
#[test]
fn test_exp_composition() {
let expr1 = gmath("0.5").exp() + gmath("0.5").exp();
let result1 = evaluate(&expr1).unwrap();
let expr2 = gmath("0.5").exp() * gmath("2");
let result2 = evaluate(&expr2).unwrap();
let s1 = format!("{}", result1);
let s2 = format!("{}", result2);
assert!(s1.starts_with("3.297"), "exp(0.5)+exp(0.5) ≈ 3.2974, got: {}", s1);
assert!(s2.starts_with("3.297"), "2*exp(0.5) ≈ 3.2974, got: {}", s2);
}
#[cfg(table_format = "q64_64")]
#[test]
fn test_exp_from_decimal() {
let expr = gmath("1.5").exp();
let result = evaluate(&expr).unwrap();
let s = format!("{}", result);
assert!(s.starts_with("4.481") || s.starts_with("4.482"),
"exp(1.5) ≈ 4.4817, got: {}", s);
}
#[test]
fn test_exp_profile_tier_selection() {
let eval_embedded = StackEvaluator::new(DeploymentProfile::Embedded);
assert_eq!(eval_embedded.profile_max_binary_tier(), 3);
let eval_balanced = StackEvaluator::new(DeploymentProfile::Balanced);
assert_eq!(eval_balanced.profile_max_binary_tier(), 4);
let eval_scientific = StackEvaluator::new(DeploymentProfile::Scientific);
assert_eq!(eval_scientific.profile_max_binary_tier(), 5);
}
#[test]
fn test_exp_display() {
let expr = gmath("1.5").exp();
let display = format!("{}", expr);
assert!(display.contains("exp"), "Expression should contain 'exp'");
assert!(display.contains("1.5"), "Expression should contain '1.5'");
}
#[test]
fn test_ln_lazy_evaluation() {
let expr = gmath("2.0").ln();
assert_eq!(expr.operation_count(), 1);
assert_eq!(expr.depth(), 2);
}
#[cfg(table_format = "q64_64")]
#[test]
fn test_ln_basic_computation() {
let expr = gmath("1").ln();
let result = evaluate(&expr).unwrap();
match result {
StackValue::Binary(tier, val, _) => {
assert_eq!(tier, 3, "Expected tier 3 (Q64.64)");
let tolerance = 1i128 << 40;
assert!(val.abs() < tolerance, "ln(1) = {} should be close to 0", val);
}
_ => panic!("Expected Binary result, got {:?}", result),
}
}
#[cfg(table_format = "q64_64")]
#[test]
fn test_ln_e_equals_one() {
let expr = gmath("2.718281828").ln();
let result = evaluate(&expr).unwrap();
let s = format!("{}", result);
assert!(s.starts_with("0.999") || s.starts_with("1.000") || s.starts_with("1.0"),
"ln(e) ≈ 1.0, got: {}", s);
}
#[cfg(table_format = "q64_64")]
#[test]
fn test_ln_2() {
let expr = gmath("2").ln();
let result = evaluate(&expr).unwrap();
match result {
StackValue::Binary(tier, val, _) => {
assert_eq!(tier, 3, "Expected tier 3 (Q64.64)");
let ln2_q64 = 0.693147180 * (1u128 << 64) as f64;
let expected_approx = ln2_q64 as i128;
let diff = (val - expected_approx).abs();
let tolerance = 1i128 << 54;
assert!(diff < tolerance,
"ln(2) = {} is not close to expected ≈ {}. Diff: {}", val, expected_approx, diff);
}
_ => panic!("Expected Binary result, got {:?}", result),
}
}
#[test]
fn test_ln_domain_error() {
let expr_zero = gmath("0").ln();
let result_zero = evaluate(&expr_zero);
assert!(matches!(result_zero, Err(OverflowDetected::DomainError)),
"ln(0) should return DomainError, got {:?}", result_zero);
let expr_neg = gmath("-1").ln();
let result_neg = evaluate(&expr_neg);
assert!(matches!(result_neg, Err(OverflowDetected::DomainError)),
"ln(-1) should return DomainError, got {:?}", result_neg);
}
#[test]
fn test_ln_exp_inverse() {
let expr = gmath("1.5").exp().ln();
let result = evaluate(&expr).unwrap();
let display = format!("{}", result);
assert!(display.starts_with("1.5"),
"ln(exp(1.5)) should start with '1.5', got '{}'", display);
}
#[test]
fn test_ln_display() {
let expr = gmath("2.0").ln();
let display = format!("{}", expr);
assert!(display.contains("ln"), "Expression should contain 'ln'");
assert!(display.contains("2.0"), "Expression should contain '2.0'");
}
#[test]
fn test_sinh_display() {
let expr = gmath("1.0").sinh();
let display = format!("{}", expr);
assert!(display.contains("sinh"), "Expression should contain 'sinh'");
}
#[test]
fn test_cosh_display() {
let expr = gmath("1.0").cosh();
let display = format!("{}", expr);
assert!(display.contains("cosh"), "Expression should contain 'cosh'");
}
#[test]
fn test_tanh_display() {
let expr = gmath("1.0").tanh();
let display = format!("{}", expr);
assert!(display.contains("tanh"), "Expression should contain 'tanh'");
}
#[test]
fn test_sinh_zero() {
let expr = gmath("0").sinh();
let result = evaluate(&expr).unwrap();
let eval = StackEvaluator::new(DeploymentProfile::default());
let val = eval.to_binary_storage(&result).unwrap();
let zero = to_binary_storage(0);
assert_eq!(val, zero, "sinh(0) should be exactly 0");
}
#[test]
fn test_cosh_zero() {
let expr = gmath("0").cosh();
let result = evaluate(&expr).unwrap();
let eval = StackEvaluator::new(DeploymentProfile::default());
let val = eval.to_binary_storage(&result).unwrap();
let one_val = eval.to_binary_storage(&eval.make_binary_int(1)).unwrap();
assert_eq!(val, one_val, "cosh(0) should be exactly 1.0");
}
#[test]
fn test_tanh_zero() {
let expr = gmath("0").tanh();
let result = evaluate(&expr).unwrap();
let eval = StackEvaluator::new(DeploymentProfile::default());
let val = eval.to_binary_storage(&result).unwrap();
let zero = to_binary_storage(0);
assert_eq!(val, zero, "tanh(0) should be exactly 0");
}
#[cfg(table_format = "q64_64")]
#[test]
fn test_sinh_basic() {
let expr = gmath("1").sinh();
let result = evaluate(&expr).unwrap();
match result {
StackValue::Binary(_, val, _) => {
let expected_approx = (1.1752011936 * (1u128 << 64) as f64) as i128;
let diff = (val - expected_approx).abs();
let tolerance = 1i128 << 54;
assert!(diff < tolerance,
"sinh(1) = {} not close to expected {}. Diff: {}", val, expected_approx, diff);
}
_ => panic!("Expected Binary result"),
}
}
#[cfg(table_format = "q64_64")]
#[test]
fn test_cosh_basic() {
let expr = gmath("1").cosh();
let result = evaluate(&expr).unwrap();
match result {
StackValue::Binary(_, val, _) => {
let expected_approx = (1.5430806348 * (1u128 << 64) as f64) as i128;
let diff = (val - expected_approx).abs();
let tolerance = 1i128 << 54;
assert!(diff < tolerance,
"cosh(1) = {} not close to expected {}. Diff: {}", val, expected_approx, diff);
}
_ => panic!("Expected Binary result"),
}
}
#[cfg(table_format = "q64_64")]
#[test]
fn test_tanh_basic() {
let expr = gmath("1").tanh();
let result = evaluate(&expr).unwrap();
match result {
StackValue::Binary(_, val, _) => {
let expected_approx = (0.7615941559 * (1u128 << 64) as f64) as i128;
let diff = (val - expected_approx).abs();
let tolerance = 1i128 << 54;
assert!(diff < tolerance,
"tanh(1) = {} not close to expected {}. Diff: {}", val, expected_approx, diff);
}
_ => panic!("Expected Binary result"),
}
}
#[cfg(table_format = "q64_64")]
#[test]
fn test_identity_cosh2_minus_sinh2() {
let expr_cosh = gmath("1").cosh();
let cosh_val = evaluate(&expr_cosh).unwrap();
let expr_sinh = gmath("1").sinh();
let sinh_val = evaluate(&expr_sinh).unwrap();
let mut eval = StackEvaluator::new(DeploymentProfile::default());
let cosh2 = eval.multiply_values(cosh_val.clone(), cosh_val).unwrap();
let sinh2 = eval.multiply_values(sinh_val.clone(), sinh_val).unwrap();
let diff = eval.subtract_values(cosh2, sinh2).unwrap();
let diff_binary = eval.to_binary_storage(&diff).unwrap();
let one_binary = eval.to_binary_storage(&eval.make_binary_int(1)).unwrap();
let error = (diff_binary - one_binary).abs();
let tolerance = 1i128 << 55;
assert!(error < tolerance,
"cosh²(1) - sinh²(1) = {} should be close to 1.0 = {}. Error: {}",
diff_binary, one_binary, error);
}
#[test]
fn test_asinh_zero() {
let expr = gmath("0").asinh();
let result = evaluate(&expr).unwrap();
let eval = StackEvaluator::new(DeploymentProfile::default());
let _val = eval.to_binary_storage(&result).unwrap();
let _zero = to_binary_storage(0);
#[cfg(table_format = "q256_256")]
{
let diff = if _val > _zero { _val - _zero } else { _zero - _val };
let tolerance = I512::from_i128(1) << 20;
assert!(diff < tolerance, "asinh(0) should be close to 0");
}
#[cfg(table_format = "q128_128")]
{
let diff = if _val > _zero { _val - _zero } else { _zero - _val };
let tolerance = I256::from_i128(1) << 20;
assert!(diff < tolerance, "asinh(0) should be close to 0");
}
#[cfg(table_format = "q64_64")]
{
let diff = (_val - _zero).abs();
let tolerance = 1i128 << 20;
assert!(diff < tolerance, "asinh(0) should be close to 0");
}
}
#[test]
fn test_acosh_domain_error() {
let expr = gmath("0.5").acosh();
let result = evaluate(&expr);
assert!(result.is_err(), "acosh(0.5) should return an error");
}
#[test]
fn test_atanh_domain_error() {
let expr = gmath("1").atanh();
let result = evaluate(&expr);
assert!(result.is_err(), "atanh(1) should return an error, got {:?}", result);
}
#[test]
fn test_atanh_zero() {
let expr = gmath("0").atanh();
let result = evaluate(&expr).unwrap();
let eval = StackEvaluator::new(DeploymentProfile::default());
let _val = eval.to_binary_storage(&result).unwrap();
let _zero = to_binary_storage(0);
#[cfg(table_format = "q256_256")]
{
let diff = if _val > _zero { _val - _zero } else { _zero - _val };
let tolerance = I512::from_i128(1) << 20;
assert!(diff < tolerance, "atanh(0) should be close to 0");
}
#[cfg(table_format = "q128_128")]
{
let diff = if _val > _zero { _val - _zero } else { _zero - _val };
let tolerance = I256::from_i128(1) << 20;
assert!(diff < tolerance, "atanh(0) should be close to 0");
}
#[cfg(table_format = "q64_64")]
{
let diff = (_val - _zero).abs();
let tolerance = 1i128 << 20;
assert!(diff < tolerance, "atanh(0) should be close to 0");
}
}
#[cfg(table_format = "q64_64")]
#[test]
fn test_asinh_roundtrip() {
let expr = gmath("1").sinh().asinh();
let result = evaluate(&expr).unwrap();
match result {
StackValue::Binary(_, val, _) => {
let one_q64 = 1i128 << 64;
let diff = (val - one_q64).abs();
let tolerance = 1i128 << 55;
assert!(diff < tolerance,
"asinh(sinh(1)) = {} should be close to 1.0 = {}. Diff: {}",
val, one_q64, diff);
}
_ => panic!("Expected Binary result"),
}
}
#[test]
fn test_sin_display() {
let expr = gmath("1.0").sin();
let display = format!("{}", expr);
assert!(display.contains("sin"), "Expression should contain 'sin'");
}
#[test]
fn test_cos_display() {
let expr = gmath("1.0").cos();
let display = format!("{}", expr);
assert!(display.contains("cos"), "Expression should contain 'cos'");
}
#[test]
fn test_tan_display() {
let expr = gmath("1.0").tan();
let display = format!("{}", expr);
assert!(display.contains("tan"), "Expression should contain 'tan'");
}
#[test]
fn test_atan2_display() {
let expr = gmath("1.0").atan2(gmath("2.0"));
let display = format!("{}", expr);
assert!(display.contains("atan2"), "Expression should contain 'atan2'");
}
#[test]
fn test_sin_zero() {
let expr = gmath("0").sin();
let result = evaluate(&expr).expect("sin(0) should succeed");
let eval = StackEvaluator::new(DeploymentProfile::default());
let zero = eval.make_binary_int(0);
let result_bs = eval.to_binary_storage(&result).unwrap();
let zero_bs = eval.to_binary_storage(&zero).unwrap();
assert_eq!(result_bs, zero_bs, "sin(0) should be 0");
}
#[test]
fn test_cos_zero() {
let expr = gmath("0").cos();
let result = evaluate(&expr).expect("cos(0) should succeed");
let eval = StackEvaluator::new(DeploymentProfile::default());
let one = eval.make_binary_int(1);
let result_bs = eval.to_binary_storage(&result).unwrap();
let one_bs = eval.to_binary_storage(&one).unwrap();
assert_eq!(result_bs, one_bs, "cos(0) should be 1");
}
#[test]
fn test_tan_zero() {
let expr = gmath("0").tan();
let result = evaluate(&expr).expect("tan(0) should succeed");
let eval = StackEvaluator::new(DeploymentProfile::default());
let zero = eval.make_binary_int(0);
let result_bs = eval.to_binary_storage(&result).unwrap();
let zero_bs = eval.to_binary_storage(&zero).unwrap();
assert_eq!(result_bs, zero_bs, "tan(0) should be 0");
}
#[test]
fn test_atan_zero() {
let expr = gmath("0").atan();
let result = evaluate(&expr).expect("atan(0) should succeed");
let eval = StackEvaluator::new(DeploymentProfile::default());
let zero = eval.make_binary_int(0);
let result_bs = eval.to_binary_storage(&result).unwrap();
let zero_bs = eval.to_binary_storage(&zero).unwrap();
assert_eq!(result_bs, zero_bs, "atan(0) should be 0");
}
#[test]
fn test_asin_zero() {
let expr = gmath("0").asin();
let result = evaluate(&expr).expect("asin(0) should succeed");
let eval = StackEvaluator::new(DeploymentProfile::default());
let result_bs = eval.to_binary_storage(&result).unwrap();
let zero = eval.make_binary_int(0);
let zero_bs = eval.to_binary_storage(&zero).unwrap();
assert_eq!(result_bs, zero_bs, "asin(0) should be 0");
}
#[test]
fn test_asin_domain_error() {
let expr = gmath("2").asin();
let result = evaluate(&expr);
assert!(result.is_err(), "asin(2) should return domain error");
}
#[test]
fn test_acos_zero() {
let expr = gmath("0").acos();
let result = evaluate(&expr).expect("acos(0) should succeed");
let eval = StackEvaluator::new(DeploymentProfile::default());
let _result_bs = eval.to_binary_storage(&result).unwrap();
#[cfg(table_format = "q256_256")]
{
use crate::fixed_point::domains::binary_fixed::transcendental::pi_half_i512;
let expected = pi_half_i512();
let diff = if _result_bs > expected { _result_bs - expected } else { expected - _result_bs };
assert!(diff <= I512::from_i128(1), "acos(0) should be π/2 within 1 ULP, diff = {:?}", diff);
}
#[cfg(table_format = "q128_128")]
{
use crate::fixed_point::domains::binary_fixed::transcendental::pi_half_i256;
let expected = pi_half_i256();
let diff = if _result_bs > expected { _result_bs - expected } else { expected - _result_bs };
assert!(diff <= I256::from_i128(1), "acos(0) should be π/2 within 1 ULP, diff = {:?}", diff);
}
#[cfg(table_format = "q64_64")]
{
use crate::fixed_point::domains::binary_fixed::transcendental::pi_half_i128;
let expected = pi_half_i128();
let diff = if _result_bs > expected { (_result_bs - expected).abs() } else { (expected - _result_bs).abs() };
assert!(diff <= 1, "acos(0) should be π/2 within 1 ULP, diff = {}", diff);
}
}
#[test]
fn test_acos_domain_error() {
let expr = gmath("2").acos();
let result = evaluate(&expr);
assert!(result.is_err(), "acos(2) should return domain error");
}
#[cfg(table_format = "q64_64")]
#[test]
fn test_sin_cos_pythagorean_identity() {
let sin_expr = gmath("1").sin();
let cos_expr = gmath("1").cos();
let sin_val = evaluate(&sin_expr).expect("sin(1) should succeed");
let cos_val = evaluate(&cos_expr).expect("cos(1) should succeed");
let mut eval = StackEvaluator::new(DeploymentProfile::default());
let sin_sq = eval.multiply_values(sin_val.clone(), sin_val).unwrap();
let cos_sq = eval.multiply_values(cos_val.clone(), cos_val).unwrap();
let sum = eval.add_values(sin_sq, cos_sq).unwrap();
let one = eval.make_binary_int(1);
let sum_bs = eval.to_binary_storage(&sum).unwrap();
let one_bs = eval.to_binary_storage(&one).unwrap();
let diff = if sum_bs > one_bs { sum_bs - one_bs } else { one_bs - sum_bs };
assert!(diff < 1024, "sin²(1) + cos²(1) should be very close to 1, diff={}", diff);
}
#[cfg(table_format = "q64_64")]
#[test]
fn test_atan_one_is_pi_over_4() {
let expr = gmath("1").atan();
let result = evaluate(&expr).expect("atan(1) should succeed");
let eval = StackEvaluator::new(DeploymentProfile::default());
let result_bs = eval.to_binary_storage(&result).unwrap();
let pi_over_4_approx: i128 = 14488038916154245685;
let diff = (result_bs - pi_over_4_approx).abs();
assert!(diff < 256, "atan(1) should be close to π/4, diff={}", diff);
}
#[cfg(table_format = "q64_64")]
#[test]
fn test_sin_basic() {
let expr = gmath("1").sin();
let result = evaluate(&expr).expect("sin(1) should succeed");
let eval = StackEvaluator::new(DeploymentProfile::default());
let result_bs = eval.to_binary_storage(&result).unwrap();
let one_q64: i128 = 1i128 << 64;
assert!(result_bs > 0, "sin(1) should be positive");
assert!(result_bs < one_q64, "sin(1) should be less than 1");
}
#[cfg(table_format = "q64_64")]
#[test]
fn test_cos_basic() {
let expr = gmath("1").cos();
let result = evaluate(&expr).expect("cos(1) should succeed");
let eval = StackEvaluator::new(DeploymentProfile::default());
let result_bs = eval.to_binary_storage(&result).unwrap();
let one_q64: i128 = 1i128 << 64;
assert!(result_bs > 0, "cos(1) should be positive");
assert!(result_bs < one_q64, "cos(1) should be less than 1");
}
#[cfg(table_format = "q64_64")]
#[test]
fn test_tan_one() {
let expr = gmath("1").tan();
let result = evaluate(&expr).expect("tan(1) should succeed");
let eval = StackEvaluator::new(DeploymentProfile::default());
let result_bs = eval.to_binary_storage(&result).unwrap();
let one_q64: i128 = 1i128 << 64;
assert!(result_bs > one_q64, "tan(1) should be > 1");
assert!(result_bs < 2 * one_q64, "tan(1) should be < 2");
}
#[cfg(table_format = "q64_64")]
#[test]
fn test_atan2_basic() {
let expr = gmath("1").atan2(gmath("1"));
let result = evaluate(&expr).expect("atan2(1,1) should succeed");
let eval = StackEvaluator::new(DeploymentProfile::default());
let result_bs = eval.to_binary_storage(&result).unwrap();
let pi_over_4_approx: i128 = 14488038916154245685;
let diff = (result_bs - pi_over_4_approx).abs();
assert!(diff < 256, "atan2(1,1) should be close to π/4, diff={}", diff);
}
#[cfg(table_format = "q64_64")]
#[test]
fn test_asin_roundtrip() {
let inner_expr = gmath("0.5").sin();
let asin_expr = inner_expr.asin();
let result = evaluate(&asin_expr).expect("asin(sin(0.5)) should succeed");
let eval = StackEvaluator::new(DeploymentProfile::default());
let result_bs = eval.to_binary_storage(&result).unwrap();
let half_q64: i128 = 1i128 << 63; let diff = (result_bs - half_q64).abs();
assert!(diff < 65536, "asin(sin(0.5)) should be close to 0.5, diff={}", diff);
}
#[test]
fn test_gmath_parse_decimal() {
let s = String::from("1.5");
let expr = gmath_parse(&s).unwrap();
let result = evaluate(&expr).unwrap();
assert!(matches!(result, StackValue::Decimal(_, _, _)),
"Expected Decimal, got {:?}", result);
}
#[test]
fn test_gmath_parse_integer() {
let s = String::from("42");
let expr = gmath_parse(&s).unwrap();
let result = evaluate(&expr).unwrap();
assert!(matches!(result, StackValue::Binary(_, _, _)),
"Expected Binary, got {:?}", result);
}
#[test]
fn test_gmath_parse_fraction() {
let s = String::from("1/3");
let expr = gmath_parse(&s).unwrap();
let result = evaluate(&expr).unwrap();
assert!(matches!(result, StackValue::Symbolic(_)),
"Expected Symbolic, got {:?}", result);
}
#[test]
fn test_gmath_parse_repeating_decimal() {
let s = String::from("0.333...");
let expr = gmath_parse(&s).unwrap();
let result = evaluate(&expr).unwrap();
assert!(matches!(result, StackValue::Symbolic(_)),
"Expected Symbolic for repeating decimal, got {:?}", result);
}
#[test]
fn test_gmath_parse_hex() {
let s = String::from("0xFF");
let expr = gmath_parse(&s).unwrap();
let result = evaluate(&expr).unwrap();
assert!(matches!(result, StackValue::Binary(_, _, _)),
"Expected Binary for hex, got {:?}", result);
}
#[test]
fn test_gmath_parse_binary_literal() {
let s = String::from("0b1010");
let expr = gmath_parse(&s).unwrap();
let result = evaluate(&expr).unwrap();
assert!(matches!(result, StackValue::Binary(_, _, _)),
"Expected Binary for 0b prefix, got {:?}", result);
}
#[test]
fn test_gmath_parse_ternary() {
let s = String::from("0t1.5");
let expr = gmath_parse(&s).unwrap();
let result = evaluate(&expr).unwrap();
assert!(matches!(result, StackValue::Ternary(_, _, _)),
"Expected Ternary for 0t prefix, got {:?}", result);
}
#[test]
fn test_gmath_parse_named_constant() {
let s = String::from("pi");
let expr = gmath_parse(&s).unwrap();
let result = evaluate(&expr).unwrap();
assert!(matches!(result, StackValue::Symbolic(_)),
"Expected Symbolic for named constant, got {:?}", result);
}
#[test]
fn test_gmath_parse_invalid_input() {
let s = String::from("not_a_number");
let result = gmath_parse(&s);
assert!(result.is_err(), "Expected parse error for invalid input");
}
#[test]
fn test_gmath_parse_empty_string() {
let s = String::from("");
let result = gmath_parse(&s);
assert!(result.is_err(), "Expected parse error for empty string");
}
#[test]
fn test_gmath_parse_arithmetic_with_gmath() {
let runtime_val = String::from("1.5");
let parsed = gmath_parse(&runtime_val).unwrap();
let expr = parsed + gmath("2.5");
let result = evaluate(&expr).unwrap();
let display = format!("{}", result);
assert!(display.starts_with("4.0"), "1.5 + 2.5 should be 4.0, got {}", display);
}
#[test]
fn test_gmath_parse_arithmetic_both_runtime() {
let a = String::from("10");
let b = String::from("3");
let expr = gmath_parse(&a).unwrap() * gmath_parse(&b).unwrap();
let result = evaluate(&expr).unwrap();
let display = format!("{}", result);
assert!(display.starts_with("30"), "10 * 3 should be 30, got {}", display);
}
#[test]
fn test_gmath_parse_transcendental() {
let s = String::from("0");
let expr = gmath_parse(&s).unwrap().exp();
let result = evaluate(&expr).unwrap();
let display = format!("{}", result);
assert!(display.starts_with("1.0"), "exp(0) should be 1.0, got {}", display);
}
#[test]
fn test_gmath_parse_chaining() {
let s = String::from("100.00");
let first = evaluate(&gmath_parse(&s).unwrap()).unwrap();
let chained = LazyExpr::from(first) * gmath("1.05");
let second = evaluate(&chained).unwrap();
let display = format!("{}", second);
assert!(display.starts_with("105"), "100 * 1.05 should be 105, got {}", display);
}
#[test]
fn test_gmath_parse_loop_accumulation() {
let values = vec!["1.0", "2.0", "3.0", "4.0"];
let mut acc = gmath_parse(values[0]).unwrap();
for v in &values[1..] {
acc = acc + gmath_parse(v).unwrap();
}
let result = evaluate(&acc).unwrap();
let display = format!("{}", result);
assert!(display.starts_with("10.0"), "1+2+3+4 should be 10, got {}", display);
}
#[test]
fn test_gmath_parse_mode_routing() {
use crate::fixed_point::universal::fasc::mode::{set_mode, reset_mode, GmathMode, ComputeMode, OutputMode};
set_mode(GmathMode { compute: ComputeMode::Symbolic, output: OutputMode::Auto });
let s = String::from("1.5");
let expr = gmath_parse(&s).unwrap();
let result = evaluate(&expr).unwrap();
assert!(matches!(result, StackValue::Symbolic(_)),
"Symbolic mode should produce Symbolic result, got {:?}", result);
reset_mode();
let expr2 = gmath_parse(&s).unwrap();
let result2 = evaluate(&expr2).unwrap();
assert!(matches!(result2, StackValue::Decimal(_, _, _)),
"Auto mode should route '1.5' to Decimal, got {:?}", result2);
}
#[test]
fn test_gmath_parse_decimal_shadow_created() {
let s = String::from("1.5");
let expr = gmath_parse(&s).unwrap();
let result = evaluate(&expr).unwrap();
let shadow = result.shadow();
assert!(shadow.is_some(), "Decimal parse should create a shadow");
let (num, den) = shadow.as_rational().expect("Shadow should be rational");
assert_eq!(num as f64 / den as f64, 1.5, "Shadow should represent 1.5, got {}/{}", num, den);
}
#[test]
fn test_gmath_parse_integer_shadow_created() {
let s = String::from("42");
let expr = gmath_parse(&s).unwrap();
let result = evaluate(&expr).unwrap();
let shadow = result.shadow();
assert!(shadow.is_some(), "Integer parse should create a shadow");
let (num, den) = shadow.as_rational().expect("Shadow should be rational");
assert_eq!(num, 42, "Shadow numerator should be 42, got {}", num);
assert_eq!(den, 1, "Shadow denominator should be 1, got {}", den);
}
#[test]
fn test_gmath_parse_shadow_survives_add() {
let a = gmath_parse("1.5").unwrap();
let b = gmath_parse("2.5").unwrap();
let result = evaluate(&(a + b)).unwrap();
let shadow = result.shadow();
assert!(shadow.is_some(), "Addition result should have shadow");
let (num, den) = shadow.as_rational().expect("Shadow should be rational");
let ratio = num as f64 / den as f64;
assert!((ratio - 4.0).abs() < 1e-10, "Shadow should represent 4.0, got {}/{} = {}", num, den, ratio);
}
#[test]
fn test_gmath_parse_shadow_survives_multiply() {
let a = gmath_parse("1.5").unwrap();
let b = gmath_parse("2.0").unwrap();
let result = evaluate(&(a * b)).unwrap();
let shadow = result.shadow();
if let Some((num, den)) = shadow.as_rational() {
let ratio = num as f64 / den as f64;
assert!((ratio - 3.0).abs() < 1e-10,
"Shadow should represent 3.0, got {}/{} = {}", num, den, ratio);
}
}
#[test]
fn test_gmath_parse_shadow_subtract() {
let a = gmath_parse("10.0").unwrap();
let b = gmath_parse("3.5").unwrap();
let result = evaluate(&(a - b)).unwrap();
let shadow = result.shadow();
assert!(shadow.is_some(), "Subtraction result should have shadow");
let (num, den) = shadow.as_rational().expect("Shadow should be rational");
let ratio = num as f64 / den as f64;
assert!((ratio - 6.5).abs() < 1e-10, "Shadow should represent 6.5, got {}/{} = {}", num, den, ratio);
}
#[test]
fn test_gmath_parse_shadow_negate() {
let a = gmath_parse("1.5").unwrap();
let result = evaluate(&(-a)).unwrap();
let shadow = result.shadow();
assert!(shadow.is_some(), "Negation result should have shadow");
let (num, den) = shadow.as_rational().expect("Shadow should be rational");
let ratio = num as f64 / den as f64;
assert!((ratio - (-1.5)).abs() < 1e-10, "Shadow should represent -1.5, got {}/{} = {}", num, den, ratio);
}
#[test]
fn test_gmath_parse_shadow_chain_preserved() {
let first = evaluate(&gmath_parse("2.5").unwrap()).unwrap();
let shadow_before = first.shadow();
assert!(shadow_before.is_some(), "First evaluation should have shadow");
let chained = LazyExpr::from(first) + gmath_parse("1.0").unwrap();
let second = evaluate(&chained).unwrap();
let shadow_after = second.shadow();
assert!(shadow_after.is_some(), "Chained result should preserve shadow");
let (num, den) = shadow_after.as_rational().expect("Shadow should be rational");
let ratio = num as f64 / den as f64;
assert!((ratio - 3.5).abs() < 1e-10, "Shadow should represent 3.5, got {}/{} = {}", num, den, ratio);
}
#[test]
fn test_gmath_parse_symbolic_no_shadow_needed() {
let result = evaluate(&gmath_parse("1/3").unwrap()).unwrap();
let shadow = result.shadow();
assert!(shadow.is_none(), "Symbolic values need no shadow (they ARE exact), got {:?}", shadow);
}
#[test]
fn test_gmath_parse_shadow_matches_static_gmath() {
let runtime_result = evaluate(&gmath_parse("1.5").unwrap()).unwrap();
let static_result = evaluate(&gmath("1.5")).unwrap();
let runtime_shadow = runtime_result.shadow();
let static_shadow = static_result.shadow();
assert_eq!(runtime_shadow, static_shadow,
"gmath_parse and gmath should produce identical shadows: {:?} vs {:?}",
runtime_shadow, static_shadow);
}
#[test]
fn test_shadow_fast_path_decimal_to_rational() {
let result = evaluate(&gmath("1.5")).unwrap();
assert!(result.shadow().is_some(), "decimal 1.5 must have shadow");
let rational = result.to_rational().unwrap();
let num = rational.numerator_i128().unwrap();
let den = rational.denominator_i128().unwrap();
assert!(den.abs() < 1000, "shadow fast path should give small denominator, got {}/{}", num, den);
assert_eq!(num * 2, den * 3, "1.5 should be 3/2 (or equivalent), got {}/{}", num, den);
}
#[test]
fn test_shadow_fast_path_integer_to_rational() {
let result = evaluate(&gmath("42")).unwrap();
assert!(result.shadow().is_some(), "integer 42 must have shadow");
let rational = result.to_rational().unwrap();
let num = rational.numerator_i128().unwrap();
let den = rational.denominator_i128().unwrap();
assert_eq!(num, 42, "should be 42/1 via shadow, got {}/{}", num, den);
assert_eq!(den, 1, "denominator should be 1 via shadow, got {}", den);
}
#[test]
fn test_shadow_fast_path_arithmetic_chain() {
let result = evaluate(&(gmath("1.5") + gmath("2.5"))).unwrap();
let shadow = result.shadow();
assert!(shadow.is_some(), "arithmetic result should preserve shadow");
let rational = result.to_rational().unwrap();
let num = rational.numerator_i128().unwrap();
let den = rational.denominator_i128().unwrap();
assert_eq!(num, 4, "1.5 + 2.5 = 4/1, got {}/{}", num, den);
assert_eq!(den, 1, "denominator should be 1, got {}", den);
}
#[test]
fn test_shadow_fast_path_multiplication() {
let result = evaluate(&(gmath("3") * gmath("7"))).unwrap();
let shadow = result.shadow();
assert!(shadow.is_some(), "integer multiply should preserve shadow");
let rational = result.to_rational().unwrap();
let num = rational.numerator_i128().unwrap();
let den = rational.denominator_i128().unwrap();
assert_eq!(num, 21, "3 * 7 = 21, got {}/{}", num, den);
assert_eq!(den, 1);
}
#[test]
fn test_shadow_none_falls_through() {
let result = evaluate(&gmath("1.0").exp()).unwrap();
assert!(result.shadow().is_none(), "transcendental should have no shadow");
let rational = result.to_rational();
assert!(rational.is_ok(), "to_rational must succeed even without shadow, got {:?}", rational.err());
#[cfg(any(table_format = "q16_16", table_format = "q32_32", table_format = "q64_64"))]
{
let num = rational.unwrap().numerator_i128();
assert!(num.is_some(), "numerator must fit in i128 for native-int profiles");
}
}
#[test]
fn test_shadow_display_rational() {
let shadow = CompactShadow::from_rational(1, 3);
assert_eq!(format!("{}", shadow), "1/3");
let shadow = CompactShadow::from_rational(42, 1);
assert_eq!(format!("{}", shadow), "42");
let shadow = CompactShadow::from_rational(-7, 2);
assert_eq!(format!("{}", shadow), "-7/2");
}
#[test]
fn test_shadow_display_special() {
let shadow = CompactShadow::None;
assert_eq!(format!("{}", shadow), "none");
let shadow = CompactShadow::ConstantRef(ShadowConstantId::Pi);
assert_eq!(format!("{}", shadow), "\u{03C0}");
let shadow = CompactShadow::ConstantRef(ShadowConstantId::E);
assert_eq!(format!("{}", shadow), "e");
let shadow = CompactShadow::ConstantRef(ShadowConstantId::Sqrt2);
assert_eq!(format!("{}", shadow), "\u{221A}2");
}
#[test]
fn test_shadow_convenience_accessors() {
let shadow = CompactShadow::from_rational(3, 4);
assert_eq!(shadow.numerator(), Some(3));
assert_eq!(shadow.denominator(), Some(4));
assert_eq!(shadow.constant_id(), None);
let shadow = CompactShadow::ConstantRef(ShadowConstantId::Phi);
assert_eq!(shadow.numerator(), None);
assert_eq!(shadow.denominator(), None);
assert_eq!(shadow.constant_id(), Some(ShadowConstantId::Phi));
let shadow = CompactShadow::None;
assert_eq!(shadow.numerator(), None);
assert_eq!(shadow.denominator(), None);
assert_eq!(shadow.constant_id(), None);
}
#[test]
fn test_shadow_hex_literal() {
let mut eval = StackEvaluator::new(DeploymentProfile::default());
let result = eval.parse_literal("0xFF").unwrap();
let shadow = result.shadow();
assert!(shadow.is_some(), "hex literal 0xFF should have shadow, got {:?}", shadow);
assert_eq!(shadow.as_rational(), Some((255, 1)));
}
#[test]
fn test_shadow_binary_literal() {
let mut eval = StackEvaluator::new(DeploymentProfile::default());
let result = eval.parse_literal("0b1010").unwrap();
let shadow = result.shadow();
assert!(shadow.is_some(), "binary literal 0b1010 should have shadow, got {:?}", shadow);
assert_eq!(shadow.as_rational(), Some((10, 1)));
}
#[test]
fn test_shadow_division_preserves() {
let result = evaluate(&(gmath("10") / gmath("4"))).unwrap();
let shadow = result.shadow();
if shadow.is_some() {
let (num, den) = shadow.as_rational().unwrap();
assert_eq!(num * 2, den as i128 * 5, "10/4 should be 5/2, got {}/{}", num, den);
}
}
#[test]
fn test_shadow_negation_preserves() {
let result = evaluate(&(-gmath("1.5"))).unwrap();
let shadow = result.shadow();
assert!(shadow.is_some(), "negation should preserve shadow");
let (num, den) = shadow.as_rational().unwrap();
assert!(num < 0, "negated value should have negative numerator, got {}/{}", num, den);
}