use std::sync::Arc;
use num_bigint::BigInt;
use num_traits::{One, Signed, ToPrimitive, Zero};
use parking_lot::RwLock;
use crate::binary::{
Binary, Bounds, FiniteBounds, ReciprocalRounding, UBinary, XBinary, reciprocal_of_biguint,
};
use crate::binary_utils::bisection::normalize_finite_to_bounds;
use crate::error::ComputableError;
use crate::node::{Node, NodeOp};
pub struct SinOp {
pub inner: Arc<Node>,
pub pi_node: Arc<Node>,
pub num_terms: RwLock<BigInt>,
}
impl NodeOp for SinOp {
fn compute_bounds(&self) -> Result<Bounds, ComputableError> {
let input_bounds = self.inner.get_bounds()?;
let pi_bounds = self.pi_node.get_bounds()?;
let num_terms = self.num_terms.read().clone();
sin_bounds(&input_bounds, &pi_bounds, &num_terms)
}
fn refine_step(&self, precision_bits: usize) -> Result<bool, ComputableError> {
let mut num_terms = self.num_terms.write();
if precision_bits <= crate::MAX_COMPUTATION_BITS {
let needed_n = (precision_bits / 3).max(1);
let needed = BigInt::from(needed_n);
if needed > *num_terms {
*num_terms = needed;
}
}
if let Ok(input_bounds) = self.inner.get_bounds()
&& let Some(width_bits) = estimate_precision_bits(&input_bounds)
{
let needed_n = (width_bits / 3).max(1);
let needed = BigInt::from(needed_n);
if needed > *num_terms {
*num_terms = needed;
}
}
*num_terms += BigInt::one();
Ok(true)
}
fn children(&self) -> Vec<Arc<Node>> {
vec![Arc::clone(&self.inner), Arc::clone(&self.pi_node)]
}
fn is_refiner(&self) -> bool {
true
}
}
fn sin_bounds(
input_bounds: &Bounds,
pi_bounds: &Bounds,
num_terms: &BigInt,
) -> Result<Bounds, ComputableError> {
let neg_one = Binary::new(BigInt::from(-1_i32), BigInt::zero());
let pos_one = Binary::new(BigInt::from(1_i32), BigInt::zero());
let lower = input_bounds.small();
let upper = input_bounds.large();
let (lower_bin, upper_bin) = match (lower, &upper) {
(XBinary::Finite(l), XBinary::Finite(u)) => (l, u),
_ => {
return Ok(Bounds::new(
XBinary::Finite(neg_one),
XBinary::Finite(pos_one),
));
}
};
let pi_lo = pi_bounds.small();
let pi_hi_xb = pi_bounds.large();
let pi_interval = match (pi_lo, &pi_hi_xb) {
(XBinary::Finite(lo), XBinary::Finite(hi)) => FiniteBounds::new(lo.clone(), hi.clone()),
_ => {
return Ok(Bounds::new(
XBinary::Finite(neg_one),
XBinary::Finite(pos_one),
));
}
};
let two = UBinary::new(num_bigint::BigUint::from(1_u32), BigInt::from(1_i32)); let two_pi_interval = pi_interval.scale_positive(&two);
let half_pi_lo = Binary::new(
pi_interval.lo().mantissa().clone(),
pi_interval.lo().exponent() - BigInt::one(),
);
let half_pi_hi_val = pi_interval.hi();
let half_pi_hi = Binary::new(
half_pi_hi_val.mantissa().clone(),
half_pi_hi_val.exponent() - BigInt::one(),
);
let half_pi_interval = FiniteBounds::new(half_pi_lo, half_pi_hi);
let n = num_terms.to_usize().unwrap_or(1).max(1);
let input_interval = FiniteBounds::new(lower_bin.clone(), upper_bin.clone());
let input_width = upper_bin.sub(lower_bin);
if input_width >= *two_pi_interval.lo() {
return Ok(Bounds::new(
XBinary::Finite(neg_one),
XBinary::Finite(pos_one),
));
}
let reduced_result = reduce_to_half_pi_range_interval(
&input_interval,
&two_pi_interval,
&pi_interval,
&half_pi_interval,
n,
);
let (result_lo, result_hi) = match reduced_result {
ReductionResult::InRange {
interval,
sign_flip,
} => {
let sin_bounds = compute_sin_on_monotonic_interval(&interval, n);
if sign_flip {
(sin_bounds.hi().neg(), sin_bounds.lo().neg())
} else {
(sin_bounds.lo().clone(), sin_bounds.hi())
}
}
ReductionResult::ContainsMax { sin_min } => {
(sin_min, pos_one.clone())
}
ReductionResult::ContainsMin { sin_max } => {
(neg_one.clone(), sin_max)
}
ReductionResult::ContainsBoth => {
(neg_one.clone(), pos_one.clone())
}
ReductionResult::SpansMultipleBranches {
overall_lo,
overall_hi,
} => {
(overall_lo, overall_hi)
}
};
let clamped_lo = if result_lo < neg_one {
neg_one.clone()
} else {
result_lo
};
let clamped_hi = if result_hi > pos_one {
pos_one
} else {
result_hi
};
let finite = FiniteBounds::new(clamped_lo, clamped_hi);
normalize_finite_to_bounds(&finite)
}
#[derive(Debug)]
enum ReductionResult {
InRange {
interval: FiniteBounds,
sign_flip: bool,
},
ContainsMax { sin_min: Binary },
ContainsMin { sin_max: Binary },
ContainsBoth,
SpansMultipleBranches {
overall_lo: Binary,
overall_hi: Binary,
},
}
fn reduce_to_pi_range_interval(
input: &FiniteBounds,
two_pi: &FiniteBounds,
pi: &FiniteBounds,
) -> FiniteBounds {
let two_pi_mid = two_pi.midpoint();
let neg_pi_hi = pi.hi().neg();
let mut current = input.clone();
let current_mid = current.midpoint();
let k = compute_reduction_factor(¤t_mid, &two_pi_mid);
if !k.is_zero() {
let k_times_two_pi = two_pi.scale_bigint(&k);
current = current.interval_sub(&k_times_two_pi);
}
for _ in 0_u32..2_u32 {
if *current.lo() >= neg_pi_hi && current.hi() <= pi.hi() {
return current;
}
let fixup_mid = current.midpoint();
let fixup_k = compute_reduction_factor(&fixup_mid, &two_pi_mid);
if fixup_k.is_zero() {
break;
}
let fixup_shift = two_pi.scale_bigint(&fixup_k);
current = current.interval_sub(&fixup_shift);
}
current
}
fn reduce_to_half_pi_range_interval(
input: &FiniteBounds,
two_pi: &FiniteBounds,
pi: &FiniteBounds,
half_pi: &FiniteBounds,
n: usize,
) -> ReductionResult {
let reduced = reduce_to_pi_range_interval(input, two_pi, pi);
let neg_half_pi = half_pi.interval_neg(); let neg_pi = pi.interval_neg();
if reduced.hi() <= *half_pi.lo() && *reduced.lo() >= neg_half_pi.hi() {
return ReductionResult::InRange {
interval: reduced,
sign_flip: false,
};
}
if *reduced.lo() >= *half_pi.lo() && reduced.hi() <= pi.hi() {
let transformed = pi.interval_sub(&reduced);
return ReductionResult::InRange {
interval: transformed,
sign_flip: false,
};
}
if reduced.hi() <= neg_half_pi.hi() && *reduced.lo() >= neg_pi.hi() {
let transformed = pi.interval_add(&reduced);
return ReductionResult::InRange {
interval: transformed,
sign_flip: true,
};
}
let spans_max = *reduced.lo() < half_pi.hi() && reduced.hi() > *half_pi.lo();
let spans_min = *reduced.lo() < neg_half_pi.hi() && reduced.hi() > *neg_half_pi.lo();
if spans_max && spans_min {
return ReductionResult::ContainsBoth;
}
if spans_max {
let sin_bounds_at_lo = compute_sin_bounds_for_point_with_pi(reduced.lo(), n, pi, half_pi);
let sin_bounds_at_hi = compute_sin_bounds_for_point_with_pi(&reduced.hi(), n, pi, half_pi);
let sin_min = if sin_bounds_at_lo.lo() < sin_bounds_at_hi.lo() {
sin_bounds_at_lo.lo().clone()
} else {
sin_bounds_at_hi.lo().clone()
};
return ReductionResult::ContainsMax { sin_min };
}
if spans_min {
let sin_bounds_at_lo = compute_sin_bounds_for_point_with_pi(reduced.lo(), n, pi, half_pi);
let sin_bounds_at_hi = compute_sin_bounds_for_point_with_pi(&reduced.hi(), n, pi, half_pi);
let sin_max = if sin_bounds_at_lo.hi() > sin_bounds_at_hi.hi() {
sin_bounds_at_lo.hi()
} else {
sin_bounds_at_hi.hi()
};
return ReductionResult::ContainsMin { sin_max };
}
let neg_one = Binary::new(BigInt::from(-1_i32), BigInt::zero());
let pos_one = Binary::new(BigInt::from(1_i32), BigInt::zero());
let sin_bounds_1 = compute_sin_bounds_for_point_with_pi(reduced.lo(), n, pi, half_pi);
let sin_bounds_2 = compute_sin_bounds_for_point_with_pi(&reduced.hi(), n, pi, half_pi);
let combined = sin_bounds_1.join(&sin_bounds_2);
ReductionResult::SpansMultipleBranches {
overall_lo: combined.lo().clone().max(neg_one),
overall_hi: combined.hi().min(pos_one),
}
}
fn compute_sin_bounds_for_point_with_pi(
x: &Binary,
n: usize,
pi: &FiniteBounds,
half_pi: &FiniteBounds,
) -> FiniteBounds {
let neg_half_pi = half_pi.interval_neg();
let x_interval = FiniteBounds::point(x.clone());
let definitely_above_half_pi = x > &half_pi.hi();
let definitely_below_neg_half_pi = *x < *neg_half_pi.lo();
let definitely_in_center = *x >= neg_half_pi.hi() && x <= half_pi.lo();
if definitely_in_center {
compute_sin_on_monotonic_interval(&x_interval, n)
} else if definitely_above_half_pi {
let reduced_interval = pi.interval_sub(&x_interval);
compute_sin_on_monotonic_interval(&reduced_interval, n)
} else if definitely_below_neg_half_pi {
let reduced_interval = pi.interval_add(&x_interval);
let sin_bounds = compute_sin_on_monotonic_interval(&reduced_interval, n);
FiniteBounds::new(sin_bounds.hi().neg(), sin_bounds.lo().neg())
} else if x >= half_pi.lo() {
let center_bounds = compute_sin_on_monotonic_interval(&x_interval, n);
let upper_reduced = pi.interval_sub(&x_interval);
let upper_bounds = compute_sin_on_monotonic_interval(&upper_reduced, n);
center_bounds.join(&upper_bounds)
} else {
let center_bounds = compute_sin_on_monotonic_interval(&x_interval, n);
let lower_reduced = pi.interval_add(&x_interval);
let lower_sin_bounds = compute_sin_on_monotonic_interval(&lower_reduced, n);
let lower_bounds =
FiniteBounds::new(lower_sin_bounds.hi().neg(), lower_sin_bounds.lo().neg());
center_bounds.join(&lower_bounds)
}
}
fn compute_reduction_factor(x: &Binary, period: &Binary) -> BigInt {
let mx = x.mantissa();
let ex = x.exponent();
let mp = period.mantissa();
let ep = period.exponent();
let mx_bits = crate::sane::bits_as_usize(mx.magnitude().bits());
let mp_bits = crate::sane::bits_as_usize(mp.magnitude().bits());
let precision_bits = mp_bits.saturating_sub(mx_bits).saturating_add(64).max(64);
let shifted_mx = mx << precision_bits;
let quotient = &shifted_mx / mp;
let result_exp = ex - ep - BigInt::from(precision_bits);
if result_exp >= BigInt::zero() {
let shift = result_exp.to_usize().unwrap_or(0);
"ient << shift
} else {
let shift_val = (-&result_exp).to_usize().unwrap_or(0);
match std::num::NonZeroUsize::new(shift_val) {
None => quotient.clone(),
Some(shift) => {
let half = BigInt::one() << crate::sane::sub_one(shift);
let rounded = if quotient.is_negative() {
"ient - &half
} else {
"ient + &half
};
rounded >> shift.get()
}
}
}
}
fn truncate_precision_directed(
x: &Binary,
precision_bits: usize,
dir: RoundingDirection,
) -> Binary {
let mantissa = x.mantissa();
let exponent = x.exponent();
let bit_length = crate::sane::bits_as_usize(mantissa.magnitude().bits());
let Some(shift_nz) = bit_length
.checked_sub(precision_bits)
.and_then(std::num::NonZeroUsize::new)
else {
return x.clone();
};
let shift = shift_nz.get();
let abs_shifted = mantissa.magnitude() >> shift;
let has_remainder = (&abs_shifted << shift) != *mantissa.magnitude();
let is_negative = mantissa.is_negative();
let round_away_from_zero = has_remainder
&& matches!(
(dir, is_negative),
(RoundingDirection::Down, true) | (RoundingDirection::Up, false)
);
let abs_result = if round_away_from_zero {
abs_shifted + 1u32
} else {
abs_shifted
};
let signed_result = if is_negative {
-BigInt::from(abs_result)
} else {
BigInt::from(abs_result)
};
Binary::new(signed_result, exponent + BigInt::from(shift))
}
fn compute_sin_on_monotonic_interval(interval: &FiniteBounds, n: usize) -> FiniteBounds {
let target_precision = crate::sane_arithmetic!(n; n * 10);
let truncated_lo =
truncate_precision_directed(interval.lo(), target_precision, RoundingDirection::Down);
let truncated_hi =
truncate_precision_directed(&interval.hi(), target_precision, RoundingDirection::Up);
let (sin_lo_bounds_lo, _) = taylor_sin_bounds(&truncated_lo, n, target_precision);
let (_, sin_hi_bounds_hi) = taylor_sin_bounds(&truncated_hi, n, target_precision);
FiniteBounds::new(sin_lo_bounds_lo, sin_hi_bounds_hi)
}
#[derive(Clone, Copy, PartialEq, Eq)]
enum RoundingDirection {
Down,
Up,
}
fn taylor_sin_bounds(x: &Binary, n: usize, target_precision: usize) -> (Binary, Binary) {
if n == 0 {
let abs_x = x.magnitude().to_binary();
let error = divide_by_factorial_directed(
&abs_x,
&BigInt::one(),
RoundingDirection::Up,
target_precision,
);
return (error.neg(), error);
}
let mut sum_lo = Binary::zero();
let mut sum_hi = Binary::zero();
let mut power = x.clone(); let mut factorial = BigInt::one();
let term_lo = divide_by_factorial_directed(
&power,
&factorial,
RoundingDirection::Down,
target_precision,
);
let term_hi =
divide_by_factorial_directed(&power, &factorial, RoundingDirection::Up, target_precision);
sum_lo = sum_lo.add(&term_lo);
sum_hi = sum_hi.add(&term_hi);
for k in 1..n {
power = power.mul(x).mul(x);
let k_big = BigInt::from(k);
factorial *= &k_big * 2_i64 * (&k_big * 2_i64 + 1_i64);
let term_num = if k % 2 == 0 {
power.clone()
} else {
power.neg()
};
let t_lo = divide_by_factorial_directed(
&term_num,
&factorial,
RoundingDirection::Down,
target_precision,
);
let t_hi = divide_by_factorial_directed(
&term_num,
&factorial,
RoundingDirection::Up,
target_precision,
);
sum_lo = sum_lo.add(&t_lo);
sum_hi = sum_hi.add(&t_hi);
}
power = power.mul(x).mul(x); let n_big = BigInt::from(n);
factorial *= &n_big * 2_i64 * (&n_big * 2_i64 + 1_i64);
let error_power = Binary::new(
BigInt::from(power.mantissa().magnitude().clone()),
power.exponent().clone(),
);
let error = divide_by_factorial_directed(
&error_power,
&factorial,
RoundingDirection::Up,
target_precision,
);
(sum_lo.sub(&error), sum_hi.add(&error))
}
fn divide_by_factorial_directed(
value: &Binary,
factorial: &BigInt,
rounding: RoundingDirection,
target_precision: usize,
) -> Binary {
if factorial.is_zero() {
return value.clone();
}
let value_bits = crate::sane::bits_as_usize(value.mantissa().magnitude().bits());
let precision_bits = target_precision.max(value_bits);
let is_negative = value.mantissa().is_negative();
let recip_rounding = match (rounding, is_negative) {
(RoundingDirection::Up, false) => ReciprocalRounding::Ceil,
(RoundingDirection::Up, true) => ReciprocalRounding::Floor,
(RoundingDirection::Down, false) => ReciprocalRounding::Floor,
(RoundingDirection::Down, true) => ReciprocalRounding::Ceil,
};
let reciprocal = reciprocal_of_biguint(factorial.magnitude(), precision_bits, recip_rounding);
value.mul(&reciprocal)
}
fn estimate_precision_bits(bounds: &Bounds) -> Option<usize> {
let lo = bounds.small();
let hi_xb = bounds.large();
let (lo_bin, hi_bin) = match (lo, &hi_xb) {
(XBinary::Finite(l), XBinary::Finite(h)) => (l, h),
_ => return None,
};
let width = hi_bin.sub(lo_bin);
if width.mantissa().is_zero() {
return None;
}
let mantissa_bits = crate::sane::bits_as_usize(width.mantissa().magnitude().bits());
let mantissa_bits_i64 = i64::try_from(mantissa_bits).unwrap_or(i64::MAX);
let exp_i64 = width.exponent().to_i64().unwrap_or(0_i64);
let log2_width = exp_i64.saturating_add(mantissa_bits_i64);
let neg_log2 = log2_width.saturating_neg().max(0_i64);
usize::try_from(neg_log2).ok()
}
#[cfg(test)]
pub fn taylor_sin_bounds_test(x: &Binary, n: usize) -> (Binary, Binary) {
taylor_sin_bounds(x, n, n * 10)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::computable::Computable;
use crate::refinement::{XUsize, bounds_width_leq};
use crate::test_utils::{bin, interval_midpoint_computable, unwrap_finite, xbin};
fn assert_bounds_compatible_with_expected(
bounds: &Bounds,
expected: &Binary,
tolerance_exp: &XUsize,
) {
let lower = unwrap_finite(bounds.small());
let upper_xb = bounds.large();
let upper = unwrap_finite(&upper_xb);
assert!(lower <= *expected && *expected <= upper);
assert!(bounds_width_leq(bounds, tolerance_exp));
}
#[test]
fn sin_of_zero() {
let zero = Computable::constant(bin(0, 0));
let sin_zero = zero.sin();
let epsilon = XUsize::Finite(8);
let bounds = sin_zero
.refine_to_default(epsilon)
.expect("refine_to should succeed");
let expected = bin(0, 0);
assert_bounds_compatible_with_expected(&bounds, &expected, &epsilon);
}
#[test]
fn sin_of_pi_over_2() {
let pi_over_2 = Computable::constant(bin(3217, -11));
let sin_pi_2 = pi_over_2.sin();
let epsilon = XUsize::Finite(6);
let bounds = sin_pi_2
.refine_to_default(epsilon)
.expect("refine_to should succeed");
let expected_f64 = (std::f64::consts::FRAC_PI_2).sin();
let expected_binary = XBinary::from_f64(expected_f64)
.expect("expected value should convert to extended binary");
let expected = unwrap_finite(&expected_binary);
let lower = unwrap_finite(bounds.small());
let upper = unwrap_finite(&bounds.large());
assert!(lower <= expected && expected <= upper);
}
#[test]
fn sin_of_pi() {
let pi_approx = Computable::constant(bin(6434, -11));
let sin_pi = pi_approx.sin();
let epsilon = XUsize::Finite(6);
let bounds = sin_pi
.refine_to_default(epsilon)
.expect("refine_to should succeed");
let lower = unwrap_finite(bounds.small());
let upper = unwrap_finite(&bounds.large());
let small_bound = bin(1, -4);
let neg_small_bound = bin(-1, -4);
assert!(lower >= neg_small_bound);
assert!(upper <= small_bound);
}
#[test]
fn sin_of_negative_pi_over_2() {
let neg_pi_over_2 = Computable::constant(bin(-3217, -11));
let sin_neg_pi_2 = neg_pi_over_2.sin();
let epsilon = XUsize::Finite(6);
let bounds = sin_neg_pi_2
.refine_to_default(epsilon)
.expect("refine_to should succeed");
let expected_f64 = (-std::f64::consts::FRAC_PI_2).sin();
let expected_binary = XBinary::from_f64(expected_f64)
.expect("expected value should convert to extended binary");
let expected = unwrap_finite(&expected_binary);
let lower = unwrap_finite(bounds.small());
let upper = unwrap_finite(&bounds.large());
assert!(lower <= expected && expected <= upper);
}
#[test]
fn sin_bounds_always_in_minus_one_to_one() {
let large_value = Computable::constant(bin(100, 0));
let sin_large = large_value.sin();
let bounds = sin_large.bounds().expect("bounds should succeed");
let lower = unwrap_finite(bounds.small());
let upper = unwrap_finite(&bounds.large());
let neg_one = bin(-1, 0);
let one = bin(1, 0);
assert!(lower >= neg_one);
assert!(upper <= one);
}
#[test]
fn sin_of_small_value() {
let small = Computable::constant(bin(1, -4)); let sin_small = small.sin();
let epsilon = XUsize::Finite(8);
let bounds = sin_small
.refine_to_default(epsilon)
.expect("refine_to should succeed");
let expected = XBinary::from_f64(0.0625_f64.sin())
.expect("expected value should convert to extended binary");
let expected_value = unwrap_finite(&expected);
let lower = unwrap_finite(bounds.small());
let upper = unwrap_finite(&bounds.large());
assert!(lower <= expected_value && expected_value <= upper);
}
#[test]
fn sin_interval_spanning_maximum() {
let computable = interval_midpoint_computable(1, 2); let sin_interval = computable.sin();
let bounds = sin_interval.bounds().expect("bounds should succeed");
let upper = unwrap_finite(&bounds.large());
assert!(upper >= bin(1, -1)); }
#[test]
fn sin_with_infinite_input_bounds() {
let unbounded = Computable::new(
0usize,
|_| Ok(Bounds::new(XBinary::NegInf, XBinary::PosInf)),
|state| Ok(state + 1),
);
let sin_unbounded = unbounded.sin();
let bounds = sin_unbounded.bounds().expect("bounds should succeed");
assert_eq!(bounds.small(), &xbin(-1, 0));
assert_eq!(&bounds.large(), &xbin(1, 0));
}
#[test]
fn sin_expression_with_arithmetic() {
let x = Computable::constant(bin(1, 0)); let sin_x = x.clone().sin();
let two = Computable::constant(bin(2, 0));
let expr = sin_x.clone() * two;
let epsilon = XUsize::Finite(8);
let bounds = expr
.refine_to_default(epsilon)
.expect("refine_to should succeed");
let expected = XBinary::from_f64(2.0 * 1.0_f64.sin())
.expect("expected value should convert to extended binary");
let expected_value = unwrap_finite(&expected);
let lower = unwrap_finite(bounds.small());
let upper = unwrap_finite(&bounds.large());
assert!(lower <= expected_value && expected_value <= upper);
}
#[test]
fn directed_rounding_produces_valid_bounds() {
let test_cases = [
bin(1, -2), bin(1, 0), bin(3, 0), bin(-1, 0), bin(5, -1), bin(-3, -1), ];
let neg_one = bin(-1, 0);
let one = bin(1, 0);
for x in &test_cases {
let (lower, upper) = taylor_sin_bounds_test(x, 10);
assert!(
lower <= upper,
"Lower bound {} should be <= upper bound {} for x = {}",
lower,
upper,
x
);
assert!(
lower >= neg_one,
"Lower bound {} should be >= -1 for x = {}",
lower,
x
);
assert!(
upper <= one,
"Upper bound {} should be <= 1 for x = {}",
upper,
x
);
}
}
#[test]
fn directed_rounding_bounds_converge() {
let x = bin(1, 0);
let (lower5, upper5) = taylor_sin_bounds_test(&x, 5);
let (lower10, upper10) = taylor_sin_bounds_test(&x, 10);
let width5 = upper5.sub(&lower5);
let width10 = upper10.sub(&lower10);
assert!(
width10 < width5,
"Bounds with 10 terms (width {}) should be tighter than 5 terms (width {})",
width10,
width5
);
}
#[test]
fn directed_rounding_symmetry() {
let x = bin(1, -2); let neg_x = bin(-1, -2);
let (lower_x, upper_x) = taylor_sin_bounds_test(&x, 10);
let (lower_neg_x, upper_neg_x) = taylor_sin_bounds_test(&neg_x, 10);
let neg_upper_x = upper_x.neg();
let neg_lower_x = lower_x.neg();
assert!(
lower_neg_x <= neg_upper_x.add(&bin(1, -50)),
"lower(sin(-x)) should be approximately -upper(sin(x))"
);
assert!(
neg_lower_x <= upper_neg_x.add(&bin(1, -50)),
"-lower(sin(x)) should be approximately upper(sin(-x))"
);
}
#[test]
fn directed_rounding_lower_bound_is_lower() {
let x = bin(1, 0); let n = 5;
let (lower, upper) = taylor_sin_bounds_test(&x, n);
assert!(
lower <= upper,
"Lower bound {} should be <= upper bound {}",
lower,
upper
);
}
#[test]
fn sin_of_large_multiple_of_pi() {
let x = Computable::constant(bin(100, 0)); let sin_x = x.sin();
let epsilon = XUsize::Finite(4);
let bounds = sin_x
.refine_to_default(epsilon)
.expect("refine_to should succeed");
let lower = unwrap_finite(bounds.small());
let upper = unwrap_finite(&bounds.large());
let neg_one = bin(-1, 0);
let one = bin(1, 0);
assert!(lower >= neg_one, "lower bound should be >= -1");
assert!(upper <= one, "upper bound should be <= 1");
let expected_approx = -0.5063_f64;
let expected_binary = XBinary::from_f64(expected_approx)
.expect("expected value should convert to extended binary");
let expected_value = unwrap_finite(&expected_binary);
let tolerance = bin(1, -2); assert!(
lower <= expected_value.add(&tolerance) && expected_value.sub(&tolerance) <= upper,
"sin(100) bounds [{}, {}] should be within tolerance of expected value {}",
lower,
upper,
expected_value
);
}
#[test]
fn sin_pi_bounds_contain_zero() {
use super::super::pi::pi_bounds_at_precision;
let (pi_lo, pi_hi) = pi_bounds_at_precision(64);
let pi_mid = pi_lo.add(&pi_hi);
let pi_approx = Binary::new(pi_mid.mantissa().clone(), pi_mid.exponent() - BigInt::one());
let sin_pi = Computable::constant(pi_approx).sin();
let epsilon = XUsize::Finite(10);
let bounds = sin_pi
.refine_to_default(epsilon)
.expect("refine_to should succeed");
let lower = unwrap_finite(bounds.small());
let upper = unwrap_finite(&bounds.large());
let zero = bin(0, 0);
assert!(
lower <= zero && zero <= upper,
"sin(pi) bounds [{}, {}] should contain zero",
lower,
upper
);
}
#[test]
fn sin_of_one_to_512_bit_precision() {
let one = Computable::constant(bin(1, 0));
let sin_one = one.sin();
let epsilon = XUsize::Finite(512);
let bounds = sin_one
.refine_to::<1024>(epsilon)
.expect("refine_to 512-bit precision should succeed");
assert!(
bounds_width_leq(&bounds, &epsilon),
"width should be <= 2^-512"
);
let lower = unwrap_finite(bounds.small());
let upper = unwrap_finite(&bounds.large());
let midpoint = FiniteBounds::new(lower, upper).midpoint();
let expected_f64 = 1.0_f64.sin();
let expected_bin =
unwrap_finite(&XBinary::from_f64(expected_f64).expect("expected value should convert"));
let diff = if midpoint > expected_bin {
midpoint.sub(&expected_bin)
} else {
expected_bin.sub(&midpoint)
};
assert!(
diff < bin(1, -52),
"midpoint of 512-bit bounds should agree with f64 sin(1) to ~52 bits, diff = {}",
diff
);
}
#[test]
fn sin_extremely_large_input() {
let large = Computable::constant(bin(1, 20)); let sin_large = large.sin();
let epsilon = XUsize::Finite(4);
let bounds = sin_large
.refine_to_default(epsilon)
.expect("refine_to should succeed for very large input");
let lower = unwrap_finite(bounds.small());
let upper = unwrap_finite(&bounds.large());
let neg_one = bin(-1, 0);
let one = bin(1, 0);
assert!(lower >= neg_one, "lower bound {} should be >= -1", lower);
assert!(upper <= one, "upper bound {} should be <= 1", upper);
let expected_f64 = (1048576.0_f64).sin();
let expected =
unwrap_finite(&XBinary::from_f64(expected_f64).expect("expected value should convert"));
assert!(
lower <= expected && expected <= upper,
"sin(2^20) bounds [{}, {}] should contain expected value {}",
lower,
upper,
expected
);
}
#[test]
fn sin_very_large_input_2_pow_30() {
let large = Computable::constant(bin(1, 30));
let sin_large = large.sin();
let epsilon = XUsize::Finite(4);
let bounds = sin_large
.refine_to_default(epsilon)
.expect("refine_to should succeed for 2^30 input");
let lower = unwrap_finite(bounds.small());
let upper = unwrap_finite(&bounds.large());
let neg_one = bin(-1, 0);
let one = bin(1, 0);
assert!(lower >= neg_one, "lower bound should be >= -1");
assert!(upper <= one, "upper bound should be <= 1");
}
#[test]
fn sin_negative_large_input() {
let x = Computable::constant(bin(-10000, 0));
let sin_x = x.sin();
let epsilon = XUsize::Finite(4);
let bounds = sin_x
.refine_to_default(epsilon)
.expect("refine_to should succeed for large negative input");
let lower = unwrap_finite(bounds.small());
let upper = unwrap_finite(&bounds.large());
let neg_one = bin(-1, 0);
let one = bin(1, 0);
assert!(lower >= neg_one, "lower bound should be >= -1");
assert!(upper <= one, "upper bound should be <= 1");
let expected_f64 = (-10000.0_f64).sin();
let expected =
unwrap_finite(&XBinary::from_f64(expected_f64).expect("expected value should convert"));
assert!(
lower <= expected && expected <= upper,
"sin(-10000) bounds [{}, {}] should contain expected value {}",
lower,
upper,
expected
);
}
#[test]
fn sin_midpoint_correctness_uses_lo_bound() {
use super::super::pi::{pi_bounds_at_precision, two_pi_interval_at_precision};
let two_pi = two_pi_interval_at_precision(64);
let lo = bin(0, 0);
let hi = two_pi.lo().add(&bin(1, -60));
let input_bounds = Bounds::new(XBinary::Finite(lo), XBinary::Finite(hi));
let (pi_lo, pi_hi) = pi_bounds_at_precision(64);
let pi_bounds = Bounds::new(XBinary::Finite(pi_lo), XBinary::Finite(pi_hi));
let result = sin_bounds(&input_bounds, &pi_bounds, &BigInt::from(5))
.expect("sin_bounds should succeed");
let lower = unwrap_finite(result.small());
let upper = unwrap_finite(&result.large());
assert!(lower <= bin(-1, 0), "lower bound {} should be <= -1", lower);
assert!(upper >= bin(1, 0), "upper bound {} should be >= 1", upper);
}
#[test]
fn sin_interval_straddling_both_critical_points() {
let computable = interval_midpoint_computable(-2, 2);
let sin_x = computable.sin();
let bounds = sin_x.bounds().expect("bounds should succeed");
let lower = unwrap_finite(bounds.small());
let upper = unwrap_finite(&bounds.large());
assert!(
lower <= bin(-1, 0),
"lower bound {} should be <= -1 (interval straddles both critical points)",
lower
);
assert!(
upper >= bin(1, 0),
"upper bound {} should be >= 1 (interval straddles both critical points)",
upper
);
}
#[test]
fn sin_wide_interval_near_period_boundary() {
let computable = interval_midpoint_computable(-3, 3);
let sin_x = computable.sin();
let bounds = sin_x.bounds().expect("bounds should succeed");
let lower = unwrap_finite(bounds.small());
let upper = unwrap_finite(&bounds.large());
assert!(lower <= bin(-1, 0), "lower bound should be <= -1");
assert!(upper >= bin(1, 0), "upper bound should be >= 1");
}
}