use sankhya::babylonian;
use sankhya::chinese;
use sankhya::egyptian;
use sankhya::greek;
use sankhya::mayan;
use sankhya::roman;
use sankhya::vedic;
#[test]
fn mayan_long_count_dec_21_2012() {
let days = 13u64 * 144_000;
let lc = mayan::LongCount::from_days(days).unwrap();
assert_eq!(lc.baktun, 13);
assert_eq!(lc.katun, 0);
assert_eq!(lc.tun, 0);
assert_eq!(lc.uinal, 0);
assert_eq!(lc.kin, 0);
assert_eq!(lc.to_string(), "13.0.0.0.0");
}
#[test]
fn mayan_long_count_roundtrip() {
for days in [0, 1, 20, 360, 7200, 144_000, 1_872_000, 2_000_000] {
let lc = mayan::LongCount::from_days(days).unwrap();
assert_eq!(lc.to_days(), days, "roundtrip failed for {days} days");
}
}
#[test]
fn mayan_vigesimal_roundtrip_400() {
let digits = mayan::to_vigesimal(400);
assert_eq!(digits, vec![1, 0, 0]);
assert_eq!(mayan::from_vigesimal(&digits).unwrap(), 400);
}
#[test]
fn mayan_vigesimal_roundtrip_large() {
for n in [0, 1, 19, 20, 399, 400, 8000, 160_000, 1_000_000] {
let digits = mayan::to_vigesimal(n);
assert_eq!(
mayan::from_vigesimal(&digits).unwrap(),
n,
"vigesimal roundtrip failed for {n}"
);
}
}
#[test]
fn mayan_calendar_round_cycle() {
let (tz1, haab1) = mayan::calendar_round(0);
let (tz2, haab2) = mayan::calendar_round(mayan::CALENDAR_ROUND_DAYS);
assert_eq!(tz1, tz2, "Tzolkin should repeat after 18,980 days");
assert_eq!(haab1, haab2, "Haab should repeat after 18,980 days");
assert_eq!(mayan::CALENDAR_ROUND_DAYS, 18_980);
}
#[test]
fn mayan_tzolkin_260_day_cycle() {
let tz1 = mayan::Tzolkin::from_days(0);
let tz2 = mayan::Tzolkin::from_days(260);
assert_eq!(tz1, tz2);
}
#[test]
fn mayan_haab_365_day_cycle() {
let h1 = mayan::Haab::from_days(0);
let h2 = mayan::Haab::from_days(365);
assert_eq!(h1, h2);
}
#[test]
fn mayan_venus_synodic_period() {
assert!((mayan::VENUS_SYNODIC_PERIOD - 583.92).abs() < 0.01);
assert_eq!(584 * 5, 365 * 8);
}
#[test]
fn mayan_venus_phases_sum_to_584() {
assert_eq!(236 + 90 + 250 + 8, 584);
}
#[test]
fn babylonian_sexagesimal_roundtrip_3600() {
let digits = babylonian::to_sexagesimal(3600);
assert_eq!(digits, vec![1, 0, 0]);
assert_eq!(babylonian::from_sexagesimal(&digits).unwrap(), 3600);
}
#[test]
fn babylonian_sexagesimal_roundtrip_large() {
for n in [0, 1, 59, 60, 3599, 3600, 216_000, 12_960_000] {
let digits = babylonian::to_sexagesimal(n);
assert_eq!(
babylonian::from_sexagesimal(&digits).unwrap(),
n,
"sexagesimal roundtrip failed for {n}"
);
}
}
#[test]
fn babylonian_sqrt2_herons_method() {
let result = babylonian::babylonian_sqrt(2.0, 10).unwrap();
assert!(
(result - std::f64::consts::SQRT_2).abs() < 1e-15,
"Heron's method sqrt(2) = {result}, expected {}",
std::f64::consts::SQRT_2
);
}
#[test]
fn babylonian_sqrt_perfect_squares() {
for n in [4.0, 9.0, 16.0, 25.0, 100.0] {
let result = babylonian::babylonian_sqrt(n, 20).unwrap();
let expected = n.sqrt();
assert!(
(result - expected).abs() < 1e-12,
"sqrt({n}) = {result}, expected {expected}"
);
}
}
#[test]
fn babylonian_plimpton_322_all_valid() {
let triples = babylonian::generate_plimpton_triples();
assert_eq!(triples.len(), 15);
for (a, b, c) in &triples {
assert_eq!(a * a + b * b, c * c, "invalid triple: ({a}, {b}, {c})");
}
}
#[test]
fn babylonian_reciprocal_table_spot_check() {
let table = babylonian::reciprocal_table();
assert_eq!(table.get(&2).unwrap(), &vec![30]);
assert_eq!(table.get(&3).unwrap(), &vec![20]);
assert_eq!(table.get(&4).unwrap(), &vec![15]);
assert_eq!(table.get(&8).unwrap(), &vec![7, 30]);
}
#[test]
fn egyptian_decompose_2_3() {
let result = egyptian::decompose(2, 3).unwrap();
assert_eq!(result, vec![2, 6]);
let sum: f64 = result.iter().map(|&d| 1.0 / d as f64).sum();
assert!((sum - 2.0 / 3.0).abs() < 1e-15);
}
#[test]
fn egyptian_decompose_various() {
let result = egyptian::decompose(3, 7).unwrap();
let sum: f64 = result.iter().map(|&d| 1.0 / d as f64).sum();
assert!(
(sum - 3.0 / 7.0).abs() < 1e-10,
"3/7 decomposition sum = {sum}"
);
}
#[test]
fn egyptian_multiply_12_13() {
assert_eq!(egyptian::egyptian_multiply(12, 13), 156);
}
#[test]
fn egyptian_multiply_various() {
assert_eq!(egyptian::egyptian_multiply(0, 100), 0);
assert_eq!(egyptian::egyptian_multiply(1, 1), 1);
assert_eq!(egyptian::egyptian_multiply(7, 11), 77);
assert_eq!(egyptian::egyptian_multiply(100, 100), 10_000);
}
#[test]
fn egyptian_divide_basic() {
let (q, rem) = egyptian::egyptian_divide(10, 3).unwrap();
assert_eq!(q, 3);
let rem_sum: f64 = rem.iter().map(|&d| 1.0 / d as f64).sum();
assert!((rem_sum - 1.0 / 3.0).abs() < 1e-15);
}
#[test]
fn egyptian_decans_count() {
assert_eq!(egyptian::DECANS.len(), 36);
for (i, decan) in egyptian::DECANS.iter().enumerate() {
assert_eq!(decan.number, (i + 1) as u8);
assert!((decan.ecliptic_longitude - (i as f64 * 10.0)).abs() < 1e-10);
}
}
#[test]
fn vedic_sulba_sqrt2_baudhayana() {
let approx = vedic::sulba_sqrt2();
let expected = 577.0 / 408.0;
assert!(
(approx - expected).abs() < 1e-15,
"sulba_sqrt2() = {approx}, expected {expected}"
);
assert!(
(approx - std::f64::consts::SQRT_2).abs() < 1e-5,
"Baudhayana's sqrt(2) should be accurate to 5 decimal places"
);
}
#[test]
fn vedic_meru_prastara_matches_pascal() {
let triangle = vedic::meru_prastara(5).unwrap();
assert_eq!(triangle.len(), 5);
assert_eq!(triangle[0], vec![1]);
assert_eq!(triangle[1], vec![1, 1]);
assert_eq!(triangle[2], vec![1, 2, 1]);
assert_eq!(triangle[3], vec![1, 3, 3, 1]);
assert_eq!(triangle[4], vec![1, 4, 6, 4, 1]);
}
#[test]
fn vedic_meru_prastara_row_sums() {
let triangle = vedic::meru_prastara(10).unwrap();
for (i, row) in triangle.iter().enumerate() {
let sum: u64 = row.iter().sum();
assert_eq!(sum, 1u64 << i, "row {i} sum should be 2^{i}");
}
}
#[test]
fn vedic_sulba_diagonal() {
assert!((vedic::sulba_diagonal(3.0, 4.0) - 5.0).abs() < 1e-15);
assert!((vedic::sulba_diagonal(5.0, 12.0) - 13.0).abs() < 1e-10);
}
#[test]
fn vedic_katapayadi_roundtrip() {
for n in [0, 1, 42, 123, 9876] {
let encoded = vedic::katapayadi_encode(n);
let decoded = vedic::katapayadi_decode(&encoded).unwrap();
assert_eq!(decoded, n, "Katapayadi roundtrip failed for {n}");
}
}
#[test]
fn chinese_remainder_sun_tzu_original() {
let result = chinese::chinese_remainder(&[(2, 3), (3, 5), (2, 7)]);
assert_eq!(result, Some(23));
}
#[test]
fn chinese_remainder_simple() {
let result = chinese::chinese_remainder(&[(1, 2), (2, 3)]);
assert_eq!(result, Some(5));
}
#[test]
fn chinese_magic_square_3x3_lo_shu() {
let sq = chinese::magic_square(3).unwrap();
assert!(chinese::is_magic_square(&sq));
let magic_sum: u64 = sq[0].iter().sum();
assert_eq!(magic_sum, 15);
let mut all: Vec<u64> = sq.iter().flat_map(|row| row.iter().copied()).collect();
all.sort();
assert_eq!(all, (1..=9).collect::<Vec<u64>>());
}
#[test]
fn chinese_magic_square_5x5() {
let sq = chinese::magic_square(5).unwrap();
assert!(chinese::is_magic_square(&sq));
let magic_sum: u64 = sq[0].iter().sum();
assert_eq!(magic_sum, 65);
}
#[test]
fn chinese_rod_arithmetic() {
let a = chinese::RodNumeral::new(42);
let b = chinese::RodNumeral::new(17);
assert_eq!(chinese::rod_add(a, b).value, 59);
assert_eq!(chinese::rod_subtract(a, b).value, 25);
assert_eq!(chinese::rod_multiply(a, b).value, 714);
}
#[test]
fn greek_archimedes_pi_bounds() {
let (lower, upper) = greek::archimedes_pi(10);
assert!(
lower < std::f64::consts::PI,
"lower bound {lower} should be < pi"
);
assert!(
upper > std::f64::consts::PI,
"upper bound {upper} should be > pi"
);
assert!(
(upper - lower) < 1e-5,
"bounds should be tight after 10 iterations"
);
}
#[test]
fn greek_archimedes_pi_initial() {
let (lower, upper) = greek::archimedes_pi(0);
assert!((lower - 3.0).abs() < 1e-10);
assert!((upper - 2.0 * 3.0_f64.sqrt()).abs() < 1e-10);
}
#[test]
fn greek_gcd_48_18() {
assert_eq!(greek::gcd(48, 18), 6);
}
#[test]
fn greek_gcd_coprime() {
assert_eq!(greek::gcd(17, 13), 1);
}
#[test]
fn greek_sieve_count() {
let primes = greek::sieve(100);
assert_eq!(primes.len(), 25);
assert_eq!(primes[0], 2);
assert_eq!(primes[24], 97);
}
#[test]
fn greek_sieve_10000() {
let primes = greek::sieve(10_000);
assert_eq!(primes.len(), 1229);
}
#[test]
fn greek_golden_ratio() {
let expected = (1.0_f64 + 5.0_f64.sqrt()) / 2.0;
assert!((greek::PHI - expected).abs() < 1e-15);
}
#[test]
fn greek_fibonacci_ratio_converges() {
let ratio = greek::fibonacci_ratio(40);
assert!((ratio - greek::PHI).abs() < 1e-10);
}
#[test]
fn greek_antikythera_metonic_cycle() {
let ratios = greek::antikythera_gear_ratios();
let metonic = ratios.get("Metonic").unwrap();
assert_eq!(metonic.gear_teeth, 235);
}
#[test]
fn roman_to_str_notable_years() {
assert_eq!(roman::to_roman_str(1776).unwrap(), "MDCCLXXVI");
assert_eq!(roman::to_roman_str(1999).unwrap(), "MCMXCIX");
assert_eq!(roman::to_roman_str(2024).unwrap(), "MMXXIV");
assert_eq!(roman::to_roman_str(3999).unwrap(), "MMMCMXCIX");
}
#[test]
fn roman_parse_roundtrip_all() {
for n in 1..=3999u32 {
let s = roman::to_roman_str(n).unwrap();
let parsed = roman::from_roman(&s).unwrap();
assert_eq!(parsed, n, "roundtrip failed for {n}");
}
}
#[test]
fn roman_subtractive_pairs() {
assert_eq!(roman::from_roman("IV").unwrap(), 4);
assert_eq!(roman::from_roman("IX").unwrap(), 9);
assert_eq!(roman::from_roman("XL").unwrap(), 40);
assert_eq!(roman::from_roman("XC").unwrap(), 90);
assert_eq!(roman::from_roman("CD").unwrap(), 400);
assert_eq!(roman::from_roman("CM").unwrap(), 900);
}
#[test]
fn roman_arithmetic_basic() {
let sum = roman::roman_add(14, 28).unwrap();
assert_eq!(sum.value(), 42);
assert_eq!(sum.text(), "XLII");
let diff = roman::roman_subtract(100, 1).unwrap();
assert_eq!(diff.value(), 99);
assert_eq!(diff.text(), "XCIX");
let product = roman::roman_multiply(12, 12).unwrap();
assert_eq!(product.value(), 144);
assert_eq!(product.text(), "CXLIV");
}
#[test]
fn roman_division_with_remainder() {
let (q, r) = roman::roman_divide(17, 5).unwrap();
assert_eq!(q.value(), 3);
assert_eq!(r.unwrap().value(), 2);
}
#[test]
fn roman_validation() {
assert!(roman::is_valid_roman("MCMXCIX"));
assert!(!roman::is_valid_roman("IIII"));
assert!(!roman::is_valid_roman("VV"));
assert!(!roman::is_valid_roman("IC"));
}
#[test]
fn serde_roundtrip_long_count() {
let lc = mayan::LongCount::from_days(1_872_000).unwrap();
let json = serde_json::to_string(&lc).unwrap();
let lc2: mayan::LongCount = serde_json::from_str(&json).unwrap();
assert_eq!(lc, lc2);
}
#[test]
fn serde_roundtrip_tzolkin() {
let tz = mayan::Tzolkin::from_days(0);
let json = serde_json::to_string(&tz).unwrap();
let tz2: mayan::Tzolkin = serde_json::from_str(&json).unwrap();
assert_eq!(tz, tz2);
}
#[test]
fn serde_roundtrip_babylonian_numeral() {
let bn = babylonian::BabylonianNumeral::from_value(42).unwrap();
let json = serde_json::to_string(&bn).unwrap();
let bn2: babylonian::BabylonianNumeral = serde_json::from_str(&json).unwrap();
assert_eq!(bn, bn2);
}
#[test]
fn serde_roundtrip_mayan_numeral() {
let mn = mayan::MayanNumeral::from_value(13).unwrap();
let json = serde_json::to_string(&mn).unwrap();
let mn2: mayan::MayanNumeral = serde_json::from_str(&json).unwrap();
assert_eq!(mn, mn2);
}
#[test]
fn serde_roundtrip_haab() {
let h = mayan::Haab::from_days(100);
let json = serde_json::to_string(&h).unwrap();
let h2: mayan::Haab = serde_json::from_str(&json).unwrap();
assert_eq!(h, h2);
}
#[test]
fn serde_roundtrip_rod_numeral() {
let rn = chinese::RodNumeral::new(42);
let json = serde_json::to_string(&rn).unwrap();
let rn2: chinese::RodNumeral = serde_json::from_str(&json).unwrap();
assert_eq!(rn, rn2);
}
#[test]
fn serde_roundtrip_venus_phase() {
let phase = mayan::venus_phase(100);
let json = serde_json::to_string(&phase).unwrap();
let phase2: mayan::VenusPhase = serde_json::from_str(&json).unwrap();
assert_eq!(phase, phase2);
}
#[test]
fn serde_roundtrip_roman_numeral() {
let r = roman::RomanNumeral::from_value(1776).unwrap();
let json = serde_json::to_string(&r).unwrap();
let r2: roman::RomanNumeral = serde_json::from_str(&json).unwrap();
assert_eq!(r, r2);
}
#[test]
fn serde_roundtrip_error() {
let err = sankhya::SankhyaError::InvalidDate("test".into());
let json = serde_json::to_string(&err).unwrap();
let err2: sankhya::SankhyaError = serde_json::from_str(&json).unwrap();
assert_eq!(err, err2);
}