From c2113c8a7107942314f4250802c8079ede60d97f Mon Sep 17 00:00:00 2001 From: Dominik Stadler Date: Fri, 7 Nov 2014 12:29:05 +0000 Subject: [PATCH] Bug 57003: Add implementation of function FIXED git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1637361 13f79535-47bb-0310-9956-ffa450edef68 --- .../poi/ss/formula/eval/FunctionEval.java | 3 +- .../poi/ss/formula/functions/Fixed.java | 106 +++++++++++++++ .../ss/formula/function/functionMetadata.txt | 3 +- .../poi/ss/formula/functions/TestFixed.java | 128 ++++++++++++++++++ .../TestFixedFunctionsFromSpreadsheet.java | 29 ++++ .../57003-FixedFunctionTestCaseData.xls | Bin 0 -> 29696 bytes 6 files changed, 267 insertions(+), 2 deletions(-) create mode 100644 src/java/org/apache/poi/ss/formula/functions/Fixed.java create mode 100644 src/testcases/org/apache/poi/ss/formula/functions/TestFixed.java create mode 100644 src/testcases/org/apache/poi/ss/formula/functions/TestFixedFunctionsFromSpreadsheet.java create mode 100644 test-data/spreadsheet/57003-FixedFunctionTestCaseData.xls diff --git a/src/java/org/apache/poi/ss/formula/eval/FunctionEval.java b/src/java/org/apache/poi/ss/formula/eval/FunctionEval.java index 2809ee63f4..5622225fcb 100644 --- a/src/java/org/apache/poi/ss/formula/eval/FunctionEval.java +++ b/src/java/org/apache/poi/ss/formula/eval/FunctionEval.java @@ -41,6 +41,7 @@ import org.apache.poi.ss.formula.functions.Days360; import org.apache.poi.ss.formula.functions.Errortype; import org.apache.poi.ss.formula.functions.Even; import org.apache.poi.ss.formula.functions.FinanceFunction; +import org.apache.poi.ss.formula.functions.Fixed; import org.apache.poi.ss.formula.functions.Function; import org.apache.poi.ss.formula.functions.Hlookup; import org.apache.poi.ss.formula.functions.Hyperlink; @@ -133,7 +134,7 @@ public final class FunctionEval { retval[11] = new Npv(); retval[12] = AggregateFunction.STDEV; retval[13] = NumericFunction.DOLLAR; - + retval[14] = new Fixed(); retval[15] = NumericFunction.SIN; retval[16] = NumericFunction.COS; retval[17] = NumericFunction.TAN; diff --git a/src/java/org/apache/poi/ss/formula/functions/Fixed.java b/src/java/org/apache/poi/ss/formula/functions/Fixed.java new file mode 100644 index 0000000000..d9026dcc73 --- /dev/null +++ b/src/java/org/apache/poi/ss/formula/functions/Fixed.java @@ -0,0 +1,106 @@ +/* ==================================================================== + 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.functions; + +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.text.DecimalFormat; +import java.text.NumberFormat; +import java.util.Locale; + +import org.apache.poi.ss.formula.eval.BoolEval; +import org.apache.poi.ss.formula.eval.ErrorEval; +import org.apache.poi.ss.formula.eval.EvaluationException; +import org.apache.poi.ss.formula.eval.NumberEval; +import org.apache.poi.ss.formula.eval.OperandResolver; +import org.apache.poi.ss.formula.eval.StringEval; +import org.apache.poi.ss.formula.eval.ValueEval; + +public final class Fixed implements Function1Arg, Function2Arg, Function3Arg { + @Override + public ValueEval evaluate( + int srcRowIndex, int srcColumnIndex, + ValueEval arg0, ValueEval arg1, ValueEval arg2) { + return fixed(arg0, arg1, arg2, srcRowIndex, srcColumnIndex); + } + + @Override + public ValueEval evaluate( + int srcRowIndex, int srcColumnIndex, ValueEval arg0, ValueEval arg1) { + return fixed(arg0, arg1, BoolEval.FALSE, srcRowIndex, srcColumnIndex); + } + + @Override + public ValueEval evaluate(int srcRowIndex, int srcColumnIndex, ValueEval arg0) { + return fixed(arg0, new NumberEval(2), BoolEval.FALSE, srcRowIndex, srcColumnIndex); + } + + @Override + public ValueEval evaluate(ValueEval[] args, int srcRowIndex, int srcColumnIndex) { + switch (args.length) { + case 1: + return fixed(args[0], new NumberEval(2), BoolEval.FALSE, + srcRowIndex, srcColumnIndex); + case 2: + return fixed(args[0], args[1], BoolEval.FALSE, + srcRowIndex, srcColumnIndex); + case 3: + return fixed(args[0], args[1], args[2], srcRowIndex, srcColumnIndex); + } + return ErrorEval.VALUE_INVALID; + } + + private ValueEval fixed( + ValueEval numberParam, ValueEval placesParam, + ValueEval skipThousandsSeparatorParam, + int srcRowIndex, int srcColumnIndex) { + try { + ValueEval numberValueEval = + OperandResolver.getSingleValue( + numberParam, srcRowIndex, srcColumnIndex); + BigDecimal number = + new BigDecimal(OperandResolver.coerceValueToDouble(numberValueEval)); + ValueEval placesValueEval = + OperandResolver.getSingleValue( + placesParam, srcRowIndex, srcColumnIndex); + int places = OperandResolver.coerceValueToInt(placesValueEval); + ValueEval skipThousandsSeparatorValueEval = + OperandResolver.getSingleValue( + skipThousandsSeparatorParam, srcRowIndex, srcColumnIndex); + Boolean skipThousandsSeparator = + OperandResolver.coerceValueToBoolean( + skipThousandsSeparatorValueEval, false); + + // Round number to respective places. + number = number.setScale(places, RoundingMode.HALF_UP); + + // Format number conditionally using a thousands separator. + NumberFormat nf = NumberFormat.getNumberInstance(Locale.US); + DecimalFormat formatter = (DecimalFormat)nf; + formatter.setGroupingUsed(! skipThousandsSeparator); + formatter.setMinimumFractionDigits(places >= 0 ? places : 0); + formatter.setMaximumFractionDigits(places >= 0 ? places : 0); + String numberString = formatter.format(number.doubleValue()); + + // Return the result as a StringEval. + return new StringEval(numberString); + } catch (EvaluationException e) { + return e.getErrorEval(); + } + } +} diff --git a/src/resources/main/org/apache/poi/ss/formula/function/functionMetadata.txt b/src/resources/main/org/apache/poi/ss/formula/function/functionMetadata.txt index 36775c859d..f9e91a1e3b 100644 --- a/src/resources/main/org/apache/poi/ss/formula/function/functionMetadata.txt +++ b/src/resources/main/org/apache/poi/ss/formula/function/functionMetadata.txt @@ -16,6 +16,7 @@ # Created by (org.apache.poi.hssf.record.formula.function.ExcelFileFormatDocFunctionExtractor) # from source file 'excelfileformat.odt' (size=356107, md5=0x8f789cb6e75594caf068f8e193004ef4) # ! + some manual edits ! +# See https://issues.apache.org/ooo/show_bug.cgi?id=125837 for difference in "FIXED" # #Columns: (index, name, minParams, maxParams, returnClass, paramClasses, isVolatile, hasFootnote ) @@ -184,7 +185,7 @@ 235 DGET 3 3 V R R R 244 INFO 1 1 V V # New Built-In Sheet Functions in BIFF4 -14 FIXED 2 3 V V V V x +14 FIXED 1 3 V V V V x 204 USDOLLAR 1 2 V V V x 215 DBCS 1 1 V V x 216 RANK 2 3 V V R V diff --git a/src/testcases/org/apache/poi/ss/formula/functions/TestFixed.java b/src/testcases/org/apache/poi/ss/formula/functions/TestFixed.java new file mode 100644 index 0000000000..d6834ebcbe --- /dev/null +++ b/src/testcases/org/apache/poi/ss/formula/functions/TestFixed.java @@ -0,0 +1,128 @@ +/* ==================================================================== + 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.functions; + +import java.io.IOException; + +import junit.framework.TestCase; + +import org.apache.poi.hssf.usermodel.HSSFCell; +import org.apache.poi.hssf.usermodel.HSSFFormulaEvaluator; +import org.apache.poi.hssf.usermodel.HSSFSheet; +import org.apache.poi.hssf.usermodel.HSSFWorkbook; +import org.apache.poi.ss.formula.eval.BoolEval; +import org.apache.poi.ss.formula.eval.ErrorEval; +import org.apache.poi.ss.formula.eval.NumberEval; +import org.apache.poi.ss.formula.eval.StringEval; +import org.apache.poi.ss.formula.eval.ValueEval; +import org.apache.poi.ss.usermodel.Cell; +import org.apache.poi.ss.usermodel.CellValue; +import org.apache.poi.ss.usermodel.ErrorConstants; + +public final class TestFixed extends TestCase { + + private HSSFCell cell11; + private HSSFFormulaEvaluator evaluator; + + @Override + public void setUp() throws IOException { + HSSFWorkbook wb = new HSSFWorkbook(); + try { + HSSFSheet sheet = wb.createSheet("new sheet"); + cell11 = sheet.createRow(0).createCell(0); + cell11.setCellType(HSSFCell.CELL_TYPE_FORMULA); + evaluator = new HSSFFormulaEvaluator(wb); + } finally { + wb.close(); + } + } + + public void testValid() { + // thousands separator + confirm("FIXED(1234.56789,2,TRUE)", "1234.57"); + confirm("FIXED(1234.56789,2,FALSE)", "1,234.57"); + // rounding + confirm("FIXED(1.8,0,TRUE)", "2"); + confirm("FIXED(1.2,0,TRUE)", "1"); + confirm("FIXED(1.5,0,TRUE)", "2"); + confirm("FIXED(1,0,TRUE)", "1"); + // fractional digits + confirm("FIXED(1234.56789,7,TRUE)", "1234.5678900"); + confirm("FIXED(1234.56789,0,TRUE)", "1235"); + confirm("FIXED(1234.56789,-1,TRUE)", "1230"); + // less than three arguments + confirm("FIXED(1234.56789)", "1,234.57"); + confirm("FIXED(1234.56789,3)", "1,234.568"); + // invalid arguments + confirmValueError("FIXED(\"invalid\")"); + confirmValueError("FIXED(1,\"invalid\")"); + confirmValueError("FIXED(1,2,\"invalid\")"); + // strange arguments + confirm("FIXED(1000,2,8)", "1000.00"); + confirm("FIXED(1000,2,0)", "1,000.00"); + // corner cases + confirm("FIXED(1.23456789012345,15,TRUE)", "1.234567890123450"); + // Seems POI accepts longer numbers than Excel does, excel trims the + // number to 15 digits and removes the "9" in the formula itself. + // Not the fault of FIXED though. + // confirm("FIXED(1.234567890123459,15,TRUE)", "1.234567890123450"); + confirm("FIXED(60,-2,TRUE)", "100"); + confirm("FIXED(10,-2,TRUE)", "0"); + // rounding propagation + confirm("FIXED(99.9,0,TRUE)", "100"); + } + + public void testOptionalParams() { + Fixed fixed = new Fixed(); + ValueEval evaluate = fixed.evaluate(0, 0, new NumberEval(1234.56789)); + assertTrue(evaluate instanceof StringEval); + assertEquals("1,234.57", ((StringEval)evaluate).getStringValue()); + + evaluate = fixed.evaluate(0, 0, new NumberEval(1234.56789), new NumberEval(1)); + assertTrue(evaluate instanceof StringEval); + assertEquals("1,234.6", ((StringEval)evaluate).getStringValue()); + + evaluate = fixed.evaluate(0, 0, new NumberEval(1234.56789), new NumberEval(1), BoolEval.TRUE); + assertTrue(evaluate instanceof StringEval); + assertEquals("1234.6", ((StringEval)evaluate).getStringValue()); + + evaluate = fixed.evaluate(new ValueEval[] {}, 1, 1); + assertTrue(evaluate instanceof ErrorEval); + + evaluate = fixed.evaluate(new ValueEval[] { new NumberEval(1), new NumberEval(1), new NumberEval(1), new NumberEval(1) }, 1, 1); + assertTrue(evaluate instanceof ErrorEval); + } + + private void confirm(String formulaText, String expectedResult) { + cell11.setCellFormula(formulaText); + evaluator.clearAllCachedResultValues(); + CellValue cv = evaluator.evaluate(cell11); + assertEquals("Wrong result type: " + cv.formatAsString(), Cell.CELL_TYPE_STRING, cv.getCellType()); + String actualValue = cv.getStringValue(); + assertEquals(expectedResult, actualValue); + } + + private void confirmValueError(String formulaText) { + cell11.setCellFormula(formulaText); + evaluator.clearAllCachedResultValues(); + CellValue cv = evaluator.evaluate(cell11); + assertTrue("Wrong result type: " + cv.formatAsString(), + cv.getCellType() == Cell.CELL_TYPE_ERROR + && cv.getErrorValue() == ErrorConstants.ERROR_VALUE); + } +} diff --git a/src/testcases/org/apache/poi/ss/formula/functions/TestFixedFunctionsFromSpreadsheet.java b/src/testcases/org/apache/poi/ss/formula/functions/TestFixedFunctionsFromSpreadsheet.java new file mode 100644 index 0000000000..70a8a3dbfd --- /dev/null +++ b/src/testcases/org/apache/poi/ss/formula/functions/TestFixedFunctionsFromSpreadsheet.java @@ -0,0 +1,29 @@ +/* ==================================================================== + 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.functions; + +/** + * Tests FIXED() as loaded from a test data spreadsheet. + */ +public class TestFixedFunctionsFromSpreadsheet extends BaseTestFunctionsFromSpreadsheet { + + @Override + protected String getFilename() { + return "57003-FixedFunctionTestCaseData.xls"; + } +} diff --git a/test-data/spreadsheet/57003-FixedFunctionTestCaseData.xls b/test-data/spreadsheet/57003-FixedFunctionTestCaseData.xls new file mode 100644 index 0000000000000000000000000000000000000000..bd8b4ba4d06cb57aef0b265173c8ba940b59e4c2 GIT binary patch literal 29696 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