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