123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271 |
- /*
- * 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.usermodel;
- import java.text.FieldPosition;
- import java.text.Format;
- import java.text.ParsePosition;
- import java.util.regex.Matcher;
- import java.util.regex.Pattern;
-
- import org.apache.poi.ss.formula.eval.NotImplementedException;
-
- /**
- * <p>Format class that handles Excel style fractions, such as "# #/#" and "#/###"</p>
- *
- * <p>As of this writing, this is still not 100% accurate, but it does a reasonable job
- * of trying to mimic Excel's fraction calculations. It does not currently
- * maintain Excel's spacing.</p>
- *
- * <p>This class relies on a method lifted nearly verbatim from org.apache.math.fraction.
- * 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+))");
-
- //this was chosen to match the earlier limitation of max denom power
- //it can be expanded to get closer to Excel's calculations
- //with custom formats # #/#########
- //but as of this writing, the numerators and denominators
- //with formats of that nature on very small values were quite
- //far from Excel's calculations
- private final static int MAX_DENOM_POW = 4;
-
- //there are two options:
- //a) an exact denominator is specified in the formatString
- //b) the maximum denominator can be calculated from the formatString
- private final int exactDenom;
- private final int maxDenom;
-
- private final String wholePartFormatString;
- /**
- * Single parameter ctor
- * @param denomFormatString The format string for the denominator
- */
- public FractionFormat(String wholePartFormatString, String denomFormatString) {
- this.wholePartFormatString = wholePartFormatString;
- //init exactDenom and maxDenom
- Matcher m = DENOM_FORMAT_PATTERN.matcher(denomFormatString);
- int tmpExact = -1;
- int tmpMax = -1;
- if (m.find()){
- if (m.group(2) != null){
- try{
- tmpExact = Integer.parseInt(m.group(2));
- //if the denom is 0, fall back to the default: tmpExact=100
-
- if (tmpExact == 0){
- tmpExact = -1;
- }
- } catch (NumberFormatException e){
- //should never happen
- }
- } else if (m.group(1) != null) {
- int len = m.group(1).length();
- len = len > MAX_DENOM_POW ? MAX_DENOM_POW : len;
- tmpMax = (int)Math.pow(10, len);
- } else {
- tmpExact = 100;
- }
- }
- if (tmpExact <= 0 && tmpMax <= 0){
- //use 100 as the default denom if something went horribly wrong
- tmpExact = 100;
- }
- exactDenom = tmpExact;
- maxDenom = tmpMax;
- }
-
- public String format(Number num) {
-
- double doubleValue = num.doubleValue();
-
- boolean isNeg = (doubleValue < 0.0f) ? true : false;
- double absDoubleValue = Math.abs(doubleValue);
-
- double wholePart = Math.floor(absDoubleValue);
- double decPart = absDoubleValue - wholePart;
- if (wholePart + decPart == 0) {
- return "0";
- }
-
- //if the absolute value is smaller than 1 over the exact or maxDenom
- //you can stop here and return "0"
- if (absDoubleValue < (1/Math.max(exactDenom, maxDenom))){
- return "0";
- }
-
- //this is necessary to prevent overflow in the maxDenom calculation
- //stink1
- if (wholePart+(int)decPart == wholePart+decPart){
-
- StringBuilder sb = new StringBuilder();
- if (isNeg){
- sb.append("-");
- }
- sb.append(Integer.toString((int)wholePart));
- return sb.toString();
- }
-
- SimpleFraction fract = null;
- try{
- //this should be the case because of the constructor
- if (exactDenom > 0){
- fract = calcFractionExactDenom(decPart, exactDenom);
- } else {
- fract = calcFractionMaxDenom(decPart, maxDenom);
- }
- } catch (SimpleFractionException e){
- e.printStackTrace();
- return Double.toString(doubleValue);
- }
-
- StringBuilder sb = new StringBuilder();
-
- //now format the results
- if (isNeg){
- sb.append("-");
- }
-
- //if whole part has to go into the numerator
- if ("".equals(wholePartFormatString)){
- int trueNum = (fract.getDenominator()*(int)wholePart)+fract.getNumerator();
- sb.append(trueNum).append("/").append(fract.getDenominator());
- return sb.toString();
- }
-
-
- //short circuit if fraction is 0 or 1
- if (fract.getNumerator() == 0){
- sb.append(Integer.toString((int)wholePart));
- return sb.toString();
- } else if (fract.getNumerator() == fract.getDenominator()){
- sb.append(Integer.toString((int)wholePart+1));
- return sb.toString();
- }
- //as mentioned above, this ignores the exact space formatting in Excel
- if (wholePart > 0){
- sb.append(Integer.toString((int)wholePart)).append(" ");
- }
- sb.append(fract.getNumerator()).append("/").append(fract.getDenominator());
- return sb.toString();
- }
-
- public StringBuffer format(Object obj, StringBuffer toAppendTo, FieldPosition pos) {
- return toAppendTo.append(format((Number)obj));
- }
-
- 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);
- }
- }
- }
|