Commit 15ea57a24b3e05b75d9e6250bdefc6000807162b
1 parent
5cd265a6
Add continued fraction and use them to get a better sqrt approximation
Showing
3 changed files
with
204 additions
and
97 deletions
1 | // | 1 | // |
2 | // Some code to support fractional numbers for full precision rational number | 2 | // Some code to support fractional numbers for full precision rational number |
3 | -// calculations. | ||
4 | -// TODO | ||
5 | -// - maybe this could be build as a generic for all integral numbers. | ||
6 | -// (Question, how can I assure that it is build from integral numbers? | 3 | +// calculations. (At least for the standard operations.) |
4 | +// This also implements a sqrt on fractional numbers, which can not be precise | ||
5 | +// because of the irrational nature of most sqare roots. | ||
6 | +// Fractions can only represent rational numbers precise. | ||
7 | // | 7 | // |
8 | // Georg Hopp <georg@steffers.org> | 8 | // Georg Hopp <georg@steffers.org> |
9 | // | 9 | // |
@@ -31,6 +31,10 @@ use std::num::TryFromIntError; | @@ -31,6 +31,10 @@ use std::num::TryFromIntError; | ||
31 | #[derive(Debug, Eq, Clone, Copy)] | 31 | #[derive(Debug, Eq, Clone, Copy)] |
32 | pub struct Fractional (pub i64, pub i64); | 32 | pub struct Fractional (pub i64, pub i64); |
33 | 33 | ||
34 | +pub type Continuous = Vec<i64>; | ||
35 | + | ||
36 | +pub type Error = &'static str; | ||
37 | + | ||
34 | #[inline] | 38 | #[inline] |
35 | fn hcf(x :i64, y :i64) -> i64 { | 39 | fn hcf(x :i64, y :i64) -> i64 { |
36 | match y { | 40 | match y { |
@@ -39,6 +43,38 @@ fn hcf(x :i64, y :i64) -> i64 { | @@ -39,6 +43,38 @@ fn hcf(x :i64, y :i64) -> i64 { | ||
39 | } | 43 | } |
40 | } | 44 | } |
41 | 45 | ||
46 | +// calculate a sqrt as continued fraction sequence. Taken from: | ||
47 | +// https://en.wikipedia.org/wiki/Methods_of_computing_square_roots#Continued_fraction_expansion | ||
48 | +fn sqrt_cfrac(x :i64, a0 :i64) -> Continuous { | ||
49 | + let v :Continuous = Vec::new(); | ||
50 | + | ||
51 | + fn inner(mut v :Continuous, | ||
52 | + x :i64, | ||
53 | + a0 :i64, | ||
54 | + mn :i64, | ||
55 | + dn :i64, | ||
56 | + an :i64) -> Continuous { | ||
57 | + let mn_1 = dn * an - mn; | ||
58 | + let dn_1 = (x - mn_1 * mn_1) / dn; | ||
59 | + let an_1 = (a0 + mn_1) / dn_1; | ||
60 | + | ||
61 | + v.push(an); | ||
62 | + match v.len() { | ||
63 | + 10 => v, | ||
64 | + _ => inner(v, x, a0, mn_1, dn_1, an_1), | ||
65 | + } | ||
66 | + | ||
67 | + // This convergence criterium is not good for very small x | ||
68 | + // thus I decided to break the iteration at constant time. | ||
69 | +// match an_1 == 2 * a0 { | ||
70 | +// true => v, | ||
71 | +// _ => inner(v, x, a0, mn_1, dn_1, an_1), | ||
72 | +// } | ||
73 | + } | ||
74 | + | ||
75 | + inner(v, x, a0, 0, 1, a0) | ||
76 | +} | ||
77 | + | ||
42 | impl Fractional { | 78 | impl Fractional { |
43 | #[inline] | 79 | #[inline] |
44 | pub fn gcd(self, other: Self) -> i64 { | 80 | pub fn gcd(self, other: Self) -> i64 { |
@@ -63,31 +99,54 @@ impl Fractional { | @@ -63,31 +99,54 @@ impl Fractional { | ||
63 | self.1 | 99 | self.1 |
64 | } | 100 | } |
65 | 101 | ||
66 | - pub fn sqrt(self) -> Self { | 102 | + // This is a really bad approximation of sqrt for a fractional... |
103 | + // for (9/3) it will result 3 which if way to far from the truth, | ||
104 | + // which is ~1.7320508075 | ||
105 | + // BUT we can use this value as starting guess for creating a | ||
106 | + // continous fraction for the sqrt... and create a much better | ||
107 | + // fractional representation of the sqrt. | ||
108 | + // So, if inner converges, but is not a perfect square (does not | ||
109 | + // end up in an Ordering::Equal - which is the l > h case) | ||
110 | + // we use the l - 1 as starting guess for sqrt_cfrac. | ||
111 | + // taken from: | ||
112 | + // https://www.geeksforgeeks.org/square-root-of-an-integer/ | ||
113 | + pub fn sqrt(self) -> Result<Self, Error> { | ||
67 | // find the sqrt of x in O(log x/2). | 114 | // find the sqrt of x in O(log x/2). |
68 | - fn floor_sqrt(x :i64) -> i64 { | ||
69 | - fn inner(l :i64, h :i64, x :i64) -> i64 { | 115 | + // This stops if a perfect sqare was found. Else it passes |
116 | + // the found value as starting guess to the continous fraction | ||
117 | + // sqrt function. | ||
118 | + fn floor_sqrt(x :i64) -> Fractional { | ||
119 | + fn inner(l :i64, h :i64, x :i64) -> Fractional { | ||
70 | if l > h { | 120 | if l > h { |
71 | - l - 1 | 121 | + (&sqrt_cfrac(x, l - 1)).into() |
72 | } else { | 122 | } else { |
73 | let m = (l + h) / 2; | 123 | let m = (l + h) / 2; |
74 | match x.cmp(&(m * m)) { | 124 | match x.cmp(&(m * m)) { |
75 | - Ordering::Equal => m, | ||
76 | - Ordering::Less => inner(l, m, x), | 125 | + Ordering::Equal => m.into(), |
126 | + Ordering::Less => inner(l, m - 1, x), | ||
77 | Ordering::Greater => inner(m + 1, h, x), | 127 | Ordering::Greater => inner(m + 1, h, x), |
78 | } | 128 | } |
79 | } | 129 | } |
80 | } | 130 | } |
81 | 131 | ||
82 | - match x.cmp(&0) { | ||
83 | - Ordering::Equal => 0, | ||
84 | - Ordering::Less => -inner(1, -x / 2, -x), | ||
85 | - Ordering::Greater => inner(1, x / 2, x), | ||
86 | - } | 132 | + inner(1, x / 2, x) |
87 | } | 133 | } |
88 | 134 | ||
89 | let Fractional(n, d) = self; | 135 | let Fractional(n, d) = self; |
90 | - Fractional(floor_sqrt(n), floor_sqrt(d)).reduce() | 136 | + |
137 | + let n = match n.cmp(&0) { | ||
138 | + Ordering::Equal => 0.into(), | ||
139 | + Ordering::Less => return Err("sqrt on negative undefined"), | ||
140 | + Ordering::Greater => floor_sqrt(n), | ||
141 | + }; | ||
142 | + | ||
143 | + let d = match d.cmp(&0) { | ||
144 | + Ordering::Equal => return Err("division by zero"), | ||
145 | + Ordering::Less => return Err("sqrt on negative undefined"), | ||
146 | + Ordering::Greater => floor_sqrt(d), | ||
147 | + }; | ||
148 | + | ||
149 | + Ok(n / d) | ||
91 | } | 150 | } |
92 | } | 151 | } |
93 | 152 | ||
@@ -139,6 +198,37 @@ impl TryInto<(i32, i32)> for Fractional { | @@ -139,6 +198,37 @@ impl TryInto<(i32, i32)> for Fractional { | ||
139 | } | 198 | } |
140 | } | 199 | } |
141 | 200 | ||
201 | +impl Into<Continuous> for Fractional { | ||
202 | + // general continous fraction form of a fractional... | ||
203 | + fn into(self) -> Continuous { | ||
204 | + let v :Continuous = Vec::new(); | ||
205 | + | ||
206 | + fn inner(mut v :Continuous, f :Fractional) -> Continuous { | ||
207 | + let Fractional(n, d) = f; | ||
208 | + let a = n / d; | ||
209 | + let Fractional(_n, _d) = f - a.into(); | ||
210 | + | ||
211 | + v.push(a); | ||
212 | + match _n { | ||
213 | + 1 => { v.push(_d); v }, | ||
214 | + _ => inner(v, Fractional(_d, _n)), | ||
215 | + } | ||
216 | + } | ||
217 | + | ||
218 | + inner(v, self) | ||
219 | + } | ||
220 | +} | ||
221 | + | ||
222 | +impl From<&Continuous> for Fractional { | ||
223 | + fn from(x :&Continuous) -> Self { | ||
224 | + let Self(n, d) = x.iter().rev().fold(Fractional(0, 1), |acc, x| { | ||
225 | + let Self(an, ad) = acc + (*x).into(); | ||
226 | + Self(ad, an) | ||
227 | + }); | ||
228 | + Self(d, n) | ||
229 | + } | ||
230 | +} | ||
231 | + | ||
142 | impl PartialEq for Fractional { | 232 | impl PartialEq for Fractional { |
143 | fn eq(&self, other: &Self) -> bool { | 233 | fn eq(&self, other: &Self) -> bool { |
144 | let Fractional(n1, d1) = self; | 234 | let Fractional(n1, d1) = self; |
@@ -18,11 +18,11 @@ | @@ -18,11 +18,11 @@ | ||
18 | // You should have received a copy of the GNU General Public License | 18 | // You should have received a copy of the GNU General Public License |
19 | // along with this program. If not, see <http://www.gnu.org/licenses/>. | 19 | // along with this program. If not, see <http://www.gnu.org/licenses/>. |
20 | // | 20 | // |
21 | -use std::convert::{TryFrom, TryInto}; | 21 | +use std::convert::{TryFrom, TryInto, Into}; |
22 | use std::num::TryFromIntError; | 22 | use std::num::TryFromIntError; |
23 | use std::f64::consts::PI as FPI; | 23 | use std::f64::consts::PI as FPI; |
24 | 24 | ||
25 | -use fractional::fractional::{Fractional, from_vector}; | 25 | +use fractional::fractional::{Fractional, from_vector, Continuous}; |
26 | use fractional::trigonometry::{sin, cos, PI}; | 26 | use fractional::trigonometry::{sin, cos, PI}; |
27 | 27 | ||
28 | struct Vector { | 28 | struct Vector { |
@@ -37,84 +37,96 @@ fn mean(v: &Vec<i64>) -> Result<Fractional, TryFromIntError> { | @@ -37,84 +37,96 @@ fn mean(v: &Vec<i64>) -> Result<Fractional, TryFromIntError> { | ||
37 | Ok(Fractional(r, l)) | 37 | Ok(Fractional(r, l)) |
38 | } | 38 | } |
39 | 39 | ||
40 | -fn main() { | ||
41 | - let a = vec![3, 6, 1, 9]; | ||
42 | - let b = from_vector(&a); | ||
43 | - let c = mean(&a).unwrap(); // This might fail if the len of the | ||
44 | - // vector (usize) does not fit into i32. | ||
45 | - let d :f64 = c.try_into().unwrap(); | ||
46 | - let e :f64 = Fractional::try_into(c).unwrap(); | ||
47 | - | ||
48 | - let f = Fractional(9, 4); | ||
49 | - let g = Fractional(-9, 4); | ||
50 | - | ||
51 | - println!(" [i32] : {:?}" , a); | ||
52 | - println!(" [Fractional] : {:?}" , b); | ||
53 | - println!(" mean of [i32] : {}" , c); | ||
54 | - println!(" as f64 : {}" , d); | ||
55 | - println!(" and as f64 : {}" , e); | ||
56 | - println!(" again as f64 : {}" , TryInto::<f64>::try_into(c).unwrap()); | ||
57 | - println!(" sqrt f : {}" , f.sqrt()); | ||
58 | - println!(" sqrt f as f64 : {}" , TryInto::<f64>::try_into(f.sqrt()).unwrap()); | ||
59 | - println!(" sqrt g : {}" , g.sqrt()); | ||
60 | - println!(" sqrt g as f64 : {}" , TryInto::<f64>::try_into(g.sqrt()).unwrap()); | 40 | +fn common_fractional() { |
41 | + let a = vec![3, 6, 1, 9]; | ||
42 | + let b = from_vector(&a); | ||
43 | + let c = mean(&a).unwrap(); // This might fail if the len of the | ||
44 | + // vector (usize) does not fit into i32. | ||
45 | + let cr :f64 = c.try_into().unwrap(); | ||
46 | + | ||
47 | + println!(" [i32] : {:?}", a); | ||
48 | + println!(" [Fractional] : {:?}", b); | ||
49 | + println!(" mean of [i32] : {}" , c); | ||
50 | + println!(" as f64 : {}" , cr); | ||
51 | + println!(" again as f64 : {}" , TryInto::<f64>::try_into(c).unwrap()); | ||
52 | +} | ||
53 | + | ||
54 | +fn continuous() { | ||
55 | + let d = Fractional(45, 16); | ||
56 | + let e = Fractional(16, 45); | ||
57 | + | ||
58 | + let dc :Continuous = d.into(); | ||
59 | + let ec :Continuous = e.into(); | ||
60 | + | ||
61 | + println!("cont frac of d : {} => {:?}", d, dc); | ||
62 | + println!("cont frac of e : {} => {:?}", e, ec); | ||
63 | + println!(" reverted dc : {:?} {}", dc, Into::<Fractional>::into(&dc)); | ||
64 | + println!(" reverted ec : {:?} {}", ec, Into::<Fractional>::into(&ec)); | ||
65 | +} | ||
66 | + | ||
67 | +fn sqrt() { | ||
68 | + let f = Fractional(-9, 4); | ||
69 | + let fr :f64 = f.try_into().unwrap(); | ||
70 | + let sq = f.sqrt(); | ||
71 | + let _sq = f64::sqrt(fr); | ||
72 | + | ||
73 | + println!("{:>14} : {:?} / {}", format!("sqrt {}", f), sq, _sq); | ||
74 | + | ||
75 | + for f in [ Fractional(9, 4) | ||
76 | + , Fractional(45, 16) | ||
77 | + , Fractional(16, 45) | ||
78 | + , Fractional(9, 3) ].iter() { | ||
79 | + let fr :f64 = (*f).try_into().unwrap(); | ||
80 | + let sq = f.sqrt().unwrap(); | ||
81 | + let sqr :f64 = sq.try_into().unwrap(); | ||
82 | + let _sqr = f64::sqrt(fr); | ||
83 | + | ||
84 | + println!("{:>14} : {} {} / {}", format!("sqrt {}", f), sq, sqr, _sqr); | ||
85 | + } | ||
86 | +} | ||
87 | + | ||
88 | +fn pi() { | ||
89 | + let pir :f64 = PI.try_into().unwrap(); | ||
90 | + let pit :(i32, i32) = PI.try_into().unwrap(); | ||
91 | + let pi2r :f64 = (PI * PI).try_into().unwrap(); | ||
92 | + | ||
61 | println!(" Rust π : {}" , FPI); | 93 | println!(" Rust π : {}" , FPI); |
62 | - println!(" π : {} {}" , TryInto::<f64>::try_into(PI).unwrap(), PI); | ||
63 | - println!(" π as tuple : {:?}" , TryInto::<(i32, i32)>::try_into(PI).unwrap()); | 94 | + println!(" π : {} {}" , PI, pir); |
95 | + println!(" π as tuple : {:?}" , pit); | ||
64 | println!(" Rust π² : {}" , FPI * FPI); | 96 | println!(" Rust π² : {}" , FPI * FPI); |
65 | - println!(" π² : {} {}" , TryInto::<f64>::try_into(PI * PI).unwrap(), PI * PI); | ||
66 | - println!(" sin 9 : {}" , sin(9)); | ||
67 | - println!(" sin 9 : {}" , TryInto::<f64>::try_into(sin(9)).unwrap()); | ||
68 | - println!(" Rust sin 9 : {}" , f64::sin(9.0 * FPI / 180.0)); | ||
69 | - println!(" sin 17 : {}" , sin(17)); | ||
70 | - println!(" sin 17 : {}" , TryInto::<f64>::try_into(sin(17)).unwrap()); | ||
71 | - println!(" Rust sin 17 : {}" , f64::sin(17.0 * FPI / 180.0)); | ||
72 | - println!(" sin 31 : {}" , sin(31)); | ||
73 | - println!(" sin 31 : {}" , TryInto::<f64>::try_into(sin(31)).unwrap()); | ||
74 | - println!(" Rust sin 31 : {}" , f64::sin(31.0 * FPI / 180.0)); | ||
75 | - println!(" sin 45 : {}" , sin(45)); | ||
76 | - println!(" sin 45 : {}" , TryInto::<f64>::try_into(sin(45)).unwrap()); | ||
77 | - println!(" Rust sin 45 : {}" , f64::sin(45.0 * FPI / 180.0)); | ||
78 | - println!(" sin 73 : {}" , sin(73)); | ||
79 | - println!(" sin 73 : {}" , TryInto::<f64>::try_into(sin(73)).unwrap()); | ||
80 | - println!(" Rust sin 73 : {}" , f64::sin(73.0 * FPI / 180.0)); | ||
81 | - println!(" sin 123 : {}" , sin(123)); | ||
82 | - println!(" sin 123 : {}" , TryInto::<f64>::try_into(sin(123)).unwrap()); | ||
83 | - println!(" Rust sin 123 : {}" , f64::sin(123.0 * FPI / 180.0)); | ||
84 | - println!(" sin 213 : {}" , sin(213)); | ||
85 | - println!(" sin 213 : {}" , TryInto::<f64>::try_into(sin(213)).unwrap()); | ||
86 | - println!(" Rust sin 213 : {}" , f64::sin(213.0 * FPI / 180.0)); | ||
87 | - println!(" sin 312 : {}" , sin(312)); | ||
88 | - println!(" sin 312 : {}" , TryInto::<f64>::try_into(sin(312)).unwrap()); | ||
89 | - println!(" Rust sin 312 : {}" , f64::sin(312.0 * FPI / 180.0)); | ||
90 | - println!(" sin 876 : {}" , sin(876)); | ||
91 | - println!(" sin 876 : {}" , TryInto::<f64>::try_into(sin(876)).unwrap()); | ||
92 | - println!(" Rust sin 876 : {}" , f64::sin(876.0 * FPI / 180.0)); | ||
93 | - println!(" cos 9 : {}" , cos(9)); | ||
94 | - println!(" cos 9 : {}" , TryInto::<f64>::try_into(cos(9)).unwrap()); | ||
95 | - println!(" Rust cos 9 : {}" , f64::cos(9.0 * FPI / 180.0)); | ||
96 | - println!(" cos 17 : {}" , cos(17)); | ||
97 | - println!(" cos 17 : {}" , TryInto::<f64>::try_into(cos(17)).unwrap()); | ||
98 | - println!(" Rust cos 17 : {}" , f64::cos(17.0 * FPI / 180.0)); | ||
99 | - println!(" cos 31 : {}" , cos(31)); | ||
100 | - println!(" cos 31 : {}" , TryInto::<f64>::try_into(cos(31)).unwrap()); | ||
101 | - println!(" Rust cos 31 : {}" , f64::cos(31.0 * FPI / 180.0)); | ||
102 | - println!(" cos 45 : {}" , cos(45)); | ||
103 | - println!(" cos 45 : {}" , TryInto::<f64>::try_into(cos(45)).unwrap()); | ||
104 | - println!(" Rust cos 45 : {}" , f64::cos(45.0 * FPI / 180.0)); | ||
105 | - println!(" cos 73 : {}" , cos(73)); | ||
106 | - println!(" cos 73 : {}" , TryInto::<f64>::try_into(cos(73)).unwrap()); | ||
107 | - println!(" Rust cos 73 : {}" , f64::cos(73.0 * FPI / 180.0)); | ||
108 | - println!(" cos 123 : {}" , cos(123)); | ||
109 | - println!(" cos 123 : {}" , TryInto::<f64>::try_into(cos(123)).unwrap()); | ||
110 | - println!(" Rust cos 123 : {}" , f64::cos(123.0 * FPI / 180.0)); | ||
111 | - println!(" cos 213 : {}" , cos(213)); | ||
112 | - println!(" cos 213 : {}" , TryInto::<f64>::try_into(cos(213)).unwrap()); | ||
113 | - println!(" Rust cos 213 : {}" , f64::cos(213.0 * FPI / 180.0)); | ||
114 | - println!(" cos 312 : {}" , cos(312)); | ||
115 | - println!(" cos 312 : {}" , TryInto::<f64>::try_into(cos(312)).unwrap()); | ||
116 | - println!(" Rust cos 312 : {}" , f64::cos(312.0 * FPI / 180.0)); | ||
117 | - println!(" cos 876 : {}" , cos(876)); | ||
118 | - println!(" cos 876 : {}" , TryInto::<f64>::try_into(cos(876)).unwrap()); | ||
119 | - println!(" Rust cos 876 : {}" , f64::cos(876.0 * FPI / 180.0)); | 97 | + println!(" π² : {} {}" , PI * PI, pi2r); |
98 | +} | ||
99 | + | ||
100 | +fn _sin() { | ||
101 | + for d in [9, 17, 31, 45, 73, 123, 213, 312, 876].iter() { | ||
102 | + let s = sin(*d as i32); | ||
103 | + let sr :f64 = s.try_into().unwrap(); | ||
104 | + let _s = f64::sin(*d as f64 * FPI / 180.0); | ||
105 | + | ||
106 | + println!("{:>14} : {} {} / {}", format!("sin {}", d), s, sr, _s); | ||
107 | + } | ||
108 | +} | ||
109 | + | ||
110 | +fn _cos() { | ||
111 | + for d in [9, 17, 31, 45, 73, 123, 213, 312, 876].iter() { | ||
112 | + let s = cos(*d as i32); | ||
113 | + let sr :f64 = s.try_into().unwrap(); | ||
114 | + let _s = f64::cos(*d as f64 * FPI / 180.0); | ||
115 | + | ||
116 | + println!("{:>14} : {} {} / {}", format!("cos {}", d), s, sr, _s); | ||
117 | + } | ||
118 | +} | ||
119 | + | ||
120 | +fn main() { | ||
121 | + common_fractional(); | ||
122 | + println!(); | ||
123 | + continuous(); | ||
124 | + println!(); | ||
125 | + sqrt(); | ||
126 | + println!(); | ||
127 | + pi(); | ||
128 | + println!(); | ||
129 | + _sin(); | ||
130 | + println!(); | ||
131 | + _cos(); | ||
120 | } | 132 | } |
1 | // | 1 | // |
2 | -// Test our fractional crate / module... | 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 | // | 8 | // |
4 | // Georg Hopp <georg@steffers.org> | 9 | // Georg Hopp <georg@steffers.org> |
5 | // | 10 | // |
Please
register
or
login
to post a comment