use metadol::ast::{BinaryOp, Expr, Literal};
use metadol::eval::{Interpreter, Value};
use metadol::parser::Parser;
mod division_by_zero {
use super::*;
#[test]
fn integer_division_by_zero() {
let mut interpreter = Interpreter::new();
let expr = Expr::Binary {
left: Box::new(Expr::Literal(Literal::Int(100))),
op: BinaryOp::Div,
right: Box::new(Expr::Literal(Literal::Int(0))),
};
let result = interpreter.eval(&expr);
match result {
Err(_) => {
}
Ok(Value::Float(f)) if f.is_infinite() => {
}
Ok(val) => {
panic!(
"BUG: Integer division by zero returned unexpected value: {:?}",
val
);
}
}
}
#[test]
fn float_division_by_zero_positive() {
let mut interpreter = Interpreter::new();
let expr = Expr::Binary {
left: Box::new(Expr::Literal(Literal::Float(1.0))),
op: BinaryOp::Div,
right: Box::new(Expr::Literal(Literal::Float(0.0))),
};
let result = interpreter.eval(&expr);
match result {
Ok(Value::Float(f)) => {
assert!(f.is_infinite() && f > 0.0, "Expected +Infinity, got {}", f);
}
Ok(val) => panic!("Expected float, got {:?}", val),
Err(e) => {
println!("NOTE: Float division by zero returns error: {:?}", e);
}
}
}
#[test]
fn float_division_by_zero_negative() {
let mut interpreter = Interpreter::new();
let expr = Expr::Binary {
left: Box::new(Expr::Literal(Literal::Float(-1.0))),
op: BinaryOp::Div,
right: Box::new(Expr::Literal(Literal::Float(0.0))),
};
let result = interpreter.eval(&expr);
match result {
Ok(Value::Float(f)) => {
assert!(f.is_infinite() && f < 0.0, "Expected -Infinity, got {}", f);
}
Ok(val) => panic!("Expected float, got {:?}", val),
Err(e) => {
println!("NOTE: Float division by zero returns error: {:?}", e);
}
}
}
#[test]
fn zero_divided_by_zero_is_nan() {
let mut interpreter = Interpreter::new();
let expr = Expr::Binary {
left: Box::new(Expr::Literal(Literal::Float(0.0))),
op: BinaryOp::Div,
right: Box::new(Expr::Literal(Literal::Float(0.0))),
};
let result = interpreter.eval(&expr);
match result {
Ok(Value::Float(f)) => {
assert!(f.is_nan(), "Expected NaN for 0.0/0.0, got {}", f);
}
Ok(val) => panic!("Expected float NaN, got {:?}", val),
Err(e) => {
println!("NOTE: 0.0/0.0 returns error instead of NaN: {:?}", e);
}
}
}
#[test]
fn gravitational_force_at_zero_distance() {
let source = r#"
fun gravitational_force(m1: f64, m2: f64, r: f64) -> f64 {
if r == 0.0 {
return 1.0e308
}
let G = 6.67430e-11
return G * m1 * m2 / (r * r)
}
"#;
let mut parser = Parser::new(source);
let result = parser.parse();
assert!(
result.is_ok(),
"Should parse division-by-zero guarding pattern"
);
}
}
mod overflow {
use super::*;
#[test]
fn i64_max_plus_one() {
let mut interpreter = Interpreter::new();
let expr = Expr::Binary {
left: Box::new(Expr::Literal(Literal::Int(i64::MAX))),
op: BinaryOp::Add,
right: Box::new(Expr::Literal(Literal::Int(1))),
};
let result = interpreter.eval(&expr);
match result {
Ok(Value::Int(n)) => {
if n == i64::MIN {
println!("NOTE: Integer overflow wraps around (two's complement)");
} else {
panic!("BUG: Unexpected overflow behavior: {}", n);
}
}
Err(e) => {
println!("NOTE: Integer overflow returns error: {:?}", e);
}
Ok(val) => panic!("Unexpected value type on overflow: {:?}", val),
}
}
#[test]
fn i64_min_minus_one() {
let mut interpreter = Interpreter::new();
let expr = Expr::Binary {
left: Box::new(Expr::Literal(Literal::Int(i64::MIN))),
op: BinaryOp::Sub,
right: Box::new(Expr::Literal(Literal::Int(1))),
};
let result = interpreter.eval(&expr);
match result {
Ok(Value::Int(n)) => {
if n == i64::MAX {
println!("NOTE: Integer underflow wraps around");
} else {
panic!("BUG: Unexpected underflow behavior: {}", n);
}
}
Err(e) => {
println!("NOTE: Integer underflow returns error: {:?}", e);
}
Ok(val) => panic!("Unexpected value type on underflow: {:?}", val),
}
}
#[test]
fn multiplication_overflow() {
let mut interpreter = Interpreter::new();
let expr = Expr::Binary {
left: Box::new(Expr::Literal(Literal::Int(i64::MAX / 2 + 1))),
op: BinaryOp::Mul,
right: Box::new(Expr::Literal(Literal::Int(2))),
};
let result = interpreter.eval(&expr);
match result {
Ok(Value::Int(n)) => {
println!("NOTE: Multiplication overflow produces: {}", n);
}
Err(e) => {
println!("NOTE: Multiplication overflow returns error: {:?}", e);
}
Ok(val) => {
println!("NOTE: Multiplication overflow produces: {:?}", val);
}
}
}
#[test]
fn physics_momentum_large_mass() {
let source = r#"
fun momentum(mass: f64, velocity: f64) -> f64 {
return mass * velocity
}
"#;
let mut parser = Parser::new(source);
assert!(parser.parse().is_ok());
let large_mass = 1.0e38_f64;
let velocity = 1.0e8_f64;
let momentum = large_mass * velocity;
assert!(
momentum.is_finite(),
"Large but valid physics values should not overflow f64"
);
}
}
mod nan_infinity {
use super::*;
#[test]
fn nan_propagation_in_addition() {
let mut interpreter = Interpreter::new();
let nan = f64::NAN;
let expr = Expr::Binary {
left: Box::new(Expr::Literal(Literal::Float(nan))),
op: BinaryOp::Add,
right: Box::new(Expr::Literal(Literal::Float(42.0))),
};
let result = interpreter.eval(&expr);
match result {
Ok(Value::Float(f)) => {
assert!(f.is_nan(), "NaN + 42 should be NaN, got {}", f);
}
Ok(val) => panic!("Expected float, got {:?}", val),
Err(e) => panic!("Unexpected error: {:?}", e),
}
}
#[test]
fn nan_propagation_in_multiplication() {
let mut interpreter = Interpreter::new();
let nan = f64::NAN;
let expr = Expr::Binary {
left: Box::new(Expr::Literal(Literal::Float(nan))),
op: BinaryOp::Mul,
right: Box::new(Expr::Literal(Literal::Float(100.0))),
};
let result = interpreter.eval(&expr);
match result {
Ok(Value::Float(f)) => {
assert!(f.is_nan(), "NaN * 100 should be NaN");
}
_ => {}
}
}
#[test]
fn infinity_minus_infinity_is_nan() {
let mut interpreter = Interpreter::new();
let expr = Expr::Binary {
left: Box::new(Expr::Literal(Literal::Float(f64::INFINITY))),
op: BinaryOp::Sub,
right: Box::new(Expr::Literal(Literal::Float(f64::INFINITY))),
};
let result = interpreter.eval(&expr);
match result {
Ok(Value::Float(f)) => {
assert!(f.is_nan(), "Infinity - Infinity should be NaN, got {}", f);
}
_ => {}
}
}
#[test]
fn infinity_times_zero_is_nan() {
let mut interpreter = Interpreter::new();
let expr = Expr::Binary {
left: Box::new(Expr::Literal(Literal::Float(f64::INFINITY))),
op: BinaryOp::Mul,
right: Box::new(Expr::Literal(Literal::Float(0.0))),
};
let result = interpreter.eval(&expr);
match result {
Ok(Value::Float(f)) => {
assert!(f.is_nan(), "Infinity * 0 should be NaN, got {}", f);
}
_ => {}
}
}
#[test]
fn nan_comparison_always_false() {
let mut interpreter = Interpreter::new();
let nan = f64::NAN;
let expr = Expr::Binary {
left: Box::new(Expr::Literal(Literal::Float(nan))),
op: BinaryOp::Eq,
right: Box::new(Expr::Literal(Literal::Float(nan))),
};
let result = interpreter.eval(&expr);
match result {
Ok(Value::Bool(b)) => {
assert!(!b, "NaN == NaN should be false per IEEE 754");
}
Ok(val) => {
println!("NOTE: NaN == NaN returned non-bool: {:?}", val);
}
Err(e) => {
println!("NOTE: NaN comparison error: {:?}", e);
}
}
}
#[test]
fn infinity_comparison() {
let mut interpreter = Interpreter::new();
let expr = Expr::Binary {
left: Box::new(Expr::Literal(Literal::Float(f64::INFINITY))),
op: BinaryOp::Gt,
right: Box::new(Expr::Literal(Literal::Float(1.0e308))),
};
let result = interpreter.eval(&expr);
match result {
Ok(Value::Bool(b)) => {
assert!(b, "Infinity should be greater than 1.0e308");
}
_ => {}
}
}
}
mod precision {
use super::*;
#[test]
fn f64_precision_near_zero() {
let mut interpreter = Interpreter::new();
let tiny = 1.0e-308_f64;
let expr = Expr::Binary {
left: Box::new(Expr::Literal(Literal::Float(tiny))),
op: BinaryOp::Div,
right: Box::new(Expr::Literal(Literal::Float(10.0))),
};
let result = interpreter.eval(&expr);
match result {
Ok(Value::Float(f)) => {
assert!(
f.is_subnormal() || f == 0.0,
"Very small division should produce subnormal or zero"
);
}
_ => {}
}
}
#[test]
fn precision_loss_in_subtraction() {
let mut interpreter = Interpreter::new();
let a = 1.0e16_f64;
let b = 1.0e16_f64 - 1.0;
let expr = Expr::Binary {
left: Box::new(Expr::Literal(Literal::Float(a))),
op: BinaryOp::Sub,
right: Box::new(Expr::Literal(Literal::Float(b))),
};
let result = interpreter.eval(&expr);
match result {
Ok(Value::Float(f)) => {
println!("NOTE: (1e16) - (1e16-1) = {} (expected ~1.0)", f);
}
_ => {}
}
}
#[test]
fn carnot_efficiency_precision() {
let t_hot = 1000.0_f64;
let t_cold = 999.9999999_f64;
let efficiency = 1.0 - (t_cold / t_hot);
assert!(efficiency > 0.0, "Carnot efficiency should be positive");
assert!(
efficiency < 1.0e-6,
"Very close temps should give tiny efficiency"
);
}
#[test]
fn ideal_gas_law_precision() {
let gas_constant = 8.314_f64; let moles = 1.0e-20_f64; let temperature = 1.0e10_f64; let volume = 1.0e-30_f64;
let pressure = (moles * gas_constant * temperature) / volume;
assert!(
pressure.is_finite(),
"Extreme but valid gas law should give finite pressure"
);
}
}
mod negative_zero {
use super::*;
#[test]
fn negative_zero_equality() {
let mut interpreter = Interpreter::new();
let expr = Expr::Binary {
left: Box::new(Expr::Literal(Literal::Float(-0.0))),
op: BinaryOp::Eq,
right: Box::new(Expr::Literal(Literal::Float(0.0))),
};
let result = interpreter.eval(&expr);
match result {
Ok(Value::Bool(b)) => {
assert!(b, "-0.0 should equal 0.0 per IEEE 754");
}
_ => {}
}
}
#[test]
fn negative_zero_sign_preservation() {
let mut interpreter = Interpreter::new();
let expr = Expr::Binary {
left: Box::new(Expr::Literal(Literal::Float(-0.0))),
op: BinaryOp::Mul,
right: Box::new(Expr::Literal(Literal::Float(42.0))),
};
let result = interpreter.eval(&expr);
match result {
Ok(Value::Float(f)) => {
assert!(f == 0.0, "-0.0 * 42 should be 0");
if f.is_sign_negative() {
println!("NOTE: -0.0 * positive preserves negative sign");
} else {
println!("NOTE: -0.0 * positive produces positive zero");
}
}
_ => {}
}
}
#[test]
fn division_producing_negative_zero() {
let mut interpreter = Interpreter::new();
let expr = Expr::Binary {
left: Box::new(Expr::Literal(Literal::Float(-1.0))),
op: BinaryOp::Div,
right: Box::new(Expr::Literal(Literal::Float(f64::INFINITY))),
};
let result = interpreter.eval(&expr);
match result {
Ok(Value::Float(f)) => {
assert!(f == 0.0, "-1/Infinity should be 0");
if f.is_sign_negative() {
println!("NOTE: -1/Infinity = -0.0 (preserves sign)");
}
}
_ => {}
}
}
}
mod subnormal {
use super::*;
#[test]
fn subnormal_addition() {
let mut interpreter = Interpreter::new();
let subnormal = f64::MIN_POSITIVE / 2.0;
assert!(subnormal.is_subnormal(), "Test value should be subnormal");
let expr = Expr::Binary {
left: Box::new(Expr::Literal(Literal::Float(subnormal))),
op: BinaryOp::Add,
right: Box::new(Expr::Literal(Literal::Float(subnormal))),
};
let result = interpreter.eval(&expr);
match result {
Ok(Value::Float(f)) => {
println!(
"NOTE: subnormal + subnormal = {} (subnormal: {})",
f,
f.is_subnormal()
);
}
_ => {}
}
}
#[test]
fn subnormal_multiplication_underflow() {
let mut interpreter = Interpreter::new();
let subnormal = f64::MIN_POSITIVE / 2.0;
let expr = Expr::Binary {
left: Box::new(Expr::Literal(Literal::Float(subnormal))),
op: BinaryOp::Mul,
right: Box::new(Expr::Literal(Literal::Float(0.5))),
};
let result = interpreter.eval(&expr);
match result {
Ok(Value::Float(f)) => {
if f == 0.0 {
println!("NOTE: Subnormal underflow to zero");
} else if f.is_subnormal() {
println!("NOTE: Subnormal * 0.5 = {} (still subnormal)", f);
}
}
_ => {}
}
}
#[test]
fn gradual_underflow_in_physics() {
let source = r#"
fun heat_capacity_near_zero(temperature: f64) -> f64 {
// Debye model approximation: C ~ T^3 as T -> 0
let debye_temp = 300.0
let ratio = temperature / debye_temp
return ratio * ratio * ratio
}
"#;
let mut parser = Parser::new(source);
assert!(parser.parse().is_ok());
let very_low_t = 1.0e-105_f64;
let ratio = very_low_t / 300.0;
let c = ratio * ratio * ratio;
println!(
"NOTE: Heat capacity at T=1e-105: {} (subnormal: {})",
c,
c.is_subnormal()
);
}
}
mod extreme_values {
use super::*;
#[test]
fn very_large_number_operations() {
let mut interpreter = Interpreter::new();
let large = 1.0e307_f64;
let expr = Expr::Binary {
left: Box::new(Expr::Literal(Literal::Float(large))),
op: BinaryOp::Mul,
right: Box::new(Expr::Literal(Literal::Float(10.0))),
};
let result = interpreter.eval(&expr);
match result {
Ok(Value::Float(f)) => {
if f.is_infinite() {
println!("NOTE: Large * 10 overflows to infinity");
} else {
println!("NOTE: Large * 10 = {} (still finite)", f);
}
}
_ => {}
}
}
#[test]
fn astronomical_distance_calculations() {
let light_year_meters = 9.461e15_f64;
let andromeda_ly = 2.537e6_f64;
let distance = andromeda_ly * light_year_meters;
assert!(
distance.is_finite(),
"Astronomical distances should be finite"
);
assert!(
distance > 1.0e22,
"Distance to Andromeda should be > 1e22 meters"
);
}
#[test]
fn quantum_scale_calculations() {
let planck_length = 1.616e-35_f64;
let planck_time = 5.391e-44_f64;
let speed = planck_length / planck_time;
assert!(speed.is_finite(), "Planck speed calculation should work");
let area = planck_length * planck_length;
assert!(
area.is_finite() || area == 0.0,
"Planck area should be finite or zero (underflow)"
);
}
}
mod physics_formulas {
use super::*;
#[test]
fn schwarzschild_radius_zero_mass() {
let g = 6.67430e-11_f64;
let c = 299792458.0_f64;
let mass = 0.0_f64;
let r_s = (2.0 * g * mass) / (c * c);
assert!(r_s == 0.0, "Schwarzschild radius of zero mass should be 0");
}
#[test]
fn lorentz_factor_at_light_speed() {
let c = 299792458.0_f64;
let v = c;
let v_ratio_sq = (v / c) * (v / c);
let denominator = 1.0 - v_ratio_sq;
if denominator == 0.0 {
let gamma = 1.0 / denominator.sqrt();
assert!(
gamma.is_infinite(),
"Lorentz factor at c should be infinite"
);
} else {
println!(
"NOTE: Floating point gives non-zero denominator: {}",
denominator
);
}
}
#[test]
fn coulomb_force_overlapping_charges() {
let k = 8.99e9_f64; let q1 = 1.6e-19_f64; let q2 = 1.6e-19_f64;
let r = 0.0_f64;
if r == 0.0 {
println!("NOTE: Coulomb force at r=0 is undefined (singularity)");
}
}
#[test]
fn wave_interference_at_node() {
let amplitude = 100.0_f64;
let phase1 = 0.0_f64;
let phase2 = std::f64::consts::PI;
let wave1 = amplitude * phase1.cos();
let wave2 = amplitude * phase2.cos();
let sum = wave1 + wave2;
assert!(sum.abs() < 1e-10, "Destructive interference should cancel");
}
#[test]
fn relativistic_mass_at_rest() {
let m0 = 9.109e-31_f64; let v = 0.0_f64;
let c = 299792458.0_f64;
let v_ratio_sq = (v / c) * (v / c);
let gamma = 1.0 / (1.0 - v_ratio_sq).sqrt();
let m_rel = m0 * gamma;
assert!(
(m_rel - m0).abs() < 1e-45,
"Relativistic mass at rest should equal rest mass"
);
}
}