Commit 7f9b67443ed79dac2fc20b86fab675821298bfc8
1 parent
93df7ada
Add tangens and improve fractional rep
Showing
1 changed file
with
77 additions
and
56 deletions
1 | 1 | // |
2 | 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 | 11 | // Georg Hopp <georg@steffers.org> |
10 | 12 | // |
... | ... | @@ -23,75 +25,94 @@ |
23 | 25 | // You should have received a copy of the GNU General Public License |
24 | 26 | // along with this program. If not, see <http://www.gnu.org/licenses/>. |
25 | 27 | // |
28 | +use std::cmp::Ordering; | |
26 | 29 | use crate::{Fractional}; |
27 | 30 | |
28 | 31 | pub const PI :Fractional = Fractional(355, 113); // This is a really close |
29 | 32 | // fractional approximation |
30 | 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 | 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 | 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