Trig Functions
P
/// Function that contains the similarities of the sine and cosine implementations
///
/// Both of them are calculated using their MacLaurin Series
///
/// Because there is just a '+1' that differs in their formula, this function has been
/// created for not repeating
fn template<T: Into<f64>>(x: T, tol: f64, kind: i32) -> f64 {
use std::f64::consts::PI;
const PERIOD: f64 = 2.0 * PI;
/* Sometimes, this function is called for a big 'n'(when tol is very small) */
fn factorial(n: i128) -> i128 {
(1..=n).product()
}
/* Function to round up to the 'decimal'th decimal of the number 'x' */
fn round_up_to_decimal(x: f64, decimal: i32) -> f64 {
let multiplier = 10f64.powi(decimal);
(x * multiplier).round() / multiplier
}
let mut value: f64 = x.into(); //<-- This is the line for which the trait 'Into' is required
/* Check for invalid arguments */
if !value.is_finite() || value.is_nan() {
eprintln!("This function does not accept invalid arguments.");
return f64::NAN;
}
/*
The argument to sine could be bigger than the sine's PERIOD
To prevent overflowing, strip the value off relative to the PERIOD
*/
while value >= PERIOD {
value -= PERIOD;
}
/* For cases when the value is smaller than the -PERIOD (e.g. sin(-3π) <=> sin(-π)) */
while value <= -PERIOD {
value += PERIOD;
}
let mut rez = 0f64;
let mut prev_rez = 1f64;
let mut step: i32 = 0;
/*
This while instruction is the MacLaurin Series for sine / cosine
sin(x) = Σ (-1)^n * x^2n+1 / (2n+1)!, for n >= 0 and x a Real number
cos(x) = Σ (-1)^n * x^2n / (2n)!, for n >= 0 and x a Real number
'+1' in sine's formula is replaced with 'kind', which values are:
-> kind = 0, for cosine
-> kind = 1, for sine
*/
while (prev_rez - rez).abs() > tol {
prev_rez = rez;
rez += (-1f64).powi(step) * value.powi(2 * step + kind)
/ factorial((2 * step + kind) as i128) as f64;
step += 1;
}
/* Round up to the 6th decimal */
round_up_to_decimal(rez, 6)
}
/// Returns the value of sin(x), approximated with the given tolerance
///
/// This function supposes the argument is in radians
///
/// ### Example
///
/// sin(1) == sin(1 rad) == sin(π/180)
pub fn sine<T: Into<f64>>(x: T, tol: f64) -> f64 {
template(x, tol, 1)
}
/// Returns the value of cos, approximated with the given tolerance, for
/// an angle 'x' in radians
pub fn cosine<T: Into<f64>>(x: T, tol: f64) -> f64 {
template(x, tol, 0)
}
/// Cosine of 'x' in degrees, with the given tolerance
pub fn cosine_no_radian_arg<T: Into<f64>>(x: T, tol: f64) -> f64 {
use std::f64::consts::PI;
let val: f64 = x.into();
cosine(val * PI / 180., tol)
}
/// Sine function for non radian angle
///
/// Interprets the argument in degrees, not in radians
///
/// ### Example
///
/// sin(1<sup>o</sup>) != \[ sin(1 rad) == sin(π/180) \]
pub fn sine_no_radian_arg<T: Into<f64>>(x: T, tol: f64) -> f64 {
use std::f64::consts::PI;
let val: f64 = x.into();
sine(val * PI / 180f64, tol)
}
/// Tangent of angle 'x' in radians, calculated with the given tolerance
pub fn tan<T: Into<f64> + Copy>(x: T, tol: f64) -> f64 {
let cos_val = cosine(x, tol);
/* Cover special cases for division */
if cos_val == 0f64 {
f64::NAN
} else {
let sin_val = sine(x, tol);
sin_val / cos_val
}
}
/// Cotangent of angle 'x' in radians, calculated with the given tolerance
pub fn cotan<T: Into<f64> + Copy>(x: T, tol: f64) -> f64 {
let sin_val = sine(x, tol);
/* Cover special cases for division */
if sin_val == 0f64 {
f64::NAN
} else {
let cos_val = cosine(x, tol);
cos_val / sin_val
}
}
/// Tangent of 'x' in degrees, approximated with the given tolerance
pub fn tan_no_radian_arg<T: Into<f64> + Copy>(x: T, tol: f64) -> f64 {
let angle: f64 = x.into();
use std::f64::consts::PI;
tan(angle * PI / 180., tol)
}
/// Cotangent of 'x' in degrees, approximated with the given tolerance
pub fn cotan_no_radian_arg<T: Into<f64> + Copy>(x: T, tol: f64) -> f64 {
let angle: f64 = x.into();
use std::f64::consts::PI;
cotan(angle * PI / 180., tol)
}
#[cfg(test)]
mod tests {
use super::*;
use std::f64::consts::PI;
enum TrigFuncType {
Sine,
Cosine,
Tan,
Cotan,
}
const TOL: f64 = 1e-10;
impl TrigFuncType {
fn verify<T: Into<f64> + Copy>(&self, angle: T, expected_result: f64, is_radian: bool) {
let value = match self {
TrigFuncType::Sine => {
if is_radian {
sine(angle, TOL)
} else {
sine_no_radian_arg(angle, TOL)
}
}
TrigFuncType::Cosine => {
if is_radian {
cosine(angle, TOL)
} else {
cosine_no_radian_arg(angle, TOL)
}
}
TrigFuncType::Tan => {
if is_radian {
tan(angle, TOL)
} else {
tan_no_radian_arg(angle, TOL)
}
}
TrigFuncType::Cotan => {
if is_radian {
cotan(angle, TOL)
} else {
cotan_no_radian_arg(angle, TOL)
}
}
};
assert_eq!(format!("{value:.5}"), format!("{:.5}", expected_result));
}
}
#[test]
fn test_sine() {
let sine_id = TrigFuncType::Sine;
sine_id.verify(0.0, 0.0, true);
sine_id.verify(-PI, 0.0, true);
sine_id.verify(-PI / 2.0, -1.0, true);
sine_id.verify(0.5, 0.4794255386, true);
/* Same tests, but angle is now in degrees */
sine_id.verify(0, 0.0, false);
sine_id.verify(-180, 0.0, false);
sine_id.verify(-180 / 2, -1.0, false);
sine_id.verify(0.5, 0.00872654, false);
}
#[test]
fn test_sine_bad_arg() {
assert!(sine(f64::NEG_INFINITY, 1e-1).is_nan());
assert!(sine_no_radian_arg(f64::NAN, 1e-1).is_nan());
}
#[test]
fn test_cosine_bad_arg() {
assert!(cosine(f64::INFINITY, 1e-1).is_nan());
assert!(cosine_no_radian_arg(f64::NAN, 1e-1).is_nan());
}
#[test]
fn test_cosine() {
let cosine_id = TrigFuncType::Cosine;
cosine_id.verify(0, 1., true);
cosine_id.verify(0, 1., false);
cosine_id.verify(45, 1. / f64::sqrt(2.), false);
cosine_id.verify(PI / 4., 1. / f64::sqrt(2.), true);
cosine_id.verify(360, 1., false);
cosine_id.verify(2. * PI, 1., true);
cosine_id.verify(15. * PI / 2., 0.0, true);
cosine_id.verify(-855, -1. / f64::sqrt(2.), false);
}
#[test]
fn test_tan_bad_arg() {
assert!(tan(PI / 2., TOL).is_nan());
assert!(tan(3. * PI / 2., TOL).is_nan());
}
#[test]
fn test_tan() {
let tan_id = TrigFuncType::Tan;
tan_id.verify(PI / 4., 1f64, true);
tan_id.verify(45, 1f64, false);
tan_id.verify(PI, 0f64, true);
tan_id.verify(180 + 45, 1f64, false);
tan_id.verify(60 - 2 * 180, 1.7320508075, false);
tan_id.verify(30 + 180 - 180, 0.57735026919, false);
}
#[test]
fn test_cotan_bad_arg() {
assert!(cotan(tan(PI / 2., TOL), TOL).is_nan());
assert!(!cotan(0, TOL).is_finite());
}
#[test]
fn test_cotan() {
let cotan_id = TrigFuncType::Cotan;
cotan_id.verify(PI / 4., 1f64, true);
cotan_id.verify(90 + 10 * 180, 0f64, false);
cotan_id.verify(30 - 5 * 180, f64::sqrt(3.), false);
}
}