Added implementation for MROUND(), VAR() and VARP()

git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1044642 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Yegor Kozlov 2010-12-11 12:41:17 +00:00
parent 544463463a
commit 72ce4e8eb7
11 changed files with 248 additions and 15 deletions

View File

@ -34,6 +34,7 @@
<changes> <changes>
<release version="3.8-beta1" date="2010-??-??"> <release version="3.8-beta1" date="2010-??-??">
<action dev="POI-DEVELOPERS" type="add">48539 - Added implementation for MROUND(), VAR() and VARP()</action>
<action dev="POI-DEVELOPERS" type="add">50446 - Code cleanup and optimizations to keep some IDE quiet</action> <action dev="POI-DEVELOPERS" type="add">50446 - Code cleanup and optimizations to keep some IDE quiet</action>
<action dev="POI-DEVELOPERS" type="add">50437 - Support passing ranges to NPV()</action> <action dev="POI-DEVELOPERS" type="add">50437 - Support passing ranges to NPV()</action>
<action dev="POI-DEVELOPERS" type="add">50409 - Added implementation for IRR()</action> <action dev="POI-DEVELOPERS" type="add">50409 - Added implementation for IRR()</action>

View File

@ -138,7 +138,7 @@ public final class AnalysisToolPak implements UDFFinder {
r(m, "JIS", null); r(m, "JIS", null);
r(m, "LCM", null); r(m, "LCM", null);
r(m, "MDURATION", null); r(m, "MDURATION", null);
r(m, "MROUND", null); r(m, "MROUND", MRound.instance);
r(m, "MULTINOMIAL", null); r(m, "MULTINOMIAL", null);
r(m, "NETWORKDAYS", null); r(m, "NETWORKDAYS", null);
r(m, "NOMINAL", null); r(m, "NOMINAL", null);

View File

@ -0,0 +1,76 @@
/* ====================================================================
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.formula.atp;
import org.apache.poi.ss.formula.OperationEvaluationContext;
import org.apache.poi.ss.formula.eval.*;
import org.apache.poi.ss.formula.functions.FreeRefFunction;
import org.apache.poi.ss.formula.functions.NumericFunction;
import org.apache.poi.ss.usermodel.DateUtil;
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.regex.Pattern;
/**
* Implementation of Excel 'Analysis ToolPak' function MROUND()<br/>
*
* Returns a number rounded to the desired multiple.<p/>
*
* <b>Syntax</b><br/>
* <b>MROUND</b>(<b>number</b>, <b>multiple</b>)
*
* <p/>
*
* @author Yegor Kozlov
*/
final class MRound implements FreeRefFunction {
public static final FreeRefFunction instance = new MRound();
private MRound() {
// enforce singleton
}
public ValueEval evaluate(ValueEval[] args, OperationEvaluationContext ec) {
double number, multiple, result;
if (args.length != 2) {
return ErrorEval.VALUE_INVALID;
}
try {
number = OperandResolver.coerceValueToDouble(OperandResolver.getSingleValue(args[0], ec.getRowIndex(), ec.getColumnIndex()));
multiple = OperandResolver.coerceValueToDouble(OperandResolver.getSingleValue(args[1], ec.getRowIndex(), ec.getColumnIndex()));
if( multiple == 0.0 ) {
result = 0.0;
} else {
if(number*multiple < 0) {
// Returns #NUM! because the number and the multiple have different signs
throw new EvaluationException(ErrorEval.NUM_ERROR);
}
result = multiple * Math.round( number / multiple );
}
NumericFunction.checkValue(result);
return new NumberEval(result);
} catch (EvaluationException e) {
return e.getErrorEval();
}
}
}

View File

@ -93,6 +93,8 @@ public final class FunctionEval {
retval[37] = BooleanFunction.OR; retval[37] = BooleanFunction.OR;
retval[38] = BooleanFunction.NOT; retval[38] = BooleanFunction.NOT;
retval[39] = NumericFunction.MOD; retval[39] = NumericFunction.MOD;
retval[46] = AggregateFunction.VAR;
retval[48] = TextFunction.TEXT; retval[48] = TextFunction.TEXT;
retval[56] = FinanceFunction.PV; retval[56] = FinanceFunction.PV;
@ -153,6 +155,7 @@ public final class FunctionEval {
retval[184] = NumericFunction.FACT; retval[184] = NumericFunction.FACT;
retval[190] = LogicalFunction.ISNONTEXT; retval[190] = LogicalFunction.ISNONTEXT;
retval[194] = AggregateFunction.VARP;
retval[197] = NumericFunction.TRUNC; retval[197] = NumericFunction.TRUNC;
retval[198] = LogicalFunction.ISLOGICAL; retval[198] = LogicalFunction.ISLOGICAL;

View File

@ -142,4 +142,20 @@ public abstract class AggregateFunction extends MultiOperandNumericFunction {
return MathX.sumsq(values); return MathX.sumsq(values);
} }
}; };
public static final Function VAR = new AggregateFunction() {
protected double evaluate(double[] values) throws EvaluationException {
if (values.length < 1) {
throw new EvaluationException(ErrorEval.DIV_ZERO);
}
return StatsLib.var(values);
}
};
public static final Function VARP = new AggregateFunction() {
protected double evaluate(double[] values) throws EvaluationException {
if (values.length < 1) {
throw new EvaluationException(ErrorEval.DIV_ZERO);
}
return StatsLib.varp(values);
}
};
} }

View File

@ -43,7 +43,7 @@ public abstract class NumericFunction implements Function {
/** /**
* @throws EvaluationException (#NUM!) if <tt>result</tt> is <tt>NaN</> or <tt>Infinity</tt> * @throws EvaluationException (#NUM!) if <tt>result</tt> is <tt>NaN</> or <tt>Infinity</tt>
*/ */
static final void checkValue(double result) throws EvaluationException { public static final void checkValue(double result) throws EvaluationException {
if (Double.isNaN(result) || Double.isInfinite(result)) { if (Double.isNaN(result) || Double.isInfinite(result)) {
throw new EvaluationException(ErrorEval.NUM_ERROR); throw new EvaluationException(ErrorEval.NUM_ERROR);
} }

View File

@ -59,6 +59,21 @@ final class StatsLib {
return r; return r;
} }
public static double var(double[] v) {
double r = Double.NaN;
if (v!=null && v.length > 1) {
r = devsq(v) / (v.length - 1);
}
return r;
}
public static double varp(double[] v) {
double r = Double.NaN;
if (v!=null && v.length > 1) {
r = devsq(v) /v.length;
}
return r;
}
public static double median(double[] v) { public static double median(double[] v) {
double r = Double.NaN; double r = Double.NaN;

View File

@ -26,16 +26,8 @@ import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.openxml4j.opc.OPCPackage; import org.apache.poi.openxml4j.opc.OPCPackage;
import org.apache.poi.openxml4j.opc.PackagePart; import org.apache.poi.openxml4j.opc.PackagePart;
import org.apache.poi.openxml4j.opc.PackagingURIHelper; import org.apache.poi.openxml4j.opc.PackagingURIHelper;
import org.apache.poi.ss.usermodel.BaseTestBugzillaIssues; import org.apache.poi.ss.usermodel.*;
import org.apache.poi.ss.usermodel.Cell; import org.apache.poi.ss.formula.eval.NotImplementedException;
import org.apache.poi.ss.usermodel.DataFormatter;
import org.apache.poi.ss.usermodel.Font;
import org.apache.poi.ss.usermodel.FormulaError;
import org.apache.poi.ss.usermodel.FormulaEvaluator;
import org.apache.poi.ss.usermodel.Name;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.xssf.XSSFITestDataProvider; import org.apache.poi.xssf.XSSFITestDataProvider;
import org.apache.poi.xssf.XSSFTestDataSamples; import org.apache.poi.xssf.XSSFTestDataSamples;
import org.apache.poi.xssf.model.CalculationChain; import org.apache.poi.xssf.model.CalculationChain;
@ -212,8 +204,10 @@ public final class TestXSSFBugs extends BaseTestBugzillaIssues {
* NameXPtgs. * NameXPtgs.
* Blows up on: * Blows up on:
* IF(B6= (ROUNDUP(B6,0) + ROUNDDOWN(B6,0))/2, MROUND(B6,2),ROUND(B6,0)) * IF(B6= (ROUNDUP(B6,0) + ROUNDDOWN(B6,0))/2, MROUND(B6,2),ROUND(B6,0))
*
* TODO: delete this test case when MROUND and VAR are implemented
*/ */
public void DISABLEDtest48539() throws Exception { public void test48539() throws Exception {
XSSFWorkbook wb = XSSFTestDataSamples.openSampleWorkbook("48539.xlsx"); XSSFWorkbook wb = XSSFTestDataSamples.openSampleWorkbook("48539.xlsx");
assertEquals(3, wb.getNumberOfSheets()); assertEquals(3, wb.getNumberOfSheets());
@ -224,14 +218,21 @@ public final class TestXSSFBugs extends BaseTestBugzillaIssues {
for(Row r : s) { for(Row r : s) {
for(Cell c : r) { for(Cell c : r) {
if(c.getCellType() == Cell.CELL_TYPE_FORMULA) { if(c.getCellType() == Cell.CELL_TYPE_FORMULA) {
eval.evaluate(c); CellValue cv = eval.evaluate(c);
if(cv.getCellType() == Cell.CELL_TYPE_NUMERIC) {
// assert that the calculated value agrees with
// the cached formula result calculated by Excel
double cachedFormulaResult = c.getNumericCellValue();
double evaluatedFormulaResult = cv.getNumberValue();
assertEquals(c.getCellFormula(), cachedFormulaResult, evaluatedFormulaResult, 1E-7);
}
} }
} }
} }
} }
// Now all of them // Now all of them
XSSFFormulaEvaluator.evaluateAllFormulaCells(wb); XSSFFormulaEvaluator.evaluateAllFormulaCells(wb);
} }
/** /**

View File

@ -0,0 +1,71 @@
/* ====================================================================
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.formula.atp;
import junit.framework.TestCase;
import org.apache.poi.hssf.HSSFTestDataSamples;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.ss.formula.eval.ErrorEval;
import org.apache.poi.ss.usermodel.*;
/**
* Testcase for 'Analysis Toolpak' function MROUND()
*
* @author Yegor Kozlov
*/
public class TestMRound extends TestCase {
/**
=MROUND(10, 3) Rounds 10 to a nearest multiple of 3 (9)
=MROUND(-10, -3) Rounds -10 to a nearest multiple of -3 (-9)
=MROUND(1.3, 0.2) Rounds 1.3 to a nearest multiple of 0.2 (1.4)
=MROUND(5, -2) Returns an error, because -2 and 5 have different signs (#NUM!) *
*/
public static void testEvaluate(){
Workbook wb = new HSSFWorkbook();
Sheet sh = wb.createSheet();
Cell cell1 = sh.createRow(0).createCell(0);
cell1.setCellFormula("MROUND(10, 3)");
Cell cell2 = sh.createRow(0).createCell(0);
cell2.setCellFormula("MROUND(-10, -3)");
Cell cell3 = sh.createRow(0).createCell(0);
cell3.setCellFormula("MROUND(1.3, 0.2)");
Cell cell4 = sh.createRow(0).createCell(0);
cell4.setCellFormula("MROUND(5, -2)");
Cell cell5 = sh.createRow(0).createCell(0);
cell5.setCellFormula("MROUND(5, 0)");
double accuracy = 1E-9;
FormulaEvaluator evaluator = wb.getCreationHelper().createFormulaEvaluator();
assertEquals("Rounds 10 to a nearest multiple of 3 (9)",
9.0, evaluator.evaluate(cell1).getNumberValue(), accuracy);
assertEquals("Rounds -10 to a nearest multiple of -3 (-9)",
-9.0, evaluator.evaluate(cell2).getNumberValue(), accuracy);
assertEquals("Rounds 1.3 to a nearest multiple of 0.2 (1.4)",
1.4, evaluator.evaluate(cell3).getNumberValue(), accuracy);
assertEquals("Returns an error, because -2 and 5 have different signs (#NUM!)",
ErrorEval.NUM_ERROR.getErrorCode(), evaluator.evaluate(cell4).getErrorValue());
assertEquals("Returns 0 because the multiple is 0",
0.0, evaluator.evaluate(cell5).getNumberValue());
}
}

View File

@ -268,4 +268,54 @@ public class TestStatsLib extends AbstractNumericTestCase {
x = 3.02765035410; x = 3.02765035410;
assertEquals("stdev ", x, d); assertEquals("stdev ", x, d);
} }
public void testVar() {
double[] v = null;
double d, x = 0;
v = new double[] {3.50, 5.00, 7.23, 2.99};
d = StatsLib.var(v);
x = 3.6178;
assertEquals("var ", x, d);
v = new double[] {34.5, 2.0, 8.9, -4.0};
d = StatsLib.var(v);
x = 286.99;
assertEquals("var ", x, d);
v = new double[] {7.0, 25.0, 21.69};
d = StatsLib.var(v);
x = 91.79203333;
assertEquals("var ", x, d);
v = new double[] {1345,1301,1368,1322,1310,1370,1318,1350,1303,1299};
d = StatsLib.var(v);
x = 754.2666667;
assertEquals("var ", x, d);
}
public void testVarp() {
double[] v = null;
double d, x = 0;
v = new double[] {3.50, 5.00, 7.23, 2.99};
d = StatsLib.varp(v);
x = 2.71335;
assertEquals("varp ", x, d);
v = new double[] {34.5, 2.0, 8.9, -4.0};
d = StatsLib.varp(v);
x = 215.2425;
assertEquals("varp ", x, d);
v = new double[] {7.0, 25.0, 21.69};
d = StatsLib.varp(v);
x = 61.19468889;
assertEquals("varp ", x, d);
v = new double[] {1345,1301,1368,1322,1310,1370,1318,1350,1303,1299};
d = StatsLib.varp(v);
x = 678.84;
assertEquals("varp ", x, d);
}
} }