| 1 | // Copyright (c) 2019-2024 Alexander Medvednikov. All rights reserved. |
| 2 | // Use of this source code is governed by an MIT license |
| 3 | // that can be found in the LICENSE file. |
| 4 | module fractions |
| 5 | |
| 6 | import math |
| 7 | |
| 8 | const default_eps = 1.0e-4 |
| 9 | const max_iterations = 50 |
| 10 | const zero = fraction(0, 1) |
| 11 | |
| 12 | // ------------------------------------------------------------------------ |
| 13 | // Unwrapped evaluation methods for fast evaluation of continued fractions. |
| 14 | // ------------------------------------------------------------------------ |
| 15 | // We need these functions because the evaluation of continued fractions |
| 16 | // always has to be done from the end. Also, the numerator-denominator pairs |
| 17 | // are generated from front to end. This means building a result from a |
| 18 | // previous one isn't possible. So we need unrolled versions to ensure that |
| 19 | // we don't take too much of a performance penalty by calling eval_cf |
| 20 | // several times. |
| 21 | // ------------------------------------------------------------------------ |
| 22 | // eval_1 returns the result of evaluating a continued fraction series of length 1 |
| 23 | fn eval_1(whole i64, d []i64) Fraction { |
| 24 | return fraction(whole * d[0] + 1, d[0]) |
| 25 | } |
| 26 | |
| 27 | // eval_2 returns the result of evaluating a continued fraction series of length 2 |
| 28 | fn eval_2(whole i64, d []i64) Fraction { |
| 29 | den := d[0] * d[1] + 1 |
| 30 | return fraction(whole * den + d[1], den) |
| 31 | } |
| 32 | |
| 33 | // eval_3 returns the result of evaluating a continued fraction series of length 3 |
| 34 | fn eval_3(whole i64, d []i64) Fraction { |
| 35 | d1d2_plus_n2 := d[1] * d[2] + 1 |
| 36 | den := d[0] * d1d2_plus_n2 + d[2] |
| 37 | return fraction(whole * den + d1d2_plus_n2, den) |
| 38 | } |
| 39 | |
| 40 | // eval_cf evaluates a continued fraction series and returns a Fraction. |
| 41 | fn eval_cf(whole i64, den []i64) Fraction { |
| 42 | count := den.len |
| 43 | // Offload some small-scale calculations |
| 44 | // to dedicated functions |
| 45 | match count { |
| 46 | 1 { |
| 47 | return eval_1(whole, den) |
| 48 | } |
| 49 | 2 { |
| 50 | return eval_2(whole, den) |
| 51 | } |
| 52 | 3 { |
| 53 | return eval_3(whole, den) |
| 54 | } |
| 55 | else { |
| 56 | last := count - 1 |
| 57 | mut n := i64(1) |
| 58 | mut d := den[last] |
| 59 | // The calculations are done from back to front |
| 60 | for index := count - 2; index >= 0; index-- { |
| 61 | t := d |
| 62 | d = den[index] * d + n |
| 63 | n = t |
| 64 | } |
| 65 | return fraction(d * whole + n, d) |
| 66 | } |
| 67 | } |
| 68 | } |
| 69 | |
| 70 | // approximate returns a Fraction that approcimates the given value to |
| 71 | // within the default epsilon value (1.0e-4). This means the result will |
| 72 | // be accurate to 3 places after the decimal. |
| 73 | pub fn approximate(val f64) Fraction { |
| 74 | return approximate_with_eps(val, default_eps) |
| 75 | } |
| 76 | |
| 77 | // approximate_with_eps returns a Fraction |
| 78 | pub fn approximate_with_eps(val f64, eps f64) Fraction { |
| 79 | if val == 0.0 { |
| 80 | return zero |
| 81 | } |
| 82 | if eps < 0.0 { |
| 83 | panic('Epsilon value cannot be negative.') |
| 84 | } |
| 85 | if math.abs(val) > max_i64 { |
| 86 | panic('Value out of range.') |
| 87 | } |
| 88 | // The integer part is separated first. Then we process the fractional |
| 89 | // part to generate numerators and denominators in tandem. |
| 90 | whole := i64(val) |
| 91 | mut frac := val - f64(whole) |
| 92 | // Quick exit for integers |
| 93 | if frac == 0.0 { |
| 94 | return fraction(whole, 1) |
| 95 | } |
| 96 | mut d := []i64{} |
| 97 | mut partial := zero |
| 98 | // We must complete the approximation within the maximum number of |
| 99 | // itertations allowed. If we can't panic. |
| 100 | // Empirically tested: the hardest constant to approximate is the |
| 101 | // golden ratio (math.phi) and for f64s, it only needs 38 iterations. |
| 102 | for _ in 0 .. max_iterations { |
| 103 | // We calculate the reciprocal. That's why the numerator is |
| 104 | // always 1. |
| 105 | frac = 1.0 / frac |
| 106 | den := i64(frac) |
| 107 | d << den |
| 108 | // eval_cf is called often so it needs to be performant |
| 109 | partial = eval_cf(whole, d) |
| 110 | // Check if we're done |
| 111 | if math.abs(val - partial.f64()) < eps { |
| 112 | return partial |
| 113 | } |
| 114 | frac -= f64(den) |
| 115 | } |
| 116 | panic("Couldn't converge. Please create an issue on https://github.com/vlang/v") |
| 117 | } |
| 118 | |