55419 and refactor SimpleFraction
git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1514123 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
parent
17f3b2cb26
commit
a62fcb2fbf
@ -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
|
||||
* <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.
|
||||
*/
|
||||
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.
|
||||
* <p>
|
||||
* References:
|
||||
* <ul>
|
||||
* <li><a href="http://mathworld.wolfram.com/ContinuedFraction.html">
|
||||
* Continued Fraction</a> equations (11) and (22)-(26)</li>
|
||||
* </ul>
|
||||
* </p>
|
||||
* @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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
170
src/java/org/apache/poi/ss/format/SimpleFraction.java
Normal file
170
src/java/org/apache/poi/ss/format/SimpleFraction.java
Normal file
@ -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.
|
||||
* <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(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;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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.</p>
|
||||
*/
|
||||
/* 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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user