use std::time::{Duration, Instant};
use symplex::prelude::*;
fn s(e: &Ex) -> String {
format!("{e}")
}
fn timed<T>(limit_ms: u64, what: &str, f: impl FnOnce() -> T) -> T {
let t = Instant::now();
let r = f();
let dt = t.elapsed();
assert!(
dt < Duration::from_millis(limit_ms),
"{what} took {dt:?} (limit {limit_ms} ms)"
);
r
}
#[test]
fn sqrt_of_large_integer_with_square_factor_is_fast() {
let ctx = Context::new();
let n = ctx.from_i128(3912608155036063);
let r = timed(50, "sqrt(3912608155036063)", || n.sqrt().eval());
assert_eq!(s(&r), "23641997*sqrt(7)");
}
#[test]
fn sqrt_of_thirty_digit_integer_is_fast() {
let ctx = Context::new();
let n = ctx.parse("343881576126216474609375000000").unwrap();
let r = timed(50, "sqrt(30-digit)", || n.sqrt().eval());
assert_eq!(s(&r), "221643721875000*sqrt(7)");
let r = timed(50, "simplify sqrt(30-digit)", || n.sqrt().simplify());
assert_eq!(s(&r), "221643721875000*sqrt(7)");
}
#[test]
fn sqrt_of_huge_composite_without_small_factors_is_left_alone_quickly() {
let ctx = Context::new();
let n = ctx
.parse("40000000000000002239200000000000000867204")
.unwrap();
let r = timed(100, "sqrt(41-digit semiprime)", || n.sqrt());
assert_eq!(s(&r), "2*sqrt(10000000000000000559800000000000000216801)");
let e = timed(100, "eval", || r.eval());
assert_eq!(e, r);
}
#[test]
fn sqrt_of_square_of_huge_prime_folds() {
let ctx = Context::new();
let n = ctx
.parse("300000000000000003060000000000000007803")
.unwrap();
let r = timed(100, "sqrt(3p²)", || n.sqrt());
assert_eq!(s(&r), "10000000000000000051*sqrt(3)");
}
#[test]
fn sqrt_12_is_2_sqrt_3() {
let ctx = Context::new();
let r = ctx.int(12).sqrt();
assert_eq!(s(&r), "2*sqrt(3)");
assert_eq!(r, ctx.int(2) * ctx.int(3).sqrt());
}
#[test]
fn sqrt_of_perfect_square_rational_folds() {
let ctx = Context::new();
assert_eq!(ctx.rational(4, 9).sqrt(), ctx.rational(2, 3));
assert_eq!(ctx.rational(4, 9).sqrt().eval(), ctx.rational(2, 3));
assert_eq!(
ctx.rational(4, 9).pow(&ctx.rational(-1, 2)),
ctx.rational(3, 2)
);
assert_eq!(
ctx.rational(8, 27).pow(&ctx.rational(1, 3)),
ctx.rational(2, 3)
);
}
#[test]
fn sqrt_one_half_normal_form_unifies_all_constructions() {
let ctx = Context::new();
let want = ctx.rational(1, 2) * ctx.int(2).sqrt();
assert_eq!(s(&want), "1/2*sqrt(2)");
assert_eq!(ctx.rational(1, 2).sqrt(), want, "sqrt(1/2)");
assert_eq!(ctx.int(2).sqrt() / ctx.int(2), want, "sqrt(2)/2");
assert_eq!(ctx.int(1) / ctx.int(2).sqrt(), want, "1/sqrt(2)");
assert_eq!(ctx.int(2).pow(&ctx.rational(-1, 2)), want, "2^(-1/2)");
assert_eq!(ctx.parse("sqrt(1/2)").unwrap(), want, "parse sqrt(1/2)");
assert_eq!(ctx.parse("2^(-1/2)").unwrap(), want, "parse 2^(-1/2)");
assert_eq!(ctx.parse("1/sqrt(2)").unwrap(), want, "parse 1/sqrt(2)");
}
#[test]
fn rational_radicand_with_square_factor() {
let ctx = Context::new();
let r = ctx.rational(8, 3).sqrt();
assert_eq!(s(&r), "2/3*sqrt(6)");
assert_eq!(s(&ctx.rational(2, 3).sqrt()), "1/3*sqrt(6)");
assert_eq!(
s(&ctx.rational(2, 3).pow(&ctx.rational(-1, 2))),
"1/2*sqrt(6)"
);
}
#[test]
fn negative_fractional_exponent_beyond_minus_one() {
let ctx = Context::new();
let r = ctx.int(2).pow(&ctx.rational(-3, 2));
assert_eq!(s(&r), "1/4*sqrt(2)");
let v = r.eval_f64().unwrap();
assert!((v - 2f64.powf(-1.5)).abs() < 1e-15, "{v}");
}
#[test]
fn cube_root_of_54_is_3_cbrt_2() {
let ctx = Context::new();
let r = ctx.int(54).pow(&ctx.rational(1, 3));
assert_eq!(s(&r), "3*cbrt(2)");
assert_eq!(r, ctx.int(3) * ctx.int(2).pow(&ctx.rational(1, 3)));
}
#[test]
fn negative_radicands() {
let ctx = Context::new();
let i = ctx.i_unit();
assert_eq!(ctx.int(-4).sqrt(), ctx.int(2) * &i, "sqrt(-4)");
assert_eq!(
ctx.int(-12).sqrt(),
ctx.int(2) * ctx.int(3).sqrt() * &i,
"sqrt(-12)"
);
assert_eq!(
ctx.rational(-4, 9).sqrt(),
ctx.rational(2, 3) * &i,
"sqrt(-4/9)"
);
assert_eq!(
ctx.rational(-1, 2).sqrt(),
ctx.rational(1, 2) * ctx.int(2).sqrt() * &i,
"sqrt(-1/2)"
);
}
#[test]
fn radical_normal_form_is_numerically_faithful() {
let ctx = Context::new();
for (p, q, a, b) in [
(1i64, 2i64, 1i64, 2i64),
(2, 3, -1, 2),
(8, 3, 1, 2),
(2, 3, 1, 3),
(5, 7, -2, 3),
(45, 8, 3, 2),
(12, 1, 3, 2),
(2, 1, -3, 2),
(1000, 1, 2, 3),
] {
let e = ctx.rational(p, q).pow(&ctx.rational(a, b));
let got = e.eval_f64().unwrap();
let want = (p as f64 / q as f64).powf(a as f64 / b as f64);
assert!(
((got - want) / want).abs() < 1e-14,
"({p}/{q})^({a}/{b}) = {e} = {got}, want {want}"
);
}
}
#[test]
fn products_of_radicals_still_combine() {
let ctx = Context::new();
assert_eq!(ctx.int(2).sqrt() * ctx.rational(1, 2).sqrt(), ctx.int(1));
assert_eq!(ctx.int(2).sqrt() * ctx.int(3).sqrt(), ctx.int(6).sqrt());
assert_eq!(
ctx.int(6).sqrt() * ctx.int(3).sqrt(),
ctx.int(3) * ctx.int(2).sqrt()
);
assert_eq!(ctx.int(2).sqrt() * ctx.int(2).sqrt(), ctx.int(2));
}