From afd936840c5bd485dcd9e0924a866ba51bfb81a0 Mon Sep 17 00:00:00 2001 From: Ken Smith Date: Thu, 25 Apr 2024 14:56:28 -0700 Subject: [PATCH 1/5] First pass at implementing Forecast function --- build/_build.csproj.DotSettings | 5 +- main/SS/Formula/Eval/FunctionEval.cs | 2 +- main/SS/Formula/Functions/Forecast.cs | 134 ++++++++++++++++++++++++++ 3 files changed, 139 insertions(+), 2 deletions(-) create mode 100644 main/SS/Formula/Functions/Forecast.cs diff --git a/build/_build.csproj.DotSettings b/build/_build.csproj.DotSettings index 7bc28484c..337271da9 100644 --- a/build/_build.csproj.DotSettings +++ b/build/_build.csproj.DotSettings @@ -16,6 +16,8 @@ False <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + <Policy><Descriptor Staticness="Instance" AccessRightKinds="Private" Description="Instance fields (private)"><ElementKinds><Kind Name="FIELD" /><Kind Name="READONLY_FIELD" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /></Policy> + <Policy><Descriptor Staticness="Static" AccessRightKinds="Private" Description="Static fields (private)"><ElementKinds><Kind Name="FIELD" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /></Policy> True True True @@ -24,4 +26,5 @@ True True True - True + True + True diff --git a/main/SS/Formula/Eval/FunctionEval.cs b/main/SS/Formula/Eval/FunctionEval.cs index af41201be..5cf2e30b0 100644 --- a/main/SS/Formula/Eval/FunctionEval.cs +++ b/main/SS/Formula/Eval/FunctionEval.cs @@ -391,7 +391,7 @@ private static Function[] ProduceFunctions() retval[306] = new NotImplementedFunction("CHITEST"); // CHITEST retval[307] = new NotImplementedFunction("CORREL"); // CORREL retval[308] = new NotImplementedFunction("COVAR"); // COVAR - retval[309] = new NotImplementedFunction("FORECAST"); // FORECAST + retval[309] = new Forecast(); // FORECAST retval[310] = new NotImplementedFunction("FTEST"); // FTEST retval[311] = new Intercept(); // INTERCEPT retval[312] = new NotImplementedFunction("PEARSON"); // PEARSON diff --git a/main/SS/Formula/Functions/Forecast.cs b/main/SS/Formula/Functions/Forecast.cs new file mode 100644 index 000000000..faea53cf0 --- /dev/null +++ b/main/SS/Formula/Functions/Forecast.cs @@ -0,0 +1,134 @@ +using System; +using NPOI.SS.Formula.Eval; + +namespace NPOI.SS.Formula.Functions +{ + /// + /// The Forecast class is a representation of the Excel FORECAST function. + /// This function predicts a future value along a linear trend line based on existing historical data. + /// The class inherits from the Fixed3ArgFunction class and overrides the Evaluate method. + /// The Evaluate method takes three arguments: the x-value for which we want to forecast a y-value, + /// and two arrays of x-values and y-values representing historical data. + /// The method calculates the slope and intercept of the line of best fit for the historical data + /// and uses these to calculate the forecast y-value. + /// The class also includes methods for converting ValueEval objects to numeric arrays and for creating ValueVectors. + /// + public class Forecast : Fixed3ArgFunction + { + public override ValueEval Evaluate(int srcRowIndex, int srcColumnIndex, ValueEval arg0, ValueEval arg1, ValueEval arg2) + { + try + { + double x = NumericFunction.SingleOperandEvaluate(arg0, srcRowIndex, srcColumnIndex); + double[] yValues = GetNumericArray(arg1); + double[] xValues = GetNumericArray(arg2); + + if(yValues.Length != xValues.Length) + { + return ErrorEval.NA; + } + + double xSum = 0, ySum = 0, xySum = 0, xSquareSum = 0; + int n = xValues.Length; + + for(int i = 0; i < n; i++) + { + xSum += xValues[i]; + ySum += yValues[i]; + xySum += xValues[i] * yValues[i]; + xSquareSum += Math.Pow(xValues[i], 2); + } + + double slope = (n * xySum - xSum * ySum) / (n * xSquareSum - Math.Pow(xSum, 2)); + double intercept = (ySum - slope * xSum) / n; + + double forecastY = slope * x + intercept; + + return new NumberEval(forecastY); + } + catch(EvaluationException e) + { + return e.GetErrorEval(); + } + } + + private static double[] GetNumericArray(ValueEval arg) + { + ValueVector vv = CreateValueVector(arg); + double[] result = new double[vv.Size]; + for(int i = 0; i < vv.Size; i++) + { + ValueEval v = vv.GetItem(i); + if(v is ErrorEval errorEval) + { + throw new EvaluationException(errorEval); + } + + if(v is NumberEval numberEval) + { + result[i] = numberEval.NumberValue; + } + } + + return result; + } + + private static ValueVector CreateValueVector(ValueEval arg) + { + return arg switch { + ErrorEval eval => throw new EvaluationException(eval), + TwoDEval dEval => new AreaValueArray(dEval), + RefEval refEval => new RefValueArray(refEval), + _ => new SingleCellValueArray(arg) + }; + } + + private abstract class ValueArray(int size) : ValueVector + { + public ValueEval GetItem(int index) + { + if(index < 0 || index > size) + { + throw new ArgumentException($"Specified index {index} is outside range (0..{(size - 1)})"); + } + + return GetItemInternal(index); + } + + protected abstract ValueEval GetItemInternal(int index); + + public int Size => size; + } + + private class SingleCellValueArray(ValueEval value) : ValueArray(1) + { + protected override ValueEval GetItemInternal(int index) + { + return value; + } + } + + private class RefValueArray(RefEval ref1) : ValueArray(ref1.NumberOfSheets) + { + private readonly int _width = ref1.NumberOfSheets; + + protected override ValueEval GetItemInternal(int index) + { + int sIx = (index % _width) + ref1.FirstSheetIndex; + return ref1.GetInnerValueEval(sIx); + } + } + + private class AreaValueArray(TwoDEval ae) : ValueArray(ae.Width * ae.Height) + { + private readonly int _width = ae.Width; + + protected override ValueEval GetItemInternal(int index) + { + int rowIx = index / _width; + int colIx = index % _width; + return ae.GetValue(rowIx, colIx); + } + } + } +} \ No newline at end of file From 4710cc902ee8cc2349acdd645cea6c5021b3ae63 Mon Sep 17 00:00:00 2001 From: Ken Smith Date: Fri, 26 Apr 2024 19:03:50 -0700 Subject: [PATCH 2/5] Add TestForecast class, mostly not working yet. --- .../main/SS/Formula/Functions/TestForecast.cs | 174 ++++++++++++++++++ testcases/test-data/spreadsheet/Forecast.xls | Bin 0 -> 23552 bytes 2 files changed, 174 insertions(+) create mode 100644 testcases/main/SS/Formula/Functions/TestForecast.cs create mode 100644 testcases/test-data/spreadsheet/Forecast.xls diff --git a/testcases/main/SS/Formula/Functions/TestForecast.cs b/testcases/main/SS/Formula/Functions/TestForecast.cs new file mode 100644 index 000000000..41c4a6e5e --- /dev/null +++ b/testcases/main/SS/Formula/Functions/TestForecast.cs @@ -0,0 +1,174 @@ +/* + * ==================================================================== + * 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. + * ==================================================================== + */ + +namespace TestCases.SS.Formula.Functions +{ + using NUnit.Framework; + using System; + using HSSF; + using NPOI.SS.Formula.Eval; + using NPOI.HSSF.UserModel; + using NPOI.SS.UserModel; + using NPOI.SS.Formula.Functions; + + /** + * Test for Excel function INTERCEPT() + * + * @author Ken Smith + */ + [TestFixture] + public class TestForecast + { + private static readonly Function FORECAST = new Forecast(); + + private static ValueEval Invoke(Function function, ValueEval x, ValueEval xArray, ValueEval yArray) + { + ValueEval[] args = [x, xArray, yArray]; + return function.Evaluate(args, -1, (short) -1); + } + + private void Confirm(Function function, ValueEval x, ValueEval xArray, ValueEval yArray, double expected) + { + ValueEval result = Invoke(function, x, xArray, yArray); + Assert.AreEqual(typeof(NumberEval), result.GetType()); + Assert.AreEqual(expected, ((NumberEval) result).NumberValue, 0); + } + + private void ConfirmError(Function function, ValueEval x, ValueEval xArray, ValueEval yArray, + ErrorEval expectedError) + { + ValueEval result = Invoke(function, x, xArray, yArray); + Assert.AreEqual(typeof(ErrorEval), result.GetType()); + Assert.AreEqual(expectedError.ErrorCode, ((ErrorEval) result).ErrorCode); + } + + private void ConfirmError(ValueEval x, ValueEval xArray, ValueEval yArray, ErrorEval expectedError) + { + ConfirmError(FORECAST, x, xArray, yArray, expectedError); + } + + [Test] + public void TestBasic() + { + double exp = Math.Pow(10, 7.5); + ValueEval x = new NumberEval(100); + ValueEval[] yValues = [ + new NumberEval(3 + exp), + new NumberEval(4 + exp), + new NumberEval(2 + exp), + new NumberEval(5 + exp), + new NumberEval(4 + exp), + new NumberEval(7 + exp) + ]; + ValueEval areaEvalY = CreateAreaEval(yValues); + + ValueEval[] xValues = [ + new NumberEval(1), + new NumberEval(2), + new NumberEval(3), + new NumberEval(4), + new NumberEval(5), + new NumberEval(6) + ]; + ValueEval areaEvalX = CreateAreaEval(xValues); + Confirm(FORECAST, x, areaEvalY, areaEvalX, 31622780.0302553); + // Excel 365 build 2402 gives 31622780.0302553 + } + + /** + * number of items in array is not limited to 30 + */ + [Test] + public void TestLargeArrays() + { + ValueEval x = new NumberEval(100); + ValueEval[] yValues = CreateMockNumberArray(100, 3); // [1,2,0,1,2,0,...,0,1] + yValues[0] = new NumberEval(2.0); // Changes first element to 2 + ValueEval[] xValues = CreateMockNumberArray(100, 101); // [1,2,3,4,...,99,100] + + Confirm(FORECAST, x, CreateAreaEval(xValues), CreateAreaEval(yValues), 51.74384236453202); + // Excel 2010 gives 51.74384236453200 + } + + private ValueEval[] CreateMockNumberArray(int size, double value) + { + ValueEval[] result = new ValueEval[size]; + for(int i = 0; i < result.Length; i++) + { + result[i] = new NumberEval((i + 1) % value); + } + + return result; + } + + private static ValueEval CreateAreaEval(ValueEval[] values) + { + string refStr = "A1:A" + values.Length; + return EvalFactory.CreateAreaEval(refStr, values); + } + + [Test] + public void TestErrors() + { + var x = new NumberEval(100); + ValueEval[] xValues = [ErrorEval.REF_INVALID, new NumberEval(2)]; + ValueEval areaEvalX = CreateAreaEval(xValues); + ValueEval[] yValues = [new NumberEval(2), ErrorEval.NULL_INTERSECTION]; + ValueEval areaEvalY = CreateAreaEval(yValues); + ValueEval[] zValues = [ + // wrong size + new NumberEval(2) + ]; + ValueEval areaEvalZ = CreateAreaEval(zValues); + + // if either arg is an error, that error propagates + ConfirmError(x, ErrorEval.REF_INVALID, ErrorEval.NAME_INVALID, ErrorEval.REF_INVALID); + ConfirmError(x, areaEvalX, ErrorEval.NAME_INVALID, ErrorEval.NAME_INVALID); + ConfirmError(x, ErrorEval.NAME_INVALID, areaEvalX, ErrorEval.NAME_INVALID); + + // array sizes must match + ConfirmError(x, areaEvalX, areaEvalZ, ErrorEval.NA); + ConfirmError(x, areaEvalZ, areaEvalY, ErrorEval.NA); + + // any error in an array item propagates up + ConfirmError(x, areaEvalX, areaEvalX, ErrorEval.REF_INVALID); + + // search for errors array by array, not pair by pair + ConfirmError(x, areaEvalX, areaEvalY, ErrorEval.NULL_INTERSECTION); + ConfirmError(x, areaEvalY, areaEvalX, ErrorEval.REF_INVALID); + } + + /** + * Example from + * https://support.microsoft.com/en-us/office/forecast-and-forecast-linear-functions-50ca49c9-7b40-4892-94e4-7ad38bbeda99 + */ + [Test] + public void TestFromFile() + { + IWorkbook wb = HSSFTestDataSamples.OpenSampleWorkbook("Forecast.xls"); + HSSFFormulaEvaluator fe = new(wb); + + ISheet example1 = wb.GetSheet("Example 1"); + ICell a8 = example1.GetRow(7).GetCell(0); + Assert.AreEqual("FORECAST(30,A2:A6,B2:B6)", a8.CellFormula); + fe.Evaluate(a8); + Assert.AreEqual(10.60725309, a8.NumericCellValue, 0.00000001); + } + } +} \ No newline at end of file diff --git a/testcases/test-data/spreadsheet/Forecast.xls b/testcases/test-data/spreadsheet/Forecast.xls new file mode 100644 index 0000000000000000000000000000000000000000..04996b5cc8ff6bd9a87c2490573b037da5be48f0 GIT binary patch literal 23552 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 literal 0 HcmV?d00001 From 1ff7bb67d7c4cd986ed0947f014c72c6bab10df4 Mon Sep 17 00:00:00 2001 From: Ken Smith Date: Tue, 30 Apr 2024 17:21:30 -0700 Subject: [PATCH 3/5] Code cleaning, fix some tests --- .../main/SS/Formula/Functions/TestForecast.cs | 127 +++++++++++------- testcases/test-data/spreadsheet/Forecast.xls | Bin 23552 -> 32256 bytes 2 files changed, 75 insertions(+), 52 deletions(-) diff --git a/testcases/main/SS/Formula/Functions/TestForecast.cs b/testcases/main/SS/Formula/Functions/TestForecast.cs index 41c4a6e5e..c1e33caee 100644 --- a/testcases/main/SS/Formula/Functions/TestForecast.cs +++ b/testcases/main/SS/Formula/Functions/TestForecast.cs @@ -37,34 +37,39 @@ public class TestForecast { private static readonly Function FORECAST = new Forecast(); - private static ValueEval Invoke(Function function, ValueEval x, ValueEval xArray, ValueEval yArray) - { - ValueEval[] args = [x, xArray, yArray]; - return function.Evaluate(args, -1, (short) -1); - } - - private void Confirm(Function function, ValueEval x, ValueEval xArray, ValueEval yArray, double expected) - { - ValueEval result = Invoke(function, x, xArray, yArray); - Assert.AreEqual(typeof(NumberEval), result.GetType()); - Assert.AreEqual(expected, ((NumberEval) result).NumberValue, 0); - } - - private void ConfirmError(Function function, ValueEval x, ValueEval xArray, ValueEval yArray, - ErrorEval expectedError) + /// + /// This test is replicated in the "TestBasic" tab of the "Forecast.xls" file. + /// + [Test] + public void TestBasic() { - ValueEval result = Invoke(function, x, xArray, yArray); - Assert.AreEqual(typeof(ErrorEval), result.GetType()); - Assert.AreEqual(expectedError.ErrorCode, ((ErrorEval) result).ErrorCode); - } + ValueEval x = new NumberEval(100); + ValueEval[] yValues = [ + new NumberEval(1), + new NumberEval(2), + new NumberEval(3), + new NumberEval(4), + new NumberEval(5), + new NumberEval(6) + ]; - private void ConfirmError(ValueEval x, ValueEval xArray, ValueEval yArray, ErrorEval expectedError) - { - ConfirmError(FORECAST, x, xArray, yArray, expectedError); + ValueEval[] xValues = [ + new NumberEval(2), + new NumberEval(4), + new NumberEval(6), + new NumberEval(8), + new NumberEval(10), + new NumberEval(12) + ]; + Confirm(x, CreateAreaEval(yValues), CreateAreaEval(xValues), 50.0); + // Excel 365 build 2402 gives 50.0 } - + + /// + /// This test is replicated in the "TestLargeNumbers" tab of the "Forecast.xls" file. + /// [Test] - public void TestBasic() + public void TestLargeNumbers() { double exp = Math.Pow(10, 7.5); ValueEval x = new NumberEval(100); @@ -76,7 +81,6 @@ public void TestBasic() new NumberEval(4 + exp), new NumberEval(7 + exp) ]; - ValueEval areaEvalY = CreateAreaEval(yValues); ValueEval[] xValues = [ new NumberEval(1), @@ -86,41 +90,23 @@ public void TestBasic() new NumberEval(5), new NumberEval(6) ]; - ValueEval areaEvalX = CreateAreaEval(xValues); - Confirm(FORECAST, x, areaEvalY, areaEvalX, 31622780.0302553); - // Excel 365 build 2402 gives 31622780.0302553 + Confirm(x, CreateAreaEval(yValues), CreateAreaEval(xValues), 31622844.1826363); + // Excel 365 build 2402 gives 31622844.1826363 } - /** - * number of items in array is not limited to 30 - */ + /// + /// This test is replicated in the "TestLargeArrays" tab of the "Forecast.xls" file. + /// [Test] public void TestLargeArrays() { ValueEval x = new NumberEval(100); - ValueEval[] yValues = CreateMockNumberArray(100, 3); // [1,2,0,1,2,0,...,0,1] + ValueEval[] yValues = CreateMockNumberArray(100, 3); // [2,2,0,1,2,0,...,0,1] yValues[0] = new NumberEval(2.0); // Changes first element to 2 ValueEval[] xValues = CreateMockNumberArray(100, 101); // [1,2,3,4,...,99,100] - Confirm(FORECAST, x, CreateAreaEval(xValues), CreateAreaEval(yValues), 51.74384236453202); - // Excel 2010 gives 51.74384236453200 - } - - private ValueEval[] CreateMockNumberArray(int size, double value) - { - ValueEval[] result = new ValueEval[size]; - for(int i = 0; i < result.Length; i++) - { - result[i] = new NumberEval((i + 1) % value); - } - - return result; - } - - private static ValueEval CreateAreaEval(ValueEval[] values) - { - string refStr = "A1:A" + values.Length; - return EvalFactory.CreateAreaEval(refStr, values); + Confirm(x, CreateAreaEval(yValues), CreateAreaEval(xValues), 0.960990099); + // Excel 365 build 2402 gives 0.98039604 } [Test] @@ -164,11 +150,48 @@ public void TestFromFile() IWorkbook wb = HSSFTestDataSamples.OpenSampleWorkbook("Forecast.xls"); HSSFFormulaEvaluator fe = new(wb); - ISheet example1 = wb.GetSheet("Example 1"); + ISheet example1 = wb.GetSheet("TestFromFile"); ICell a8 = example1.GetRow(7).GetCell(0); Assert.AreEqual("FORECAST(30,A2:A6,B2:B6)", a8.CellFormula); fe.Evaluate(a8); Assert.AreEqual(10.60725309, a8.NumericCellValue, 0.00000001); } + + private static ValueEval Invoke(ValueEval x, ValueEval yArray, ValueEval xArray) + { + ValueEval[] args = [x, yArray, xArray]; + return FORECAST.Evaluate(args, -1, (short) -1); + } + + private static void Confirm(ValueEval x, ValueEval yArray, ValueEval xArray, double expected) + { + ValueEval result = Invoke(x, yArray, xArray); + Assert.AreEqual(typeof(NumberEval), result.GetType()); + Assert.AreEqual(expected, ((NumberEval) result).NumberValue, expected * .000000001); + } + + private static void ConfirmError(ValueEval x, ValueEval yArray, ValueEval xArray, ErrorEval expectedError) + { + ValueEval result = Invoke(x, yArray, xArray); + Assert.AreEqual(typeof(ErrorEval), result.GetType()); + Assert.AreEqual(expectedError.ErrorCode, ((ErrorEval) result).ErrorCode); + } + + private static ValueEval[] CreateMockNumberArray(int size, double value) + { + ValueEval[] result = new ValueEval[size]; + for(int i = 0; i < result.Length; i++) + { + result[i] = new NumberEval((i + 1) % value); + } + + return result; + } + + private static ValueEval CreateAreaEval(ValueEval[] values) + { + string refStr = "A1:A" + values.Length; + return EvalFactory.CreateAreaEval(refStr, values); + } } } \ No newline at end of file diff --git a/testcases/test-data/spreadsheet/Forecast.xls b/testcases/test-data/spreadsheet/Forecast.xls index 04996b5cc8ff6bd9a87c2490573b037da5be48f0..cddc1f6e390f81c7231d9bff298d6b54b271a602 100644 GIT binary patch literal 32256 zcmeHQ3vgUldH(NeC9V9F{E!{Tk=OD|ekqD%I}T2)m1R3Iksom!><|Y>mRGiFMe;~; zuxBt{DCrDM3c*g3lDLHwQkoDFAhdxgGz!xam=SHKg-MfoAaqiO*HB2GFbUi5JLjJJ zID2-z3ZVncUd_3C?|0An{&Uay&VL^F?&=@@zTxFZKC|FEVr`qHN`8^AlbQ(b;rKdp zUN5+w&-2uLUW;QumF+jE1nN?#$eL<-`|hvTeoYcmlM?w3K0lp&4ttS*MK~xYc%1(K z+_tDZ^m@$0ID~hdmy&9!g&tFK0oNMjGL^qsYCl`;pHi{EsP@;%EV=)!`Lp@6$M$Z7 zmPI@{kjSWbf1viwYCi*ePo7a}UQ)MzH^Ni&1yU zoX)vK3{Td{7g^Lr@79yLOT-EJ!$rDoTbtwf?&057t znWoaS0l0OZ6s;b23|ffhGHRhIxe#r3o=Ef3<|S*EENMHma?`;Ub8>KHQ*trd^TOEm z+xBLU+#ZkAjAnmD7^$@_&VzGOt-x(I+p#P*KkC)8O4>yZF)ORmCTITX!4?G^T)D{y zn%6cX--lWLwGBpYTYmg@(u}CyppDUjd^gGSBW=ESh`TJeN%}vP#@}$hg zg?%F@v$*1aZ0{W$&Eo1Dc}iyD>h2+2)4TqD;J3@CQOe$JvQ(Z(uSMP7jy@|bO8_px zUz@Zdw8&AZ+DlL`e_aIs{UZ1mir_jwh5R}{h4^16p4x)^Xq~{x>3rVb(D2~Y z2d7H%0Tmy<&d2-vpI!9qB1t}@@M`#CbzlFB2x>F>Tv-HPQv_e-#Nkmj{+~X*X#V*{ zaJX9?KPYFdbGTfMe?#`*a1ln)D*w~tIFf68Mu?|z2rp~lR({Z{JM)Qz8V|}-w-w3}kRgK=sRWGwmd@KKR4E_xa z?PYUNKInX$_tgko`6UX5o9~x6WL9`m!y*2d6Tb}6Y@T~Rd12z{493z*i#EtsnN%D+cNX_H`GG~#!Z2BP&(u@NlocRI=; z$#<3}Syv8;-9OnvSPzm=IR&>RUezw4$5VQRWG4gUV{v?VOI=M}V8Lcz;qo~MDrd*!)SLfueMW#Zj7L-(>RQV`A zOkiuTotAL9+O4iREs$?Pz;99Zq3RI)j{0Zm5iSd7eqNSUJfcn&-?(A$eCo z&diljE?X{@z|Ku~(cGL*%))aDOnlqhmZjIGyZZXFLt|H+OYM|%9A)^Mmu$*U$5V$k zSte;&i_j%~_{++WjPc10iq*qIklJLn|KfLlny==ux)I4Qn{g;@bqO<3rA_oqjKAbIv%ogvP}-Fx%tVzo(euo=zUMY`xoyUww2l&HqDq_S zdF`8@bDLR6GaE`PEe$gp%1~N+#~(iZg4@g$wi$=gmXioWP8XoBf zq)?Piy4WI(stAvr_Uv?%E)7YcADeWUMHHf++h7HL!uA~!zq6*uXMkQ7R=Nw2a@vV2*ku=o+gzZp4BKM@Ky7s)}FZ^>_&b6X5RN!M>A&y<-EzL+4WOi4!Io^HW8? zrq?GY@p@A)hF$|Qj3F4u`W#7+ltX%BfGaM_K#kz(87^`@<{@(QEEPMei=CZM%*2~S zm?L%{%k~{lcV`C&&!x^<`I?uXf_xprV6ab)V`$ioqe1*dbH&vZH7JY<-Q z`po1?pP5|gv-63htj<;C&!y7wtW;OQNH>b)PW<)4lnCRl$<-ikixsy9@BW5y(^%zi z!y7Xu&DvZNX_i}QlKB+UoWvVuBWmp1C#QoHt+`sHXth$*=BE{;h})VgSMp7vZKqBQ z_73%q4Ue2o@5`PVJD0jXPL)`nua|9d3a_fN?uS(U4Q=S!ycS=qk4 zlG2u|L+)<0DC_dGWIM`>)io^d1j!MQ-2loHI358y07J(E+FEEdCpkk~hoL`~IIbTa zhW=!)PUscqU3O` zHu)?ExpuEb$n}ULp~%-q$>CxxKT|c^K~6WTg-{zU7B+?rxfFz8kt9-zq-eFPRidDK znj%;8t*{+KCr(19C*$>=n1e#uf$BMdc0xghu8Au|rslbf^h>&?dc0f;J@yO?9nT)| zeUmNl`S6)NzKFHBL+?}-w3NV48qdgOqZ*X{G`R-(mtEPDV8n4DQJvnL#Ts}c)@uBle^GdMwIg+m0yJ(4$*V?$?Nd4C8WJ; zcr1G^bvVwEn1V(}jxl6PaDS#(;Odicw_G_2I{|%W+^s${?pB|jPfW#2g?Re0`{bCa z@poEA=Yo2-Ducp0k9J4&-my9-5avXmnVje|lM{VL{a%}^M%gWmXQ8?p6|qOTEVC%V zB0Ha$g0FKxs{01U22om9#Zy(yMD^^0I>u0OEU&^Q>7w3tZQJ zQi&?I_Q7&D&Pd;_IitiH@DlYjk^Q|RL-c#v^>@ZInwW}SWIrs7pj{c)=W_WRV5k!Q zKeAhLjSAMUOJfBQgkSl#W-$nt4*v=jGEk9rciYey*#$X!Z7?ikm|9eht(?#5c$ zO>|<&{l4tz*!Gd(JGT!EX77fTN4R7Rw?%wQ@903^-7;Tp;_@GQI)XjDBS*8lPTqMq zJ2HAV-k)B<6)n7y6Fl53=e-#dPyE7 z=iv=slV-jwmwT#YF7xhhhfWRl54CRJy?0x8*UkG{dv@&F*0p!x70=;|7|$Wu`X9`Y z*W{zPAFjI6k;(SqkvlOy!_kdH!|xnQpAL>r%@Ns}9qk(#IKk0dx@B*6^yJ{!N|b~# z@RszRk?fIyzOm7CZ~Cs@!IRl^|L{oqbo%JPUD=^@?@;=b4-faJJKEs5F(yDh4j3c1 zj+&zbqquWqCfnCLI+n*5K~k~?`sKUc9eu3nnsq0cla5NKN#o?VFNU%HT z!U*Bb+l-?5$+F}pCy%y;M=&+5L%Fj>c!+r*aUjv5wtwn0pPpR6AooI8&^C!&tJ*l> zTfmW$GfHJ5k1xn5>pUm_Elb05D6jH- zp+c|qQRbCsvqC$4l({9^s?cs9WqygSSLjxtw9OoMBj}ve2y;T#8oct)oOjFLHfLY{ z{)S~4U}<@6zBDyS)?=47F5~LJ8~4V$)_Y{*V60pDffwMve-F2EFTS4A82l}iFMHE4 zs;Y&JI$TZ4W~|)*CG6tb8s7uoa8FD%KiF^uRA0rcQIb_ElOL9ZEUY`4nkPpi zkW&=&{G}$0^z`zfsTJ z_?hrOSwGkBxH;yw=uf;~nLqhJ-Atu*<4+#1*XQE<52`@tFZ3U2o%NXih+~vvLgJYA z6>&`aia4fyMI6(rw`zZN& zK1an}>Z80@otFC+pu7v-t-cH1<4jIsQNNtA@$U4tBcpv*vus78@yjyeWs{5v&lQ~b z-s=UtQUBkb;LCVZ;IX1Us}TsjmVSkV{CM5Hz-!TBq1Q^_dw1|!Ou}OKIHC031b?*y zPoh7B;;8s5D)YC|Um5>o{MGZbL;uCj&-k;r|6pxFSuNuA96n<>(Vg+UQ5Fk;S;kRdluG-j7Cw=KYJQvK6f3*d@l3Z`4oL;2T3M zNtL%R=)8dy>tIb)ieIYebUC4<)BQ;&6Y!=~g0JZWAYg)IBmvi4m_Ra2z&cO~9@7a> zu1t`KB;b&a2~sKnD@Y}HQ73>!Ca8`i;24ex>Qw^PUNlRvXlIF&U?!NO60mAjf}d#% zu*d|p5es#G0`vx#^yG;-Kl<`#l%49Y=4>tEXX|_65tCBC8>}M|Oy;*e&wZK6v;26%VgxxW7#vo*vuO zIK7+p*hHe9j$ZsX%{w#C94FcLfmbC8FwWZTcyuB*#<_gL>dClbW@ED)*lY*JwH-Sx z*LrM>>sdB7&w=raF?KwD&&9_0wG|t?+=1~+C3ZZ1N5saO92mPhJ6_tt-gtk;AAzZY zqrdp=mv;?~|G3jNwmvG6vGq}OqH1LzI%kkf7jpEIPS>2l%p};WF_zw^GCD3jwNA^u znO~SWgXW#%$7LrJGMHHh#||OGLO(8h0~I&Jy_t(p$00+rAD2oF8O#iYS`HbO`*Eq} zkb(0SmIL)1GOY4ZDmr9XMx4y?_A@oBnUet4WvfN@FKj>n}U8^c3@0t}0X1sEO_ z6kvS8-{!+2U;)P0@9lV-92lQI+41!IFv!!hFw8Lu;=z>`U>OUe@1XvuYQ5j= zw*xAWs9udRYrxc?iksm+pwwf) zAl-gkDl%Zm8I+m~7-Xv-m#PdHat5U?0|wdV$E7j@hMYmE&459+`*EqxfFWm4>N8-F z>;1S?Xuyy&C^Z@|$PIp6sx)B88I(E=7-WYZmr4y7at5VV0|t4kAD3zk7;*-sUIPZX z(T_{T1`IiaQnLYr^!Rb9+JGTvQ0g{dkez;9DmP%r8I;-$7-W|pHxp&Z5q$ykzYM(a zV4^&KTPzm+FzqEQKfJ|(bvrOju?o^|b70#Y*!2$V1_y>IT!D?ZIA-e5 zu-y)9j|02Of$eo*H#@L>4(t{O_BIE$-+>))V7EH3w>z-g9N0kzcDn<6hXXt0!0xax z`eEuXXbH00*E|&%w1k{NsX?OJJMZ!1QiTCS#-P+;$Y4+^F<=-sD76?c$W1;k)fg~j z3`#u)3^{{RkpaWFL8-}rLH7E*RAs=BF(`EzFystMWd;o62BkIw2D#bir8)zKj6tc- zfFWm4Dl}jiHz+k4FvvcimnsbyG6tni1BRSIsnmdB+@REIz#zByyi{wzkTEFr8ZhJx zO2q~Y;|8T>0|t4U&r8(?3>kw`w*f=Wpj2+aFm6z4H(-$cKJOg{<+TjaH`5RIf<0mR z;lmEB&w(9rU|9#&@4${auwxEvz=8da13T`(1|8U)4s6JQ4OWJl~k623j zh-IUXSR(p}<)Dvv_xgzU+y!+tOJf4h`Mh}lJq7Fb;h%n*#?^%Bc%VP5pQhD-Kr-B6 zn~6~PG;NB0npTHJB8;c8Xp5udC7z~{?N_?}BG1=(c7d+<`C+;S|Djg!duLMz{}wmR>=kCVVn2H~Y9A z*v^5zk>Szd{;~A7Q+?S%U9{uxn!e=4uXq_;&prK}MYlivl2?V}%2p|XN(od-pi%;r z5~!3wr35M^P$_{*2~{U9^Y?m{9&pjmCJ=MI zfIQJJ)M4>L1L~V}>f30+*W~=K(5XBHp#JWhOamYDz_kbFegE*t@zG=1?AR#Qb;6|y z99FhU2~|{yG9bE1NEXy?B9zzt5D$kLU5%s`-O?li3>aKV?l>Qb*|&_xv7> z+P3SD_@3o&ko~Yeg5S_PtnzW(z-52p|33%znU8W4euQtxVXKy#)lWO|7ZG{y{B5>= zl_w(MPe|(6h5HuRXB%SsDcAlQ(y;Mh<5yRQ@zap}c`5yS;gieH#y4>P>uA`w|9>K^ Ba-{$O literal 23552 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 From 27bd03be610776108183752b5e46701b77741611 Mon Sep 17 00:00:00 2001 From: Ken Smith Date: Tue, 30 Apr 2024 18:06:52 -0700 Subject: [PATCH 4/5] Get final tests working. --- main/SS/Formula/Functions/Forecast.cs | 18 +++++- .../main/SS/Formula/Functions/TestForecast.cs | 56 +++++++++---------- 2 files changed, 43 insertions(+), 31 deletions(-) diff --git a/main/SS/Formula/Functions/Forecast.cs b/main/SS/Formula/Functions/Forecast.cs index faea53cf0..5b381aa93 100644 --- a/main/SS/Formula/Functions/Forecast.cs +++ b/main/SS/Formula/Functions/Forecast.cs @@ -15,10 +15,26 @@ namespace NPOI.SS.Formula.Functions /// public class Forecast : Fixed3ArgFunction { - public override ValueEval Evaluate(int srcRowIndex, int srcColumnIndex, ValueEval arg0, ValueEval arg1, ValueEval arg2) + public override ValueEval Evaluate(int srcRowIndex, int srcColumnIndex, ValueEval arg0, ValueEval arg1, + ValueEval arg2) { try { + if(arg0 is ErrorEval arg0Error) + { + return arg0Error; + } + + if(arg1 is ErrorEval arg1Error) + { + return arg1Error; + } + + if(arg2 is ErrorEval arg2Error) + { + return arg2Error; + } + double x = NumericFunction.SingleOperandEvaluate(arg0, srcRowIndex, srcColumnIndex); double[] yValues = GetNumericArray(arg1); double[] xValues = GetNumericArray(arg2); diff --git a/testcases/main/SS/Formula/Functions/TestForecast.cs b/testcases/main/SS/Formula/Functions/TestForecast.cs index c1e33caee..51c6347b1 100644 --- a/testcases/main/SS/Formula/Functions/TestForecast.cs +++ b/testcases/main/SS/Formula/Functions/TestForecast.cs @@ -45,7 +45,7 @@ public void TestBasic() { ValueEval x = new NumberEval(100); ValueEval[] yValues = [ - new NumberEval(1), + new NumberEval(1), new NumberEval(2), new NumberEval(3), new NumberEval(4), @@ -54,17 +54,17 @@ public void TestBasic() ]; ValueEval[] xValues = [ - new NumberEval(2), - new NumberEval(4), - new NumberEval(6), - new NumberEval(8), + new NumberEval(2), + new NumberEval(4), + new NumberEval(6), + new NumberEval(8), new NumberEval(10), new NumberEval(12) ]; Confirm(x, CreateAreaEval(yValues), CreateAreaEval(xValues), 50.0); // Excel 365 build 2402 gives 50.0 } - + /// /// This test is replicated in the "TestLargeNumbers" tab of the "Forecast.xls" file. /// @@ -74,7 +74,7 @@ public void TestLargeNumbers() double exp = Math.Pow(10, 7.5); ValueEval x = new NumberEval(100); ValueEval[] yValues = [ - new NumberEval(3 + exp), + new NumberEval(3 + exp), new NumberEval(4 + exp), new NumberEval(2 + exp), new NumberEval(5 + exp), @@ -83,10 +83,10 @@ public void TestLargeNumbers() ]; ValueEval[] xValues = [ - new NumberEval(1), - new NumberEval(2), - new NumberEval(3), - new NumberEval(4), + new NumberEval(1), + new NumberEval(2), + new NumberEval(3), + new NumberEval(4), new NumberEval(5), new NumberEval(6) ]; @@ -112,32 +112,28 @@ public void TestLargeArrays() [Test] public void TestErrors() { - var x = new NumberEval(100); - ValueEval[] xValues = [ErrorEval.REF_INVALID, new NumberEval(2)]; - ValueEval areaEvalX = CreateAreaEval(xValues); - ValueEval[] yValues = [new NumberEval(2), ErrorEval.NULL_INTERSECTION]; - ValueEval areaEvalY = CreateAreaEval(yValues); - ValueEval[] zValues = [ - // wrong size - new NumberEval(2) - ]; - ValueEval areaEvalZ = CreateAreaEval(zValues); + NumberEval x = new(100); + + ValueEval areaEval1 = CreateAreaEval([new NumberEval(2)]); + ValueEval areaEval2 = CreateAreaEval([new NumberEval(2), new NumberEval(2)]); // different size + + ValueEval areaEvalWithNullError = CreateAreaEval([new NumberEval(2), ErrorEval.NULL_INTERSECTION]); + ValueEval areaEvalWithRefError = CreateAreaEval([ErrorEval.REF_INVALID, new NumberEval(2)]); // if either arg is an error, that error propagates ConfirmError(x, ErrorEval.REF_INVALID, ErrorEval.NAME_INVALID, ErrorEval.REF_INVALID); - ConfirmError(x, areaEvalX, ErrorEval.NAME_INVALID, ErrorEval.NAME_INVALID); - ConfirmError(x, ErrorEval.NAME_INVALID, areaEvalX, ErrorEval.NAME_INVALID); + ConfirmError(x, areaEvalWithRefError, ErrorEval.NAME_INVALID, ErrorEval.NAME_INVALID); + ConfirmError(x, ErrorEval.NAME_INVALID, areaEvalWithRefError, ErrorEval.NAME_INVALID); // array sizes must match - ConfirmError(x, areaEvalX, areaEvalZ, ErrorEval.NA); - ConfirmError(x, areaEvalZ, areaEvalY, ErrorEval.NA); + ConfirmError(x, areaEval1, areaEval2, ErrorEval.NA); // any error in an array item propagates up - ConfirmError(x, areaEvalX, areaEvalX, ErrorEval.REF_INVALID); + ConfirmError(x, areaEvalWithRefError, areaEvalWithRefError, ErrorEval.REF_INVALID); // search for errors array by array, not pair by pair - ConfirmError(x, areaEvalX, areaEvalY, ErrorEval.NULL_INTERSECTION); - ConfirmError(x, areaEvalY, areaEvalX, ErrorEval.REF_INVALID); + ConfirmError(x, areaEvalWithRefError, areaEvalWithNullError, ErrorEval.REF_INVALID); + ConfirmError(x, areaEvalWithNullError, areaEvalWithRefError, ErrorEval.NULL_INTERSECTION); } /** @@ -156,7 +152,7 @@ public void TestFromFile() fe.Evaluate(a8); Assert.AreEqual(10.60725309, a8.NumericCellValue, 0.00000001); } - + private static ValueEval Invoke(ValueEval x, ValueEval yArray, ValueEval xArray) { ValueEval[] args = [x, yArray, xArray]; @@ -174,7 +170,7 @@ private static void ConfirmError(ValueEval x, ValueEval yArray, ValueEval xArray { ValueEval result = Invoke(x, yArray, xArray); Assert.AreEqual(typeof(ErrorEval), result.GetType()); - Assert.AreEqual(expectedError.ErrorCode, ((ErrorEval) result).ErrorCode); + Assert.AreEqual(expectedError, (ErrorEval) result); } private static ValueEval[] CreateMockNumberArray(int size, double value) From ea2c0d3dd61cb65f51cf28cfeb28581465265177 Mon Sep 17 00:00:00 2001 From: Ken Smith Date: Tue, 30 Apr 2024 18:49:57 -0700 Subject: [PATCH 5/5] Fix comments --- testcases/main/SS/Formula/Functions/TestForecast.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/testcases/main/SS/Formula/Functions/TestForecast.cs b/testcases/main/SS/Formula/Functions/TestForecast.cs index 51c6347b1..256c0113b 100644 --- a/testcases/main/SS/Formula/Functions/TestForecast.cs +++ b/testcases/main/SS/Formula/Functions/TestForecast.cs @@ -28,7 +28,7 @@ namespace TestCases.SS.Formula.Functions using NPOI.SS.Formula.Functions; /** - * Test for Excel function INTERCEPT() + * Test for Excel function FORECAST() * * @author Ken Smith */ @@ -106,7 +106,7 @@ public void TestLargeArrays() ValueEval[] xValues = CreateMockNumberArray(100, 101); // [1,2,3,4,...,99,100] Confirm(x, CreateAreaEval(yValues), CreateAreaEval(xValues), 0.960990099); - // Excel 365 build 2402 gives 0.98039604 + // Excel 365 build 2402 gives 0.960990099 } [Test]