use crate::Computable;
use crate::Rational;
use crate::computable::{Precision, Signal, scale, shift, should_stop, signed};
use num::bigint::Sign;
use num::{BigInt, BigUint, Signed, ToPrimitive};
use num::{One, Zero};
use serde::Deserialize;
use serde::Serialize;
use std::ops::Deref;
use std::sync::LazyLock;
static HALF_RATIONAL: LazyLock<Rational> = LazyLock::new(|| Rational::fraction(1, 2).unwrap());
static FOUR_THIRDS_RATIONAL: LazyLock<Rational> =
LazyLock::new(|| Rational::fraction(4, 3).unwrap());
static SEVEN_FOURTHS_RATIONAL: LazyLock<Rational> =
LazyLock::new(|| Rational::fraction(7, 4).unwrap());
static TWO_RATIONAL: LazyLock<Rational> = LazyLock::new(|| Rational::new(2));
static SEVENTY_NINE_TWENTIETHS_RATIONAL: LazyLock<Rational> =
LazyLock::new(|| Rational::fraction(79, 20).unwrap());
static FOUR_RATIONAL: LazyLock<Rational> = LazyLock::new(|| Rational::new(4));
static TWENTY_SEVEN_FIFTHS_RATIONAL: LazyLock<Rational> =
LazyLock::new(|| Rational::fraction(27, 5).unwrap());
static ELEVEN_HALVES_RATIONAL: LazyLock<Rational> =
LazyLock::new(|| Rational::fraction(11, 2).unwrap());
static SEVEN_RATIONAL: LazyLock<Rational> = LazyLock::new(|| Rational::new(7));
static SEVENTEEN_HALVES_RATIONAL: LazyLock<Rational> =
LazyLock::new(|| Rational::fraction(17, 2).unwrap());
static QUARTER_PI_TAN_RESIDUAL_THRESHOLD: LazyLock<BigInt> = LazyLock::new(|| BigInt::from(128));
static NEG_FOUR_RATIONAL: LazyLock<Rational> = LazyLock::new(|| Rational::new(-4));
static NEG_FOUR_BIGINT: LazyLock<BigInt> = LazyLock::new(|| BigInt::from(-4));
static NEG_SEVENTY_NINE_TWENTIETHS_RATIONAL: LazyLock<Rational> =
LazyLock::new(|| Rational::fraction(-79, 20).unwrap());
static NEG_TWENTY_SEVEN_FIFTHS_RATIONAL: LazyLock<Rational> =
LazyLock::new(|| Rational::fraction(-27, 5).unwrap());
static NEG_ELEVEN_HALVES_RATIONAL: LazyLock<Rational> =
LazyLock::new(|| Rational::fraction(-11, 2).unwrap());
static NEG_SEVEN_RATIONAL: LazyLock<Rational> = LazyLock::new(|| Rational::new(-7));
static NEG_SEVENTEEN_HALVES_RATIONAL: LazyLock<Rational> =
LazyLock::new(|| Rational::fraction(-17, 2).unwrap());
#[derive(Clone, Debug, Serialize, Deserialize)]
pub(super) enum Approximation {
Int(BigInt),
One,
Constant(SharedConstant),
Inverse(Computable),
Negate(Computable),
Add(Computable, Computable),
Multiply(Computable, Computable),
Square(Computable),
Ratio(Rational),
Offset(Computable, i32),
PrescaledExp(Computable),
Sqrt(Computable),
PrescaledLn(Computable),
PrescaledLnRational(Rational),
BinaryScaledLnRational { residual: Rational, shift: i32 },
IntegralAtan(BigInt),
PrescaledAtan(Computable),
AtanRational(Rational),
AsinRational(Rational),
PrescaledAsin(Computable),
AsinDeferred(Computable),
AcosPositive(Computable),
AcosPositiveRational(Rational),
AcosNegativeRational(Rational),
AcoshNearOne(Computable),
AcoshDirect(Computable),
AsinhNearZero(Computable),
AsinhDirect(Computable),
PrescaledAsinh(Computable),
AsinhRational(Rational),
AtanhDirect(Computable),
PrescaledAtanh(Computable),
AtanhRational(Rational),
PrescaledCos(Computable),
PrescaledCosRational(Rational),
CosLargeRational(Rational),
PrescaledCosHalfPiMinusRational(Rational),
PrescaledSin(Computable),
PrescaledSinRational(Rational),
SinLargeRational(Rational),
PrescaledSinHalfPiMinusRational(Rational),
PrescaledCotHalfPiMinusRational(Rational),
TanLargeRational(Rational),
PrescaledTan(Computable),
PrescaledTanRational(Rational),
PrescaledCot(Computable),
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub(super) enum SharedConstant {
E,
Pi,
InvPi,
Tau,
Ln2,
Ln3,
Ln5,
Ln6,
Ln7,
Ln10,
Sqrt2,
Sqrt3,
Acosh2,
Asinh1,
AtanInv2,
AtanInv5,
}
impl SharedConstant {
pub(super) const COUNT: usize = 16;
pub(super) fn cache_index(self) -> usize {
match self {
Self::E => 0,
Self::Pi => 1,
Self::InvPi => 2,
Self::Tau => 3,
Self::Ln2 => 4,
Self::Ln3 => 5,
Self::Ln5 => 6,
Self::Ln6 => 7,
Self::Ln7 => 8,
Self::Ln10 => 9,
Self::Sqrt2 => 10,
Self::Sqrt3 => 11,
Self::Acosh2 => 12,
Self::Asinh1 => 13,
Self::AtanInv2 => 14,
Self::AtanInv5 => 15,
}
}
}
impl Approximation {
pub fn approximate(&self, signal: &Option<Signal>, p: Precision) -> BigInt {
use Approximation::*;
match self {
Int(i) => scale(i.clone(), -p),
One => scale(signed::ONE.deref().clone(), -p),
Constant(c) => c.approximate(signal, p),
Inverse(c) => inverse(signal, c, p),
Negate(c) => -c.approx_signal(signal, p),
Add(c1, c2) => add(signal, c1, c2, p),
Multiply(c1, c2) => multiply(signal, c1, c2, p),
Square(c) => square(signal, c, p),
Ratio(r) => ratio(r, p),
Offset(c, n) => offset(signal, c, *n, p),
PrescaledExp(c) => exp(signal, c, p),
Sqrt(c) => sqrt(signal, c, p),
PrescaledLn(c) => ln(signal, c, p),
PrescaledLnRational(r) => ln_rational(signal, r, p),
BinaryScaledLnRational { residual, shift } => {
binary_scaled_ln_rational(signal, residual, *shift, p)
}
IntegralAtan(i) => atan(signal, i, p),
PrescaledAtan(c) => atan_computable(signal, c, p),
AtanRational(r) => atan_rational(signal, r, p),
AsinRational(r) => asin_rational(signal, r, p),
PrescaledAsin(c) => asin_computable(signal, c, p),
AsinDeferred(c) => asin_deferred(signal, c, p),
AcosPositive(c) => acos_positive(signal, c, p),
AcosPositiveRational(r) => acos_positive_rational(signal, r, p),
AcosNegativeRational(r) => acos_negative_rational(signal, r, p),
AcoshNearOne(c) => acosh_near_one(signal, c, p),
AcoshDirect(c) => acosh_direct(signal, c, p),
AsinhNearZero(c) => asinh_near_zero(signal, c, p),
AsinhDirect(c) => asinh_direct(signal, c, p),
PrescaledAsinh(c) => asinh_computable(signal, c, p),
AsinhRational(r) => asinh_rational(signal, r, p),
AtanhDirect(c) => atanh_direct(signal, c, p),
PrescaledAtanh(c) => atanh_computable(signal, c, p),
AtanhRational(r) => atanh_rational(signal, r, p),
PrescaledCos(c) => cos(signal, c, p),
PrescaledCosRational(r) => cos_rational(signal, r, p),
CosLargeRational(r) => cos_large_rational(signal, r, p),
PrescaledCosHalfPiMinusRational(r) => cos_half_pi_minus_rational(signal, r, p),
PrescaledSin(c) => sin(signal, c, p),
PrescaledSinRational(r) => sin_rational(signal, r, p),
SinLargeRational(r) => sin_large_rational(signal, r, p),
PrescaledSinHalfPiMinusRational(r) => sin_half_pi_minus_rational(signal, r, p),
PrescaledCotHalfPiMinusRational(r) => cot_half_pi_minus_rational(signal, r, p),
TanLargeRational(r) => tan_large_rational(signal, r, p),
PrescaledTan(c) => tan(signal, c, p),
PrescaledTanRational(r) => tan_rational(signal, r, p),
PrescaledCot(c) => cot(signal, c, p),
}
}
}
impl SharedConstant {
fn approximate(self, signal: &Option<Signal>, p: Precision) -> BigInt {
match self {
Self::E => e(p),
Self::Pi => pi(signal, p),
Self::InvPi => inverse(signal, &Computable::pi(), p),
Self::Tau => pi(signal, p - 1),
Self::Ln2 => ln2(signal, p),
Self::Ln3 => ln_constant(signal, Rational::new(3), p),
Self::Ln5 => ln_constant(signal, Rational::new(5), p),
Self::Ln6 => ln_constant(signal, Rational::new(6), p),
Self::Ln7 => ln_constant(signal, Rational::new(7), p),
Self::Ln10 => ln_constant(signal, Rational::new(10), p),
Self::Sqrt2 => sqrt_constant(signal, Rational::new(2), p),
Self::Sqrt3 => sqrt_constant(signal, Rational::new(3), p),
Self::Acosh2 => acosh2_constant(signal, p),
Self::Asinh1 => asinh1_constant(signal, p),
Self::AtanInv2 => atan(signal, &BigInt::from(2_u8), p),
Self::AtanInv5 => atan(signal, &BigInt::from(5_u8), p),
}
}
}
fn raw(kind: Approximation) -> Computable {
Computable {
internal: Box::new(kind),
cache: std::cell::RefCell::new(crate::computable::Cache::Invalid),
bound: std::cell::RefCell::new(crate::computable::BoundCache::Invalid),
exact_sign: std::cell::RefCell::new(crate::computable::ExactSignCache::Invalid),
signal: None,
}
}
fn pi(signal: &Option<Signal>, p: Precision) -> BigInt {
let atan5 = Computable::prescaled_atan(BigInt::from(5_u8));
let atan_239 = Computable::prescaled_atan(BigInt::from(239_u16));
let four = Computable::integer(BigInt::from(4_u8));
let four_atan5 = four.clone().multiply(atan5);
let sum = four_atan5.add(atan_239.negate());
four.multiply(sum).approx_signal(signal, p)
}
fn ln2(signal: &Option<Signal>, p: Precision) -> BigInt {
let prescaled_9 = raw(Approximation::PrescaledLn(Computable::rational(
Rational::fraction(1, 9).unwrap(),
)));
let prescaled_24 = raw(Approximation::PrescaledLn(Computable::rational(
Rational::fraction(1, 24).unwrap(),
)));
let prescaled_80 = raw(Approximation::PrescaledLn(Computable::rational(
Rational::fraction(1, 80).unwrap(),
)));
let ln2_1 = Computable::integer(BigInt::from(7_u8)).multiply(prescaled_9);
let ln2_2 = Computable::integer(BigInt::from(2_u8)).multiply(prescaled_24);
let ln2_3 = Computable::integer(BigInt::from(3_u8)).multiply(prescaled_80);
ln2_1
.add(ln2_2.negate())
.add(ln2_3)
.approx_signal(signal, p)
}
fn ln_constant(signal: &Option<Signal>, n: Rational, p: Precision) -> BigInt {
Computable::rational(n).ln().approx_signal(signal, p)
}
fn sqrt_constant(signal: &Option<Signal>, n: Rational, p: Precision) -> BigInt {
raw(Approximation::Sqrt(Computable::rational(n))).approx_signal(signal, p)
}
fn acosh2_constant(signal: &Option<Signal>, p: Precision) -> BigInt {
Computable::rational(Rational::new(2))
.add(Computable::sqrt_constant(3).unwrap())
.ln()
.approx_signal(signal, p)
}
fn asinh1_constant(signal: &Option<Signal>, p: Precision) -> BigInt {
Computable::one()
.add(Computable::sqrt_constant(2).unwrap())
.ln()
.approx_signal(signal, p)
}
fn e_terms_for_precision(p: Precision) -> u32 {
let needed_bits = if p < 0 { (-p) as u64 + 4 } else { 4 };
let mut factorial = BigUint::one();
let mut n = 0_u32;
loop {
let next = &factorial * BigUint::from(n + 1);
if next.bits() > needed_bits {
return n;
}
factorial = next;
n += 1;
}
}
fn e_binary_split(a: u32, b: u32) -> (BigUint, BigUint) {
if b - a == 1 {
return (BigUint::one(), BigUint::from(a));
}
let mid = a + (b - a) / 2;
let (left_p, left_q) = e_binary_split(a, mid);
let (right_p, right_q) = e_binary_split(mid, b);
(left_p * &right_q + right_p, left_q * right_q)
}
fn rounded_ratio(numerator: BigUint, denominator: BigUint, p: Precision) -> BigInt {
if p <= 0 {
let shift = usize::try_from(-p).expect("precision shift should fit in usize");
let dividend = numerator << shift;
BigInt::from((dividend + (&denominator >> 1)) / denominator)
} else {
let shift = usize::try_from(p).expect("precision shift should fit in usize");
let scaled_denominator = denominator << shift;
BigInt::from((numerator + (&scaled_denominator >> 1)) / scaled_denominator)
}
}
fn e(p: Precision) -> BigInt {
let terms = e_terms_for_precision(p);
if terms == 0 {
return rounded_ratio(BigUint::one(), BigUint::one(), p);
}
let (tail_p, tail_q) = e_binary_split(1, terms + 1);
rounded_ratio(tail_q.clone() + tail_p, tail_q, p)
}
fn inverse(signal: &Option<Signal>, c: &Computable, p: Precision) -> BigInt {
let (sign, planned_msd) = c.planning_sign_and_msd();
if sign == Some(Sign::NoSign) {
return Zero::zero();
}
let msd = planned_msd.flatten().unwrap_or_else(|| c.iter_msd());
let inv_msd = 1 - msd;
let digits_needed = inv_msd - p + 3;
let mut prec_needed = msd - digits_needed;
let mut log_scale_factor = -p - prec_needed;
let scaled_divisor = loop {
if log_scale_factor < 0 {
return Zero::zero();
}
let scaled = c.approx_signal(signal, prec_needed);
if !scaled.is_zero() {
break scaled;
}
if should_stop(signal) {
return Zero::zero();
}
prec_needed -= 8;
log_scale_factor += 8;
if log_scale_factor > 16_384 {
panic!("ArithmeticException");
}
};
let dividend = signed::ONE.deref() << log_scale_factor;
let abs_scaled_divisor = scaled_divisor.abs();
let adj_dividend = dividend + (&abs_scaled_divisor >> 1);
let result: BigInt = adj_dividend / abs_scaled_divisor;
if scaled_divisor.sign() == Sign::Minus {
-result
} else {
result
}
}
fn add(signal: &Option<Signal>, c1: &Computable, c2: &Computable, p: Precision) -> BigInt {
let extra = 4;
let cutoff = p - extra;
let (sign1, planning_msd1) = c1.planning_sign_and_msd();
let (sign2, planning_msd2) = c2.planning_sign_and_msd();
if sign1 == Some(Sign::NoSign) {
return c2.approx_signal(signal, p);
}
if sign2 == Some(Sign::NoSign) {
return c1.approx_signal(signal, p);
}
let msd1 = planning_msd1.unwrap_or_else(|| c1.msd(cutoff));
let msd2 = planning_msd2.unwrap_or_else(|| c2.msd(cutoff));
match (msd1, msd2) {
(None, None) => return Zero::zero(),
(None, Some(_)) if sign2.is_some_and(|sign| sign != Sign::NoSign) => {
return scale(c2.approx_signal(signal, p - extra), -extra);
}
(Some(_), None) if sign1.is_some_and(|sign| sign != Sign::NoSign) => {
return scale(c1.approx_signal(signal, p - extra), -extra);
}
(Some(left), Some(_right))
if sign1 == sign2
&& sign2.is_some_and(|sign| sign != Sign::NoSign)
&& left < cutoff =>
{
return scale(c2.approx_signal(signal, p - extra), -extra);
}
(Some(_left), Some(right))
if sign1 == sign2
&& sign1.is_some_and(|sign| sign != Sign::NoSign)
&& right < cutoff =>
{
return scale(c1.approx_signal(signal, p - extra), -extra);
}
_ => (),
}
scale(
c1.approx_signal(signal, p - 2) + c2.approx_signal(signal, p - 2),
-2,
)
}
fn msd_from_appr(prec: Precision, appr: &BigInt) -> Precision {
prec + appr.magnitude().bits() as Precision - 1
}
fn multiply_with_known_msd(
signal: &Option<Signal>,
known: &Computable,
known_msd: Precision,
other: &Computable,
p: Precision,
) -> BigInt {
let prec_other = p - known_msd - 3;
let appr_other = other.approx_signal(signal, prec_other);
if appr_other.sign() == Sign::NoSign {
return Zero::zero();
}
let msd_other = msd_from_appr(prec_other, &appr_other);
let prec_known = p - msd_other - 3;
let appr_known = known.approx_signal(signal, prec_known);
let scale_digits = prec_known + prec_other - p;
scale(appr_known * appr_other, scale_digits)
}
fn multiply(signal: &Option<Signal>, c1: &Computable, c2: &Computable, p: Precision) -> BigInt {
let half_prec = (p >> 1) - 1;
let (sign1, msd1) = c1.planning_sign_and_msd();
let (sign2, msd2) = c2.planning_sign_and_msd();
if sign1 == Some(Sign::NoSign) || sign2 == Some(Sign::NoSign) {
return Zero::zero();
}
let msd1 = msd1.unwrap_or_else(|| c1.msd(half_prec));
let msd2 = msd2.unwrap_or_else(|| c2.msd(half_prec));
match (msd1, msd2) {
(None, None) => Zero::zero(),
(Some(msd_op1), None) => multiply_with_known_msd(signal, c1, msd_op1, c2, p),
(None, Some(msd_op2)) => multiply_with_known_msd(signal, c2, msd_op2, c1, p),
(Some(msd_op1), Some(msd_op2)) if msd_op2 > msd_op1 => {
multiply_with_known_msd(signal, c2, msd_op2, c1, p)
}
(Some(msd_op1), Some(_msd_op2)) => multiply_with_known_msd(signal, c1, msd_op1, c2, p),
}
}
fn square(signal: &Option<Signal>, c: &Computable, p: Precision) -> BigInt {
let half_prec = (p >> 1) - 1;
let (sign, msd) = c.planning_sign_and_msd();
if sign == Some(Sign::NoSign) {
return Zero::zero();
}
let msd = match msd.unwrap_or_else(|| c.msd(half_prec)) {
None => {
return Zero::zero();
}
Some(msd) => msd,
};
let prec = p - msd - 3;
let appr = c.approx_signal(signal, prec);
if appr.sign() == Sign::NoSign {
return Zero::zero();
}
let scale_digits = prec + prec - p;
scale(&appr * &appr, scale_digits)
}
fn ratio(r: &Rational, p: Precision) -> BigInt {
if p >= 0 {
scale(r.shifted_big_integer(0), -p)
} else {
r.shifted_big_integer(-p)
}
}
fn offset(signal: &Option<Signal>, c: &Computable, n: i32, p: Precision) -> BigInt {
c.approx_signal(signal, p - n)
}
fn bound_log2(n: i32) -> i32 {
let abs_n = n.abs();
let ln2 = 2.0_f64.ln();
let n_plus_1: f64 = (abs_n + 1).into();
let ans: f64 = (n_plus_1.ln() / ln2).ceil();
ans as i32
}
fn exp(signal: &Option<Signal>, c: &Computable, p: Precision) -> BigInt {
if p >= 1 {
return Zero::zero();
}
let iterations_needed = -p / 2 + 2;
let calc_precision = p - bound_log2(2 * iterations_needed) - 4; let op_prec = p - 3;
let op_appr = c.approx_signal(signal, op_prec);
let scaled_1 = signed::ONE.deref() << -calc_precision;
let max_trunc_error = BigUint::one()
<< usize::try_from(p - 4 - calc_precision).expect("truncation shift is nonnegative");
let mut current_term = scaled_1.clone();
let mut sum = scaled_1;
let mut n: i32 = 0;
while current_term.magnitude() > &max_trunc_error {
if should_stop(signal) {
break;
}
n += 1;
current_term = scale(current_term * &op_appr, op_prec) / n;
sum += ¤t_term;
}
scale(sum, calc_precision - p)
}
fn sqrt(signal: &Option<Signal>, c: &Computable, p: Precision) -> BigInt {
let fp_prec: i32 = 140;
let fp_op_prec: i32 = 150;
let max_prec_needed = p.saturating_mul(2).saturating_sub(1);
let (known_sign, planned_msd) = c.planning_sign_and_msd();
if known_sign == Some(Sign::NoSign) {
return Zero::zero();
}
let msd = match planned_msd {
Some(Some(msd)) => msd,
_ => match c.msd(max_prec_needed) {
Some(msd) => msd,
None => {
let rough = c.approx_signal(signal, max_prec_needed);
if rough.is_zero() {
return Zero::zero();
}
rough.magnitude().bits() as Precision - 1 + max_prec_needed
}
},
};
if msd <= max_prec_needed {
return Zero::zero();
}
if should_stop(signal) {
return signed::ONE.deref().clone();
}
let result_msd = msd / 2;
let result_digits = result_msd - p;
if result_digits > fp_prec {
let appr_digits = result_digits / 2 + 6;
let appr_prec = result_msd - appr_digits;
let last_appr = sqrt(signal, c, appr_prec);
let prod_prec = 2 * appr_prec;
let op_appr = c.approx_signal(signal, prod_prec);
let prod_prec_scaled_numerator = (&last_appr * &last_appr) + op_appr;
let scaled_numerator = scale(prod_prec_scaled_numerator, appr_prec - p);
let shifted_result = scaled_numerator / last_appr;
(shifted_result + signed::ONE.deref()) / signed::TWO.deref()
} else {
let op_prec = (msd - fp_op_prec) & !1;
let working_prec = op_prec - fp_op_prec;
let scaled_bi_appr = c.approx_signal(signal, op_prec) << fp_op_prec;
let scaled_sqrt = scaled_bi_appr.sqrt();
let shift_count = working_prec / 2 - p;
shift(scaled_sqrt, shift_count)
}
}
fn cos(signal: &Option<Signal>, c: &Computable, p: Precision) -> BigInt {
if p >= 1 {
return signed::ONE.deref().clone();
}
let iterations_needed = -p / 2 + 4;
if should_stop(signal) {
return signed::ONE.deref().clone();
}
let calc_precision = p - bound_log2(2 * iterations_needed) - 4; let op_prec = p - 2;
let op_appr = c.approx_signal(signal, op_prec);
let op_squared = scale(&op_appr * &op_appr, op_prec);
let max_trunc_error = BigUint::one()
<< usize::try_from(p - 4 - calc_precision).expect("truncation shift is nonnegative");
let mut n = 0;
let mut current_term = signed::ONE.deref() << (-calc_precision);
let mut current_sum = current_term.clone();
while current_term.magnitude() > &max_trunc_error {
if should_stop(signal) {
break;
}
n += 2;
current_term = scale(current_term * &op_squared, op_prec);
current_term /= -(n * (n - 1));
current_sum += ¤t_term;
}
scale(current_sum, calc_precision - p)
}
fn cos_rational(signal: &Option<Signal>, r: &Rational, p: Precision) -> BigInt {
if p >= 1 {
return signed::ONE.deref().clone();
}
let iterations_needed = -p / 2 + 4;
if should_stop(signal) {
return signed::ONE.deref().clone();
}
let calc_precision = p - bound_log2(2 * iterations_needed) - 5;
let op_prec = p - 2;
let op_appr = ratio(r, op_prec);
let op_squared = scale(&op_appr * &op_appr, op_prec);
let max_trunc_error = BigUint::one()
<< usize::try_from(p - 4 - calc_precision).expect("truncation shift is nonnegative");
let mut n = 0;
let mut current_term = signed::ONE.deref() << (-calc_precision);
let mut current_sum = current_term.clone();
while current_term.magnitude() > &max_trunc_error {
if should_stop(signal) {
break;
}
n += 2;
current_term = scale(current_term * &op_squared, op_prec);
current_term /= -(n * (n - 1));
current_sum += ¤t_term;
}
scale(current_sum, calc_precision - p)
}
fn large_rational_half_pi_multiple(signal: &Option<Signal>, r: &Rational) -> BigInt {
if let Some(multiple) = fixed_small_large_rational_half_pi_multiple(r) {
return multiple;
}
let mut multiple = Computable::half_pi_multiple_exact_rational(r)
.unwrap_or_else(|| Computable::rational(r.clone()).half_pi_multiple());
let rough_appr = large_rational_half_pi_residual(signal, r, &multiple, -1);
if rough_appr >= *crate::computable::signed::TWO {
multiple += 1;
} else if rough_appr <= -crate::computable::signed::TWO.deref().clone() {
multiple -= 1;
}
multiple
}
fn fixed_small_large_rational_half_pi_multiple(r: &Rational) -> Option<BigInt> {
if r >= SEVENTY_NINE_TWENTIETHS_RATIONAL.deref() && r <= FOUR_RATIONAL.deref() {
crate::trace_dispatch!("computable_approx", "trig", "fixed-half-pi-multiple-3");
return Some(BigInt::from(3));
}
if r <= NEG_SEVENTY_NINE_TWENTIETHS_RATIONAL.deref() && r >= NEG_FOUR_RATIONAL.deref() {
crate::trace_dispatch!("computable_approx", "trig", "fixed-half-pi-multiple-neg3");
return Some(BigInt::from(-3));
}
if r.msd_exact() != Some(2) {
return None;
}
if r >= FOUR_RATIONAL.deref() && r <= TWENTY_SEVEN_FIFTHS_RATIONAL.deref() {
crate::trace_dispatch!("computable_approx", "trig", "fixed-half-pi-multiple-3");
return Some(BigInt::from(3));
}
if r <= NEG_FOUR_RATIONAL.deref() && r >= NEG_TWENTY_SEVEN_FIFTHS_RATIONAL.deref() {
crate::trace_dispatch!("computable_approx", "trig", "fixed-half-pi-multiple-neg3");
return Some(BigInt::from(-3));
}
if r >= ELEVEN_HALVES_RATIONAL.deref() && r <= SEVEN_RATIONAL.deref() {
crate::trace_dispatch!("computable_approx", "trig", "fixed-half-pi-multiple-4");
return Some(BigInt::from(4));
}
if r <= NEG_ELEVEN_HALVES_RATIONAL.deref() && r >= NEG_SEVEN_RATIONAL.deref() {
crate::trace_dispatch!("computable_approx", "trig", "fixed-half-pi-multiple-neg4");
return Some(BigInt::from(-4));
}
if r >= SEVEN_RATIONAL.deref() && r <= SEVENTEEN_HALVES_RATIONAL.deref() {
crate::trace_dispatch!("computable_approx", "trig", "fixed-half-pi-multiple-5");
return Some(BigInt::from(5));
}
if r <= NEG_SEVEN_RATIONAL.deref() && r >= NEG_SEVENTEEN_HALVES_RATIONAL.deref() {
crate::trace_dispatch!("computable_approx", "trig", "fixed-half-pi-multiple-neg5");
return Some(BigInt::from(-5));
}
None
}
fn large_rational_half_pi_residual(
signal: &Option<Signal>,
r: &Rational,
multiple: &BigInt,
p: Precision,
) -> BigInt {
let extra = 3;
let work_precision = p - extra;
let rational = ratio(r, work_precision);
if multiple == signed::FOUR.deref() || multiple == NEG_FOUR_BIGINT.deref() {
crate::trace_dispatch!("computable_approx", "trig", "fixed-half-pi-residual-two-pi");
let pi_precision = work_precision - 6;
let pi = Computable::pi().approx_signal(signal, pi_precision);
let two_pi = scale(pi, pi_precision - work_precision + 1);
let half_pi_multiple = if multiple.sign() == Sign::Minus {
-two_pi
} else {
two_pi
};
return scale(rational - half_pi_multiple, -extra);
}
let multiple_msd = i32::try_from(multiple.magnitude().bits().saturating_sub(1))
.expect("large trig quotient bits should fit in i32");
let pi_precision = work_precision - multiple_msd - 4;
let pi = Computable::pi().approx_signal(signal, pi_precision);
let half_pi_multiple = scale(pi * multiple, pi_precision - work_precision - 1);
scale(rational - half_pi_multiple, -extra)
}
fn large_rational_quadrant(multiple: &BigInt) -> BigInt {
((multiple % crate::computable::signed::FOUR.deref()) + crate::computable::signed::FOUR.deref())
% crate::computable::signed::FOUR.deref()
}
fn large_rational_quarter_pi_residual(
signal: &Option<Signal>,
r: &Rational,
multiple: &BigInt,
quarter_sign: i8,
p: Precision,
) -> BigInt {
let extra = 3;
let work_precision = p - extra;
let rational = ratio(r, work_precision);
let quarter_multiple: BigInt = (multiple << 1) + BigInt::from(quarter_sign);
let multiple_msd = i32::try_from(quarter_multiple.magnitude().bits().saturating_sub(1))
.expect("large trig quotient bits should fit in i32");
let pi_precision = work_precision - multiple_msd - 5;
let pi = Computable::pi().approx_signal(signal, pi_precision);
let quarter_pi_multiple = scale(pi * quarter_multiple, pi_precision - work_precision - 2);
scale(rational - quarter_pi_multiple, -extra)
}
fn sin_cos_scaled_argument(
signal: &Option<Signal>,
op_appr: BigInt,
op_prec: Precision,
p: Precision,
) -> (BigInt, BigInt) {
if p >= 1 {
return (Zero::zero(), signed::ONE.deref().clone());
}
let iterations_needed = -p / 2 + 4;
if should_stop(signal) {
return (Zero::zero(), signed::ONE.deref().clone());
}
let calc_precision = p - bound_log2(2 * iterations_needed) - 5;
let op_squared = scale(&op_appr * &op_appr, op_prec);
let max_trunc_error = BigUint::one()
<< usize::try_from(p - 4 - calc_precision).expect("truncation shift is nonnegative");
let mut sin_n = 1;
let mut sin_term = scale(op_appr, op_prec - calc_precision);
let mut sin_sum = sin_term.clone();
while sin_term.magnitude() > &max_trunc_error {
if should_stop(signal) {
break;
}
sin_n += 2;
sin_term = scale(sin_term * &op_squared, op_prec);
sin_term /= -(sin_n * (sin_n - 1));
sin_sum += &sin_term;
}
let mut cos_n = 0;
let mut cos_term = signed::ONE.deref() << (-calc_precision);
let mut cos_sum = cos_term.clone();
while cos_term.magnitude() > &max_trunc_error {
if should_stop(signal) {
break;
}
cos_n += 2;
cos_term = scale(cos_term * &op_squared, op_prec);
cos_term /= -(cos_n * (cos_n - 1));
cos_sum += &cos_term;
}
(
scale(sin_sum, calc_precision - p),
scale(cos_sum, calc_precision - p),
)
}
fn divide_scaled(numerator: BigInt, denominator: BigInt, p: Precision) -> BigInt {
let abs_denominator = denominator.abs();
if abs_denominator.is_zero() {
panic!("ArithmeticException");
}
let scaled_numerator = if denominator.sign() == Sign::Minus {
-numerator << -p
} else {
numerator << -p
};
let adjustment = &abs_denominator >> 1;
if scaled_numerator.sign() == Sign::Minus {
let rounded: BigInt = ((-scaled_numerator) + adjustment) / abs_denominator;
-rounded
} else {
(scaled_numerator + adjustment) / abs_denominator
}
}
fn tan_scaled_argument(
signal: &Option<Signal>,
op_appr: BigInt,
op_prec: Precision,
p: Precision,
) -> BigInt {
let (sin_appr, cos_appr) = sin_cos_scaled_argument(signal, op_appr, op_prec, p);
divide_scaled(sin_appr, cos_appr, p)
}
fn tan_large_rational_quarter_pi(
signal: &Option<Signal>,
r: &Rational,
multiple: &BigInt,
p: Precision,
) -> Option<BigInt> {
let quadrant = large_rational_quadrant(multiple).to_u8()?;
let rough_residual = large_rational_half_pi_residual(signal, r, multiple, -8);
let quarter_sign = if rough_residual >= *QUARTER_PI_TAN_RESIDUAL_THRESHOLD.deref() {
1
} else if rough_residual <= -QUARTER_PI_TAN_RESIDUAL_THRESHOLD.deref().clone() {
-1
} else {
return None;
};
crate::trace_dispatch!("computable_approx", "tan", "quarter-pi-large-rational");
let working_prec = p - 8;
let op_prec = working_prec - 2;
let delta = large_rational_quarter_pi_residual(signal, r, multiple, quarter_sign, op_prec);
let tan_delta = tan_scaled_argument(signal, delta, op_prec, working_prec);
let one = signed::ONE.deref() << -working_prec;
let direct_tangent_form = (quadrant % 2 == 0) == (quarter_sign > 0);
let (numerator, denominator) = if direct_tangent_form {
(one.clone() + &tan_delta, one - tan_delta)
} else {
(tan_delta.clone() - &one, one + tan_delta)
};
Some(divide_scaled(numerator, denominator, p))
}
fn cos_large_rational_residual(
signal: &Option<Signal>,
r: &Rational,
multiple: &BigInt,
p: Precision,
) -> BigInt {
if p >= 1 {
return signed::ONE.deref().clone();
}
let iterations_needed = -p / 2 + 4;
if should_stop(signal) {
return signed::ONE.deref().clone();
}
let calc_precision = p - bound_log2(2 * iterations_needed) - 5;
let op_prec = p - 2;
let op_appr = large_rational_half_pi_residual(signal, r, multiple, op_prec);
let op_squared = scale(&op_appr * &op_appr, op_prec);
let max_trunc_error = BigUint::one()
<< usize::try_from(p - 4 - calc_precision).expect("truncation shift is nonnegative");
let mut n = 0;
let mut current_term = signed::ONE.deref() << (-calc_precision);
let mut current_sum = current_term.clone();
while current_term.magnitude() > &max_trunc_error {
if should_stop(signal) {
break;
}
n += 2;
current_term = scale(current_term * &op_squared, op_prec);
current_term /= -(n * (n - 1));
current_sum += ¤t_term;
}
scale(current_sum, calc_precision - p)
}
fn sin_large_rational_residual(
signal: &Option<Signal>,
r: &Rational,
multiple: &BigInt,
p: Precision,
) -> BigInt {
if p >= 1 {
return Zero::zero();
}
let iterations_needed = -p / 2 + 4;
if should_stop(signal) {
return Zero::zero();
}
let calc_precision = p - bound_log2(2 * iterations_needed) - 4;
let op_prec = p - 2;
let op_appr = large_rational_half_pi_residual(signal, r, multiple, op_prec);
let op_squared = scale(&op_appr * &op_appr, op_prec);
let max_trunc_error = BigUint::one()
<< usize::try_from(p - 4 - calc_precision).expect("truncation shift is nonnegative");
let mut n = 1;
let mut current_term = scale(op_appr.clone(), op_prec - calc_precision);
let mut current_sum = current_term.clone();
while current_term.magnitude() > &max_trunc_error {
if should_stop(signal) {
break;
}
n += 2;
current_term = scale(current_term * &op_squared, op_prec);
current_term /= -(n * (n - 1));
current_sum += ¤t_term;
}
scale(current_sum, calc_precision - p)
}
fn sin_cos_large_rational_residual(
signal: &Option<Signal>,
r: &Rational,
multiple: &BigInt,
p: Precision,
) -> (BigInt, BigInt) {
if p >= 1 {
return (Zero::zero(), signed::ONE.deref().clone());
}
let iterations_needed = -p / 2 + 4;
if should_stop(signal) {
return (Zero::zero(), signed::ONE.deref().clone());
}
let calc_precision = p - bound_log2(2 * iterations_needed) - 4;
let op_prec = p - 2;
let op_appr = large_rational_half_pi_residual(signal, r, multiple, op_prec);
let op_squared = scale(&op_appr * &op_appr, op_prec);
let max_trunc_error = BigUint::one()
<< usize::try_from(p - 4 - calc_precision).expect("truncation shift is nonnegative");
let mut sin_n = 1;
let mut sin_term = scale(op_appr, op_prec - calc_precision);
let mut sin_sum = sin_term.clone();
while sin_term.magnitude() > &max_trunc_error {
if should_stop(signal) {
break;
}
sin_n += 2;
sin_term = scale(sin_term * &op_squared, op_prec);
sin_term /= -(sin_n * (sin_n - 1));
sin_sum += &sin_term;
}
let mut cos_n = 0;
let mut cos_term = signed::ONE.deref() << (-calc_precision);
let mut cos_sum = cos_term.clone();
while cos_term.magnitude() > &max_trunc_error {
if should_stop(signal) {
break;
}
cos_n += 2;
cos_term = scale(cos_term * &op_squared, op_prec);
cos_term /= -(cos_n * (cos_n - 1));
cos_sum += &cos_term;
}
(
scale(sin_sum, calc_precision - p),
scale(cos_sum, calc_precision - p),
)
}
fn cos_large_rational(signal: &Option<Signal>, r: &Rational, p: Precision) -> BigInt {
let multiple = large_rational_half_pi_multiple(signal, r);
match large_rational_quadrant(&multiple).to_u8() {
Some(0) => cos_large_rational_residual(signal, r, &multiple, p),
Some(1) => -sin_large_rational_residual(signal, r, &multiple, p),
Some(2) => -cos_large_rational_residual(signal, r, &multiple, p),
Some(3) => sin_large_rational_residual(signal, r, &multiple, p),
_ => unreachable!("quadrant reduction is modulo four"),
}
}
fn half_pi_minus_rational(signal: &Option<Signal>, r: &Rational, p: Precision) -> BigInt {
let extra = 2;
let work_precision = p - extra;
let half_pi = Computable::pi().approx_signal(signal, work_precision + 1);
let rational = ratio(r, work_precision);
scale(half_pi - rational, -extra)
}
fn cos_half_pi_minus_rational(signal: &Option<Signal>, r: &Rational, p: Precision) -> BigInt {
if p >= 1 {
return signed::ONE.deref().clone();
}
let iterations_needed = -p / 2 + 4;
if should_stop(signal) {
return signed::ONE.deref().clone();
}
let calc_precision = p - bound_log2(2 * iterations_needed) - 4;
let op_prec = p - 2;
let op_appr = half_pi_minus_rational(signal, r, op_prec);
let op_squared = scale(&op_appr * &op_appr, op_prec);
let max_trunc_error = BigUint::one()
<< usize::try_from(p - 4 - calc_precision).expect("truncation shift is nonnegative");
let mut n = 0;
let mut current_term = signed::ONE.deref() << (-calc_precision);
let mut current_sum = current_term.clone();
while current_term.magnitude() > &max_trunc_error {
if should_stop(signal) {
break;
}
n += 2;
current_term = scale(current_term * &op_squared, op_prec);
current_term /= -(n * (n - 1));
current_sum += ¤t_term;
}
scale(current_sum, calc_precision - p)
}
fn sin(signal: &Option<Signal>, c: &Computable, p: Precision) -> BigInt {
if p >= 1 {
return Zero::zero();
}
let iterations_needed = -p / 2 + 4;
if should_stop(signal) {
return Zero::zero();
}
let calc_precision = p - bound_log2(2 * iterations_needed) - 4; let op_prec = p - 2;
let op_appr = c.approx_signal(signal, op_prec);
let op_squared = scale(&op_appr * &op_appr, op_prec);
let max_trunc_error = BigUint::one()
<< usize::try_from(p - 4 - calc_precision).expect("truncation shift is nonnegative");
let mut n = 1;
let mut current_term = scale(op_appr.clone(), op_prec - calc_precision);
let mut current_sum = current_term.clone();
while current_term.magnitude() > &max_trunc_error {
if should_stop(signal) {
break;
}
n += 2;
current_term = scale(current_term * &op_squared, op_prec);
current_term /= -(n * (n - 1));
current_sum += ¤t_term;
}
scale(current_sum, calc_precision - p)
}
fn sin_rational(signal: &Option<Signal>, r: &Rational, p: Precision) -> BigInt {
if p >= 1 {
return Zero::zero();
}
let iterations_needed = -p / 2 + 4;
if should_stop(signal) {
return Zero::zero();
}
let calc_precision = p - bound_log2(2 * iterations_needed) - 4;
let op_prec = p - 2;
let op_appr = ratio(r, op_prec);
let op_squared = scale(&op_appr * &op_appr, op_prec);
let max_trunc_error = BigUint::one()
<< usize::try_from(p - 4 - calc_precision).expect("truncation shift is nonnegative");
let mut n = 1;
let mut current_term = scale(op_appr.clone(), op_prec - calc_precision);
let mut current_sum = current_term.clone();
while current_term.magnitude() > &max_trunc_error {
if should_stop(signal) {
break;
}
n += 2;
current_term = scale(current_term * &op_squared, op_prec);
current_term /= -(n * (n - 1));
current_sum += ¤t_term;
}
scale(current_sum, calc_precision - p)
}
fn sin_large_rational(signal: &Option<Signal>, r: &Rational, p: Precision) -> BigInt {
let multiple = large_rational_half_pi_multiple(signal, r);
match large_rational_quadrant(&multiple).to_u8() {
Some(0) => sin_large_rational_residual(signal, r, &multiple, p),
Some(1) => cos_large_rational_residual(signal, r, &multiple, p),
Some(2) => -sin_large_rational_residual(signal, r, &multiple, p),
Some(3) => -cos_large_rational_residual(signal, r, &multiple, p),
_ => unreachable!("quadrant reduction is modulo four"),
}
}
fn sin_half_pi_minus_rational(signal: &Option<Signal>, r: &Rational, p: Precision) -> BigInt {
if p >= 1 {
return Zero::zero();
}
let iterations_needed = -p / 2 + 4;
if should_stop(signal) {
return Zero::zero();
}
let calc_precision = p - bound_log2(2 * iterations_needed) - 4;
let op_prec = p - 2;
let op_appr = half_pi_minus_rational(signal, r, op_prec);
let op_squared = scale(&op_appr * &op_appr, op_prec);
let max_trunc_error = BigUint::one()
<< usize::try_from(p - 4 - calc_precision).expect("truncation shift is nonnegative");
let mut n = 1;
let mut current_term = scale(op_appr.clone(), op_prec - calc_precision);
let mut current_sum = current_term.clone();
while current_term.magnitude() > &max_trunc_error {
if should_stop(signal) {
break;
}
n += 2;
current_term = scale(current_term * &op_squared, op_prec);
current_term /= -(n * (n - 1));
current_sum += ¤t_term;
}
scale(current_sum, calc_precision - p)
}
fn sin_cos_half_pi_minus_rational(
signal: &Option<Signal>,
r: &Rational,
p: Precision,
) -> (BigInt, BigInt) {
if p >= 1 {
return (Zero::zero(), signed::ONE.deref().clone());
}
let iterations_needed = -p / 2 + 4;
if should_stop(signal) {
return (Zero::zero(), signed::ONE.deref().clone());
}
let calc_precision = p - bound_log2(2 * iterations_needed) - 4;
let op_prec = p - 2;
let op_appr = half_pi_minus_rational(signal, r, op_prec);
let op_squared = scale(&op_appr * &op_appr, op_prec);
let max_trunc_error = BigUint::one()
<< usize::try_from(p - 4 - calc_precision).expect("truncation shift is nonnegative");
let mut sin_n = 1;
let mut sin_term = scale(op_appr, op_prec - calc_precision);
let mut sin_sum = sin_term.clone();
while sin_term.magnitude() > &max_trunc_error {
if should_stop(signal) {
break;
}
sin_n += 2;
sin_term = scale(sin_term * &op_squared, op_prec);
sin_term /= -(sin_n * (sin_n - 1));
sin_sum += &sin_term;
}
let mut cos_n = 0;
let mut cos_term = signed::ONE.deref() << (-calc_precision);
let mut cos_sum = cos_term.clone();
while cos_term.magnitude() > &max_trunc_error {
if should_stop(signal) {
break;
}
cos_n += 2;
cos_term = scale(cos_term * &op_squared, op_prec);
cos_term /= -(cos_n * (cos_n - 1));
cos_sum += &cos_term;
}
(
scale(sin_sum, calc_precision - p),
scale(cos_sum, calc_precision - p),
)
}
fn cot_half_pi_minus_rational(signal: &Option<Signal>, r: &Rational, p: Precision) -> BigInt {
if p >= 1 {
return Zero::zero();
}
let working_prec = p - 8;
let (sin_appr, cos_appr) = sin_cos_half_pi_minus_rational(signal, r, working_prec);
let abs_sin = sin_appr.abs();
if abs_sin.is_zero() {
panic!("ArithmeticException");
}
let scaled_numerator = cos_appr << -p;
let adjustment = &abs_sin >> 1;
if scaled_numerator.sign() == Sign::Minus {
let rounded: BigInt = ((-scaled_numerator) + adjustment) / abs_sin;
-rounded
} else {
(scaled_numerator + adjustment) / abs_sin
}
}
fn tan(signal: &Option<Signal>, c: &Computable, p: Precision) -> BigInt {
if p >= 1 {
return Zero::zero();
}
let working_prec = p - 8;
let sin_appr = sin(signal, c, working_prec);
let cos_appr = cos(signal, c, working_prec);
let abs_cos = cos_appr.abs();
if abs_cos.is_zero() {
panic!("ArithmeticException");
}
let scaled_numerator = if cos_appr.sign() == Sign::Minus {
-sin_appr << -p
} else {
sin_appr << -p
};
let adjustment = &abs_cos >> 1;
if scaled_numerator.sign() == Sign::Minus {
let rounded: BigInt = ((-scaled_numerator) + adjustment) / abs_cos;
-rounded
} else {
(scaled_numerator + adjustment) / abs_cos
}
}
fn tan_rational(signal: &Option<Signal>, r: &Rational, p: Precision) -> BigInt {
if p >= 1 {
return Zero::zero();
}
let working_prec = p - 8;
let sin_appr = sin_rational(signal, r, working_prec);
let cos_appr = cos_rational(signal, r, working_prec);
let abs_cos = cos_appr.abs();
if abs_cos.is_zero() {
panic!("ArithmeticException");
}
let scaled_numerator = if cos_appr.sign() == Sign::Minus {
-sin_appr << -p
} else {
sin_appr << -p
};
let adjustment = &abs_cos >> 1;
if scaled_numerator.sign() == Sign::Minus {
let rounded: BigInt = ((-scaled_numerator) + adjustment) / abs_cos;
-rounded
} else {
(scaled_numerator + adjustment) / abs_cos
}
}
fn tan_large_rational(signal: &Option<Signal>, r: &Rational, p: Precision) -> BigInt {
crate::trace_dispatch!("computable_approx", "tan", "large-rational-direct-quotient");
if p >= 1 {
return Zero::zero();
}
let working_prec = p - 8;
let multiple = large_rational_half_pi_multiple(signal, r);
if let Some(reduced) = tan_large_rational_quarter_pi(signal, r, &multiple, p) {
return reduced;
}
let (sin_residual, cos_residual) =
sin_cos_large_rational_residual(signal, r, &multiple, working_prec);
let (sin_appr, cos_appr) = match large_rational_quadrant(&multiple).to_u8() {
Some(0) => (sin_residual, cos_residual),
Some(1) => (cos_residual, -sin_residual),
Some(2) => (-sin_residual, -cos_residual),
Some(3) => (-cos_residual, sin_residual),
_ => unreachable!("quadrant reduction is modulo four"),
};
let abs_cos = cos_appr.abs();
if abs_cos.is_zero() {
panic!("ArithmeticException");
}
let scaled_numerator = if cos_appr.sign() == Sign::Minus {
-sin_appr << -p
} else {
sin_appr << -p
};
let adjustment = &abs_cos >> 1;
if scaled_numerator.sign() == Sign::Minus {
let rounded: BigInt = ((-scaled_numerator) + adjustment) / abs_cos;
-rounded
} else {
(scaled_numerator + adjustment) / abs_cos
}
}
fn cot(signal: &Option<Signal>, c: &Computable, p: Precision) -> BigInt {
if p >= 1 {
return Zero::zero();
}
let working_prec = p - 8;
let sin_appr = sin(signal, c, working_prec);
let cos_appr = cos(signal, c, working_prec);
let abs_sin = sin_appr.abs();
if abs_sin.is_zero() {
panic!("ArithmeticException");
}
let scaled_numerator = cos_appr << -p;
let adjustment = &abs_sin >> 1;
if scaled_numerator.sign() == Sign::Minus {
let rounded: BigInt = ((-scaled_numerator) + adjustment) / abs_sin;
-rounded
} else {
(scaled_numerator + adjustment) / abs_sin
}
}
fn ln(signal: &Option<Signal>, c: &Computable, p: Precision) -> BigInt {
if p >= 0 {
return Zero::zero();
}
let iterations_needed = -p / 2 + 4;
let calc_precision = p - bound_log2(2 * iterations_needed) - 4;
let op_prec = calc_precision - 3;
let op_appr = c.approx_signal(signal, op_prec);
let scaled_x = scale(op_appr, op_prec - calc_precision);
let scaled_one = signed::ONE.deref() << -calc_precision;
let denominator = (&scaled_one << 1) + &scaled_x;
let numerator = &scaled_x << -calc_precision;
let y: BigInt = if numerator.sign() == Sign::Minus {
let rounded: BigInt = ((-&numerator) + (&denominator >> 1)) / &denominator;
-rounded
} else {
(&numerator + (&denominator >> 1)) / &denominator
};
let y_squared = scale(&y * &y, calc_precision);
let mut current_power = y.clone();
let mut current_term = y.clone();
let mut sum = current_term.clone();
let mut n = 1;
let max_trunc_error = BigUint::one()
<< usize::try_from(p - 4 - calc_precision).expect("truncation shift is nonnegative");
while current_term.magnitude() > &max_trunc_error {
if should_stop(signal) {
break;
}
n += 2;
current_power = scale(current_power * &y_squared, calc_precision);
current_term = ¤t_power / n;
sum += ¤t_term;
}
scale(sum << 1, calc_precision - p)
}
fn ln_rational(signal: &Option<Signal>, r: &Rational, p: Precision) -> BigInt {
if p >= 0 {
return Zero::zero();
}
let iterations_needed = -p / 2 + 4;
let calc_precision = p - bound_log2(2 * iterations_needed) - 4;
let op_prec = calc_precision - 3;
let op_appr = ratio(r, op_prec);
let scaled_x = scale(op_appr, op_prec - calc_precision);
let scaled_one = signed::ONE.deref() << -calc_precision;
let denominator = (&scaled_one << 1) + &scaled_x;
let numerator = &scaled_x << -calc_precision;
let y: BigInt = if numerator.sign() == Sign::Minus {
let rounded: BigInt = ((-&numerator) + (&denominator >> 1)) / &denominator;
-rounded
} else {
(&numerator + (&denominator >> 1)) / &denominator
};
let y_squared = scale(&y * &y, calc_precision);
let mut current_power = y.clone();
let mut current_term = y.clone();
let mut sum = current_term.clone();
let mut n = 1;
let max_trunc_error = BigUint::one()
<< usize::try_from(p - 4 - calc_precision).expect("truncation shift is nonnegative");
while current_term.magnitude() > &max_trunc_error {
if should_stop(signal) {
break;
}
n += 2;
current_power = scale(current_power * &y_squared, calc_precision);
current_term = ¤t_power / n;
sum += ¤t_term;
}
scale(sum << 1, calc_precision - p)
}
fn binary_scaled_ln_rational(
signal: &Option<Signal>,
residual: &Rational,
shift: i32,
p: Precision,
) -> BigInt {
crate::trace_dispatch!("computable_approx", "ln", "binary-scaled-rational");
if shift == 0 {
return ln_rational(signal, residual, p);
}
let sum_precision = p - 2;
let residual_appr = ln_rational(signal, residual, sum_precision);
let shift_abs = shift.unsigned_abs();
let shift_msd = i32::try_from(u32::BITS - 1 - shift_abs.leading_zeros())
.expect("shift magnitude bits fit in i32");
let ln2_precision = sum_precision - shift_msd - 3;
let ln2_appr = Computable::ln2().approx_signal(signal, ln2_precision);
let ln2_term = scale(
BigInt::from(shift) * ln2_appr,
ln2_precision - sum_precision,
);
scale(residual_appr + ln2_term, -2)
}
fn atan(signal: &Option<Signal>, i: &BigInt, p: Precision) -> BigInt {
if p >= 1 {
return Zero::zero();
}
let iterations_needed: i32 = -p / 2 + 2;
let calc_precision = p - bound_log2(2 * iterations_needed) - 2;
let max_trunc_error: BigUint = BigUint::one() << (p - 2 - calc_precision);
let scaled_1 = signed::ONE.deref() << (-calc_precision);
let big_op_squared: BigInt = i * i;
let inverse: BigInt = scaled_1 / i;
let mut current_power = inverse.clone();
let mut current_term = inverse.clone();
let mut sum = inverse;
let mut sign = 1;
let mut n = 1;
while current_term.magnitude() > &max_trunc_error {
if should_stop(signal) {
break;
}
n += 2;
current_power /= &big_op_squared;
sign = -sign;
let signed_n: BigInt = (n * sign).into();
current_term = ¤t_power / signed_n;
sum += ¤t_term;
}
scale(sum, calc_precision - p)
}
fn atan_computable(signal: &Option<Signal>, c: &Computable, p: Precision) -> BigInt {
if p >= 1 {
return Zero::zero();
}
let iterations_needed: i32 = -p / 2 + 4;
let calc_precision = p - bound_log2(2 * iterations_needed) - 5;
let op_prec = calc_precision - 3;
let op_appr = c.approx_signal(signal, op_prec);
let op_squared = scale(&op_appr * &op_appr, op_prec);
let max_trunc_error = BigUint::one()
<< usize::try_from(p - 4 - calc_precision).expect("truncation shift is nonnegative");
let mut current_term = scale(op_appr, op_prec - calc_precision);
let mut sum = current_term.clone();
let mut n = 1;
while current_term.magnitude() > &max_trunc_error {
if should_stop(signal) {
break;
}
n += 2;
current_term = scale(current_term * &op_squared, op_prec);
current_term *= -(n - 2);
current_term /= n;
sum += ¤t_term;
}
scale(sum, calc_precision - p)
}
fn atan_rational_small(signal: &Option<Signal>, r: &Rational, p: Precision) -> BigInt {
if p >= 1 {
return Zero::zero();
}
let iterations_needed: i32 = -p / 2 + 4;
let calc_precision = p - bound_log2(2 * iterations_needed) - 5;
let op_prec = calc_precision - 3;
let op_appr = ratio(r, op_prec);
let op_squared = scale(&op_appr * &op_appr, op_prec);
let max_trunc_error = BigUint::one()
<< usize::try_from(p - 4 - calc_precision).expect("truncation shift is nonnegative");
let mut current_term = scale(op_appr, op_prec - calc_precision);
let mut sum = current_term.clone();
let mut n = 1;
while current_term.magnitude() > &max_trunc_error {
if should_stop(signal) {
break;
}
n += 2;
current_term = scale(current_term * &op_squared, op_prec);
current_term *= -(n - 2);
current_term /= n;
sum += ¤t_term;
}
scale(sum, calc_precision - p)
}
fn atan_anchor_residual(r: &Rational, anchor_numerator: u8, anchor_denominator: u8) -> Rational {
let numerator_positive = r.numerator() * anchor_denominator;
let numerator_negative = r.denominator() * anchor_numerator;
let numerator = BigInt::from_biguint(Sign::Plus, numerator_positive)
- BigInt::from_biguint(Sign::Plus, numerator_negative);
let denominator = r.denominator() * anchor_denominator + r.numerator() * anchor_numerator;
Rational::from_bigint_fraction(numerator, denominator)
.expect("atan anchor residual denominator is positive")
}
fn atan_sqrt_rational_small(signal: &Option<Signal>, r: &Rational, p: Precision) -> BigInt {
if p >= 1 {
return Zero::zero();
}
let iterations_needed: i32 = -p / 2 + 4;
let calc_precision = p - bound_log2(2 * iterations_needed) - 5;
let op_prec = calc_precision - 3;
let sqrt_extra = 8;
let op_appr = shift(ratio(r, 2 * op_prec - sqrt_extra).sqrt(), -(sqrt_extra / 2));
let op_squared = scale(&op_appr * &op_appr, op_prec);
let max_trunc_error = BigUint::one()
<< usize::try_from(p - 4 - calc_precision).expect("truncation shift is nonnegative");
let mut current_term = scale(op_appr, op_prec - calc_precision);
let mut sum = current_term.clone();
let mut n = 1;
while current_term.magnitude() > &max_trunc_error {
if should_stop(signal) {
break;
}
n += 2;
current_term = scale(current_term * &op_squared, op_prec);
current_term *= -(n - 2);
current_term /= n;
sum += ¤t_term;
}
scale(sum, calc_precision - p)
}
fn atan_rational(signal: &Option<Signal>, r: &Rational, p: Precision) -> BigInt {
crate::trace_dispatch!("computable_approx", "atan", "exact-rational-reduction");
match r.sign() {
Sign::NoSign => return Zero::zero(),
Sign::Minus => return -atan_rational(signal, &(-r.clone()), p),
Sign::Plus => {}
}
if r.numerator() == &BigUint::one() {
let denominator = BigInt::from_biguint(Sign::Plus, r.denominator().clone());
if denominator > *signed::ONE.deref() {
return atan(signal, &denominator, p);
}
}
let half = HALF_RATIONAL.deref();
if r <= &half {
return atan_rational_small(signal, r, p);
}
if r.denominator() == &BigUint::one() && r.numerator() > &BigUint::one() {
crate::trace_dispatch!("computable_approx", "atan", "large-integer-reciprocal");
let extra = 3;
let work_precision = p - extra;
let half_pi = Computable::pi().approx_signal(signal, work_precision + 1);
let denominator = BigInt::from_biguint(Sign::Plus, r.numerator().clone());
let reduced = atan(signal, &denominator, work_precision);
return scale(half_pi - reduced, -extra);
}
if r.msd_exact().is_some_and(|msd| msd >= 1) {
let extra = 3;
let work_precision = p - extra;
let half_pi = Computable::pi().approx_signal(signal, work_precision + 1);
let reciprocal = r.clone().inverse().expect("positive rational is nonzero");
let reduced = atan_rational(signal, &reciprocal, work_precision);
return scale(half_pi - reduced, -extra);
}
let extra = 3;
let work_precision = p - extra;
if r >= SEVEN_FOURTHS_RATIONAL.deref() && r <= TWO_RATIONAL.deref() {
crate::trace_dispatch!("computable_approx", "atan", "two-anchor-half-pi");
let half_pi = Computable::pi().approx_signal(signal, work_precision + 1);
let anchor_tail =
Computable::atan_inv2_constant().approx_signal(signal, work_precision + 1);
let residual = atan_anchor_residual(r, 2, 1);
let reduced = atan_rational_small(signal, &residual, work_precision);
return scale(half_pi - anchor_tail + reduced, -extra);
}
if r >= FOUR_THIRDS_RATIONAL.deref() && r <= TWO_RATIONAL.deref() {
crate::trace_dispatch!("computable_approx", "atan", "three-halves-anchor");
let quarter_pi = Computable::pi().approx_signal(signal, work_precision + 2);
let anchor_tail =
Computable::atan_inv5_constant().approx_signal(signal, work_precision + 2);
let residual = atan_anchor_residual(r, 3, 2);
let reduced = atan_rational_small(signal, &residual, work_precision);
return scale(quarter_pi + anchor_tail + scale(reduced, 2), -(extra + 2));
}
if r <= TWO_RATIONAL.deref() {
crate::trace_dispatch!("computable_approx", "atan", "unit-anchor-pi-quarter");
let quarter_pi = Computable::pi().approx_signal(signal, work_precision + 2);
let residual = atan_anchor_residual(r, 1, 1);
let reduced = atan_rational_small(signal, &residual, work_precision);
return scale(quarter_pi + scale(reduced, 2), -(extra + 2));
}
let anchor = atan(signal, signed::TWO.deref(), work_precision);
let residual = atan_anchor_residual(r, 1, 2);
let reduced = atan_rational_small(signal, &residual, work_precision);
scale(anchor + reduced, -extra)
}
fn asin_rational(signal: &Option<Signal>, r: &Rational, p: Precision) -> BigInt {
if p >= 1 {
return Zero::zero();
}
let iterations_needed: i32 = -p / 2 + 4;
let calc_precision = p - bound_log2(2 * iterations_needed) - 5;
let op_prec = calc_precision - 3;
let op_appr = ratio(r, op_prec);
let op_squared = scale(&op_appr * &op_appr, op_prec);
let max_trunc_error = BigUint::one()
<< usize::try_from(p - 4 - calc_precision).expect("truncation shift is nonnegative");
let mut current_term = scale(op_appr, op_prec - calc_precision);
let mut sum = current_term.clone();
let mut n = 0_i32;
while current_term.magnitude() > &max_trunc_error {
if should_stop(signal) {
break;
}
n += 1;
current_term = scale(current_term * &op_squared, op_prec);
let numerator = (2 * n - 1) * (2 * n - 1);
let denominator = (2 * n) * (2 * n + 1);
current_term *= numerator;
current_term /= denominator;
sum += ¤t_term;
}
scale(sum, calc_precision - p)
}
fn asin_computable(signal: &Option<Signal>, c: &Computable, p: Precision) -> BigInt {
if p >= 1 {
return Zero::zero();
}
let iterations_needed: i32 = -p / 2 + 4;
let calc_precision = p - bound_log2(2 * iterations_needed) - 5;
let op_prec = calc_precision - 3;
let op_appr = c.approx_signal(signal, op_prec);
let op_squared = scale(&op_appr * &op_appr, op_prec);
let max_trunc_error = BigUint::one()
<< usize::try_from(p - 4 - calc_precision).expect("truncation shift is nonnegative");
let mut current_term = scale(op_appr, op_prec - calc_precision);
let mut sum = current_term.clone();
let mut n = 0_i32;
while current_term.magnitude() > &max_trunc_error {
if should_stop(signal) {
break;
}
n += 1;
current_term = scale(current_term * &op_squared, op_prec);
let numerator = (2 * n - 1) * (2 * n - 1);
let denominator = (2 * n) * (2 * n + 1);
current_term *= numerator;
current_term /= denominator;
sum += ¤t_term;
}
scale(sum, calc_precision - p)
}
fn asin_deferred(signal: &Option<Signal>, c: &Computable, p: Precision) -> BigInt {
let one = Computable::one();
let denominator = one.clone().add(c.clone().square().negate()).sqrt().add(one);
c.clone()
.multiply(denominator.inverse())
.atan()
.shift_left(1)
.approx_signal(signal, p)
}
fn acos_positive(signal: &Option<Signal>, c: &Computable, p: Precision) -> BigInt {
let one = Computable::one();
let numerator = one.clone().add(c.clone().negate());
let denominator = one.add(c.clone());
numerator
.multiply(denominator.inverse())
.sqrt()
.atan()
.shift_left(1)
.approx_signal(signal, p)
}
fn acos_positive_rational(signal: &Option<Signal>, r: &Rational, p: Precision) -> BigInt {
let residual = (Rational::one() - r.clone()) / (Rational::one() + r.clone());
if residual <= Rational::fraction(1, 4).expect("nonzero denominator") {
crate::trace_dispatch!("computable_approx", "acos", "small-rational-residual");
return atan_sqrt_rational_small(signal, &residual, p - 1);
}
Computable::sqrt_rational(residual)
.atan()
.shift_left(1)
.approx_signal(signal, p)
}
fn acos_negative_rational(signal: &Option<Signal>, r: &Rational, p: Precision) -> BigInt {
Computable::pi().approx_signal(signal, p) - acos_positive_rational(signal, r, p)
}
fn acosh_near_one(signal: &Option<Signal>, c: &Computable, p: Precision) -> BigInt {
let one = Computable::one();
let shifted = c.clone().add(one.clone().negate());
let radicand = c.clone().square().add(one.negate());
shifted
.add(radicand.sqrt())
.ln_1p()
.approx_signal(signal, p)
}
fn acosh_direct(signal: &Option<Signal>, c: &Computable, p: Precision) -> BigInt {
let one = Computable::one();
let radicand = c.clone().square().add(one.negate());
c.clone().add(radicand.sqrt()).ln().approx_signal(signal, p)
}
fn asinh_near_zero(signal: &Option<Signal>, c: &Computable, p: Precision) -> BigInt {
let square = c.clone().square();
let one = Computable::one();
let denominator = square.clone().add(one.clone()).sqrt().add(one);
c.clone()
.add(square.multiply(denominator.inverse()))
.ln_1p()
.approx_signal(signal, p)
}
fn asinh_direct(signal: &Option<Signal>, c: &Computable, p: Precision) -> BigInt {
let radicand = c.clone().square().add(Computable::one());
c.clone().add(radicand.sqrt()).ln().approx_signal(signal, p)
}
fn asinh_computable(signal: &Option<Signal>, c: &Computable, p: Precision) -> BigInt {
if p >= 1 {
return Zero::zero();
}
let iterations_needed: i32 = -p / 2 + 4;
let calc_precision = p - bound_log2(2 * iterations_needed) - 5;
let op_prec = calc_precision - 3;
let op_appr = c.approx_signal(signal, op_prec);
let op_squared = scale(&op_appr * &op_appr, op_prec);
let max_trunc_error = BigUint::one()
<< usize::try_from(p - 4 - calc_precision).expect("truncation shift is nonnegative");
let mut current_term = scale(op_appr, op_prec - calc_precision);
let mut sum = current_term.clone();
let mut n = 0_i32;
while current_term.magnitude() > &max_trunc_error {
if should_stop(signal) {
break;
}
n += 1;
current_term = scale(current_term * &op_squared, op_prec);
let numerator = -((2 * n - 1) * (2 * n - 1));
let denominator = (2 * n) * (2 * n + 1);
current_term *= numerator;
current_term /= denominator;
sum += ¤t_term;
}
scale(sum, calc_precision - p)
}
fn asinh_rational(signal: &Option<Signal>, r: &Rational, p: Precision) -> BigInt {
if p >= 1 {
return Zero::zero();
}
let iterations_needed: i32 = -p / 2 + 4;
let calc_precision = p - bound_log2(2 * iterations_needed) - 5;
let op_prec = calc_precision - 3;
let op_appr = ratio(r, op_prec);
let op_squared = scale(&op_appr * &op_appr, op_prec);
let max_trunc_error = BigUint::one()
<< usize::try_from(p - 4 - calc_precision).expect("truncation shift is nonnegative");
let mut current_term = scale(op_appr, op_prec - calc_precision);
let mut sum = current_term.clone();
let mut n = 0_i32;
while current_term.magnitude() > &max_trunc_error {
if should_stop(signal) {
break;
}
n += 1;
current_term = scale(current_term * &op_squared, op_prec);
let numerator = -((2 * n - 1) * (2 * n - 1));
let denominator = (2 * n) * (2 * n + 1);
current_term *= numerator;
current_term /= denominator;
sum += ¤t_term;
}
scale(sum, calc_precision - p)
}
fn atanh_direct(signal: &Option<Signal>, c: &Computable, p: Precision) -> BigInt {
let one = Computable::one();
let numerator = one.clone().add(c.clone());
let denominator = one.add(c.clone().negate());
numerator
.multiply(denominator.inverse())
.ln()
.multiply(Computable::rational(HALF_RATIONAL.clone()))
.approx_signal(signal, p)
}
fn atanh_computable(signal: &Option<Signal>, c: &Computable, p: Precision) -> BigInt {
if p >= 1 {
return Zero::zero();
}
let iterations_needed: i32 = -p / 2 + 4;
let calc_precision = p - bound_log2(2 * iterations_needed) - 5;
let op_prec = calc_precision - 3;
let op_appr = c.approx_signal(signal, op_prec);
let op_squared = scale(&op_appr * &op_appr, op_prec);
let max_trunc_error = BigUint::one()
<< usize::try_from(p - 4 - calc_precision).expect("truncation shift is nonnegative");
let mut current_power = scale(op_appr, op_prec - calc_precision);
let mut current_term = current_power.clone();
let mut sum = current_term.clone();
let mut n = 1_i32;
while current_term.magnitude() > &max_trunc_error {
if should_stop(signal) {
break;
}
n += 2;
current_power = scale(current_power * &op_squared, op_prec);
current_term = ¤t_power / n;
sum += ¤t_term;
}
scale(sum, calc_precision - p)
}
fn atanh_rational(signal: &Option<Signal>, r: &Rational, p: Precision) -> BigInt {
if p >= 1 {
return Zero::zero();
}
let iterations_needed: i32 = -p / 2 + 4;
let calc_precision = p - bound_log2(2 * iterations_needed) - 5;
let op_prec = calc_precision - 3;
let op_appr = ratio(r, op_prec);
let op_squared = scale(&op_appr * &op_appr, op_prec);
let max_trunc_error = BigUint::one()
<< usize::try_from(p - 4 - calc_precision).expect("truncation shift is nonnegative");
let mut current_power = scale(op_appr, op_prec - calc_precision);
let mut current_term = current_power.clone();
let mut sum = current_term.clone();
let mut n = 1_i32;
while current_term.magnitude() > &max_trunc_error {
if should_stop(signal) {
break;
}
n += 2;
current_power = scale(current_power * &op_squared, op_prec);
current_term = ¤t_power / n;
sum += ¤t_term;
}
scale(sum, calc_precision - p)
}