From 19869d73bbd5eaa045cfa1b85f278c2ab842e2d5 Mon Sep 17 00:00:00 2001 From: Gan Keyu Date: Thu, 27 Jul 2023 19:24:52 +0800 Subject: [PATCH 01/10] added ceiling/floor.math --- main/SS/Formula/Atp/AnalysisToolPak.cs | 7 +- main/SS/Formula/Functions/CeilingMath.cs | 22 ++++ .../Formula/Functions/FloorCeilingMathBase.cs | 103 ++++++++++++++++++ main/SS/Formula/Functions/FloorMath.cs | 23 ++++ 4 files changed, 154 insertions(+), 1 deletion(-) create mode 100644 main/SS/Formula/Functions/CeilingMath.cs create mode 100644 main/SS/Formula/Functions/FloorCeilingMathBase.cs create mode 100644 main/SS/Formula/Functions/FloorMath.cs diff --git a/main/SS/Formula/Atp/AnalysisToolPak.cs b/main/SS/Formula/Atp/AnalysisToolPak.cs index 449f646f3..1440e1a5e 100644 --- a/main/SS/Formula/Atp/AnalysisToolPak.cs +++ b/main/SS/Formula/Atp/AnalysisToolPak.cs @@ -19,7 +19,8 @@ limitations under the License. using System.Collections.ObjectModel; using NPOI.SS.Formula.Function; -namespace NPOI.SS.Formula.Atp { +namespace NPOI.SS.Formula.Atp +{ using System; using System.Collections; using NPOI.SS.Formula; @@ -192,6 +193,10 @@ private static Dictionary CreateFunctionsMap() r(m, "YIELD", null); r(m, "YIELDDISC", null); r(m, "YIELDMAT", null); + + r(m, "CEILING.MATH", CeilingMath.Instance); + r(m, "FLOOR.MATH", FloorMath.Instance); + return m; } diff --git a/main/SS/Formula/Functions/CeilingMath.cs b/main/SS/Formula/Functions/CeilingMath.cs new file mode 100644 index 000000000..deaf4a7c6 --- /dev/null +++ b/main/SS/Formula/Functions/CeilingMath.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace NPOI.SS.Formula.Functions +{ + public sealed class CeilingMath : FloorCeilingMathBase + { + private CeilingMath() + { + } + public static readonly CeilingMath Instance = new(); + + protected override double EvaluateMajorDirection(double number) + => Math.Ceiling(number); + + protected override double EvaluateAlternativeDirection(double number) + => Math.Floor(number); + } +} diff --git a/main/SS/Formula/Functions/FloorCeilingMathBase.cs b/main/SS/Formula/Functions/FloorCeilingMathBase.cs new file mode 100644 index 000000000..c21de5b2a --- /dev/null +++ b/main/SS/Formula/Functions/FloorCeilingMathBase.cs @@ -0,0 +1,103 @@ +using NPOI.SS.Formula.Eval; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace NPOI.SS.Formula.Functions +{ + public abstract class FloorCeilingMathBase : FreeRefFunction + { + public ValueEval Evaluate(ValueEval[] args, OperationEvaluationContext ec) + => args.Length switch + { + 1 => Evaluate(0, 0, args[0]), + 2 => Evaluate(0, 0, args[0], args[1]), + 3 => Evaluate(0, 0, args[0], args[1], args[2]), + _ => ErrorEval.VALUE_INVALID + }; + private ValueEval Evaluate(int srcRowIndex, int srcColumnIndex, ValueEval arg0) + { + return Evaluate(srcRowIndex, srcColumnIndex, arg0, null, null); + } + private ValueEval Evaluate(int srcRowIndex, int srcColumnIndex, ValueEval arg0, ValueEval arg1) + { + return Evaluate(srcRowIndex, srcColumnIndex, arg0, arg1, null); + } + + private ValueEval Evaluate(int srcRowIndex, int srcColumnIndex, ValueEval arg0, ValueEval arg1, ValueEval arg2) + { + try + { + double number = NumericFunction.SingleOperandEvaluate(arg0, srcRowIndex, srcColumnIndex); + double significance = arg1 is null ? 1.0 : + NumericFunction.SingleOperandEvaluate(arg1, srcRowIndex, srcColumnIndex); + + bool? method = null; + + if (arg2 is not null) + { + ValueEval ve = OperandResolver.GetSingleValue(arg2, srcRowIndex, srcColumnIndex); + method = OperandResolver.CoerceValueToBoolean(ve, false); + } + + var result = Evaluate(number, significance, method ?? false); + + return result == 0.0 ? NumberEval.ZERO : new NumberEval(result); + } + catch (EvaluationException e) + { + return e.GetErrorEval(); + } + } + + public double Evaluate(double number, double significance, bool mode) + { + if (significance == 0.0 || number == 0.0) + { + // FLOOR|CEILING.MATH 's behavior is different from FLOOR|CEILING + // when significance is zero & number isn't 0, the MATH one returns 0 instead of #DIV/0. + + return 0.0; + } + + if (number > 0 && significance < 0 || number < 0 && significance > 0) + { + // This is how Excel behaves + significance = -significance; + } + + return EvaluateMath(number, significance, mode); + } + + protected abstract double EvaluateMajorDirection(double number); + protected abstract double EvaluateAlternativeDirection(double number); + private double EvaluateMath(double number, double significance, bool mode) + { + if (number >= 0) + { + // number is positive + + return EvaluateMajorDirection(number / significance) * significance; + } + else + { + // number is negative + + if (mode) + { + // Towards zero for FLOOR && Away from zero for CEILING + + return EvaluateAlternativeDirection(number / -significance) * -significance; + } + else + { + // vice versa + + return EvaluateMajorDirection(number / -significance) * -significance; + } + } + } + } +} diff --git a/main/SS/Formula/Functions/FloorMath.cs b/main/SS/Formula/Functions/FloorMath.cs new file mode 100644 index 000000000..ee50f9b5d --- /dev/null +++ b/main/SS/Formula/Functions/FloorMath.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace NPOI.SS.Formula.Functions +{ + public sealed class FloorMath : FloorCeilingMathBase + { + private FloorMath() + { + + } + public static readonly FloorMath Instance = new(); + + protected override double EvaluateMajorDirection(double number) + => Math.Floor(number); + + protected override double EvaluateAlternativeDirection(double number) + => Math.Ceiling(number); + } +} From 299a81d244105c4f7307c7cb103a54782ebf37ac Mon Sep 17 00:00:00 2001 From: Gan Keyu Date: Fri, 28 Jul 2023 00:46:01 +0800 Subject: [PATCH 02/10] improved FLOOR|CEILING.MATH function --- main/SS/Formula/Functions/CeilingMath.cs | 1 - .../Functions/DoublePrecisionHelper.cs | 40 ++++++ .../Formula/Functions/FloorCeilingMathBase.cs | 48 ++++--- main/SS/Formula/Functions/FloorMath.cs | 1 - test.runsettings | 1 + .../Formula/Functions/TestFloorCeilingMath.cs | 119 ++++++++++++++++++ .../test-data/functions/FloorCeilingMath.xlsx | Bin 0 -> 49694 bytes 7 files changed, 182 insertions(+), 28 deletions(-) create mode 100644 main/SS/Formula/Functions/DoublePrecisionHelper.cs create mode 100644 testcases/ooxml/SS/Formula/Functions/TestFloorCeilingMath.cs create mode 100644 testcases/test-data/functions/FloorCeilingMath.xlsx diff --git a/main/SS/Formula/Functions/CeilingMath.cs b/main/SS/Formula/Functions/CeilingMath.cs index deaf4a7c6..ac9cba2f5 100644 --- a/main/SS/Formula/Functions/CeilingMath.cs +++ b/main/SS/Formula/Functions/CeilingMath.cs @@ -12,7 +12,6 @@ private CeilingMath() { } public static readonly CeilingMath Instance = new(); - protected override double EvaluateMajorDirection(double number) => Math.Ceiling(number); diff --git a/main/SS/Formula/Functions/DoublePrecisionHelper.cs b/main/SS/Formula/Functions/DoublePrecisionHelper.cs new file mode 100644 index 000000000..6b16b58bc --- /dev/null +++ b/main/SS/Formula/Functions/DoublePrecisionHelper.cs @@ -0,0 +1,40 @@ +using NPOI.SS.Formula.UDF; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Security.Permissions; +using System.Text; +using System.Threading.Tasks; + +namespace NPOI.SS.Formula.Functions +{ + internal static class DoublePrecisionHelper + { + public static double GetFractionPart(double number) + { + return Math.Abs(number - Math.Truncate(number)); + } + public static double DropDigitsAfterSignificantOnes(double number, int digits) + { + if (number == 0.0) return 0.0; + + var isNegative = number < 0; + var positiveNumber = isNegative ? -number : number; + + var mostSignificantDigit = Math.Floor(Math.Log10(positiveNumber)); + var multiplier = Math.Pow(10, digits - mostSignificantDigit - 1); + + var newNumber = positiveNumber * multiplier; + newNumber = GetFractionPart(newNumber) >= 0.5 ? Math.Truncate(newNumber) + 1 : Math.Truncate(newNumber); + + newNumber /= multiplier; + return isNegative ? -newNumber : newNumber; + } + + public static bool IsIntegerWithDigitsDropped(double number, int significantDigits) + { + return Math.Abs(GetFractionPart(DropDigitsAfterSignificantOnes(number, significantDigits))) == 0.0; + } + } +} diff --git a/main/SS/Formula/Functions/FloorCeilingMathBase.cs b/main/SS/Formula/Functions/FloorCeilingMathBase.cs index c21de5b2a..87f41cceb 100644 --- a/main/SS/Formula/Functions/FloorCeilingMathBase.cs +++ b/main/SS/Formula/Functions/FloorCeilingMathBase.cs @@ -1,6 +1,7 @@ using NPOI.SS.Formula.Eval; using System; using System.Collections.Generic; +using System.Globalization; using System.Linq; using System.Text; using System.Threading.Tasks; @@ -9,12 +10,15 @@ namespace NPOI.SS.Formula.Functions { public abstract class FloorCeilingMathBase : FreeRefFunction { + // Excel has an internal precision of 15 significant digits + private const int SignificantThreshold = 15; + public ValueEval Evaluate(ValueEval[] args, OperationEvaluationContext ec) => args.Length switch { - 1 => Evaluate(0, 0, args[0]), - 2 => Evaluate(0, 0, args[0], args[1]), - 3 => Evaluate(0, 0, args[0], args[1], args[2]), + 1 => Evaluate(ec.RowIndex, ec.ColumnIndex, args[0]), + 2 => Evaluate(ec.RowIndex, ec.ColumnIndex, args[0], args[1]), + 3 => Evaluate(ec.RowIndex, ec.ColumnIndex, args[0], args[1], args[2]), _ => ErrorEval.VALUE_INVALID }; private ValueEval Evaluate(int srcRowIndex, int srcColumnIndex, ValueEval arg0) @@ -25,14 +29,12 @@ private ValueEval Evaluate(int srcRowIndex, int srcColumnIndex, ValueEval arg0, { return Evaluate(srcRowIndex, srcColumnIndex, arg0, arg1, null); } - private ValueEval Evaluate(int srcRowIndex, int srcColumnIndex, ValueEval arg0, ValueEval arg1, ValueEval arg2) { try { - double number = NumericFunction.SingleOperandEvaluate(arg0, srcRowIndex, srcColumnIndex); - double significance = arg1 is null ? 1.0 : - NumericFunction.SingleOperandEvaluate(arg1, srcRowIndex, srcColumnIndex); + var number = NumericFunction.SingleOperandEvaluate(arg0, srcRowIndex, srcColumnIndex); + var significance = arg1 is null ? 1.0 : NumericFunction.SingleOperandEvaluate(arg1, srcRowIndex, srcColumnIndex); bool? method = null; @@ -43,7 +45,6 @@ private ValueEval Evaluate(int srcRowIndex, int srcColumnIndex, ValueEval arg0, } var result = Evaluate(number, significance, method ?? false); - return result == 0.0 ? NumberEval.ZERO : new NumberEval(result); } catch (EvaluationException e) @@ -58,7 +59,6 @@ public double Evaluate(double number, double significance, bool mode) { // FLOOR|CEILING.MATH 's behavior is different from FLOOR|CEILING // when significance is zero & number isn't 0, the MATH one returns 0 instead of #DIV/0. - return 0.0; } @@ -68,36 +68,32 @@ public double Evaluate(double number, double significance, bool mode) significance = -significance; } - return EvaluateMath(number, significance, mode); - } + // Workaround without BigDecimal + var numberToTest = number / significance; + if (DoublePrecisionHelper.IsIntegerWithDigitsDropped(numberToTest, SignificantThreshold)) + return number; - protected abstract double EvaluateMajorDirection(double number); - protected abstract double EvaluateAlternativeDirection(double number); - private double EvaluateMath(double number, double significance, bool mode) - { - if (number >= 0) + if (number > 0) { - // number is positive - - return EvaluateMajorDirection(number / significance) * significance; + // mode is meaningless when number is positive + return EvaluateMajorDirection(numberToTest) * significance; } else { - // number is negative - if (mode) { // Towards zero for FLOOR && Away from zero for CEILING - - return EvaluateAlternativeDirection(number / -significance) * -significance; + return EvaluateAlternativeDirection(-numberToTest) * -significance; } else { - // vice versa - - return EvaluateMajorDirection(number / -significance) * -significance; + // Vice versa + return EvaluateMajorDirection(-numberToTest) * -significance; } } } + + protected abstract double EvaluateMajorDirection(double number); + protected abstract double EvaluateAlternativeDirection(double number); } } diff --git a/main/SS/Formula/Functions/FloorMath.cs b/main/SS/Formula/Functions/FloorMath.cs index ee50f9b5d..04ee4ddf3 100644 --- a/main/SS/Formula/Functions/FloorMath.cs +++ b/main/SS/Formula/Functions/FloorMath.cs @@ -13,7 +13,6 @@ private FloorMath() } public static readonly FloorMath Instance = new(); - protected override double EvaluateMajorDirection(double number) => Math.Floor(number); diff --git a/test.runsettings b/test.runsettings index 0f970a486..1b7ebee7f 100644 --- a/test.runsettings +++ b/test.runsettings @@ -13,5 +13,6 @@ + diff --git a/testcases/ooxml/SS/Formula/Functions/TestFloorCeilingMath.cs b/testcases/ooxml/SS/Formula/Functions/TestFloorCeilingMath.cs new file mode 100644 index 000000000..6d9d0632b --- /dev/null +++ b/testcases/ooxml/SS/Formula/Functions/TestFloorCeilingMath.cs @@ -0,0 +1,119 @@ +using NUnit.Framework; +using System; +using System.Collections.Generic; +using System.IO; +using NPOI.SS.UserModel; +using NPOI.XSSF.UserModel; +using NPOI.SS.Formula.Functions; +using NUnit.Framework.Constraints; +using NPOI.SS.Util; + +namespace TestCases.SS.Formula.Functions +{ + /// + /// Testing FLOOR.MATH & CEILING.MATH + /// + [TestFixture] + public class TestFloorCeilingMath + { + // In real-world Excel's tolerance control is more complicated. + // Save it for now. + private const double Tolerance = 1e-7; + public enum FunctionTested + { + Ceiling, + Floor + } + public sealed class TestFunction + { + public TestFunction(FunctionTested func, bool mode) + { + Function = func; + Mode = mode; + } + public FunctionTested Function { get; set; } + public bool Mode { get; set; } + public string SheetName + => (Function == FunctionTested.Ceiling ? "CEILING" : "FLOOR") + "," + Mode.ToString().ToUpperInvariant(); + + public FloorCeilingMathBase Evaluator + => Function == FunctionTested.Ceiling ? CeilingMath.Instance : (FloorCeilingMathBase)FloorMath.Instance; + + public double Evaluate(double number, double significance) + => Evaluator.Evaluate(number, significance, Mode); + public override string ToString() + => SheetName; + } + + private XSSFWorkbook _workbook; + [OneTimeSetUp] + public void LoadData() + { + var fldr = Path.Combine(TestContext.CurrentContext.TestDirectory, TestContext.Parameters["function"]); + const string filename = "FloorCeilingMath.xlsx"; + var file = Path.Combine(fldr, filename); + + using (var fs = new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) + { + _workbook = new XSSFWorkbook(fs); + } + } + + [OneTimeTearDown] + public void Dispose() + { + _workbook?.Close(); + } + public static TestFunction[] FunctionVariants => new[] + { + new TestFunction(FunctionTested.Ceiling, false), + new TestFunction(FunctionTested.Ceiling, true), + new TestFunction(FunctionTested.Floor, false), + new TestFunction(FunctionTested.Floor, true), + }; + + [Test, Order(1)] + [TestCaseSource(nameof(FunctionVariants))] + public void TestEvaluate(TestFunction function) + { + const int StartRowIndex = 1; + const int StartColumnIndex = 0; + const int Count = 34; + + Assert.Multiple(() => + { + var sheet = _workbook.GetSheet(function.SheetName); + for (var i = 1; i <= Count; i++) + { + var row = sheet.GetRow(i + StartRowIndex); + var significance = row.GetCell(StartColumnIndex).NumericCellValue; + + for (var j = 1; j <= Count; j++) + { + var number = sheet.GetRow(StartRowIndex).GetCell(j + StartColumnIndex).NumericCellValue; + var expected = row.GetCell(j + StartColumnIndex).NumericCellValue; + + var functionResult = function.Evaluate(number, significance); + + // Excel also has bugs on =FLOOR.MATH(4, -2, FALSE|TRUE) + // as it recognizes auto-filled 4 as 3.99999999999999. + // See the cell of AF13 in the test data file. + // So the specific pair is skipped. + + if (Math.Abs(number - (4)) < Tolerance && Math.Abs(significance - (-2)) < Tolerance) + continue; + + Assert.AreEqual(expected, functionResult, Tolerance, $"{function}, {number}, {significance}"); + } + } + }); + } + [Test, Order(2), NonParallelizable] + public void EvaluateAllFormulas() + { + var evaluator = new XSSFFormulaEvaluator(_workbook); + evaluator.ClearAllCachedResultValues(); + Assert.DoesNotThrow(() => evaluator.EvaluateAll()); + } + } +} diff --git a/testcases/test-data/functions/FloorCeilingMath.xlsx b/testcases/test-data/functions/FloorCeilingMath.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..c973ce667aed49ee6c8ea7bb9aefc32416bb7335 GIT binary patch literal 49694 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 literal 0 HcmV?d00001 From ae48723475e1d5ef32654ff12e6949cbf6ff1729 Mon Sep 17 00:00:00 2001 From: Gan Keyu Date: Fri, 28 Jul 2023 01:03:10 +0800 Subject: [PATCH 03/10] added license info --- main/SS/Formula/Functions/CeilingMath.cs | 20 ++++++++++++++++++- .../Functions/DoublePrecisionHelper.cs | 20 ++++++++++++++++++- .../Formula/Functions/FloorCeilingMathBase.cs | 20 ++++++++++++++++++- main/SS/Formula/Functions/FloorMath.cs | 20 ++++++++++++++++++- .../Formula/Functions/TestFloorCeilingMath.cs | 20 ++++++++++++++++++- 5 files changed, 95 insertions(+), 5 deletions(-) diff --git a/main/SS/Formula/Functions/CeilingMath.cs b/main/SS/Formula/Functions/CeilingMath.cs index ac9cba2f5..c8d501f20 100644 --- a/main/SS/Formula/Functions/CeilingMath.cs +++ b/main/SS/Formula/Functions/CeilingMath.cs @@ -1,4 +1,22 @@ -using System; +/* + * ==================================================================== + * 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. + * ==================================================================== + */ +using System; using System.Collections.Generic; using System.Linq; using System.Text; diff --git a/main/SS/Formula/Functions/DoublePrecisionHelper.cs b/main/SS/Formula/Functions/DoublePrecisionHelper.cs index 6b16b58bc..84b7998c1 100644 --- a/main/SS/Formula/Functions/DoublePrecisionHelper.cs +++ b/main/SS/Formula/Functions/DoublePrecisionHelper.cs @@ -1,4 +1,22 @@ -using NPOI.SS.Formula.UDF; +/* + * ==================================================================== + * 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. + * ==================================================================== + */ +using NPOI.SS.Formula.UDF; using System; using System.Collections.Generic; using System.Linq; diff --git a/main/SS/Formula/Functions/FloorCeilingMathBase.cs b/main/SS/Formula/Functions/FloorCeilingMathBase.cs index 87f41cceb..f717c3a75 100644 --- a/main/SS/Formula/Functions/FloorCeilingMathBase.cs +++ b/main/SS/Formula/Functions/FloorCeilingMathBase.cs @@ -1,4 +1,22 @@ -using NPOI.SS.Formula.Eval; +/* + * ==================================================================== + * 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. + * ==================================================================== + */ +using NPOI.SS.Formula.Eval; using System; using System.Collections.Generic; using System.Globalization; diff --git a/main/SS/Formula/Functions/FloorMath.cs b/main/SS/Formula/Functions/FloorMath.cs index 04ee4ddf3..34cd90cef 100644 --- a/main/SS/Formula/Functions/FloorMath.cs +++ b/main/SS/Formula/Functions/FloorMath.cs @@ -1,4 +1,22 @@ -using System; +/* + * ==================================================================== + * 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. + * ==================================================================== + */ +using System; using System.Collections.Generic; using System.Linq; using System.Text; diff --git a/testcases/ooxml/SS/Formula/Functions/TestFloorCeilingMath.cs b/testcases/ooxml/SS/Formula/Functions/TestFloorCeilingMath.cs index 6d9d0632b..f0ce13f17 100644 --- a/testcases/ooxml/SS/Formula/Functions/TestFloorCeilingMath.cs +++ b/testcases/ooxml/SS/Formula/Functions/TestFloorCeilingMath.cs @@ -1,4 +1,22 @@ -using NUnit.Framework; +/* + * ==================================================================== + * 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. + * ==================================================================== + */ +using NUnit.Framework; using System; using System.Collections.Generic; using System.IO; From c9f37b46360091fe86b438f685e43cafb357b6aa Mon Sep 17 00:00:00 2001 From: Gan Keyu Date: Fri, 28 Jul 2023 15:04:02 +0800 Subject: [PATCH 04/10] updated license --- main/SS/Formula/Functions/CeilingMath.cs | 4 ++-- main/SS/Formula/Functions/DoublePrecisionHelper.cs | 4 ++-- main/SS/Formula/Functions/FloorCeilingMathBase.cs | 4 ++-- main/SS/Formula/Functions/FloorMath.cs | 4 ++-- testcases/ooxml/SS/Formula/Functions/TestFloorCeilingMath.cs | 4 ++-- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/main/SS/Formula/Functions/CeilingMath.cs b/main/SS/Formula/Functions/CeilingMath.cs index c8d501f20..d2e4615d8 100644 --- a/main/SS/Formula/Functions/CeilingMath.cs +++ b/main/SS/Formula/Functions/CeilingMath.cs @@ -1,9 +1,9 @@ /* * ==================================================================== - * Licensed to the Apache Software Foundation (ASF) under one or more + * Licensed to the collaborators of the NPOI project 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 collaborators 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 * diff --git a/main/SS/Formula/Functions/DoublePrecisionHelper.cs b/main/SS/Formula/Functions/DoublePrecisionHelper.cs index 84b7998c1..b0530311c 100644 --- a/main/SS/Formula/Functions/DoublePrecisionHelper.cs +++ b/main/SS/Formula/Functions/DoublePrecisionHelper.cs @@ -1,9 +1,9 @@ /* * ==================================================================== - * Licensed to the Apache Software Foundation (ASF) under one or more + * Licensed to the collaborators of the NPOI project 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 collaborators 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 * diff --git a/main/SS/Formula/Functions/FloorCeilingMathBase.cs b/main/SS/Formula/Functions/FloorCeilingMathBase.cs index f717c3a75..f7bf9ecb1 100644 --- a/main/SS/Formula/Functions/FloorCeilingMathBase.cs +++ b/main/SS/Formula/Functions/FloorCeilingMathBase.cs @@ -1,9 +1,9 @@ /* * ==================================================================== - * Licensed to the Apache Software Foundation (ASF) under one or more + * Licensed to the collaborators of the NPOI project 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 collaborators 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 * diff --git a/main/SS/Formula/Functions/FloorMath.cs b/main/SS/Formula/Functions/FloorMath.cs index 34cd90cef..e04736bcd 100644 --- a/main/SS/Formula/Functions/FloorMath.cs +++ b/main/SS/Formula/Functions/FloorMath.cs @@ -1,9 +1,9 @@ /* * ==================================================================== - * Licensed to the Apache Software Foundation (ASF) under one or more + * Licensed to the collaborators of the NPOI project 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 collaborators 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 * diff --git a/testcases/ooxml/SS/Formula/Functions/TestFloorCeilingMath.cs b/testcases/ooxml/SS/Formula/Functions/TestFloorCeilingMath.cs index f0ce13f17..235469ecd 100644 --- a/testcases/ooxml/SS/Formula/Functions/TestFloorCeilingMath.cs +++ b/testcases/ooxml/SS/Formula/Functions/TestFloorCeilingMath.cs @@ -1,9 +1,9 @@ /* * ==================================================================== - * Licensed to the Apache Software Foundation (ASF) under one or more + * Licensed to the collaborators of the NPOI project 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 collaborators 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 * From 2358a462d0edae22bb92d5b55f1d56a1bbfd3beb Mon Sep 17 00:00:00 2001 From: Gan Keyu Date: Fri, 28 Jul 2023 18:49:14 +0800 Subject: [PATCH 05/10] Added (VAR|STDEV).(P|S) functions --- main/SS/Formula/Atp/AnalysisToolPak.cs | 5 + .../Formula/Functions/NumberListFuncBase.cs | 73 +++++++++++ main/SS/Formula/Functions/StdevP.cs | 36 ++++++ main/SS/Formula/Functions/StdevS.cs | 36 ++++++ .../Functions/ValueEvaluationHelper.cs | 88 ++++++++++++++ main/SS/Formula/Functions/VarP.cs | 47 ++++++++ main/SS/Formula/Functions/VarS.cs | 48 ++++++++ .../CompareNumericFuncEvalTestBase.cs | 113 ++++++++++++++++++ .../Functions/TestStdevAndVarVariants.cs | 31 +++++ .../test-data/functions/StDevAndVar.xlsx | Bin 0 -> 10427 bytes 10 files changed, 477 insertions(+) create mode 100644 main/SS/Formula/Functions/NumberListFuncBase.cs create mode 100644 main/SS/Formula/Functions/StdevP.cs create mode 100644 main/SS/Formula/Functions/StdevS.cs create mode 100644 main/SS/Formula/Functions/ValueEvaluationHelper.cs create mode 100644 main/SS/Formula/Functions/VarP.cs create mode 100644 main/SS/Formula/Functions/VarS.cs create mode 100644 testcases/ooxml/SS/Formula/Functions/CompareNumericFuncEvalTestBase.cs create mode 100644 testcases/ooxml/SS/Formula/Functions/TestStdevAndVarVariants.cs create mode 100644 testcases/test-data/functions/StDevAndVar.xlsx diff --git a/main/SS/Formula/Atp/AnalysisToolPak.cs b/main/SS/Formula/Atp/AnalysisToolPak.cs index 1440e1a5e..34b65d4c5 100644 --- a/main/SS/Formula/Atp/AnalysisToolPak.cs +++ b/main/SS/Formula/Atp/AnalysisToolPak.cs @@ -197,6 +197,11 @@ private static Dictionary CreateFunctionsMap() r(m, "CEILING.MATH", CeilingMath.Instance); r(m, "FLOOR.MATH", FloorMath.Instance); + r(m, "STDEV.S", StdevS.Instance); + r(m, "STDEV.P", StdevP.Instance); + r(m, "VAR.S", VarS.Instance); + r(m, "VAR.P", VarP.Instance); + return m; } diff --git a/main/SS/Formula/Functions/NumberListFuncBase.cs b/main/SS/Formula/Functions/NumberListFuncBase.cs new file mode 100644 index 000000000..73464148b --- /dev/null +++ b/main/SS/Formula/Functions/NumberListFuncBase.cs @@ -0,0 +1,73 @@ +/* + * ==================================================================== + * Licensed to the collaborators of the NPOI project under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The collaborators license 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. + * ==================================================================== + */ +using NPOI.SS.Formula.Eval; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace NPOI.SS.Formula.Functions +{ + public abstract class NumberListFuncBase : FreeRefFunction + { + public bool AllowEmptyList { get; set; } = false; + public ValueEval ErrorOnEmptyList { get; set; } = ErrorEval.DIV_ZERO; + public ValueEval Evaluate(ValueEval[] args, OperationEvaluationContext ec) + { + if (args.Length == 0) + return ErrorEval.VALUE_INVALID; + + try + { + var list = new List(); + + foreach (var arg in args) + { + switch (arg) + { + case AreaEval ae: + ValueEvaluationHelper.GetArrayValues(ae, list); + break; + case NumericValueEval: + case RefEval: + var val = ValueEvaluationHelper.GetScalarValue(arg); + if (val.HasValue) + list.Add(val.Value); + break; + default: + return ErrorEval.VALUE_INVALID; + } + } + + if (!AllowEmptyList && list.Count == 0) + return ErrorOnEmptyList; + + var result = CalculateFromNumberList(list); + return result == 0.0 ? NumberEval.ZERO : new NumberEval(result); + } + catch (EvaluationException e) + { + return e.GetErrorEval(); + } + } + + public abstract double CalculateFromNumberList(List list); + } +} diff --git a/main/SS/Formula/Functions/StdevP.cs b/main/SS/Formula/Functions/StdevP.cs new file mode 100644 index 000000000..9a0e1d909 --- /dev/null +++ b/main/SS/Formula/Functions/StdevP.cs @@ -0,0 +1,36 @@ +/* + * ==================================================================== + * Licensed to the collaborators of the NPOI project under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The collaborators license 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. + * ==================================================================== + */ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace NPOI.SS.Formula.Functions +{ + public sealed class StdevP : NumberListFuncBase + { + public static readonly StdevP Instance = new(); + private StdevP() + { + } + public override double CalculateFromNumberList(List list) + => Math.Sqrt(VarP.Instance.CalculateFromNumberList(list)); + } +} diff --git a/main/SS/Formula/Functions/StdevS.cs b/main/SS/Formula/Functions/StdevS.cs new file mode 100644 index 000000000..bbd3fb439 --- /dev/null +++ b/main/SS/Formula/Functions/StdevS.cs @@ -0,0 +1,36 @@ +/* + * ==================================================================== + * Licensed to the collaborators of the NPOI project under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The collaborators license 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. + * ==================================================================== + */ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace NPOI.SS.Formula.Functions +{ + public sealed class StdevS : NumberListFuncBase + { + public static readonly StdevS Instance = new(); + private StdevS() + { + } + public override double CalculateFromNumberList(List list) + => Math.Sqrt(VarS.Instance.CalculateFromNumberList(list)); + } +} diff --git a/main/SS/Formula/Functions/ValueEvaluationHelper.cs b/main/SS/Formula/Functions/ValueEvaluationHelper.cs new file mode 100644 index 000000000..91c9e9496 --- /dev/null +++ b/main/SS/Formula/Functions/ValueEvaluationHelper.cs @@ -0,0 +1,88 @@ +/* + * ==================================================================== + * Licensed to the collaborators of the NPOI project under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The collaborators license 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. + * ==================================================================== + */ +using NPOI.SS.Formula.Eval; +using NPOI.Util; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace NPOI.SS.Formula.Functions +{ + internal static class ValueEvaluationHelper + { + public static double? GetScalarValue(ValueEval arg) + { + + ValueEval eval; + if (arg is RefEval re) + { + if (re.NumberOfSheets > 1) + { + throw new EvaluationException(ErrorEval.VALUE_INVALID); + } + eval = re.GetInnerValueEval(re.FirstSheetIndex); + } + else + { + eval = arg; + } + + if (eval is AreaEval ae) + { + // an area ref can work as a scalar value if it is 1x1 + if (!ae.IsColumn || !ae.IsRow) + { + throw new EvaluationException(ErrorEval.VALUE_INVALID); + } + eval = ae.GetRelativeValue(0, 0); + } + + if (eval is null) + { + throw new ArgumentException("parameter (eval) may not be null"); + } + + return GetSingleValue(eval); + } + public static void GetArrayValues(AreaEval evalArg, in List numList) + { + int height = evalArg.LastRow - evalArg.FirstRow + 1; + int width = evalArg.LastColumn - evalArg.FirstColumn + 1; // TODO - junit + + for (int rrIx = 0; rrIx < height; rrIx++) + { + for (int rcIx = 0; rcIx < width; rcIx++) + { + var val = GetSingleValue(evalArg.GetRelativeValue(rrIx, rcIx)); + if (val.HasValue) + numList.Add(val.Value); + } + } + } + private static double? GetSingleValue(ValueEval ve) => ve switch + { + NumericValueEval nve => nve.NumberValue, + null or BlankEval or StringEval => null, + ErrorEval ev => throw new EvaluationException(ev), + _ => throw new RuntimeException($"Unexpected value eval class ({ve.GetType().Name})") + }; + } +} diff --git a/main/SS/Formula/Functions/VarP.cs b/main/SS/Formula/Functions/VarP.cs new file mode 100644 index 000000000..751fd463a --- /dev/null +++ b/main/SS/Formula/Functions/VarP.cs @@ -0,0 +1,47 @@ +/* + * ==================================================================== + * Licensed to the collaborators of the NPOI project under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The collaborators license 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. + * ==================================================================== + */ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace NPOI.SS.Formula.Functions +{ + public sealed class VarP : NumberListFuncBase + { + public static readonly VarP Instance = new(); + private VarP() + { + } + public override double CalculateFromNumberList(List list) + { + if (list.Count == 1) return 0.0; + var average = list.Average(); + var sum = 0.0; + + foreach (var item in list) + { + sum += Math.Pow(item - average, 2); + } + + return sum / list.Count; + } + } +} diff --git a/main/SS/Formula/Functions/VarS.cs b/main/SS/Formula/Functions/VarS.cs new file mode 100644 index 000000000..34af38a40 --- /dev/null +++ b/main/SS/Formula/Functions/VarS.cs @@ -0,0 +1,48 @@ +/* + * ==================================================================== + * Licensed to the collaborators of the NPOI project under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The collaborators license 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. + * ==================================================================== + */ +using NPOI.SS.Formula.Eval; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace NPOI.SS.Formula.Functions +{ + public sealed class VarS : NumberListFuncBase + { + public static readonly VarS Instance = new(); + private VarS() + { + } + public override double CalculateFromNumberList(List list) + { + if (list.Count == 1) throw new EvaluationException(ErrorEval.DIV_ZERO); + var average = list.Average(); + var sum = 0.0; + + foreach (var item in list) + { + sum += Math.Pow(item - average, 2); + } + + return sum / (list.Count - 1); + } + } +} diff --git a/testcases/ooxml/SS/Formula/Functions/CompareNumericFuncEvalTestBase.cs b/testcases/ooxml/SS/Formula/Functions/CompareNumericFuncEvalTestBase.cs new file mode 100644 index 000000000..0eb3c98f5 --- /dev/null +++ b/testcases/ooxml/SS/Formula/Functions/CompareNumericFuncEvalTestBase.cs @@ -0,0 +1,113 @@ +/* + * ==================================================================== + * Licensed to the collaborators of the NPOI project under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The collaborators license 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. + * ==================================================================== + */ +using NPOI.SS.UserModel; +using NPOI.XSSF.UserModel; +using NUnit.Framework; +using System.Collections.Generic; +using System.IO; + +namespace TestCases.SS.Formula.Functions +{ + /// + /// This class provides a simple comparison test + /// to show if the cached formula result in an Excel file + /// is still same after being recalculated by NPOI. + /// The specific class is for numeric and error values. + /// + [TestFixture] + public abstract class CompareNumericFuncEvalTestBase + { + public abstract string TestFileName { get; } + // In real-world Excel's tolerance control is more complicated. + // Save it for now. + private const double Tolerance = 1e-7; + private XSSFWorkbook _workbook; + [OneTimeSetUp] + public void LoadData() + { + var fldr = Path.Combine(TestContext.CurrentContext.TestDirectory, TestContext.Parameters["function"]); + var file = Path.Combine(fldr, TestFileName); + + using (var fs = new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) + { + _workbook = new XSSFWorkbook(fs); + } + } + + [OneTimeTearDown] + public void Dispose() + { + _workbook?.Close(); + } + + internal static SortedList GetSpreadsheetContent(IWorkbook workbook) + { + var list = new SortedList(); + + var sheet = workbook.GetSheetAt(0); + + for (var rowId = sheet.FirstRowNum; rowId <= sheet.LastRowNum; rowId++) + { + var row = sheet.GetRow(rowId); + if (row is null || row.FirstCellNum < 0) + continue; + + for (var colId = row.FirstCellNum; colId <= row.LastCellNum; colId++) + { + var cell = row.GetCell(colId); + if (cell is null) continue; + if (cell.CellType != CellType.Formula) continue; + if (cell.CachedFormulaResultType != CellType.Numeric + && cell.CachedFormulaResultType != CellType.Error) continue; + + list[cell.Address.FormatAsString()] = + cell.CachedFormulaResultType == CellType.Numeric ? + cell.NumericCellValue : cell.ErrorCellValue; + } + } + return list; + } + + [Test] + public void TestEvaluate() + { + var originalData = GetSpreadsheetContent(_workbook); + + var evaluator = new XSSFFormulaEvaluator(_workbook); + evaluator.ClearAllCachedResultValues(); + Assert.DoesNotThrow(() => evaluator.EvaluateAll()); + + var evaluatedData = GetSpreadsheetContent(_workbook); + + Assert.Multiple(() => + { + foreach (var kv in evaluatedData) + { + if (!originalData.TryGetValue(kv.Key, out var val)) + { + Assert.Fail($"Spreadsheet structure changed! No {kv.Key} cell in the original spreadsheet."); + break; + } + + Assert.AreEqual(val, kv.Value, Tolerance, kv.Key); + } + }); + } + } +} diff --git a/testcases/ooxml/SS/Formula/Functions/TestStdevAndVarVariants.cs b/testcases/ooxml/SS/Formula/Functions/TestStdevAndVarVariants.cs new file mode 100644 index 000000000..20d454d50 --- /dev/null +++ b/testcases/ooxml/SS/Formula/Functions/TestStdevAndVarVariants.cs @@ -0,0 +1,31 @@ +/* + * ==================================================================== + * Licensed to the collaborators of the NPOI project under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The collaborators license 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. + * ==================================================================== + */ +using NUnit.Framework; + +namespace TestCases.SS.Formula.Functions +{ + /// + /// Test (STDEV|VAR).(P|S) + /// + [TestFixture] + public class TestStdevAndVarVariants : CompareNumericFuncEvalTestBase + { + public override string TestFileName => "StDevAndVar.xlsx"; + } +} diff --git a/testcases/test-data/functions/StDevAndVar.xlsx b/testcases/test-data/functions/StDevAndVar.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..9bc982de13ad8c3a77ab95d0a0211113009234c2 GIT binary patch literal 10427 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 Date: Mon, 7 Aug 2023 19:09:15 +0800 Subject: [PATCH 06/10] refactored with ExtendNumerics --- main/NPOI.Core.csproj | 1 + .../Functions/DoublePrecisionHelper.cs | 42 +++++++++- .../Formula/Functions/FloorCeilingMathBase.cs | 81 ++++++++++++------ .../Functions/TestPrecisionedDecimalHelper.cs | 84 +++++++++++++++++++ .../Functions/TestPrecisionedDecimalHelper.cs | 54 ++++++++++++ 5 files changed, 237 insertions(+), 25 deletions(-) create mode 100644 testcases/main/SS/Formula/Functions/TestPrecisionedDecimalHelper.cs create mode 100644 testcases/ooxml/SS/Formula/Functions/TestPrecisionedDecimalHelper.cs diff --git a/main/NPOI.Core.csproj b/main/NPOI.Core.csproj index a7def2ca6..1de3b45e6 100644 --- a/main/NPOI.Core.csproj +++ b/main/NPOI.Core.csproj @@ -15,6 +15,7 @@ + diff --git a/main/SS/Formula/Functions/DoublePrecisionHelper.cs b/main/SS/Formula/Functions/DoublePrecisionHelper.cs index b0530311c..866d6c3f0 100644 --- a/main/SS/Formula/Functions/DoublePrecisionHelper.cs +++ b/main/SS/Formula/Functions/DoublePrecisionHelper.cs @@ -16,6 +16,7 @@ * limitations under the License. * ==================================================================== */ +using ExtendedNumerics; using NPOI.SS.Formula.UDF; using System; using System.Collections.Generic; @@ -50,9 +51,48 @@ public static double DropDigitsAfterSignificantOnes(double number, int digits) return isNegative ? -newNumber : newNumber; } - public static bool IsIntegerWithDigitsDropped(double number, int significantDigits) + public static bool IsIntegerWithDigitsDropped(this double number, int significantDigits) { return Math.Abs(GetFractionPart(DropDigitsAfterSignificantOnes(number, significantDigits))) == 0.0; } + + public static bool IsIntegerWithDigitsDropped(this BigDecimal number, int significantDigits) + { + const int PADDING_ONE = 1; + + int decimalPlaces = number.DecimalPlaces; + int realSigDigits = number.SignifigantDigits; + int integerPlaces = realSigDigits - decimalPlaces; + + if (integerPlaces > significantDigits) + { + return true; + } + + BigDecimal fracPart = number.GetFractionalPart(); + + if (fracPart.IsZero()) + { + return true; + } + + decimalPlaces = Math.Min(decimalPlaces, significantDigits - integerPlaces); + + BigDecimal exp = BigDecimal.Pow(10, decimalPlaces + PADDING_ONE); + fracPart *= exp; + fracPart = BigDecimal.Truncate(fracPart); + + if (fracPart.IsZero()) + { + return true; + } + + if (fracPart == exp || (fracPart + BigDecimal.One) == exp) + { + return true; + } + + return false; + } } } diff --git a/main/SS/Formula/Functions/FloorCeilingMathBase.cs b/main/SS/Formula/Functions/FloorCeilingMathBase.cs index f7bf9ecb1..a5a53a086 100644 --- a/main/SS/Formula/Functions/FloorCeilingMathBase.cs +++ b/main/SS/Formula/Functions/FloorCeilingMathBase.cs @@ -16,6 +16,7 @@ * limitations under the License. * ==================================================================== */ +using ExtendedNumerics; using NPOI.SS.Formula.Eval; using System; using System.Collections.Generic; @@ -29,24 +30,19 @@ namespace NPOI.SS.Formula.Functions public abstract class FloorCeilingMathBase : FreeRefFunction { // Excel has an internal precision of 15 significant digits - private const int SignificantThreshold = 15; + private const int SignificantDigits = 15; + // Use high-precision decimal calculations or customized double workaround + private const bool UseHighPrecisionCalculation = true; + private const int SignificantDigitsForHighPrecision = SignificantDigits + 1; public ValueEval Evaluate(ValueEval[] args, OperationEvaluationContext ec) => args.Length switch { - 1 => Evaluate(ec.RowIndex, ec.ColumnIndex, args[0]), - 2 => Evaluate(ec.RowIndex, ec.ColumnIndex, args[0], args[1]), + 1 => Evaluate(ec.RowIndex, ec.ColumnIndex, args[0], null, null), + 2 => Evaluate(ec.RowIndex, ec.ColumnIndex, args[0], args[1], null), 3 => Evaluate(ec.RowIndex, ec.ColumnIndex, args[0], args[1], args[2]), _ => ErrorEval.VALUE_INVALID }; - private ValueEval Evaluate(int srcRowIndex, int srcColumnIndex, ValueEval arg0) - { - return Evaluate(srcRowIndex, srcColumnIndex, arg0, null, null); - } - private ValueEval Evaluate(int srcRowIndex, int srcColumnIndex, ValueEval arg0, ValueEval arg1) - { - return Evaluate(srcRowIndex, srcColumnIndex, arg0, arg1, null); - } private ValueEval Evaluate(int srcRowIndex, int srcColumnIndex, ValueEval arg0, ValueEval arg1, ValueEval arg2) { try @@ -86,27 +82,64 @@ public double Evaluate(double number, double significance, bool mode) significance = -significance; } - // Workaround without BigDecimal - var numberToTest = number / significance; - if (DoublePrecisionHelper.IsIntegerWithDigitsDropped(numberToTest, SignificantThreshold)) - return number; - - if (number > 0) + if (UseHighPrecisionCalculation) { - // mode is meaningless when number is positive - return EvaluateMajorDirection(numberToTest) * significance; + BigDecimal.Precision = SignificantDigitsForHighPrecision; + + var bigNumber = new BigDecimal(number); + var bigSignificance = new BigDecimal(significance); + + BigDecimal bigNumberToTest = bigNumber / bigSignificance; + BigDecimal bigNumberWithPrecisionDropped = BigDecimal.Round(bigNumberToTest, SignificantDigits); + + if (bigNumberWithPrecisionDropped.IsIntegerWithDigitsDropped(SignificantDigits)) + return number; + + var numberToTest = number / significance; + + if (number > 0) + { + // mode is meaningless when number is positive + return EvaluateMajorDirection(numberToTest) * significance; + } + else + { + if (mode) + { + // Towards zero for FLOOR && Away from zero for CEILING + return EvaluateAlternativeDirection(-numberToTest) * -significance; + } + else + { + // Vice versa + return EvaluateMajorDirection(-numberToTest) * -significance; + } + } } else { - if (mode) + // Workaround without BigDecimal + double numberToTest = number / significance; + if (numberToTest.IsIntegerWithDigitsDropped(SignificantDigits)) + return number; + + if (number > 0) { - // Towards zero for FLOOR && Away from zero for CEILING - return EvaluateAlternativeDirection(-numberToTest) * -significance; + // mode is meaningless when number is positive + return EvaluateMajorDirection(numberToTest) * significance; } else { - // Vice versa - return EvaluateMajorDirection(-numberToTest) * -significance; + if (mode) + { + // Towards zero for FLOOR && Away from zero for CEILING + return EvaluateAlternativeDirection(-numberToTest) * -significance; + } + else + { + // Vice versa + return EvaluateMajorDirection(-numberToTest) * -significance; + } } } } diff --git a/testcases/main/SS/Formula/Functions/TestPrecisionedDecimalHelper.cs b/testcases/main/SS/Formula/Functions/TestPrecisionedDecimalHelper.cs new file mode 100644 index 000000000..9f994e141 --- /dev/null +++ b/testcases/main/SS/Formula/Functions/TestPrecisionedDecimalHelper.cs @@ -0,0 +1,84 @@ +/* + * ==================================================================== + * Licensed to the collaborators of the NPOI project under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The collaborators 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. + * ==================================================================== + */ +using NUnit.Framework; +using System; +using System.Collections.Generic; +using System.IO; +using NPOI.SS.UserModel; +using NPOI.SS.Formula.Functions; +using NUnit.Framework.Constraints; +using NPOI.SS.Util; +using ExtendedNumerics; +using MathNet.Numerics; + +namespace TestCases.SS.Formula.Functions +{ + [TestFixture] + public class TestPrecisionedDecimalHelper + { + public const int Precision = 15; + + private static void BigDecimalPrecisionTest(bool expected, string repre) + { + Assert.AreEqual(expected, DoublePrecisionHelper.IsIntegerWithDigitsDropped( + BigDecimal.Parse(repre), Precision), repre); + } + private static void DoublePrecisionTest(bool expected, string repre) + { + Assert.AreEqual(expected, DoublePrecisionHelper.IsIntegerWithDigitsDropped( + double.Parse(repre), Precision), repre); + } + + [Test] + public void IsBigDecimalAlmostInteger() + { + Assert.Multiple(() => + { + BigDecimalPrecisionTest(true, "4.9999999999999999"); + BigDecimalPrecisionTest(true, "4.999999999999999"); + BigDecimalPrecisionTest(false, "4.99999999999999"); + BigDecimalPrecisionTest(false, "4.9999999999999"); + BigDecimalPrecisionTest(false, "12345678901234.5"); + BigDecimalPrecisionTest(false, "12345678901234.9"); + BigDecimalPrecisionTest(true, "12345678901234.99"); + BigDecimalPrecisionTest(true, "123456789012345"); + BigDecimalPrecisionTest(false, "123456789012345.25"); + }); + + } + [Ignore("Currently unused. Bugs to be solved.")] + [Test] + public void IsDoubleAlmostInteger() + { + Assert.Multiple(() => + { + DoublePrecisionTest(true, "4.9999999999999999"); + DoublePrecisionTest(true, "4.999999999999999"); + DoublePrecisionTest(false, "4.99999999999999"); + DoublePrecisionTest(false, "4.9999999999999"); + DoublePrecisionTest(false, "12345678901234.5"); + DoublePrecisionTest(false, "12345678901234.9"); + DoublePrecisionTest(true, "12345678901234.99"); + DoublePrecisionTest(true, "123456789012345"); + DoublePrecisionTest(false, "123456789012345.25"); // TODO: bugged + }); + + } + } +} diff --git a/testcases/ooxml/SS/Formula/Functions/TestPrecisionedDecimalHelper.cs b/testcases/ooxml/SS/Formula/Functions/TestPrecisionedDecimalHelper.cs new file mode 100644 index 000000000..c8c57e264 --- /dev/null +++ b/testcases/ooxml/SS/Formula/Functions/TestPrecisionedDecimalHelper.cs @@ -0,0 +1,54 @@ +/* + * ==================================================================== + * Licensed to the collaborators of the NPOI project under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The collaborators 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. + * ==================================================================== + */ +using NUnit.Framework; +using System; +using System.Collections.Generic; +using System.IO; +using NPOI.SS.UserModel; +using NPOI.XSSF.UserModel; +using NPOI.SS.Formula.Functions; +using NUnit.Framework.Constraints; +using NPOI.SS.Util; +using ExtendedNumerics; +using MathNet.Numerics; + +namespace TestCases.SS.Formula.Functions +{ + [TestFixture] + public class TestPrecisionedDecimalHelper + { + [Test] + public void IsInteger() + { + Assert.Multiple(() => + { + Assert.AreEqual(IsIntegerWithDigitsDropped(BigDecimal.Parse("4.9999999999999999"), Precision)); + Assert.AreEqual(IsIntegerWithDigitsDropped(BigDecimal.Parse("4.999999999999999"), Precision)); + Assert.AreEqual(IsIntegerWithDigitsDropped(BigDecimal.Parse("4.99999999999999"), Precision)); + Assert.AreEqual(IsIntegerWithDigitsDropped(BigDecimal.Parse("4.9999999999999"), Precision)); + Assert.AreEqual(IsIntegerWithDigitsDropped(BigDecimal.Parse("12345678901234.5"), Precision)); + Assert.AreEqual(IsIntegerWithDigitsDropped(BigDecimal.Parse("12345678901234.9"), Precision)); + Assert.AreEqual(IsIntegerWithDigitsDropped(BigDecimal.Parse("12345678901234.99"), Precision)); + Assert.AreEqual(IsIntegerWithDigitsDropped(BigDecimal.Parse("123456789012345"), Precision)); + Assert.AreEqual(IsIntegerWithDigitsDropped(BigDecimal.Parse("123456789012345.0"), Precision)); + }) + + } + } +} From d089e22d4f3a11a79060cf7ab64907eb478da3a9 Mon Sep 17 00:00:00 2001 From: Gan Keyu Date: Mon, 7 Aug 2023 20:31:39 +0800 Subject: [PATCH 07/10] fix bugs in DoublePrecisionHelper --- .../Formula/Functions/FloorCeilingMathBase.cs | 57 ++++--------- .../DoublePrecisionHelper.cs | 26 +++--- .../Functions/TestPrecisionedDecimalHelper.cs | 84 ------------------- .../Functions/TestPrecisionedDecimalHelper.cs | 54 ------------ 4 files changed, 28 insertions(+), 193 deletions(-) rename main/SS/{Formula/Functions => Util}/DoublePrecisionHelper.cs (86%) delete mode 100644 testcases/main/SS/Formula/Functions/TestPrecisionedDecimalHelper.cs delete mode 100644 testcases/ooxml/SS/Formula/Functions/TestPrecisionedDecimalHelper.cs diff --git a/main/SS/Formula/Functions/FloorCeilingMathBase.cs b/main/SS/Formula/Functions/FloorCeilingMathBase.cs index a5a53a086..da1222d57 100644 --- a/main/SS/Formula/Functions/FloorCeilingMathBase.cs +++ b/main/SS/Formula/Functions/FloorCeilingMathBase.cs @@ -18,12 +18,9 @@ */ using ExtendedNumerics; using NPOI.SS.Formula.Eval; +using NPOI.SS.Util; using System; using System.Collections.Generic; -using System.Globalization; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace NPOI.SS.Formula.Functions { @@ -33,7 +30,7 @@ public abstract class FloorCeilingMathBase : FreeRefFunction private const int SignificantDigits = 15; // Use high-precision decimal calculations or customized double workaround private const bool UseHighPrecisionCalculation = true; - private const int SignificantDigitsForHighPrecision = SignificantDigits + 1; + private const int SignificantDigitsForHighPrecision = SignificantDigits + 2; public ValueEval Evaluate(ValueEval[] args, OperationEvaluationContext ec) => args.Length switch @@ -82,6 +79,8 @@ public double Evaluate(double number, double significance, bool mode) significance = -significance; } + double numberToTest = number / significance; + if (UseHighPrecisionCalculation) { BigDecimal.Precision = SignificantDigitsForHighPrecision; @@ -95,51 +94,31 @@ public double Evaluate(double number, double significance, bool mode) if (bigNumberWithPrecisionDropped.IsIntegerWithDigitsDropped(SignificantDigits)) return number; - var numberToTest = number / significance; - - if (number > 0) - { - // mode is meaningless when number is positive - return EvaluateMajorDirection(numberToTest) * significance; - } - else - { - if (mode) - { - // Towards zero for FLOOR && Away from zero for CEILING - return EvaluateAlternativeDirection(-numberToTest) * -significance; - } - else - { - // Vice versa - return EvaluateMajorDirection(-numberToTest) * -significance; - } - } + // High-precision number is only for integer determination. We don't need it later. } else { // Workaround without BigDecimal - double numberToTest = number / significance; if (numberToTest.IsIntegerWithDigitsDropped(SignificantDigits)) return number; + } - if (number > 0) + if (number > 0) + { + // mode is meaningless when number is positive + return EvaluateMajorDirection(numberToTest) * significance; + } + else + { + if (mode) { - // mode is meaningless when number is positive - return EvaluateMajorDirection(numberToTest) * significance; + // Towards zero for FLOOR && Away from zero for CEILING + return EvaluateAlternativeDirection(-numberToTest) * -significance; } else { - if (mode) - { - // Towards zero for FLOOR && Away from zero for CEILING - return EvaluateAlternativeDirection(-numberToTest) * -significance; - } - else - { - // Vice versa - return EvaluateMajorDirection(-numberToTest) * -significance; - } + // Vice versa + return EvaluateMajorDirection(-numberToTest) * -significance; } } } diff --git a/main/SS/Formula/Functions/DoublePrecisionHelper.cs b/main/SS/Util/DoublePrecisionHelper.cs similarity index 86% rename from main/SS/Formula/Functions/DoublePrecisionHelper.cs rename to main/SS/Util/DoublePrecisionHelper.cs index 866d6c3f0..182943765 100644 --- a/main/SS/Formula/Functions/DoublePrecisionHelper.cs +++ b/main/SS/Util/DoublePrecisionHelper.cs @@ -26,7 +26,7 @@ using System.Text; using System.Threading.Tasks; -namespace NPOI.SS.Formula.Functions +namespace NPOI.SS.Util { internal static class DoublePrecisionHelper { @@ -58,13 +58,17 @@ public static bool IsIntegerWithDigitsDropped(this double number, int significan public static bool IsIntegerWithDigitsDropped(this BigDecimal number, int significantDigits) { - const int PADDING_ONE = 1; + if (number.IsZero()) + return true; + + if (number.IsNegative()) + number = -number; int decimalPlaces = number.DecimalPlaces; int realSigDigits = number.SignifigantDigits; int integerPlaces = realSigDigits - decimalPlaces; - if (integerPlaces > significantDigits) + if (integerPlaces >= significantDigits) { return true; } @@ -78,21 +82,11 @@ public static bool IsIntegerWithDigitsDropped(this BigDecimal number, int signif decimalPlaces = Math.Min(decimalPlaces, significantDigits - integerPlaces); - BigDecimal exp = BigDecimal.Pow(10, decimalPlaces + PADDING_ONE); + BigDecimal exp = BigDecimal.Pow(10, decimalPlaces); fracPart *= exp; - fracPart = BigDecimal.Truncate(fracPart); - - if (fracPart.IsZero()) - { - return true; - } - - if (fracPart == exp || (fracPart + BigDecimal.One) == exp) - { - return true; - } + fracPart = BigDecimal.Round(fracPart); - return false; + return fracPart.IsZero() || fracPart == exp; } } } diff --git a/testcases/main/SS/Formula/Functions/TestPrecisionedDecimalHelper.cs b/testcases/main/SS/Formula/Functions/TestPrecisionedDecimalHelper.cs deleted file mode 100644 index 9f994e141..000000000 --- a/testcases/main/SS/Formula/Functions/TestPrecisionedDecimalHelper.cs +++ /dev/null @@ -1,84 +0,0 @@ -/* - * ==================================================================== - * Licensed to the collaborators of the NPOI project under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The collaborators 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. - * ==================================================================== - */ -using NUnit.Framework; -using System; -using System.Collections.Generic; -using System.IO; -using NPOI.SS.UserModel; -using NPOI.SS.Formula.Functions; -using NUnit.Framework.Constraints; -using NPOI.SS.Util; -using ExtendedNumerics; -using MathNet.Numerics; - -namespace TestCases.SS.Formula.Functions -{ - [TestFixture] - public class TestPrecisionedDecimalHelper - { - public const int Precision = 15; - - private static void BigDecimalPrecisionTest(bool expected, string repre) - { - Assert.AreEqual(expected, DoublePrecisionHelper.IsIntegerWithDigitsDropped( - BigDecimal.Parse(repre), Precision), repre); - } - private static void DoublePrecisionTest(bool expected, string repre) - { - Assert.AreEqual(expected, DoublePrecisionHelper.IsIntegerWithDigitsDropped( - double.Parse(repre), Precision), repre); - } - - [Test] - public void IsBigDecimalAlmostInteger() - { - Assert.Multiple(() => - { - BigDecimalPrecisionTest(true, "4.9999999999999999"); - BigDecimalPrecisionTest(true, "4.999999999999999"); - BigDecimalPrecisionTest(false, "4.99999999999999"); - BigDecimalPrecisionTest(false, "4.9999999999999"); - BigDecimalPrecisionTest(false, "12345678901234.5"); - BigDecimalPrecisionTest(false, "12345678901234.9"); - BigDecimalPrecisionTest(true, "12345678901234.99"); - BigDecimalPrecisionTest(true, "123456789012345"); - BigDecimalPrecisionTest(false, "123456789012345.25"); - }); - - } - [Ignore("Currently unused. Bugs to be solved.")] - [Test] - public void IsDoubleAlmostInteger() - { - Assert.Multiple(() => - { - DoublePrecisionTest(true, "4.9999999999999999"); - DoublePrecisionTest(true, "4.999999999999999"); - DoublePrecisionTest(false, "4.99999999999999"); - DoublePrecisionTest(false, "4.9999999999999"); - DoublePrecisionTest(false, "12345678901234.5"); - DoublePrecisionTest(false, "12345678901234.9"); - DoublePrecisionTest(true, "12345678901234.99"); - DoublePrecisionTest(true, "123456789012345"); - DoublePrecisionTest(false, "123456789012345.25"); // TODO: bugged - }); - - } - } -} diff --git a/testcases/ooxml/SS/Formula/Functions/TestPrecisionedDecimalHelper.cs b/testcases/ooxml/SS/Formula/Functions/TestPrecisionedDecimalHelper.cs deleted file mode 100644 index c8c57e264..000000000 --- a/testcases/ooxml/SS/Formula/Functions/TestPrecisionedDecimalHelper.cs +++ /dev/null @@ -1,54 +0,0 @@ -/* - * ==================================================================== - * Licensed to the collaborators of the NPOI project under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The collaborators 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. - * ==================================================================== - */ -using NUnit.Framework; -using System; -using System.Collections.Generic; -using System.IO; -using NPOI.SS.UserModel; -using NPOI.XSSF.UserModel; -using NPOI.SS.Formula.Functions; -using NUnit.Framework.Constraints; -using NPOI.SS.Util; -using ExtendedNumerics; -using MathNet.Numerics; - -namespace TestCases.SS.Formula.Functions -{ - [TestFixture] - public class TestPrecisionedDecimalHelper - { - [Test] - public void IsInteger() - { - Assert.Multiple(() => - { - Assert.AreEqual(IsIntegerWithDigitsDropped(BigDecimal.Parse("4.9999999999999999"), Precision)); - Assert.AreEqual(IsIntegerWithDigitsDropped(BigDecimal.Parse("4.999999999999999"), Precision)); - Assert.AreEqual(IsIntegerWithDigitsDropped(BigDecimal.Parse("4.99999999999999"), Precision)); - Assert.AreEqual(IsIntegerWithDigitsDropped(BigDecimal.Parse("4.9999999999999"), Precision)); - Assert.AreEqual(IsIntegerWithDigitsDropped(BigDecimal.Parse("12345678901234.5"), Precision)); - Assert.AreEqual(IsIntegerWithDigitsDropped(BigDecimal.Parse("12345678901234.9"), Precision)); - Assert.AreEqual(IsIntegerWithDigitsDropped(BigDecimal.Parse("12345678901234.99"), Precision)); - Assert.AreEqual(IsIntegerWithDigitsDropped(BigDecimal.Parse("123456789012345"), Precision)); - Assert.AreEqual(IsIntegerWithDigitsDropped(BigDecimal.Parse("123456789012345.0"), Precision)); - }) - - } - } -} From 33a34a6838cceb1d5deba0fdf5318c0637a4da6e Mon Sep 17 00:00:00 2001 From: Gan Keyu Date: Mon, 7 Aug 2023 21:00:19 +0800 Subject: [PATCH 08/10] fixed bugs due to stale code & errors in test data --- .../Formula/Functions/FloorCeilingMathBase.cs | 3 +- solution/NPOI.Core.Test.sln | 6 +- .../main/Util/TestDoublePrecisionHelper.cs | 92 ++++++++++++++++++ .../Formula/Functions/TestFloorCeilingMath.cs | 8 -- .../test-data/functions/FloorCeilingMath.xlsx | Bin 49694 -> 49242 bytes 5 files changed, 98 insertions(+), 11 deletions(-) create mode 100644 testcases/main/Util/TestDoublePrecisionHelper.cs diff --git a/main/SS/Formula/Functions/FloorCeilingMathBase.cs b/main/SS/Formula/Functions/FloorCeilingMathBase.cs index da1222d57..eb62b882c 100644 --- a/main/SS/Formula/Functions/FloorCeilingMathBase.cs +++ b/main/SS/Formula/Functions/FloorCeilingMathBase.cs @@ -89,9 +89,8 @@ public double Evaluate(double number, double significance, bool mode) var bigSignificance = new BigDecimal(significance); BigDecimal bigNumberToTest = bigNumber / bigSignificance; - BigDecimal bigNumberWithPrecisionDropped = BigDecimal.Round(bigNumberToTest, SignificantDigits); - if (bigNumberWithPrecisionDropped.IsIntegerWithDigitsDropped(SignificantDigits)) + if (bigNumberToTest.IsIntegerWithDigitsDropped(SignificantDigits)) return number; // High-precision number is only for integer determination. We don't need it later. diff --git a/solution/NPOI.Core.Test.sln b/solution/NPOI.Core.Test.sln index f7659e966..9328d5a3b 100644 --- a/solution/NPOI.Core.Test.sln +++ b/solution/NPOI.Core.Test.sln @@ -26,7 +26,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "_build", "..\build\_build.c EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NPOI.Benchmarks", "..\benchmarks\NPOI.Benchmarks\NPOI.Benchmarks.csproj", "{3DA1149D-46F8-4181-9976-E002BF2BFB76}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NPOI.Pack", "NPOI.Pack.csproj", "{6D7A6E15-C914-4FCA-B8E4-FF5C7437C2E0}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NPOI.Pack", "NPOI.Pack.csproj", "{6D7A6E15-C914-4FCA-B8E4-FF5C7437C2E0}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -62,6 +62,10 @@ Global {DA2CA3BD-1CAC-470C-9FA2-611A5768A76A}.Debug|Any CPU.Build.0 = Debug|Any CPU {DA2CA3BD-1CAC-470C-9FA2-611A5768A76A}.Release|Any CPU.ActiveCfg = Release|Any CPU {DA2CA3BD-1CAC-470C-9FA2-611A5768A76A}.Release|Any CPU.Build.0 = Release|Any CPU + {94B18BCF-84E8-401F-BAAB-0496AA136628}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {94B18BCF-84E8-401F-BAAB-0496AA136628}.Debug|Any CPU.Build.0 = Debug|Any CPU + {94B18BCF-84E8-401F-BAAB-0496AA136628}.Release|Any CPU.ActiveCfg = Release|Any CPU + {94B18BCF-84E8-401F-BAAB-0496AA136628}.Release|Any CPU.Build.0 = Release|Any CPU {3DA1149D-46F8-4181-9976-E002BF2BFB76}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {3DA1149D-46F8-4181-9976-E002BF2BFB76}.Debug|Any CPU.Build.0 = Debug|Any CPU {3DA1149D-46F8-4181-9976-E002BF2BFB76}.Release|Any CPU.ActiveCfg = Release|Any CPU diff --git a/testcases/main/Util/TestDoublePrecisionHelper.cs b/testcases/main/Util/TestDoublePrecisionHelper.cs new file mode 100644 index 000000000..4c77a8815 --- /dev/null +++ b/testcases/main/Util/TestDoublePrecisionHelper.cs @@ -0,0 +1,92 @@ +/* + * ==================================================================== + * Licensed to the collaborators of the NPOI project under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The collaborators 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. + * ==================================================================== + */ +using NUnit.Framework; +using System; +using System.Collections.Generic; +using System.IO; +using NPOI.SS.UserModel; +using NUnit.Framework.Constraints; +using NPOI.SS.Util; +using ExtendedNumerics; +using MathNet.Numerics; + +namespace TestCases.SS.Util +{ + [TestFixture] + public class TestDoublePrecisionHelper + { + public const int Precision = 15; + + private static void BigDecimalPrecisionTest(bool expected, string repre) + { + Assert.AreEqual(expected, BigDecimal.Parse(repre).IsIntegerWithDigitsDropped(Precision), repre); + } + private static void BigDecimalPrecisionTest(bool expected, double repre) + { + Assert.AreEqual(expected, new BigDecimal(repre).IsIntegerWithDigitsDropped(Precision), repre.ToString()); + } + private static void DoublePrecisionTest(bool expected, string repre) + { + Assert.AreEqual(expected, double.Parse(repre).IsIntegerWithDigitsDropped(Precision), repre); + } + private static void DoublePrecisionTest(bool expected, double repre) + { + Assert.AreEqual(expected, repre.IsIntegerWithDigitsDropped(Precision), repre.ToString()); + } + + [Test] + public void IsBigDecimalAlmostInteger() + { + Assert.Multiple(() => + { + BigDecimalPrecisionTest(true, "4.9999999999999999"); + BigDecimalPrecisionTest(true, "4.999999999999999"); + BigDecimalPrecisionTest(false, "4.99999999999999"); + BigDecimalPrecisionTest(false, "4.9999999999999"); + BigDecimalPrecisionTest(false, "12345678901234.5"); + BigDecimalPrecisionTest(false, "12345678901234.9"); + BigDecimalPrecisionTest(true, "12345678901234.99"); + BigDecimalPrecisionTest(true, "123456789012345"); + BigDecimalPrecisionTest(true, "123456789012345.25"); + + BigDecimalPrecisionTest(true, 1 / 0.0999999999999996); + }); + } + + [Test] + public void IsDoubleAlmostInteger() + { + Assert.Multiple(() => + { + DoublePrecisionTest(true, "4.9999999999999999"); + DoublePrecisionTest(true, "4.999999999999999"); + DoublePrecisionTest(false, "4.99999999999999"); + DoublePrecisionTest(false, "4.9999999999999"); + DoublePrecisionTest(false, "12345678901234.5"); + DoublePrecisionTest(false, "12345678901234.9"); + DoublePrecisionTest(true, "12345678901234.99"); + DoublePrecisionTest(true, "123456789012345"); + DoublePrecisionTest(true, "123456789012345.25"); + + DoublePrecisionTest(true, 1 / 0.0999999999999996); + }); + + } + } +} diff --git a/testcases/ooxml/SS/Formula/Functions/TestFloorCeilingMath.cs b/testcases/ooxml/SS/Formula/Functions/TestFloorCeilingMath.cs index 235469ecd..332b6044b 100644 --- a/testcases/ooxml/SS/Formula/Functions/TestFloorCeilingMath.cs +++ b/testcases/ooxml/SS/Formula/Functions/TestFloorCeilingMath.cs @@ -112,14 +112,6 @@ public void TestEvaluate(TestFunction function) var expected = row.GetCell(j + StartColumnIndex).NumericCellValue; var functionResult = function.Evaluate(number, significance); - - // Excel also has bugs on =FLOOR.MATH(4, -2, FALSE|TRUE) - // as it recognizes auto-filled 4 as 3.99999999999999. - // See the cell of AF13 in the test data file. - // So the specific pair is skipped. - - if (Math.Abs(number - (4)) < Tolerance && Math.Abs(significance - (-2)) < Tolerance) - continue; Assert.AreEqual(expected, functionResult, Tolerance, $"{function}, {number}, {significance}"); } diff --git a/testcases/test-data/functions/FloorCeilingMath.xlsx b/testcases/test-data/functions/FloorCeilingMath.xlsx index c973ce667aed49ee6c8ea7bb9aefc32416bb7335..e15ac584c197a8ce69965603512ba52c7e8b9dda 100644 GIT binary patch delta 26765 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�dS^Jrz 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 delta 27178 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 z z78;jK>eQKxK^ef#J*G&7)wR;Ld(KCIyO|Z_F7P*z1 zc5I}>1~a+xKccC1vthU}i}hAcCNEXz<%^DNK=CKK_mdW1+E}KEHK|iA$TQVY5~vrI z=OdOw6D$?eoQ!Zp{MQGw<^95+-QCI>3DHBXhM1;`>mg%!*WDwfo~VhudUr5W)IVD5 z@WUqHb!^sL%uu%x+|GCfrB9-rs)h(9N3j_#dK-p*8JfWBecGxe!F$v0CuTAQOc0aY zxP6Lv^enFAc~!v@mLZW}wL0b7ynA!LXcGNBv|M8x3%XHWLE$aMak{tic~!nvQS*)f zJav~D(TOZ&>d++Ae_tO26>E4vJOX{yz{&RaWi{XpLv*0F{PQ6|5e=8w{(ZT|KW~YM z-awBU1lj+-B!WOh1RfLk?Q{Axbc^-^)V=8}``><(D2V?fQ4=qih#8vO$jtuRNrV4L zboS0kqGd4AZwovBk?6nlI>Cj2VNvyhWF zp>Ss5|Fp7yDvl2MA2tVdZeoI_!Y`0~4TaXgFSDPhHF#%<4hazv{XhQ;#$;=u+i;ny o6`zQRtevd1U7eiWge{z%e;KW|8ac(Uv@GDCJs5l2`0LyM0bV)6djJ3c From 49d82fb693f4df5234f2025b0e08805faa6b71b1 Mon Sep 17 00:00:00 2001 From: Gan Keyu Date: Mon, 7 Aug 2023 21:03:09 +0800 Subject: [PATCH 09/10] fixed mistaken commit --- solution/NPOI.Core.Test.sln | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/solution/NPOI.Core.Test.sln b/solution/NPOI.Core.Test.sln index 9328d5a3b..f7659e966 100644 --- a/solution/NPOI.Core.Test.sln +++ b/solution/NPOI.Core.Test.sln @@ -26,7 +26,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "_build", "..\build\_build.c EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NPOI.Benchmarks", "..\benchmarks\NPOI.Benchmarks\NPOI.Benchmarks.csproj", "{3DA1149D-46F8-4181-9976-E002BF2BFB76}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NPOI.Pack", "NPOI.Pack.csproj", "{6D7A6E15-C914-4FCA-B8E4-FF5C7437C2E0}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NPOI.Pack", "NPOI.Pack.csproj", "{6D7A6E15-C914-4FCA-B8E4-FF5C7437C2E0}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -62,10 +62,6 @@ Global {DA2CA3BD-1CAC-470C-9FA2-611A5768A76A}.Debug|Any CPU.Build.0 = Debug|Any CPU {DA2CA3BD-1CAC-470C-9FA2-611A5768A76A}.Release|Any CPU.ActiveCfg = Release|Any CPU {DA2CA3BD-1CAC-470C-9FA2-611A5768A76A}.Release|Any CPU.Build.0 = Release|Any CPU - {94B18BCF-84E8-401F-BAAB-0496AA136628}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {94B18BCF-84E8-401F-BAAB-0496AA136628}.Debug|Any CPU.Build.0 = Debug|Any CPU - {94B18BCF-84E8-401F-BAAB-0496AA136628}.Release|Any CPU.ActiveCfg = Release|Any CPU - {94B18BCF-84E8-401F-BAAB-0496AA136628}.Release|Any CPU.Build.0 = Release|Any CPU {3DA1149D-46F8-4181-9976-E002BF2BFB76}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {3DA1149D-46F8-4181-9976-E002BF2BFB76}.Debug|Any CPU.Build.0 = Debug|Any CPU {3DA1149D-46F8-4181-9976-E002BF2BFB76}.Release|Any CPU.ActiveCfg = Release|Any CPU From f650873509569360ad91ed47f04b775419b1a726 Mon Sep 17 00:00:00 2001 From: Gan Keyu Date: Sun, 20 Aug 2023 23:26:57 +0800 Subject: [PATCH 10/10] update dependency version --- main/NPOI.Core.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main/NPOI.Core.csproj b/main/NPOI.Core.csproj index 1de3b45e6..62ecf7a79 100644 --- a/main/NPOI.Core.csproj +++ b/main/NPOI.Core.csproj @@ -15,7 +15,7 @@ - +