View Javadoc
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 }