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