trigonometry.rs 4.36 KB
//
// 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()
    }
}