1 package com.github.mygreen.cellformatter.number;
2
3 /**
4 * 簡易的な分数を表現するクラス。
5 * <p>古いPOIだと提供されていないため、同じものを実装。</p>
6 *
7 * @author T.TSUCHIE
8 *
9 */
10 public class SimpleFraction {
11
12 /** 分母 */
13 private final int denominator;
14
15 /** 分子 */
16 private final int numerator;
17
18 /**
19 * 分子と分母を指定してインスタンスを作成する。
20 * @param numerator 分子。
21 * @param denominator 分母。
22 */
23 public SimpleFraction(final int numerator, final int denominator) {
24 if(denominator == 0) {
25 throw new IllegalArgumentException("denominator should not be zero.");
26 }
27
28 this.numerator = numerator;
29 this.denominator = denominator;
30 }
31
32 /**
33 * 分母の値を指定してインスタンスを作成する。
34 *
35 * @param val 分数として表現するもとの数値の値。
36 * @param exactDenom 分母の値。
37 * @return
38 */
39 public static SimpleFraction createFractionExactDenominator(final double val, final int exactDenom){
40 int num = (int)Math.round(val*(double)exactDenom);
41 return new SimpleFraction(num,exactDenom);
42 }
43
44 /**
45 * 分母の最大値を指定してインスタンスを作成する。
46 * <p>指定した分母の値以下に近似した分数を取得する。
47 *
48 * @param value 分数として表現するもとの数値の値。
49 * @param maxDenominator 分母の最大値。
50 *
51 * @throws RuntimeException if the continued fraction failed to converge.
52 * @throws IllegalArgumentException {@literal if value > Integer.MAX_VALUE}
53 */
54 public static SimpleFraction createFractionMaxDenominator(final double value, final int maxDenominator){
55 return buildFractionMaxDenominator(value, 0, maxDenominator, 100);
56 }
57 /**
58 * Create a fraction given the double value and either the maximum error
59 * allowed or the maximum number of denominator digits.
60 * <p>
61 * References:
62 * <ul>
63 * <li><a href="http://mathworld.wolfram.com/ContinuedFraction.html">
64 * Continued Fraction</a> equations (11) and (22)-(26)</li>
65 * </ul>
66 * </p>
67 *
68 * Based on org.apache.commons.math.fraction.Fraction from Apache Commons-Math.
69 * YK: The only reason of having this class is to avoid dependency on the Commons-Math jar.
70 *
71 * @param value the double value to convert to a fraction.
72 * @param epsilon maximum error allowed. The resulting fraction is within
73 * <code>epsilon</code> of <code>value</code>, in absolute terms.
74 * @param maxDenominator maximum denominator value allowed.
75 * @param maxIterations maximum number of convergents
76 * @throws RuntimeException if the continued fraction failed to
77 * converge.
78 * @throws IllegalArgumentException if value > Integer.MAX_VALUE
79 */
80 private static SimpleFraction buildFractionMaxDenominator(final double value, final double epsilon,
81 final int maxDenominator, final int maxIterations) {
82
83 final long overflow = Integer.MAX_VALUE;
84 double r0 = value;
85 long a0 = (long)Math.floor(r0);
86 if (a0 > overflow) {
87 throw new IllegalArgumentException("Overflow trying to convert "+value+" to fraction ("+a0+"/"+1l+")");
88 }
89
90 // check for (almost) integer arguments, which should not go
91 // to iterations.
92 if (Math.abs(a0 - value) < epsilon) {
93 return new SimpleFraction((int)a0, 1);
94 }
95
96 long p0 = 1;
97 long q0 = 0;
98 long p1 = a0;
99 long q1 = 1;
100
101 long p2;
102 long q2;
103
104 int n = 0;
105 boolean stop = false;
106 do {
107 ++n;
108 double r1 = 1.0 / (r0 - a0);
109 long a1 = (long)Math.floor(r1);
110 p2 = (a1 * p1) + p0;
111 q2 = (a1 * q1) + q0;
112 //MATH-996/POI-55419
113 if (epsilon == 0.0f && maxDenominator > 0 && Math.abs(q2) > maxDenominator &&
114 Math.abs(q1) < maxDenominator){
115
116 return new SimpleFraction((int)p1, (int)q1);
117 }
118 if ((p2 > overflow) || (q2 > overflow)) {
119 throw new RuntimeException("Overflow trying to convert "+value+" to fraction ("+p2+"/"+q2+")");
120 }
121
122 double convergent = (double)p2 / (double)q2;
123 if (n < maxIterations && Math.abs(convergent - value) > epsilon && q2 < maxDenominator) {
124 p0 = p1;
125 p1 = p2;
126 q0 = q1;
127 q1 = q2;
128 a0 = a1;
129 r0 = r1;
130 } else {
131 stop = true;
132 }
133 } while (!stop);
134
135 if (n >= maxIterations) {
136 throw new RuntimeException("Unable to convert "+value+" to fraction after "+maxIterations+" iterations");
137 }
138
139 if (q2 < maxDenominator) {
140 return new SimpleFraction((int) p2, (int)q2);
141 } else {
142 return new SimpleFraction((int)p1, (int)q1);
143 }
144
145 }
146
147 /**
148 * 分母の取得
149 *
150 * @return the denominator.
151 */
152 public int getDenominator() {
153 return denominator;
154 }
155
156 /**
157 * 分子の取得
158 *
159 * @return the numerator.
160 */
161 public int getNumerator() {
162 return numerator;
163 }
164 }