#![cfg(table_format = "q128_128")]
use g_math::fixed_point::DecimalFixed;
use std::path::PathBuf;
const SCALE: u8 = 28;
const TOL_LSB: i128 = 1;
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
enum Cls {
Exact, Low, Tie, High, }
impl Cls {
fn parse(s: &str) -> Cls {
match s {
"Z" => Cls::Exact,
"L" => Cls::Low,
"E" => Cls::Tie,
"G" => Cls::High,
other => panic!("unknown class column: {other:?}"),
}
}
}
#[derive(Clone, Copy)]
enum Mode {
HalfAwayFromZero, HalfToEven, }
fn oracle_correctly_rounded(floor: i128, cls: Cls, mode: Mode) -> i128 {
match cls {
Cls::Exact => floor,
Cls::Low => floor,
Cls::High => floor + 1,
Cls::Tie => match mode {
Mode::HalfAwayFromZero => {
if floor >= 0 {
floor + 1
} else {
floor
}
}
Mode::HalfToEven => {
if floor.rem_euclid(2) == 0 {
floor
} else {
floor + 1
}
}
},
}
}
struct GoldenLine {
input: i128,
floor: i128,
cls: Cls,
}
fn load(func: &str) -> Vec<GoldenLine> {
let mut path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
path.push("tests/oracle_golden");
path.push(format!("{func}_s{SCALE}.txt"));
let text = std::fs::read_to_string(&path)
.unwrap_or_else(|e| panic!("cannot read {}: {e}", path.display()));
let mut out = Vec::new();
for line in text.lines() {
let line = line.trim();
if line.is_empty() || line.starts_with('#') {
continue;
}
let cols: Vec<&str> = line.split('\t').collect();
assert_eq!(cols.len(), 3, "unary golden line must have 3 columns: {line:?}");
out.push(GoldenLine {
input: cols[0].parse().expect("input_raw i128"),
floor: cols[1].parse().expect("floor_raw i128"),
cls: Cls::parse(cols[2]),
});
}
assert!(!out.is_empty(), "no golden lines for {func}");
out
}
#[derive(Default)]
struct Report {
n: usize,
correctly_rounded: usize, within_tol: usize, violations: usize, max_delta: i128,
ties: usize, tie_divergences: usize, worst_input: i128,
}
fn run(func: &str, compute: impl Fn(DecimalFixed<28>) -> i128) -> Report {
let mut r = Report::default();
for g in load(func) {
r.n += 1;
let actual = compute(DecimalFixed::<28>::from_raw(g.input));
let expected = oracle_correctly_rounded(g.floor, g.cls, Mode::HalfAwayFromZero);
let even = oracle_correctly_rounded(g.floor, g.cls, Mode::HalfToEven);
if expected != even {
r.tie_divergences += 1;
}
if g.cls == Cls::Tie {
r.ties += 1;
}
let delta = (actual - expected).abs();
if delta == 0 {
r.correctly_rounded += 1;
} else if delta <= TOL_LSB {
r.within_tol += 1;
} else {
r.violations += 1;
}
if delta > r.max_delta {
r.max_delta = delta;
r.worst_input = g.input;
}
}
r
}
fn report_and_assert(func: &str, r: &Report) {
eprintln!(
"[decimal/{func}] n={} correct={} within_tol(<= {})={} violations={} \
max_delta={} ties(E)={} financial_gap(away!=even)={}{}",
r.n,
r.correctly_rounded,
TOL_LSB,
r.within_tol,
r.violations,
r.max_delta,
r.ties,
r.tie_divergences,
if r.violations > 0 {
format!(" worst_input={}", r.worst_input)
} else {
String::new()
},
);
assert_eq!(
r.violations, 0,
"{func}: {} contract violations (max_delta={} LSB > tol {}), worst input_raw={}",
r.violations, r.max_delta, TOL_LSB, r.worst_input
);
}
#[test]
fn decimal_exp_contract() {
let r = run("exp", |d| d.exp().raw_value());
report_and_assert("exp", &r);
}
#[test]
fn decimal_ln_contract() {
let r = run("ln", |d| d.ln().raw_value());
report_and_assert("ln", &r);
}
#[test]
fn decimal_sqrt_contract() {
let r = run("sqrt", |d| d.sqrt().raw_value());
report_and_assert("sqrt", &r);
}
#[test]
fn decimal_sin_contract() {
let r = run("sin", |d| d.sin().raw_value());
report_and_assert("sin", &r);
}
fn oracle2_dir() -> Option<PathBuf> {
std::env::var_os("GMATH_DECIMAL_SCALED_GOLDEN").map(PathBuf::from)
}
fn parse_unary_file(path: &std::path::Path) -> Vec<GoldenLine> {
let text = std::fs::read_to_string(path)
.unwrap_or_else(|e| panic!("cannot read {}: {e}", path.display()));
let mut out = Vec::new();
for line in text.lines() {
let line = line.trim();
if line.is_empty() || line.starts_with('#') {
continue;
}
let cols: Vec<&str> = line.split('\t').collect();
if cols.len() != 3 {
continue; }
let (Ok(input), Ok(floor)) = (cols[0].parse::<i128>(), cols[1].parse::<i128>()) else {
continue; };
out.push(GoldenLine { input, floor, cls: Cls::parse(cols[2]) });
}
out
}
#[derive(Default, Clone)]
struct O2 {
n: usize,
exact: usize, one: usize, small: usize, large: usize, skipped_domain: usize,
skipped_range: usize,
errored: usize, max_delta: i128,
worst_input: i128,
worst_scale: u8,
ties: usize,
financial_gap: usize,
}
impl O2 {
fn merge(&mut self, o: &O2) {
self.n += o.n;
self.exact += o.exact;
self.one += o.one;
self.small += o.small;
self.large += o.large;
self.skipped_domain += o.skipped_domain;
self.skipped_range += o.skipped_range;
self.errored += o.errored;
self.ties += o.ties;
self.financial_gap += o.financial_gap;
if o.max_delta > self.max_delta {
self.max_delta = o.max_delta;
self.worst_input = o.worst_input;
self.worst_scale = o.worst_scale;
}
}
}
fn pow10_i128(s: u8) -> i128 {
10i128.pow(s as u32)
}
fn in_domain(func: &str, input: i128, s: u8) -> bool {
let one = pow10_i128(s);
match func {
"ln" => input > 0,
"sqrt" => input >= 0,
"asin" | "acos" => input.unsigned_abs() <= one as u128,
"atanh" => input.unsigned_abs() < one as u128,
"acosh" => input >= one,
_ => true, }
}
fn compute_unary<const S: u8>(func: &str, input: i128) -> i128 {
let d = DecimalFixed::<S>::from_raw(input);
match func {
"exp" => d.exp().raw_value(),
"ln" => d.ln().raw_value(),
"sqrt" => d.sqrt().raw_value(),
"sin" => d.sin().raw_value(),
"cos" => d.cos().raw_value(),
"tan" => d.tan().raw_value(),
"atan" => d.atan().raw_value(),
"asin" => d.asin().raw_value(),
"acos" => d.acos().raw_value(),
"sinh" => d.sinh().raw_value(),
"cosh" => d.cosh().raw_value(),
"tanh" => d.tanh().raw_value(),
"asinh" => d.asinh().raw_value(),
"acosh" => d.acosh().raw_value(),
"atanh" => d.atanh().raw_value(),
other => panic!("compute_unary: unknown func {other:?}"),
}
}
fn grade_scale<const S: u8>(func: &str, lines: &[GoldenLine], r: &mut O2) {
for g in lines {
if !in_domain(func, g.input, S) {
r.skipped_domain += 1;
continue;
}
if g.floor.checked_abs().map_or(true, |a| a > i128::MAX / 2) {
r.skipped_range += 1;
continue;
}
r.n += 1;
if g.cls == Cls::Tie {
r.ties += 1;
}
let expected = oracle_correctly_rounded(g.floor, g.cls, Mode::HalfAwayFromZero);
if expected != oracle_correctly_rounded(g.floor, g.cls, Mode::HalfToEven) {
r.financial_gap += 1;
}
let computed = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
compute_unary::<S>(func, g.input)
}));
let actual = match computed {
Ok(v) => v,
Err(_) => {
r.errored += 1;
continue;
}
};
let delta = (actual - expected).abs();
match delta {
0 => r.exact += 1,
1 => r.one += 1,
2..=8 => r.small += 1,
_ => r.large += 1,
}
if delta > r.max_delta {
r.max_delta = delta;
r.worst_input = g.input;
r.worst_scale = S;
}
}
}
fn dispatch_scale(scale: u8, func: &str, lines: &[GoldenLine], r: &mut O2) {
macro_rules! arms {
($($s:literal),* $(,)?) => {
match scale {
$( $s => grade_scale::<$s>(func, lines, r), )*
_ => r.skipped_range += lines.len(),
}
};
}
arms!(0, 2, 3, 4, 6, 9, 10, 12, 13, 17, 18, 19, 28, 37);
}
fn report_o2(label: &str, r: &O2) {
eprintln!(
"[oracle2/{label}] n={} exact={} 1LSB={} 2-8={} >8={} max_delta={} \
errored={} skip_dom={} skip_rng={} ties(E)={} fin_gap={}{}",
r.n, r.exact, r.one, r.small, r.large, r.max_delta, r.errored,
r.skipped_domain, r.skipped_range, r.ties, r.financial_gap,
if r.max_delta > 1 {
format!(" worst=(input={}, scale={})", r.worst_input, r.worst_scale)
} else {
String::new()
},
);
}
fn gate_policy(func: &str) -> Option<i128> {
match func {
"exp" | "ln" | "sqrt" | "sin" | "cos" | "atan" | "tan" | "asin" | "acos"
| "sinh" | "cosh" | "tanh" | "asinh" | "acosh" | "atanh" => Some(0),
_ => None,
}
}
#[test]
fn oracle2_decimal_sweep() {
let Some(dir) = oracle2_dir() else {
eprintln!("[oracle2] SKIP — set GMATH_DECIMAL_SCALED_GOLDEN to a decimal-scaled clone");
return;
};
const FUNCS: &[&str] = &[
"exp", "ln", "sqrt", "sin", "cos", "tan", "atan", "asin", "acos",
"sinh", "cosh", "tanh", "asinh", "acosh", "atanh",
];
const SCALES: &[u8] = &[0, 2, 3, 4, 6, 9, 10, 12, 13, 17, 18, 19, 28, 37];
let prev_hook = std::panic::take_hook();
std::panic::set_hook(Box::new(|_| {}));
let mut grand = O2::default();
let mut results: Vec<(&str, O2)> = Vec::new();
for func in FUNCS {
let mut r = O2::default();
for tier in [18u8, 38] {
for &s in SCALES {
let path = dir
.join("tests/golden")
.join(format!("{func}_d{tier}_s{s}.txt"));
if !path.exists() {
continue;
}
let lines = parse_unary_file(&path);
dispatch_scale(s, func, &lines, &mut r);
}
}
report_o2(func, &r);
grand.merge(&r);
results.push((*func, r));
}
std::panic::set_hook(prev_hook);
report_o2("TOTAL", &grand);
assert!(grand.n > 0, "oracle2 sweep found no inputs — wrong corpus path?");
let mut failures = Vec::new();
for (func, r) in &results {
match gate_policy(func) {
Some(tol) => {
if r.errored != 0 || r.max_delta > tol {
failures.push(format!(
"{func} (errored={}, max_delta={}, tol={})",
r.errored, r.max_delta, tol
));
}
}
None => eprintln!(
"[oracle2/{func}] REPORT-ONLY (staged, not yet gated): \
errored={} max_delta={}",
r.errored, r.max_delta
),
}
}
assert!(failures.is_empty(), "gated functions regressed: {failures:?}");
}
#[test]
fn arm_b_fasc_probe() {
use g_math::canonical::{evaluate, gmath_parse};
let cases: &[(&str, &str, &str)] = &[
("asinh", "-1.4661736815306297448", "-1.17585209122032172461"),
("atanh", "-0.9999999999999999999999999999", "-32.58276489219661223096"),
("tan", "14.133754388", "293.03459111392422611133"),
("asin", "0.99999999999", "1.57079185465894161592"),
("acosh", "2.0", "1.31695789692481670862"),
("tanh", "-5.2874712800902", "-0.99994890484746193280"),
];
let prev = std::panic::take_hook();
std::panic::set_hook(Box::new(|_| {}));
for &(func, input, truth) in cases {
let out = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
let base = gmath_parse(input).expect("parse");
let expr = match func {
"asinh" => base.asinh(),
"atanh" => base.atanh(),
"tan" => base.tan(),
"asin" => base.asin(),
"acosh" => base.acosh(),
"tanh" => base.tanh(),
_ => unreachable!(),
};
evaluate(&expr).map(|v| format!("{v}"))
}));
match out {
Ok(Ok(s)) => eprintln!("[armB/{func}] FASC = {s} (true {truth})"),
Ok(Err(e)) => eprintln!("[armB/{func}] FASC Err({e:?}) (true {truth})"),
Err(_) => eprintln!("[armB/{func}] FASC PANICKED (true {truth})"),
}
}
std::panic::set_hook(prev);
}
#[test]
fn parse_negative_subunit_sign() {
use g_math::canonical::{evaluate, gmath_parse};
for s in ["-0.5", "-0.999", "-0.0001", "-1.5", "-2.0"] {
let out = format!("{}", evaluate(&gmath_parse(s).unwrap()).unwrap());
assert!(out.starts_with('-'), "parse({s}) lost its sign: got {out}");
}
let p = format!("{}", evaluate(&gmath_parse("0.5").unwrap()).unwrap());
assert!(!p.starts_with('-'), "parse(0.5) wrongly negative: {p}");
}
fn raw_to_decimal_string(raw: i128, scale: u8) -> String {
if scale == 0 {
return format!("{raw}");
}
let neg = raw < 0;
let mag = raw.unsigned_abs();
let p = 10u128.pow(scale as u32);
format!(
"{}{}.{:0width$}",
if neg { "-" } else { "" },
mag / p,
mag % p,
width = scale as usize
)
}
fn decimal_string_to_scaled(s: &str, scale: u8) -> i128 {
let neg = s.starts_with('-');
let body = s.trim_start_matches('-');
let (int_part, frac_part) = body.split_once('.').unwrap_or((body, ""));
let int: i128 = int_part.parse().unwrap_or(0);
let mut scaled = int * 10i128.pow(scale as u32);
let fb = frac_part.as_bytes();
let mut fv: i128 = 0;
for i in 0..scale as usize {
fv = fv * 10 + if i < fb.len() { (fb[i] - b'0') as i128 } else { 0 };
}
scaled += fv;
if fb.len() > scale as usize && fb[scale as usize] - b'0' >= 5 {
scaled += 1;
}
if neg { -scaled } else { scaled }
}
fn fasc_compute(func: &str, input: i128, scale: u8) -> Option<i128> {
use g_math::canonical::{evaluate, gmath_parse};
let base = gmath_parse(&raw_to_decimal_string(input, scale)).ok()?;
let expr = match func {
"exp" => base.exp(),
"ln" => base.ln(),
"sqrt" => base.sqrt(),
"sin" => base.sin(),
"cos" => base.cos(),
"tan" => base.tan(),
"atan" => base.atan(),
"asin" => base.asin(),
"acos" => base.acos(),
"sinh" => base.sinh(),
"cosh" => base.cosh(),
"tanh" => base.tanh(),
"asinh" => base.asinh(),
"acosh" => base.acosh(),
"atanh" => base.atanh(),
_ => return None,
};
let v = evaluate(&expr).ok()?;
Some(decimal_string_to_scaled(&format!("{v}"), scale))
}
#[test]
fn oracle2_fasc_sweep() {
let Some(dir) = oracle2_dir() else {
eprintln!("[fasc] SKIP — set GMATH_DECIMAL_SCALED_GOLDEN");
return;
};
const FUNCS: &[&str] = &[
"exp", "ln", "sqrt", "sin", "cos", "tan", "atan", "asin", "acos",
"sinh", "cosh", "tanh", "asinh", "acosh", "atanh",
];
const SCALES: &[u8] = &[0, 2, 3, 4, 6, 9, 10, 12, 13, 17, 18, 19, 28];
let prev = std::panic::take_hook();
std::panic::set_hook(Box::new(|_| {}));
let mut grand = O2::default();
for func in FUNCS {
let mut r = O2::default();
for tier in [18u8, 38] {
for &s in SCALES {
let path = dir.join("tests/golden").join(format!("{func}_d{tier}_s{s}.txt"));
if !path.exists() {
continue;
}
for g in parse_unary_file(&path) {
if !in_domain(func, g.input, s) {
r.skipped_domain += 1;
continue;
}
if g.floor.checked_abs().map_or(true, |a| a > i128::MAX / 2) {
r.skipped_range += 1;
continue;
}
r.n += 1;
if g.cls == Cls::Tie {
r.ties += 1;
}
let expected = oracle_correctly_rounded(g.floor, g.cls, Mode::HalfAwayFromZero);
let computed = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
fasc_compute(func, g.input, s)
}));
let actual = match computed {
Ok(Some(v)) => v,
_ => {
r.errored += 1;
continue;
}
};
let delta = (actual - expected).abs();
match delta {
0 => r.exact += 1,
1 => r.one += 1,
2..=8 => r.small += 1,
_ => r.large += 1,
}
if delta > r.max_delta {
r.max_delta = delta;
r.worst_input = g.input;
r.worst_scale = s;
}
}
}
}
report_o2(&format!("fasc/{func}"), &r);
grand.merge(&r);
}
std::panic::set_hook(prev);
report_o2("fasc/TOTAL", &grand);
assert!(grand.n > 0, "fasc sweep found no inputs");
assert_eq!(grand.errored, 0, "fasc: {} panics on valid-domain inputs", grand.errored);
assert_eq!(
grand.max_delta, 0,
"fasc canonical path not correctly rounded: max_delta={} at input={} scale={}",
grand.max_delta, grand.worst_input, grand.worst_scale
);
}
fn op_mode(op: &str) -> Mode {
match op {
"mul" | "div" => Mode::HalfToEven, _ => Mode::HalfAwayFromZero, }
}
fn arith_gated(_op: &str) -> bool {
true
}
fn compute_binary<const S: u8>(op: &str, a: i128, b: i128) -> Option<i128> {
let x = DecimalFixed::<S>::from_raw(a);
let y = DecimalFixed::<S>::from_raw(b);
Some(match op {
"add" => (x + y).raw_value(),
"sub" => (x - y).raw_value(),
"mul" => (x * y).raw_value(),
"div" => {
if b == 0 {
return None;
}
(x / y).raw_value()
}
"atan2" => {
if a == 0 && b == 0 {
return None;
}
x.atan2(y).raw_value()
}
_ => return None,
})
}
fn dispatch_scale_binary(scale: u8, op: &str, a: i128, b: i128) -> Option<i128> {
macro_rules! arms {
($($s:literal),* $(,)?) => {
match scale {
$( $s => compute_binary::<$s>(op, a, b), )*
_ => None,
}
};
}
arms!(0, 2, 3, 4, 6, 9, 10, 12, 13, 17, 18, 19, 28, 37)
}
fn parse_binary_file(path: &std::path::Path) -> Vec<(i128, i128, i128, Cls)> {
let text = std::fs::read_to_string(path)
.unwrap_or_else(|e| panic!("cannot read {}: {e}", path.display()));
let mut out = Vec::new();
for line in text.lines() {
let line = line.trim();
if line.is_empty() || line.starts_with('#') {
continue;
}
let c: Vec<&str> = line.split('\t').collect();
if c.len() != 4 {
continue;
}
let (Ok(a), Ok(b), Ok(f)) =
(c[0].parse::<i128>(), c[1].parse::<i128>(), c[2].parse::<i128>())
else {
continue;
};
out.push((a, b, f, Cls::parse(c[3])));
}
out
}
#[test]
fn oracle2_arithmetic_sweep() {
let Some(dir) = oracle2_dir() else {
eprintln!("[arith] SKIP — set GMATH_DECIMAL_SCALED_GOLDEN");
return;
};
const OPS: &[&str] = &["add", "sub", "mul", "div", "atan2"];
const SCALES: &[u8] = &[0, 2, 3, 4, 6, 9, 10, 12, 13, 17, 18, 19, 28, 37];
let prev = std::panic::take_hook();
std::panic::set_hook(Box::new(|_| {}));
let mut grand = O2::default();
let mut gated = O2::default();
for op in OPS {
let mut r = O2::default();
let mode = op_mode(op);
for tier in [18u8, 38] {
for &s in SCALES {
let path = dir.join("tests/golden").join(format!("{op}_d{tier}_s{s}.txt"));
if !path.exists() {
continue;
}
for (a, b, floor, cls) in parse_binary_file(&path) {
if floor.checked_abs().map_or(true, |x| x > i128::MAX / 2)
|| a.checked_abs().map_or(true, |x| x > i128::MAX / 2)
|| b.checked_abs().map_or(true, |x| x > i128::MAX / 2)
{
r.skipped_range += 1;
continue;
}
r.n += 1;
if cls == Cls::Tie {
r.ties += 1;
}
let expected = oracle_correctly_rounded(floor, cls, mode);
let computed = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
dispatch_scale_binary(s, op, a, b)
}));
let actual = match computed {
Ok(Some(v)) => v,
_ => {
r.errored += 1;
continue;
}
};
let delta = (actual - expected).abs();
match delta {
0 => r.exact += 1,
1 => r.one += 1,
2..=8 => r.small += 1,
_ => r.large += 1,
}
if delta > r.max_delta {
r.max_delta = delta;
r.worst_input = a;
r.worst_scale = s;
}
}
}
}
report_o2(&format!("arith/{op}"), &r);
grand.merge(&r);
if arith_gated(op) {
gated.merge(&r);
} else {
eprintln!("[arith/{op}] REPORT-ONLY — known operator overflow (FINDINGS.md)");
}
}
std::panic::set_hook(prev);
report_o2("arith/TOTAL", &grand);
assert!(grand.n > 0, "arith sweep found no inputs");
assert_eq!(gated.errored, 0, "arith(add/sub/atan2): {} panics", gated.errored);
assert_eq!(
gated.max_delta, 0,
"arith(add/sub/atan2) not correctly rounded: max_delta={} at input={} scale={}",
gated.max_delta, gated.worst_input, gated.worst_scale
);
}
#[test]
fn symbolic_transcendental_accuracy_probe() {
use g_math::canonical::{evaluate, gmath_parse};
let cases = [
("atanh", "1/3"), ("atanh", "1/10"), ("atanh", "1/4"),
("asinh", "1/7"), ("tanh", "2/3"), ("acosh", "7/3"),
];
for (f, x) in cases {
let base = gmath_parse(x).unwrap();
let expr = match f {
"atanh" => base.atanh(),
"asinh" => base.asinh(),
"tanh" => base.tanh(),
"acosh" => base.acosh(),
_ => unreachable!(),
};
eprintln!("SYMB {f}({x}) = {}", evaluate(&expr).unwrap());
}
}
#[test]
fn grader_truth_table() {
use Cls::*;
use Mode::*;
let cases: &[(i128, Cls, Mode, i128)] = &[
(10, Exact, HalfAwayFromZero, 10),
(-10, Exact, HalfToEven, -10),
(10, Low, HalfAwayFromZero, 10),
(-10, Low, HalfToEven, -10),
(10, High, HalfAwayFromZero, 11),
(-10, High, HalfToEven, -9),
(2, Tie, HalfAwayFromZero, 3), (-3, Tie, HalfAwayFromZero, -3), (2, Tie, HalfToEven, 2), (3, Tie, HalfToEven, 4), (-3, Tie, HalfToEven, -2), (-4, Tie, HalfToEven, -4), ];
for &(floor, cls, mode, want) in cases {
let got = oracle_correctly_rounded(floor, cls, mode);
assert_eq!(got, want, "grader({floor}, {cls:?}, mode) = {got}, want {want}");
}
}