You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
264 lines
8.7 KiB
264 lines
8.7 KiB
//
|
|
// 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::cmp::Ordering;
|
|
use std::ops::Neg;
|
|
use std::marker::Sized;
|
|
use crate::{Fractional, Error};
|
|
use crate::continuous::Continuous;
|
|
|
|
pub trait Trig {
|
|
fn pi() -> Self;
|
|
fn recip(self) -> Self;
|
|
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 })
|
|
},
|
|
}
|
|
}
|
|
}
|
|
|
|
// Try to keep precision as high as possible while having a denominator
|
|
// as small as possible. The values are taken by try and error.
|
|
const PRECISION :i64 = 1000000;
|
|
const MAX_DENOMINATOR :i64 = 7000;
|
|
|
|
// This is a really close fractional approximation for pi.
|
|
impl Trig for Fractional {
|
|
fn pi() -> Self {
|
|
Fractional(355, 113)
|
|
}
|
|
|
|
fn recip(self) -> Self {
|
|
let Fractional(n, d) = self;
|
|
Fractional(d, n)
|
|
}
|
|
|
|
// This is a really bad approximation of sqrt for a fractional...
|
|
// for (9/3) it will result 3 which if way to far from the truth,
|
|
// which is ~1.7320508075
|
|
// BUT we can use this value as starting guess for creating a
|
|
// continous fraction for the sqrt... and create a much better
|
|
// fractional representation of the sqrt.
|
|
// So, if inner converges, but is not a perfect square (does not
|
|
// end up in an Ordering::Equal - which is the l > h case)
|
|
// we use the l - 1 as starting guess for sqrt_cfrac.
|
|
// taken from:
|
|
// https://www.geeksforgeeks.org/square-root-of-an-integer/
|
|
fn sqrt(self) -> Result<Self, Error> {
|
|
// find the sqrt of x in O(log x/2).
|
|
// This stops if a perfect sqare was found. Else it passes
|
|
// the found value as starting guess to the continous fraction
|
|
// sqrt function.
|
|
fn floor_sqrt(x :i64) -> Fractional {
|
|
fn inner(l :i64, h :i64, x :i64) -> Fractional {
|
|
if l > h {
|
|
(&Continuous::sqrt(x, l - 1)).into()
|
|
} else {
|
|
let m = (l + h) / 2;
|
|
match x.cmp(&(m * m)) {
|
|
Ordering::Equal => m.into(),
|
|
Ordering::Less => inner(l, m - 1, x),
|
|
Ordering::Greater => inner(m + 1, h, x),
|
|
}
|
|
}
|
|
}
|
|
|
|
match x {
|
|
0 => 0.into(),
|
|
1 => 1.into(),
|
|
_ => inner(1, x / 2, x),
|
|
}
|
|
}
|
|
|
|
let Fractional(n, d) = self;
|
|
|
|
let n = match n.cmp(&0) {
|
|
Ordering::Equal => 0.into(),
|
|
Ordering::Less => return Err("sqrt on negative undefined"),
|
|
Ordering::Greater => floor_sqrt(n),
|
|
};
|
|
|
|
let d = match d.cmp(&0) {
|
|
Ordering::Equal => 0.into(),
|
|
Ordering::Less => return Err("sqrt on negative undefined"),
|
|
Ordering::Greater => floor_sqrt(d),
|
|
};
|
|
|
|
Ok(n / d)
|
|
}
|
|
|
|
fn sintab() -> Vec<Self> {
|
|
// hold sin Fractionals from 0 to 89 ...
|
|
// luckily with a bit of index tweeking this can also be used for
|
|
// cosine values.
|
|
lazy_static::lazy_static! {
|
|
static ref SINTAB :Vec<Fractional> =
|
|
(0..=90).map(|x| _sin(x)).collect();
|
|
}
|
|
|
|
// fractional sin from f64 sin. (From 0° to 90°)
|
|
fn _sin(d: u32) -> Fractional {
|
|
match d {
|
|
0 => Fractional(0, 1),
|
|
90 => Fractional(1, 1),
|
|
_ => reduce(d, PRECISION, &f64::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<Fractional> =
|
|
(0..180).map(|x| _tan(x)).collect();
|
|
}
|
|
|
|
// fractional tan from f64 tan. (From 0° to 179°)
|
|
fn _tan(d: u32) -> Fractional {
|
|
match d {
|
|
0 => Fractional(0, 1),
|
|
45 => Fractional(1, 1),
|
|
90 => Fractional(1, 0), // although they are both inf and -inf.
|
|
135 => -Fractional(1, 1),
|
|
_ => reduce(d, PRECISION, &f64::tan),
|
|
}
|
|
}
|
|
|
|
TANTAB.to_vec()
|
|
}
|
|
}
|
|
|
|
impl Trig for f64 {
|
|
fn pi() -> Self {
|
|
std::f64::consts::PI
|
|
}
|
|
|
|
fn recip(self) -> Self {
|
|
self.recip()
|
|
}
|
|
|
|
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()
|
|
}
|
|
}
|
|
|
|
// search for a fraction with a denominator less than MAX_DENOMINATOR that
|
|
// provides the minimal PRECISION criteria.
|
|
// !! With f = &f64::tan and d close to the inf boundarys of tan
|
|
// we get very large numerators because the numerator becomes a
|
|
// multiple of the denominator.
|
|
fn reduce(d :u32, p :i64, f :&dyn Fn(f64) -> f64) -> Fractional {
|
|
// This is undefined behaviour for very large f64, but our f64
|
|
// is always between 0.0 and 1000000.0 which should be fine.
|
|
let s = (f((d as f64).to_radians()) * p as f64).round() as i64;
|
|
let Fractional(n, dn) = Fractional(s, p).reduce();
|
|
match dn.abs().cmp(&MAX_DENOMINATOR) {
|
|
Ordering::Less => Fractional(n, dn),
|
|
_ => reduce(d, p + 1, f),
|
|
}
|
|
}
|