use crate::{Ratio, BigInt, gcd_bi};
mod exp;
mod ln;
mod pow;
mod root;
mod trigo;
pub use exp::exp_iter;
pub use ln::{ln_iter, log_iter};
pub use pow::pow_iter;
pub use root::{sqrt_iter, cbrt_iter};
pub use trigo::{sin_iter, cos_iter, tan_iter};
#[must_use = "method returns a new number and does not mutate the original value"]
pub fn common_denom(a: &Ratio, b: &Ratio) -> (BigInt, BigInt, BigInt) {
let gcd = gcd_bi(&a.denom, &b.denom);
let a_den_gcd = a.denom.div_bi(&gcd);
let b_den_gcd = b.denom.div_bi(&gcd);
(
a.numer.mul_bi(&b_den_gcd),
b.numer.mul_bi(&a_den_gcd),
a.denom.mul_bi(&b_den_gcd),
)
}
impl Ratio {
#[must_use = "method returns a new number and does not mutate the original value"]
pub fn neg(&self) -> Self {
Ratio::from_denom_and_numer_raw(self.denom.clone(), self.numer.neg())
}
pub fn neg_mut(&mut self) {
self.numer.neg_mut();
}
#[must_use = "method returns a new number and does not mutate the original value"]
pub fn abs(&self) -> Self {
Ratio::from_denom_and_numer_raw(self.denom.clone(), self.numer.abs())
}
pub fn abs_mut(&mut self) {
self.numer.abs_mut();
}
#[must_use = "method returns a new number and does not mutate the original value"]
pub fn reci(&self) -> Self {
let mut result = Ratio::from_denom_and_numer_raw(
self.numer.clone(),
self.denom.clone()
);
if result.denom.is_zero() {
panic!("Attempt to divide by zero: 1 / {self:?}");
}
if result.denom.is_neg() {
result.denom.neg_mut();
result.numer.neg_mut();
}
#[cfg(test)] assert!(result.is_valid());
result
}
pub fn reci_mut(&mut self) {
*self = self.reci();
}
#[must_use = "method returns a new number and does not mutate the original value"]
pub fn truncate(&self) -> Self {
Ratio::from_bi(self.truncate_bi())
}
pub fn truncate_mut(&mut self) {
self.numer.div_bi_mut(&self.denom);
self.denom = BigInt::one();
#[cfg(test)] assert!(self.is_valid());
}
#[must_use = "method returns a new number and does not mutate the original value"]
pub fn truncate_bi(&self) -> BigInt {
let result = self.numer.div_bi(&self.denom);
#[cfg(test)] assert!(result.is_valid());
result
}
#[must_use = "method returns a new number and does not mutate the original value"]
pub fn frac(&self) -> Self {
let result = Ratio::from_denom_and_numer_raw(self.denom.clone(), self.numer.rem_bi(&self.denom));
#[cfg(test)] {
assert!(result.is_valid());
assert_eq!(&result.add_rat(&self.truncate()), self);
let mut self_clone = self.clone();
self_clone.frac_mut();
assert_eq!(self_clone, result);
}
result
}
pub fn frac_mut(&mut self) {
self.numer.rem_bi_mut(&self.denom);
}
pub fn truncate_and_frac(&self) -> (BigInt, Self) {
let trun = self.truncate_bi();
let frac = Ratio::from_denom_and_numer_raw(self.denom.clone(), self.numer.sub_bi(&self.denom.mul_bi(&trun)));
#[cfg(test)] assert_eq!(frac, self.frac());
(trun, frac)
}
pub fn floor(&self) -> Ratio {
Ratio::from_bi(self.floor_bi())
}
pub fn floor_bi(&self) -> BigInt {
if self.is_neg() {
if self.is_integer() {
self.numer.clone()
}
else {
self.truncate_bi().sub_i32(1)
}
}
else {
self.truncate_bi()
}
}
pub fn round(&self) -> Self {
Ratio::from_bi(self.round_bi())
}
pub fn round_bi(&self) -> BigInt {
let (trun, frac) = self.truncate_and_frac();
let numer_double_abs = frac.numer.mul_i32(2).abs();
use std::cmp::Ordering;
match numer_double_abs.comp_bi(&frac.denom) {
Ordering::Less => trun,
_ => if self.is_neg() {
trun.sub_i32(1)
} else {
trun.add_i32(1)
}
}
}
#[cfg(feature = "rand")]
pub fn random() -> Self {
let mut result = Ratio::from_raw(
vec![0, 0, 0, 0, 1],
false,
(0..4).map(|_| rand::random::<u32>().max(1)).collect(),
false
);
result.fit();
#[cfg(test)] {
assert!(result.is_valid());
assert!(result.lt_one());
assert!(!result.is_zero());
}
result
}
}
#[cfg(test)]
mod tests {
use crate::{Ratio, common_denom};
#[test]
fn round_test() {
let mut curr = -8.0f64;
while curr < 8.0 {
let rounded = curr.round();
let rounded_rat = Ratio::try_from(curr).unwrap().round();
assert_eq!(<f64 as TryInto<Ratio>>::try_into(rounded).unwrap(), rounded_rat);
curr += 0.125;
}
}
#[test]
fn frac_trunc_floor_test() {
let samples = vec![
("3.7", "3.0", "3.0"),
("-3.7", "-3.0", "-4.0"),
("4.0", "4.0", "4.0"),
("-4.0", "-4.0", "-4.0"),
("0.0", "0.0", "0.0"),
("-0.0", "-0.0", "-0.0"),
];
for (before, trun, floor) in samples.into_iter() {
assert_eq!(
Ratio::from_string(before).unwrap().truncate(),
Ratio::from_string(trun).unwrap()
);
assert_eq!(
Ratio::from_string(before).unwrap().floor(),
Ratio::from_string(floor).unwrap()
);
let _ = Ratio::from_string(before).unwrap().frac();
let _ = Ratio::from_string(trun).unwrap().frac();
}
}
#[test]
fn common_denom_test() {
for denom1 in 1..11 {
for denom1 in [denom1, -denom1] {
for numer1 in -10..11 {
for denom2 in 1..11 {
for denom2 in [denom2, -denom2] {
for numer2 in -10..11 {
let n1 = Ratio::from_denom_and_numer_i32(denom1, numer1);
let n2 = Ratio::from_denom_and_numer_i32(denom2, numer2);
let (numer_res1, numer_res2, denom_res) = common_denom(&n1, &n2);
let n1_res = Ratio::from_denom_and_numer(denom_res.clone(), numer_res1);
let n2_res = Ratio::from_denom_and_numer(denom_res, numer_res2);
assert_eq!(n1, n1_res);
assert_eq!(n2, n2_res);
}
}
}
}
}
}
}
}