/* ==================================================================== 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.hssf.view; import java.text.*; /** * This class is used to format cells into their fractional format. * * I cant be 100% sure that the same fractional value will be displayed as in * excel but then again it is a lossy formating mode anyway * * @author Jason Height * @since 15 July 2002 */ public class SVFractionalFormat extends Format { private short ONE_DIGIT = 1; private short TWO_DIGIT = 2; private short THREE_DIGIT = 3; private short UNITS = 4; private int units = 1; private short mode = -1; /** Constructs a new FractionalFormatter * * The formatStr defines how the number will be formatted * # ?/? Up to one digit * # ??/?? Up to two digits * # ???/??? Up to three digits * # ?/2 In halves * # ?/4 In quarters * # ?/8 In eighths * # ?/16 In sixteenths * # ?/10 In tenths * # ?/100 In hundredths */ public SVFractionalFormat(String formatStr) { if ("# ?/?".equals(formatStr)) mode = ONE_DIGIT; else if ("# ??/??".equals(formatStr)) mode = TWO_DIGIT; else if ("# ???/???".equals(formatStr)) mode = THREE_DIGIT; else if ("# ?/2".equals(formatStr)) { mode = UNITS; units = 2; } else if ("# ?/4".equals(formatStr)) { mode = UNITS; units = 4; } else if ("# ?/8".equals(formatStr)) { mode = UNITS; units = 8; } else if ("# ?/16".equals(formatStr)) { mode = UNITS; units = 16; } else if ("# ?/10".equals(formatStr)) { mode = UNITS; units = 10; } else if ("# ?/100".equals(formatStr)) { mode = UNITS; units = 100; } } /** * Returns a fractional string representation of a double to a maximum denominator size * * This code has been translated to java from the following web page. * http://www.codeproject.com/cpp/fraction.asp * Originally coded in c++ By Dean Wyant dwyant@mindspring.com * The code on the web page is freely available. * * @param f Description of the Parameter * @param MaxDen Description of the Parameter * @return Description of the Return Value */ private String format(final double f, final int MaxDen) { long Whole = (long)f; int sign = 1; if (f < 0) { sign = -1; } double Precision = 0.00001; double AllowedError = Precision; double d = Math.abs(f); d -= Whole; double Frac = d; double Diff = Frac; long Num = 1; long Den = 0; long A = 0; long B = 0; long i = 0; if (Frac > Precision) { while (true) { d = 1.0 / d; i = (long) (d + Precision); d -= i; if (A > 0) { Num = i * Num + B; } Den = (long) (Num / Frac + 0.5); Diff = Math.abs((double) Num / Den - Frac); if (Den > MaxDen) { if (A > 0) { Num = A; Den = (long) (Num / Frac + 0.5); Diff = Math.abs((double) Num / Den - Frac); } else { Den = MaxDen; Num = 1; Diff = Math.abs((double) Num / Den - Frac); if (Diff > Frac) { Num = 0; Den = 1; // Keeps final check below from adding 1 and keeps Den from being 0 Diff = Frac; } } break; } if ((Diff <= AllowedError) || (d < Precision)) { break; } Precision = AllowedError / Diff; // This calcualtion of Precision does not always provide results within // Allowed Error. It compensates for loss of significant digits that occurs. // It helps to round the inprecise reciprocal values to i. B = A; A = Num; } } if (Num == Den) { Whole++; Num = 0; Den = 0; } else if (Den == 0) { Num = 0; } if (sign < 0) { if (Whole == 0) { Num = -Num; } else { Whole = -Whole; } } return new StringBuffer().append(Whole).append(" ").append(Num).append("/").append(Den).toString(); } /** This method formats the double in the units specified. * The usints could be any number but in this current implementation it is * halves (2), quaters (4), eigths (8) etc */ private String formatUnit(double f, int units) { long Whole = (long)f; f -= Whole; long Num = Math.round(f * units); return new StringBuffer().append(Whole).append(" ").append(Num).append("/").append(units).toString(); } public final String format(double val) { if (mode == ONE_DIGIT) { return format(val, 9); } else if (mode == TWO_DIGIT) { return format(val, 99); } else if (mode == THREE_DIGIT) { return format(val, 999); } else if (mode == UNITS) { return formatUnit(val , units); } throw new RuntimeException("Unexpected Case"); } @Override public StringBuffer format(Object obj, StringBuffer toAppendTo, FieldPosition pos) { if (obj instanceof Number) { toAppendTo.append(format(((Number)obj).doubleValue())); return toAppendTo; } throw new IllegalArgumentException("Can only handle Numbers"); } @Override public Object parseObject(String source, ParsePosition status) { //JMH TBD return null; } @Override public Object parseObject(String source) throws ParseException { //JMH TBD return null; } @Override public Object clone() { //JMH TBD return null; } }