From a62fcb2fbfc5801e7b74f144b7ccb40683dd6989 Mon Sep 17 00:00:00 2001 From: Tim Allison Date: Thu, 15 Aug 2013 01:46:25 +0000 Subject: [PATCH] 55419 and refactor SimpleFraction git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1514123 13f79535-47bb-0310-9956-ffa450edef68 --- .../poi/ss/format/CellNumberFormatter.java | 124 +------------ .../apache/poi/ss/format/SimpleFraction.java | 170 ++++++++++++++++++ .../poi/ss/usermodel/FractionFormat.java | 104 +---------- .../apache/poi/ss/format/TestCellFormat.java | 11 ++ 4 files changed, 188 insertions(+), 221 deletions(-) create mode 100644 src/java/org/apache/poi/ss/format/SimpleFraction.java diff --git a/src/java/org/apache/poi/ss/format/CellNumberFormatter.java b/src/java/org/apache/poi/ss/format/CellNumberFormatter.java index 91f0979a0..d2b910055 100644 --- a/src/java/org/apache/poi/ss/format/CellNumberFormatter.java +++ b/src/java/org/apache/poi/ss/format/CellNumberFormatter.java @@ -824,7 +824,7 @@ public class CellNumberFormatter extends CellFormatter { n = (int) Math.round(fractional); d = 1; } else { - Fraction frac = new Fraction(fractional, maxDenominator); + SimpleFraction frac = SimpleFraction.buildFractionMaxDenominator(fractional, maxDenominator); n = frac.getNumerator(); d = frac.getDenominator(); } @@ -971,128 +971,6 @@ public class CellNumberFormatter extends CellFormatter { SIMPLE_NUMBER.formatValue(toAppendTo, value); } - /** - * Based on org.apache.commons.math.fraction.Fraction from Apache Commons-Math. - * YK: The only reason of having this inner class is to avoid dependency on the Commons-Math jar. - */ - private static class Fraction { - /** The denominator. */ - private final int denominator; - /** The numerator. */ - private final int numerator; - - /** - * Create a fraction given the double value and either the maximum error - * allowed or the maximum number of denominator digits. - * - * @param value the double value to convert to a fraction. - * @param epsilon maximum error allowed. The resulting fraction is within - * epsilon of value, in absolute terms. - * @param maxDenominator maximum denominator value allowed. - * @param maxIterations maximum number of convergents - * @throws RuntimeException if the continued fraction failed to - * converge. - */ - private Fraction(double value, double epsilon, int maxDenominator, int maxIterations) - { - 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) { - this.numerator = (int) a0; - this.denominator = 1; - return; - } - - 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; - 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) { - this.numerator = (int) p2; - this.denominator = (int) q2; - } else { - this.numerator = (int) p1; - this.denominator = (int) q1; - } - - } - - /** - * Create a fraction given the double value and maximum denominator. - *

- * References: - *

- *

- * @param value the double value to convert to a fraction. - * @param maxDenominator The maximum allowed value for denominator - * @throws RuntimeException if the continued fraction failed to - * converge - */ - public Fraction(double value, int maxDenominator) - { - this(value, 0, maxDenominator, 100); - } - - /** - * Access the denominator. - * @return the denominator. - */ - public int getDenominator() { - return denominator; - } - - /** - * Access the numerator. - * @return the numerator. - */ - public int getNumerator() { - return numerator; - } - - } } diff --git a/src/java/org/apache/poi/ss/format/SimpleFraction.java b/src/java/org/apache/poi/ss/format/SimpleFraction.java new file mode 100644 index 000000000..1452b520f --- /dev/null +++ b/src/java/org/apache/poi/ss/format/SimpleFraction.java @@ -0,0 +1,170 @@ +/* ==================================================================== + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ +package org.apache.poi.ss.format; + +public class SimpleFraction { + + + /** The denominator. */ + private final int denominator; + + /** The numerator. */ + private final int numerator; + /** + * Create a fraction given a double value and a denominator. + * + * @param val double value of fraction + * @param exactDenom the exact denominator + * @return + */ + public static SimpleFraction buildFractionExactDenominator(double val, int exactDenom){ + int num = (int)Math.round(val*(double)exactDenom); + return new SimpleFraction(num,exactDenom); + } + + /** + * Create a fraction given the double value and either the maximum error + * allowed or the maximum number of denominator digits. + * + * @param value the double value to convert to a fraction. + * @param maxDenominator maximum denominator value allowed. + * + * @throws RuntimeException if the continued fraction failed to + * converge. + * @throws IllegalArgumentException if value > Integer.MAX_VALUE + */ + public static SimpleFraction buildFractionMaxDenominator(double value, 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. + *

+ * References: + *

+ *

+ * + * 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 + * epsilon of value, 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(double value, double epsilon, int maxDenominator, int maxIterations) + { + 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); + } + + } + + /** + * Create a fraction given a numerator and denominator. + * @param numerator + * @param denominator maxDenominator The maximum allowed value for denominator + */ + public SimpleFraction(int numerator, int denominator) + { + this.numerator = numerator; + this.denominator = denominator; + } + + /** + * Access the denominator. + * @return the denominator. + */ + public int getDenominator() { + return denominator; + } + + /** + * Access the numerator. + * @return the numerator. + */ + public int getNumerator() { + return numerator; + } + +} + diff --git a/src/java/org/apache/poi/ss/usermodel/FractionFormat.java b/src/java/org/apache/poi/ss/usermodel/FractionFormat.java index 1522e7afb..43564c885 100644 --- a/src/java/org/apache/poi/ss/usermodel/FractionFormat.java +++ b/src/java/org/apache/poi/ss/usermodel/FractionFormat.java @@ -22,6 +22,7 @@ import java.text.ParsePosition; import java.util.regex.Matcher; import java.util.regex.Pattern; +import org.apache.poi.ss.format.SimpleFraction; import org.apache.poi.ss.formula.eval.NotImplementedException; /** @@ -35,7 +36,7 @@ import org.apache.poi.ss.formula.eval.NotImplementedException; * If further uses for Commons Math are found, we will consider adding it as a dependency. * For now, we have in-lined the one method to keep things simple.

*/ -/* One question remains...is the value of epsilon in calcFractionMaxDenom reasonable? */ + @SuppressWarnings("serial") public class FractionFormat extends Format { private final static Pattern DENOM_FORMAT_PATTERN = Pattern.compile("(?:(#+)|(\\d+))"); @@ -113,7 +114,6 @@ public class FractionFormat extends Format { } //this is necessary to prevent overflow in the maxDenom calculation - //stink1 if (wholePart+(int)decPart == wholePart+decPart){ StringBuilder sb = new StringBuilder(); @@ -128,11 +128,11 @@ public class FractionFormat extends Format { try{ //this should be the case because of the constructor if (exactDenom > 0){ - fract = calcFractionExactDenom(decPart, exactDenom); + fract = SimpleFraction.buildFractionExactDenominator(decPart, exactDenom); } else { - fract = calcFractionMaxDenom(decPart, maxDenom); + fract = SimpleFraction.buildFractionMaxDenominator((double)decPart, maxDenom); } - } catch (SimpleFractionException e){ + } catch (RuntimeException e){ e.printStackTrace(); return Double.toString(doubleValue); } @@ -175,97 +175,5 @@ public class FractionFormat extends Format { public Object parseObject(String source, ParsePosition pos) { throw new NotImplementedException("Reverse parsing not supported"); } - - private SimpleFraction calcFractionMaxDenom(double value, int maxDenominator) - throws SimpleFractionException{ - /* - * Lifted wholesale from org.apache.math.fraction.Fraction 2.2 - */ - double epsilon = 0.000000000001f; - int maxIterations = 100; - long overflow = Integer.MAX_VALUE; - double r0 = value; - long a0 = (long)Math.floor(r0); - if (Math.abs(a0) > overflow) { - throw new SimpleFractionException( - String.format("value > Integer.MAX_VALUE: %d.", a0)); - } - - // 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 = 0; - long q2 = 1; - - 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; - if ((Math.abs(p2) > overflow) || (Math.abs(q2) > overflow)) { - throw new SimpleFractionException( - String.format("Greater than overflow in loop %f, %d, %d", value, 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 SimpleFractionException("n greater than max iterations " + value + " : " + maxIterations); - } - - if (q2 < maxDenominator) { - return new SimpleFraction((int) p2, (int) q2); - } else { - return new SimpleFraction((int) p1, (int) q1); - } - } - - private SimpleFraction calcFractionExactDenom(double val, int exactDenom){ - int num = (int)Math.round(val*(double)exactDenom); - return new SimpleFraction(num,exactDenom); - } - - private class SimpleFraction { - private final int num; - private final int denom; - - public SimpleFraction(int num, int denom) { - this.num = num; - this.denom = denom; - } - - public int getNumerator() { - return num; - } - public int getDenominator() { - return denom; - } - } - private class SimpleFractionException extends Throwable{ - private SimpleFractionException(String message){ - super(message); - } - } + } diff --git a/src/testcases/org/apache/poi/ss/format/TestCellFormat.java b/src/testcases/org/apache/poi/ss/format/TestCellFormat.java index fdc57f4ea..42adda983 100644 --- a/src/testcases/org/apache/poi/ss/format/TestCellFormat.java +++ b/src/testcases/org/apache/poi/ss/format/TestCellFormat.java @@ -825,4 +825,15 @@ public class TestCellFormat extends TestCase { } + public void testSimpleFractionFormat() { + CellFormat cf1 = CellFormat.getInstance("# ?/?"); + // Create a workbook, row and cell to test with + Workbook wb = new HSSFWorkbook(); + Sheet sheet = wb.createSheet(); + Row row = sheet.createRow(0); + Cell cell = row.createCell(0); + cell.setCellValue(123456.6); + System.out.println(cf1.apply(cell).text); + assertEquals("123456 3/5", cf1.apply(cell).text); + } } \ No newline at end of file