Commit 7f9b67443ed79dac2fc20b86fab675821298bfc8
1 parent
93df7ada
Add tangens and improve fractional rep
Showing
1 changed file
with
77 additions
and
56 deletions
| 1 | // | 1 | // |
| 2 | // Some trigonometic functions with Fractions results. | 2 | // Some trigonometic functions with Fractions results. |
| 3 | -// Currently only sin and cos are implemented. As I was unable | ||
| 4 | -// to find a really good integral approximation for them I | ||
| 5 | -// implement them as tables which are predefined using the | ||
| 6 | -// floating point function f64::sin and then transformed into | ||
| 7 | -// a fraction of a given PRECISION. | 3 | +// Currently only sin, cos and tan are implemented. |
| 4 | +// As I was unable to find a really good integral approximation for them I | ||
| 5 | +// implement them as a table which is predefined using the floating point | ||
| 6 | +// function f64::sin and then transformed into a fraction of a given | ||
| 7 | +// PRECISION. | ||
| 8 | +// These approximations are quite good and for a few edge cases | ||
| 9 | +// even better than the floating point implementations. | ||
| 8 | // | 10 | // |
| 9 | // Georg Hopp <georg@steffers.org> | 11 | // Georg Hopp <georg@steffers.org> |
| 10 | // | 12 | // |
| @@ -23,75 +25,94 @@ | @@ -23,75 +25,94 @@ | ||
| 23 | // You should have received a copy of the GNU General Public License | 25 | // You should have received a copy of the GNU General Public License |
| 24 | // along with this program. If not, see <http://www.gnu.org/licenses/>. | 26 | // along with this program. If not, see <http://www.gnu.org/licenses/>. |
| 25 | // | 27 | // |
| 28 | +use std::cmp::Ordering; | ||
| 26 | use crate::{Fractional}; | 29 | use crate::{Fractional}; |
| 27 | 30 | ||
| 28 | pub const PI :Fractional = Fractional(355, 113); // This is a really close | 31 | pub const PI :Fractional = Fractional(355, 113); // This is a really close |
| 29 | // fractional approximation | 32 | // fractional approximation |
| 30 | // for pi | 33 | // for pi |
| 31 | 34 | ||
| 32 | -const PRECISION :i64 = 100000; | 35 | +// Try to keep precision as high as possible while having a denominator |
| 36 | +// as small as possible. | ||
| 37 | +// The values are taken by triel and error. | ||
| 38 | +const PRECISION :i64 = 1000000; | ||
| 39 | +const MAX_DENOMINATOR :i64 = 7000; | ||
| 33 | 40 | ||
| 34 | -#[inline] | ||
| 35 | -pub fn rad(d: u32) -> f64 { | ||
| 36 | - use std::f64::consts::PI; | ||
| 37 | - d as f64 * PI / 180.0 | ||
| 38 | -} | ||
| 39 | - | ||
| 40 | -pub fn sin(d: i32) -> Fractional { | ||
| 41 | - // hold sin Fractionals from 0 to 89 ... | ||
| 42 | - lazy_static::lazy_static! { | ||
| 43 | - static ref SINTAB :Vec<Fractional> = | ||
| 44 | - (0..90).map(|x| _sin(x)).collect(); | 41 | +pub fn sin(d :i32) -> Fractional { |
| 42 | + match d { | ||
| 43 | + 0 ..=90 => SINTAB[d as usize], | ||
| 44 | + 91 ..=180 => SINTAB[180 - d as usize], | ||
| 45 | + 181..=270 => -SINTAB[d as usize - 180], | ||
| 46 | + 271..=359 => -SINTAB[360 - d as usize], | ||
| 47 | + _ => sin(d % 360), | ||
| 45 | } | 48 | } |
| 49 | +} | ||
| 46 | 50 | ||
| 47 | - // fractional sin from f64 sin. (From 1° to 89°) | ||
| 48 | - fn _sin(d: u32) -> Fractional { | ||
| 49 | - match d { | ||
| 50 | - 0 => Fractional(0, 1), | ||
| 51 | - _ => { | ||
| 52 | - // This is undefined behaviour for very large f64, but our f64 | ||
| 53 | - // is always between 0.0 and 10000.0 which should be fine. | ||
| 54 | - let s = (f64::sin(rad(d)) * PRECISION as f64).round() as i64; | ||
| 55 | - Fractional(s, PRECISION).reduce() | ||
| 56 | - } | ||
| 57 | - } | 51 | +pub fn cos(d :i32) -> Fractional { |
| 52 | + match d { | ||
| 53 | + 0 ..=90 => SINTAB[90 - d as usize], | ||
| 54 | + 91 ..=180 => -SINTAB[90 - (180 - d as usize)], | ||
| 55 | + 181..=270 => -SINTAB[90 - (d as usize - 180)], | ||
| 56 | + 271..=359 => SINTAB[90 - (360 - d as usize)], | ||
| 57 | + _ => cos(d % 360), | ||
| 58 | } | 58 | } |
| 59 | +} | ||
| 59 | 60 | ||
| 61 | +pub fn tan(d :i32) -> Fractional { | ||
| 60 | match d { | 62 | match d { |
| 61 | - 90 => Fractional(1, 1), | ||
| 62 | - 180 => SINTAB[0], | ||
| 63 | - 270 => -Fractional(1, 1), | ||
| 64 | - 1..=89 => SINTAB[d as usize], | ||
| 65 | - 91..=179 => SINTAB[180 - d as usize], | ||
| 66 | - 181..=269 => -SINTAB[d as usize - 180], | ||
| 67 | - 271..=359 => -SINTAB[360 - d as usize], | ||
| 68 | - _ => sin(d % 360), | 63 | + 0 ..=179 => TANTAB[d as usize], |
| 64 | + 180..=359 => TANTAB[d as usize - 180], | ||
| 65 | + _ => tan(d % 360), | ||
| 69 | } | 66 | } |
| 70 | } | 67 | } |
| 71 | 68 | ||
| 72 | -pub fn cos(d: i32) -> Fractional { | ||
| 73 | - lazy_static::lazy_static! { | ||
| 74 | - static ref COSTAB :Vec<Fractional> = | ||
| 75 | - (0..90).map(|x| _cos(x)).collect(); | ||
| 76 | - } | 69 | +// hold sin Fractionals from 0 to 89 ... |
| 70 | +// luckily with a bit of index tweeking this can also be used for | ||
| 71 | +// cosine values. | ||
| 72 | +lazy_static::lazy_static! { | ||
| 73 | + static ref SINTAB :Vec<Fractional> = | ||
| 74 | + (0..=90).map(|x| _sin(x)).collect(); | ||
| 75 | +} | ||
| 77 | 76 | ||
| 78 | - fn _cos(d: u32) -> Fractional { | ||
| 79 | - match d { | ||
| 80 | - 0 => Fractional(1, 1), | ||
| 81 | - _ => { | ||
| 82 | - let s = (f64::cos(rad(d)) * PRECISION as f64).round() as i64; | ||
| 83 | - Fractional(s, PRECISION).reduce() | ||
| 84 | - } | ||
| 85 | - } | 77 | +// This table exists only because the sin(α) / cos(α) method |
| 78 | +// yields very large unreducable denominators in a lot of cases. | ||
| 79 | +lazy_static::lazy_static! { | ||
| 80 | + static ref TANTAB :Vec<Fractional> = | ||
| 81 | + (0..180).map(|x| _tan(x)).collect(); | ||
| 82 | +} | ||
| 83 | + | ||
| 84 | +// fractional sin from f64 sin. (From 0° to 90°) | ||
| 85 | +fn _sin(d: u32) -> Fractional { | ||
| 86 | + match d { | ||
| 87 | + 0 => Fractional(0, 1), | ||
| 88 | + 90 => Fractional(1, 1), | ||
| 89 | + _ => reduce(d, PRECISION, &f64::sin), | ||
| 86 | } | 90 | } |
| 91 | +} | ||
| 87 | 92 | ||
| 93 | +// fractional tan from f64 tan. (From 0° to 179°) | ||
| 94 | +fn _tan(d: u32) -> Fractional { | ||
| 88 | match d { | 95 | match d { |
| 89 | - 90 | 270 => Fractional(0, 1), | ||
| 90 | - 180 => -COSTAB[0], | ||
| 91 | - 1..=89 => COSTAB[d as usize], | ||
| 92 | - 91..=179 => -COSTAB[180 - d as usize], | ||
| 93 | - 181..=269 => -COSTAB[d as usize - 180], | ||
| 94 | - 271..=359 => COSTAB[360 - d as usize], | ||
| 95 | - _ => cos(d % 360), | 96 | + 0 => Fractional(0, 1), |
| 97 | + 45 => Fractional(1, 1), | ||
| 98 | + 90 => Fractional(1, 0), // although they are both inf and -inf. | ||
| 99 | + 135 => -Fractional(1, 1), | ||
| 100 | + _ => reduce(d, PRECISION, &f64::tan), | ||
| 101 | + } | ||
| 102 | +} | ||
| 103 | + | ||
| 104 | +// search for a fraction with a denominator less than 10000 that | ||
| 105 | +// provides the minimal precision criteria. | ||
| 106 | +// !! With f = &f64::tan and d close to the inf boundarys of tan | ||
| 107 | +// we get very large numerators because the numerator becomes a | ||
| 108 | +// multiple of the denominator. | ||
| 109 | +fn reduce(d :u32, p :i64, f :&dyn Fn(f64) -> f64) -> Fractional { | ||
| 110 | + // This is undefined behaviour for very large f64, but our f64 | ||
| 111 | + // is always between 0.0 and 10000000.0 which should be fine. | ||
| 112 | + let s = (f(f64::to_radians(d as f64)) * p as f64).round() as i64; | ||
| 113 | + let Fractional(n, dn) = Fractional(s, p).reduce(); | ||
| 114 | + match dn.abs().cmp(&MAX_DENOMINATOR) { | ||
| 115 | + Ordering::Less => Fractional(n, dn), | ||
| 116 | + _ => reduce(d, p + 1, f), | ||
| 96 | } | 117 | } |
| 97 | } | 118 | } |
Please
register
or
login
to post a comment