diff --git a/src/java/org/apache/poi/hssf/record/formula/eval/ValueEvalToNumericXlator.java b/src/java/org/apache/poi/hssf/record/formula/eval/ValueEvalToNumericXlator.java index 37f0b9b4b..bb04e411b 100644 --- a/src/java/org/apache/poi/hssf/record/formula/eval/ValueEvalToNumericXlator.java +++ b/src/java/org/apache/poi/hssf/record/formula/eval/ValueEvalToNumericXlator.java @@ -23,11 +23,9 @@ package org.apache.poi.hssf.record.formula.eval; */ public final class ValueEvalToNumericXlator { - public static final int STRING_IS_PARSED = 0x0001; - public static final int BOOL_IS_PARSED = 0x0002; - public static final int BLANK_IS_PARSED = 0x0004; // => blanks are not ignored, converted to 0 + public static final int BLANK_IS_PARSED = 0x0001; // => blanks are not ignored, converted to 0 - public static final int REF_BOOL_IS_PARSED = 0x0008; + public static final int REF_BOOL_IS_PARSED = 0x0002; private final int flags; @@ -59,9 +57,7 @@ public final class ValueEvalToNumericXlator { } if (eval instanceof BoolEval) { - return ((flags & BOOL_IS_PARSED) > 0) - ? (NumericValueEval) eval - : xlateBlankEval(); + return eval; } if (eval instanceof StringEval) { @@ -135,17 +131,13 @@ public final class ValueEvalToNumericXlator { * uses the relevant flags to decode the StringEval * @param eval */ - private ValueEval xlateStringEval(StringEval eval) { + private static ValueEval xlateStringEval(StringEval eval) { - if ((flags & STRING_IS_PARSED) > 0) { - String s = eval.getStringValue(); - Double d = OperandResolver.parseDouble(s); - if(d == null) { - return ErrorEval.VALUE_INVALID; - } - return new NumberEval(d.doubleValue()); + String s = eval.getStringValue(); + Double d = OperandResolver.parseDouble(s); + if(d == null) { + return ErrorEval.VALUE_INVALID; } - // else strings are errors? - return ErrorEval.VALUE_INVALID; + return new NumberEval(d.doubleValue()); } } diff --git a/src/java/org/apache/poi/hssf/record/formula/functions/AggregateFunction.java b/src/java/org/apache/poi/hssf/record/formula/functions/AggregateFunction.java index f04e64070..075868622 100644 --- a/src/java/org/apache/poi/hssf/record/formula/functions/AggregateFunction.java +++ b/src/java/org/apache/poi/hssf/record/formula/functions/AggregateFunction.java @@ -28,10 +28,7 @@ import org.apache.poi.hssf.record.formula.eval.ValueEvalToNumericXlator; */ public abstract class AggregateFunction extends MultiOperandNumericFunction { private static final ValueEvalToNumericXlator DEFAULT_NUM_XLATOR = - new ValueEvalToNumericXlator((short) ( - ValueEvalToNumericXlator.BOOL_IS_PARSED - | ValueEvalToNumericXlator.STRING_IS_PARSED - )); + new ValueEvalToNumericXlator(0); protected ValueEval attemptXlateToNumeric(ValueEval ve) { return DEFAULT_NUM_XLATOR.attemptXlateToNumeric(ve); diff --git a/src/java/org/apache/poi/hssf/record/formula/functions/Maxa.java b/src/java/org/apache/poi/hssf/record/formula/functions/Maxa.java index 7ae722889..95c4c8c91 100644 --- a/src/java/org/apache/poi/hssf/record/formula/functions/Maxa.java +++ b/src/java/org/apache/poi/hssf/record/formula/functions/Maxa.java @@ -27,9 +27,7 @@ import org.apache.poi.hssf.record.formula.eval.ValueEvalToNumericXlator; public final class Maxa extends MultiOperandNumericFunction { private static final ValueEvalToNumericXlator DEFAULT_NUM_XLATOR = new ValueEvalToNumericXlator((short) ( - ValueEvalToNumericXlator.BOOL_IS_PARSED - | ValueEvalToNumericXlator.REF_BOOL_IS_PARSED - | ValueEvalToNumericXlator.STRING_IS_PARSED + ValueEvalToNumericXlator.REF_BOOL_IS_PARSED | ValueEvalToNumericXlator.BLANK_IS_PARSED )); diff --git a/src/java/org/apache/poi/hssf/record/formula/functions/Mina.java b/src/java/org/apache/poi/hssf/record/formula/functions/Mina.java index b3965eefd..734493631 100644 --- a/src/java/org/apache/poi/hssf/record/formula/functions/Mina.java +++ b/src/java/org/apache/poi/hssf/record/formula/functions/Mina.java @@ -27,10 +27,8 @@ import org.apache.poi.hssf.record.formula.eval.ValueEvalToNumericXlator; public final class Mina extends MultiOperandNumericFunction { private static final ValueEvalToNumericXlator DEFAULT_NUM_XLATOR = new ValueEvalToNumericXlator((short) ( - ValueEvalToNumericXlator.BOOL_IS_PARSED - | ValueEvalToNumericXlator.REF_BOOL_IS_PARSED - | ValueEvalToNumericXlator.STRING_IS_PARSED - | ValueEvalToNumericXlator.BLANK_IS_PARSED + ValueEvalToNumericXlator.REF_BOOL_IS_PARSED + | ValueEvalToNumericXlator.BLANK_IS_PARSED )); protected ValueEval attemptXlateToNumeric(ValueEval ve) { diff --git a/src/java/org/apache/poi/hssf/record/formula/functions/Mode.java b/src/java/org/apache/poi/hssf/record/formula/functions/Mode.java index 64f017e8e..c236ccee6 100644 --- a/src/java/org/apache/poi/hssf/record/formula/functions/Mode.java +++ b/src/java/org/apache/poi/hssf/record/formula/functions/Mode.java @@ -17,35 +17,118 @@ package org.apache.poi.hssf.record.formula.functions; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import org.apache.poi.hssf.record.formula.eval.AreaEval; +import org.apache.poi.hssf.record.formula.eval.BlankEval; +import org.apache.poi.hssf.record.formula.eval.BoolEval; import org.apache.poi.hssf.record.formula.eval.ErrorEval; +import org.apache.poi.hssf.record.formula.eval.Eval; import org.apache.poi.hssf.record.formula.eval.EvaluationException; +import org.apache.poi.hssf.record.formula.eval.NumberEval; +import org.apache.poi.hssf.record.formula.eval.RefEval; +import org.apache.poi.hssf.record.formula.eval.StringEval; import org.apache.poi.hssf.record.formula.eval.ValueEval; -import org.apache.poi.hssf.record.formula.eval.ValueEvalToNumericXlator; /** * @author Amol S. Deshmukh < amolweb at ya hoo dot com > - * + * */ -public class Mode extends MultiOperandNumericFunction { - private static final ValueEvalToNumericXlator DEFAULT_NUM_XLATOR = - new ValueEvalToNumericXlator(0); +public class Mode implements Function { /** - * this is the default impl for the factory method getXlator - * of the super class NumericFunction. Subclasses can override this method - * if they desire to return a different ValueEvalToNumericXlator instance - * than the default. + * if v is zero length or contains no duplicates, return value is + * Double.NaN. Else returns the value that occurs most times and if there is + * a tie, returns the first such value. + * + * @param v */ - protected ValueEval attemptXlateToNumeric(ValueEval ve) { - return DEFAULT_NUM_XLATOR.attemptXlateToNumeric(ve); - } - - protected double evaluate(double[] values) throws EvaluationException { - double d = StatsLib.mode(values); - if (Double.isNaN(d)) { - // TODO - StatsLib is returning NaN to denote 'no duplicate values' + public static double evaluate(double[] v) throws EvaluationException { + if (v.length < 2) { throw new EvaluationException(ErrorEval.NA); } - return d; + + // very naive impl, may need to be optimized + int[] counts = new int[v.length]; + Arrays.fill(counts, 1); + for (int i = 0, iSize = v.length; i < iSize; i++) { + for (int j = i + 1, jSize = v.length; j < jSize; j++) { + if (v[i] == v[j]) + counts[i]++; + } + } + double maxv = 0; + int maxc = 0; + for (int i = 0, iSize = counts.length; i < iSize; i++) { + if (counts[i] > maxc) { + maxv = v[i]; + maxc = counts[i]; + } + } + if (maxc > 1) { + return maxv; + } + throw new EvaluationException(ErrorEval.NA); + + } + + public Eval evaluate(Eval[] args, int srcCellRow, short srcCellCol) { + double result; + try { + List temp = new ArrayList(); + for (int i = 0; i < args.length; i++) { + collectValues(args[i], temp); + } + double[] values = new double[temp.size()]; + for (int i = 0; i < values.length; i++) { + values[i] = ((Double) temp.get(i)).doubleValue(); + } + result = evaluate(values); + } catch (EvaluationException e) { + return e.getErrorEval(); + } + return new NumberEval(result); + } + + private static void collectValues(Eval arg, List temp) throws EvaluationException { + if (arg instanceof AreaEval) { + AreaEval ae = (AreaEval) arg; + int width = ae.getWidth(); + int height = ae.getHeight(); + for (int rrIx = 0; rrIx < height; rrIx++) { + for (int rcIx = 0; rcIx < width; rcIx++) { + ValueEval ve1 = ae.getRelativeValue(rrIx, rcIx); + collectValue(ve1, temp, false); + } + } + return; + } + if (arg instanceof RefEval) { + RefEval re = (RefEval) arg; + collectValue(re.getInnerValueEval(), temp, true); + return; + } + collectValue(arg, temp, true); + + } + + private static void collectValue(Eval arg, List temp, boolean mustBeNumber) + throws EvaluationException { + if (arg instanceof ErrorEval) { + throw new EvaluationException((ErrorEval) arg); + } + if (arg == BlankEval.INSTANCE || arg instanceof BoolEval || arg instanceof StringEval) { + if (mustBeNumber) { + throw EvaluationException.invalidValue(); + } + return; + } + if (arg instanceof NumberEval) { + temp.add(new Double(((NumberEval) arg).getNumberValue())); + return; + } + throw new RuntimeException("Unexpected value type (" + arg.getClass().getName() + ")"); } } diff --git a/src/java/org/apache/poi/hssf/record/formula/functions/StatsLib.java b/src/java/org/apache/poi/hssf/record/formula/functions/StatsLib.java index 8ebccfd95..e78f38caa 100644 --- a/src/java/org/apache/poi/hssf/record/formula/functions/StatsLib.java +++ b/src/java/org/apache/poi/hssf/record/formula/functions/StatsLib.java @@ -60,36 +60,6 @@ public final class StatsLib { return r; } - /** - * if v is zero length or contains no duplicates, return value - * is Double.NaN. Else returns the value that occurs most times - * and if there is a tie, returns the first such value. - * @param v - */ - public static double mode(double[] v) { - double r = Double.NaN; - - // very naive impl, may need to be optimized - if (v!=null && v.length > 1) { - int[] counts = new int[v.length]; - Arrays.fill(counts, 1); - for (int i=0, iSize=v.length; i maxc) { - maxv = v[i]; - maxc = counts[i]; - } - } - r = (maxc > 1) ? maxv : Double.NaN; // "no-dups" check - } - return r; - } public static double median(double[] v) { double r = Double.NaN; diff --git a/src/testcases/org/apache/poi/hssf/data/FormulaEvalTestData.xls b/src/testcases/org/apache/poi/hssf/data/FormulaEvalTestData.xls index acf9c8b16..f046382ec 100644 Binary files a/src/testcases/org/apache/poi/hssf/data/FormulaEvalTestData.xls and b/src/testcases/org/apache/poi/hssf/data/FormulaEvalTestData.xls differ diff --git a/src/testcases/org/apache/poi/hssf/record/formula/functions/TestStatsLib.java b/src/testcases/org/apache/poi/hssf/record/formula/functions/TestStatsLib.java index 237366baf..3562a6778 100644 --- a/src/testcases/org/apache/poi/hssf/record/formula/functions/TestStatsLib.java +++ b/src/testcases/org/apache/poi/hssf/record/formula/functions/TestStatsLib.java @@ -20,6 +20,11 @@ */ package org.apache.poi.hssf.record.formula.functions; +import junit.framework.AssertionFailedError; + +import org.apache.poi.hssf.record.formula.eval.ErrorEval; +import org.apache.poi.hssf.record.formula.eval.EvaluationException; + /** * @author Amol S. Deshmukh < amolweb at ya hoo dot com > @@ -181,49 +186,53 @@ public class TestStatsLib extends AbstractNumericTestCase { } public void testMode() { - double[] v = null; + double[] v; double d, x = 0; v = new double[] {1,2,3,4,5,6,7,8,9,10}; - d = StatsLib.mode(v); - x = Double.NaN; - assertEquals("mode ", x, d); + confirmMode(v, null); v = new double[] {1,1,1,1,1,1,1,1,1,1}; - d = StatsLib.mode(v); - x = 1; - assertEquals("mode ", x, d); + confirmMode(v, 1.0); v = new double[] {0,0,0,0,0,0,0,0,0,0}; - d = StatsLib.mode(v); - x = 0; - assertEquals("mode ", x, d); + confirmMode(v, 0.0); v = new double[] {1,2,1,2,1,2,1,2,1,2}; - d = StatsLib.mode(v); - x = 1; - assertEquals("mode ", x, d); + confirmMode(v, 1.0); v = new double[] {123.12,33.3333,2d/3d,5.37828,0.999}; - d = StatsLib.mode(v); - x = Double.NaN; - assertEquals("mode ", x, d); + confirmMode(v, null); v = new double[] {-1,-2,-3,-4,-5,-6,-7,-8,-9,-10}; - d = StatsLib.mode(v); - x = Double.NaN; - assertEquals("mode ", x, d); + confirmMode(v, null); v = new double[] {1,2,3,4,1,1,1,1,0,0,0,0,0}; - d = StatsLib.mode(v); - x = 1; - assertEquals("mode ", x, d); + confirmMode(v, 1.0); v = new double[] {0,1,2,3,4,1,1,1,0,0,0,0,1}; - d = StatsLib.mode(v); - x = 0; - assertEquals("mode ", x, d); + confirmMode(v, 0.0); } + private static void confirmMode(double[] v, double expectedResult) { + confirmMode(v, new Double(expectedResult)); + } + private static void confirmMode(double[] v, Double expectedResult) { + double actual; + try { + actual = Mode.evaluate(v); + if (expectedResult == null) { + throw new AssertionFailedError("Expected N/A exception was not thrown"); + } + } catch (EvaluationException e) { + if (expectedResult == null) { + assertEquals(ErrorEval.NA, e.getErrorEval()); + return; + } + throw new RuntimeException(e); + } + assertEquals("mode", expectedResult.doubleValue(), actual); + } + public void testStddev() { double[] v = null;