hyperreal 0.12.0

Exact rational and computable real arithmetic in Rust
Documentation
use hyperreal::{Computable, Rational};
use num::BigInt;

#[derive(Clone)]
struct Graphed {
    id: usize,
    label: String,
    value: Computable,
}

#[derive(Default)]
struct Graph {
    name: &'static str,
    nodes: Vec<(usize, String)>,
    edges: Vec<(usize, usize)>,
}

impl Graph {
    fn new(name: &'static str) -> Self {
        Self {
            name,
            ..Self::default()
        }
    }

    fn leaf(&mut self, label: impl Into<String>, value: Computable) -> Graphed {
        let id = self.nodes.len();
        let label = label.into();
        self.nodes.push((id, label.clone()));
        Graphed { id, label, value }
    }

    fn unary(
        &mut self,
        label: impl Into<String>,
        input: &Graphed,
        op: impl FnOnce(Computable) -> Computable,
    ) -> Graphed {
        let id = self.nodes.len();
        let label = label.into();
        self.nodes.push((id, label.clone()));
        self.edges.push((input.id, id));
        Graphed {
            id,
            label,
            value: op(input.value.clone()),
        }
    }

    fn binary(
        &mut self,
        label: impl Into<String>,
        left: &Graphed,
        right: &Graphed,
        op: impl FnOnce(Computable, Computable) -> Computable,
    ) -> Graphed {
        let id = self.nodes.len();
        let label = label.into();
        self.nodes.push((id, label.clone()));
        self.edges.push((left.id, id));
        self.edges.push((right.id, id));
        Graphed {
            id,
            label,
            value: op(left.value.clone(), right.value.clone()),
        }
    }

    fn markdown(&self, root: &Graphed) -> String {
        let mut out = String::new();
        out.push_str("```mermaid\nflowchart TD\n");
        for (id, label) in &self.nodes {
            out.push_str(&format!("    n{id}[\"{}\"]\n", escape_mermaid(label)));
        }
        for (from, to) in &self.edges {
            out.push_str(&format!("    n{from} --> n{to}\n"));
        }
        out.push_str(&format!("    n{}:::root\n", root.id));
        out.push_str("    classDef root fill:#f7f3c6,stroke:#8a6d00,stroke-width:2px\n");
        out.push_str("```\n");
        out
    }
}

fn escape_mermaid(label: &str) -> String {
    label.replace('\\', "\\\\").replace('"', "\\\"")
}

fn rational(n: i64, d: u64) -> Computable {
    Computable::rational(Rational::fraction(n, d).expect("valid nonzero denominator"))
}

fn integer(n: i64) -> Computable {
    Computable::rational(Rational::new(n))
}

fn huge_integer_pow10(exp: u32) -> Computable {
    Computable::rational(Rational::from_bigint(BigInt::from(10_u8).pow(exp)))
}

fn build_argument_reduction_graph() -> (Graph, Graphed) {
    let mut graph = Graph::new("argument_reduction_tower");

    let pi = graph.leaf("pi shared constant", Computable::pi());
    let huge = graph.leaf("10^30 exact integer", huge_integer_pow10(30));
    let seven_fifths = graph.leaf("7/5 exact rational", rational(7, 5));
    let three_fifths = graph.leaf("3/5 exact rational", rational(3, 5));
    let seven_tenths = graph.leaf("7/10 exact rational", rational(7, 10));
    let tiny = graph.leaf("1/2^40 exact rational", rational(1, 1_u64 << 40));

    let huge_pi = graph.binary("multiply", &pi, &huge, Computable::multiply);
    let phase = graph.binary("add residual", &huge_pi, &seven_fifths, Computable::add);
    let phase_plus_tiny = graph.binary("add tiny perturbation", &phase, &tiny, Computable::add);

    let sin_phase = graph.unary("sin", &phase_plus_tiny, Computable::sin);
    let cos_phase = graph.unary("cos", &phase, Computable::cos);
    let tan_atan = {
        let atan = graph.unary("atan", &seven_tenths, Computable::atan);
        graph.unary("tan", &atan, Computable::tan)
    };
    let asin_sin = {
        let asin = graph.unary("asin", &three_fifths, Computable::asin);
        graph.unary("sin", &asin, Computable::sin)
    };

    let sin_sq = graph.unary("square", &sin_phase, Computable::square);
    let cos_sq = graph.unary("square", &cos_phase, Computable::square);
    let trig_norm = graph.binary("add", &sin_sq, &cos_sq, Computable::add);
    let inverse_norm = graph.unary("inverse", &trig_norm, Computable::inverse);
    let numerator = graph.binary("add", &tan_atan, &asin_sin, Computable::add);
    let product = graph.binary("multiply", &numerator, &inverse_norm, Computable::multiply);
    let root = graph.unary("sqrt", &product, Computable::sqrt);

    (graph, root)
}

fn build_cancellation_graph() -> (Graph, Graphed) {
    let mut graph = Graph::new("cancellation_and_nested_inverse_tower");

    let pi = graph.leaf("pi shared constant", Computable::pi());
    let e = graph.leaf("e shared constant", Computable::e());
    let two = graph.leaf("2 exact integer", integer(2));
    let twelve = graph.leaf("12 exact integer", integer(12));
    let forty_five_fourteen = graph.leaf("45/14 exact rational", rational(45, 14));
    let almost_one = graph.leaf(
        "999999/1000000 exact rational",
        rational(999_999, 1_000_000),
    );
    let half = graph.leaf("1/2 exact rational", rational(1, 2));
    let epsilon = graph.leaf("1/2^50 exact rational", rational(1, 1_u64 << 50));

    let sqrt2 = graph.unary("sqrt", &two, Computable::sqrt);
    let sqrt2_square = graph.unary("square", &sqrt2, Computable::square);

    let sqrt12 = graph.unary("sqrt", &twelve, Computable::sqrt);
    let radical_plus_e = graph.binary("add", &sqrt12, &e, Computable::add);
    let ln_radical = graph.unary("ln", &radical_plus_e, Computable::ln);
    let exp_ln_radical = graph.unary("exp", &ln_radical, Computable::exp);

    let smooth_ln = graph.unary("ln", &forty_five_fourteen, Computable::ln);
    let smooth_exp = graph.unary("exp", &smooth_ln, Computable::exp);

    let atanh = graph.unary("atanh", &almost_one, Computable::atanh);
    let asinh = graph.unary("asinh", &half, Computable::asinh);
    let acosh_input = graph.binary("add", &sqrt2_square, &half, Computable::add);
    let acosh = graph.unary("acosh", &acosh_input, Computable::acosh);
    let hyperbolic_sum = graph.binary("add", &atanh, &asinh, Computable::add);
    let hyperbolic_sum = graph.binary("add", &hyperbolic_sum, &acosh, Computable::add);

    let pi_plus_epsilon = graph.binary("add", &pi, &epsilon, Computable::add);
    let neg_pi = graph.unary("negate", &pi, Computable::negate);
    let near_cancel = graph.binary("add", &pi_plus_epsilon, &neg_pi, Computable::add);
    let inverse_cancel = graph.unary("inverse", &near_cancel, Computable::inverse);
    let inverse_pair = graph.unary("inverse", &inverse_cancel, Computable::inverse);

    let positive_sum = graph.binary("add", &exp_ln_radical, &smooth_exp, Computable::add);
    let positive_sum = graph.binary("add", &positive_sum, &hyperbolic_sum, Computable::add);
    let positive_sum = graph.binary("add", &positive_sum, &inverse_pair, Computable::add);
    let root = graph.unary("sqrt", &positive_sum, Computable::sqrt);

    (graph, root)
}

fn print_report(graph: &Graph, root: &Graphed) {
    let p = -80;
    let scaled = root.value.approx(p);

    println!("## {}", graph.name);
    println!();
    println!("{}", graph.markdown(root));
    println!();
    println!("Root node: `{}`", root.label);
    println!("Evaluation request: `root.approx({p})`");
    println!("Scaled integer result: `{scaled}`");
    println!("Decimal display: `{:.24}`", root.value);
    println!();
}

fn main() {
    let (argument_graph, argument_root) = build_argument_reduction_graph();
    print_report(&argument_graph, &argument_root);

    let (cancellation_graph, cancellation_root) = build_cancellation_graph();
    print_report(&cancellation_graph, &cancellation_root);
}