// // Some trigonometic functions with Fractions results. // Currently only sin, cos and tan are implemented. // As I was unable to find a really good integral approximation for them I // implement them as a table which is predefined using the floating point // function f64::sin and then transformed into a fraction of a given // PRECISION. // These approximations are quite good and for a few edge cases // even better than the floating point implementations. // // Georg Hopp <georg@steffers.org> // // Copyright © 2019 Georg Hopp // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program. If not, see <http://www.gnu.org/licenses/>. // use std::ops::Div; use std::ops::Neg; use std::marker::Sized; use crate::Error; pub trait Trig { fn pi() -> Self; fn recip(self) -> Self; fn round(&self) -> i32; fn sqrt(self) -> Result<Self, Error> where Self: Sized; fn sintab() -> Vec<Self> where Self: Sized; fn tantab() -> Vec<Self> where Self: Sized; fn sin(d :i32) -> Self where Self: Sized + Neg<Output = Self> + Copy { match d { 0 ..=90 => Self::sintab()[d as usize], 91 ..=180 => Self::sintab()[180 - d as usize], 181..=270 => -Self::sintab()[d as usize - 180], 271..=359 => -Self::sintab()[360 - d as usize], _ => { Self::sin(if d < 0 { d % 360 + 360 } else { d % 360 }) }, } } fn cos(d :i32) -> Self where Self: Sized + Neg<Output = Self> + Copy { match d { 0 ..=90 => Self::sintab()[90 - d as usize], 91 ..=180 => -Self::sintab()[90 - (180 - d as usize)], 181..=270 => -Self::sintab()[90 - (d as usize - 180)], 271..=359 => Self::sintab()[90 - (360 - d as usize)], _ => { Self::cos(if d < 0 { d % 360 + 360 } else { d % 360 }) }, } } fn tan(d :i32) -> Self where Self: Sized + Copy { match d { 0 ..=179 => Self::tantab()[d as usize], 180..=359 => Self::tantab()[d as usize - 180], _ => { Self::tan(if d < 0 { d % 360 + 360 } else { d % 360 }) }, } } fn cot(d :i32) -> Self where Self: Sized + Copy + From<i32> + Div<Output = Self> { Into::<Self>::into(1) / Self::tan(d) } } impl Trig for f64 { fn pi() -> Self { std::f64::consts::PI } fn recip(self) -> Self { self.recip() } fn round(&self) -> i32 { f64::round(*self) as i32 } fn sqrt(self) -> Result<Self, Error> { let x = self.sqrt(); match x.is_nan() { true => Err("sqrt on negative undefined"), false => Ok(x), } } fn sintab() -> Vec<Self> { lazy_static::lazy_static! { static ref SINTAB :Vec<f64> = (0..=90).map(|x| _sin(x)).collect(); } // f64 sin. (From 0° to 90°) fn _sin(d: u32) -> f64 { match d { 0 => 0.0, 90 => 1.0, _ => (d as f64).to_radians().sin(), } } SINTAB.to_vec() } fn tantab() -> Vec<Self> { // This table exists only because the sin(α) / cos(α) method // yields very large unreducable denominators in a lot of cases. lazy_static::lazy_static! { static ref TANTAB :Vec<f64> = (0..180).map(|x| _tan(x)).collect(); } // fractional tan from f64 tan. (From 0° to 179°) fn _tan(d: u32) -> f64 { match d { 0 => 0.0, 45 => 1.0, 90 => std::f64::INFINITY, 135 => -1.0, _ => (d as f64).to_radians().tan(), } } TANTAB.to_vec() } }