SimpleFraction.java

package com.github.mygreen.cellformatter.number;

/**
 * 簡易的な分数を表現するクラス。
 * <p>古いPOIだと提供されていないため、同じものを実装。</p>
 *
 * @author T.TSUCHIE
 *
 */
public class SimpleFraction {
    
    /** 分母 */
    private final int denominator;
    
    /** 分子 */
    private final int numerator;
    
    /**
     * 分子と分母を指定してインスタンスを作成する。
     * @param numerator 分子。
     * @param denominator 分母。
     */
    public SimpleFraction(final int numerator, final int denominator) {
        if(denominator == 0) {
            throw new IllegalArgumentException("denominator should not be zero.");
        }
        
        this.numerator = numerator;
        this.denominator = denominator;
    }
    
    /**
     * 分母の値を指定してインスタンスを作成する。
     * 
     * @param val 分数として表現するもとの数値の値。
     * @param exactDenom 分母の値。
     * @return 
     */
    public static SimpleFraction createFractionExactDenominator(final double val, final int exactDenom){
        int num =  (int)Math.round(val*(double)exactDenom);
        return new SimpleFraction(num,exactDenom);
    }
    
    /**
     * 分母の最大値を指定してインスタンスを作成する。
     * <p>指定した分母の値以下に近似した分数を取得する。
     * 
     * @param value 分数として表現するもとの数値の値。
     * @param maxDenominator 分母の最大値。
     * 
     * @throws RuntimeException if the continued fraction failed to converge.
     * @throws IllegalArgumentException {@literal if value > Integer.MAX_VALUE}
     */
    public static SimpleFraction createFractionMaxDenominator(final double value, final int maxDenominator){
        return buildFractionMaxDenominator(value, 0, maxDenominator, 100);
    }
    /**
     * Create a fraction given the double value and either the maximum error
     * allowed or the maximum number of denominator digits.
     * <p>
     * References:
     * <ul>
     * <li><a href="http://mathworld.wolfram.com/ContinuedFraction.html">
     * Continued Fraction</a> equations (11) and (22)-(26)</li>
     * </ul>
     * </p>
     *
     *  Based on org.apache.commons.math.fraction.Fraction from Apache Commons-Math.
     *  YK: The only reason of having this class is to avoid dependency on the Commons-Math jar.
     *
     * @param value the double value to convert to a fraction.
     * @param epsilon maximum error allowed.  The resulting fraction is within
     *        <code>epsilon</code> of <code>value</code>, in absolute terms.
     * @param maxDenominator maximum denominator value allowed.
     * @param maxIterations maximum number of convergents
     * @throws RuntimeException if the continued fraction failed to
     *         converge.
     * @throws IllegalArgumentException if value > Integer.MAX_VALUE
     */
    private static SimpleFraction buildFractionMaxDenominator(final double value, final double epsilon,
            final int maxDenominator, final int maxIterations) {
        
        final long overflow = Integer.MAX_VALUE;
        double r0 = value;
        long a0 = (long)Math.floor(r0);
        if (a0 > overflow) {
            throw new IllegalArgumentException("Overflow trying to convert "+value+" to fraction ("+a0+"/"+1l+")");
        }
        
        // check for (almost) integer arguments, which should not go
        // to iterations.
        if (Math.abs(a0 - value) < epsilon) {
            return new SimpleFraction((int)a0, 1);
        }

        long p0 = 1;
        long q0 = 0;
        long p1 = a0;
        long q1 = 1;

        long p2;
        long q2;

        int n = 0;
        boolean stop = false;
        do {
            ++n;
            double r1 = 1.0 / (r0 - a0);
            long a1 = (long)Math.floor(r1);
            p2 = (a1 * p1) + p0;
            q2 = (a1 * q1) + q0;
            //MATH-996/POI-55419
            if (epsilon == 0.0f && maxDenominator > 0 && Math.abs(q2) > maxDenominator &&
                    Math.abs(q1) < maxDenominator){

                return new SimpleFraction((int)p1, (int)q1);
            }
            if ((p2 > overflow) || (q2 > overflow)) {
                throw new RuntimeException("Overflow trying to convert "+value+" to fraction ("+p2+"/"+q2+")");
            }

            double convergent = (double)p2 / (double)q2;
            if (n < maxIterations && Math.abs(convergent - value) > epsilon && q2 < maxDenominator) {
                p0 = p1;
                p1 = p2;
                q0 = q1;
                q1 = q2;
                a0 = a1;
                r0 = r1;
            } else {
                stop = true;
            }
        } while (!stop);

        if (n >= maxIterations) {
            throw new RuntimeException("Unable to convert "+value+" to fraction after "+maxIterations+" iterations");
        }

        if (q2 < maxDenominator) {
            return new SimpleFraction((int) p2, (int)q2);
        } else {
            return new SimpleFraction((int)p1, (int)q1);
        }

    }
    
    /**
     * 分母の取得
     * 
     * @return the denominator.
     */
    public int getDenominator() {
        return denominator;
    }
    
    /**
     * 分子の取得
     * 
     * @return the numerator.
     */
    public int getNumerator() {
        return numerator;
    }
}