diff --git a/ClosedXML/ClosedXML/ClosedXML/ClosedXML.csproj b/ClosedXML/ClosedXML/ClosedXML/ClosedXML.csproj index 5f7d00c..193e78a 100644 --- a/ClosedXML/ClosedXML/ClosedXML/ClosedXML.csproj +++ b/ClosedXML/ClosedXML/ClosedXML/ClosedXML.csproj @@ -147,6 +147,8 @@ + + diff --git a/ClosedXML/ClosedXML/ClosedXML/Excel/CalcEngine/Functions/MathTrig.cs b/ClosedXML/ClosedXML/ClosedXML/Excel/CalcEngine/Functions/MathTrig.cs index 84fb2f0..e30ecb1 100644 --- a/ClosedXML/ClosedXML/ClosedXML/Excel/CalcEngine/Functions/MathTrig.cs +++ b/ClosedXML/ClosedXML/ClosedXML/Excel/CalcEngine/Functions/MathTrig.cs @@ -62,12 +62,12 @@ ce.RegisterFunction("SIN", 1, Sin); ce.RegisterFunction("SINH", 1, Sinh); ce.RegisterFunction("SQRT", 1, Sqrt); - //ce.RegisterFunction("SQRTPI", SqrtPi, 1); - //ce.RegisterFunction("SUBTOTAL", Subtotal, 1); + ce.RegisterFunction("SQRTPI", 1, SqrtPi); + ce.RegisterFunction("SUBTOTAL", 2, 255, Subtotal); ce.RegisterFunction("SUM", 1, int.MaxValue, Sum); ce.RegisterFunction("SUMIF", 2, 3, SumIf); - //ce.RegisterFunction("SUMPRODUCT", SumProduct, 1); - //ce.RegisterFunction("SUMSQ", SumSq, 1); + //ce.RegisterFunction("SUMPRODUCT", 1, SumProduct); + ce.RegisterFunction("SUMSQ", 1, 255, SumSq); //ce.RegisterFunction("SUMX2MY2", SumX2MY2, 1); //ce.RegisterFunction("SUMX2PY2", SumX2PY2, 1); //ce.RegisterFunction("SUMXMY2", SumXMY2, 1); @@ -589,5 +589,51 @@ return total; } + + private static object SqrtPi(List p) + { + var num = (Double)p[0]; + return Math.Sqrt(Math.PI * num); + } + + private static object Subtotal(List p) + { + var fId = (int)(Double)p[0]; + var tally = new Tally(p.Skip(1)); + + switch (fId) + { + case 1: + return tally.Average(); + case 2: + return tally.Count(); + case 3: + return tally.CountA(); + case 4: + return tally.Max(); + case 5: + return tally.Min(); + case 6: + return tally.Product(); + case 7: + return tally.Std(); + case 8: + return tally.StdP(); + case 9: + return tally.Sum(); + case 10: + return tally.Var(); + case 11: + return tally.VarP(); + default: + throw new ArgumentException("Function not supported."); + } + } + + private static object SumSq(List p) + { + var t = new Tally(p); + return t.Numerics().Sum(v => Math.Pow(v, 2)); + } } } \ No newline at end of file diff --git a/ClosedXML/ClosedXML/ClosedXML/Excel/CalcEngine/Functions/Statistical.cs b/ClosedXML/ClosedXML/ClosedXML/Excel/CalcEngine/Functions/Statistical.cs index c0e91cd..973bb90 100644 --- a/ClosedXML/ClosedXML/ClosedXML/Excel/CalcEngine/Functions/Statistical.cs +++ b/ClosedXML/ClosedXML/ClosedXML/Excel/CalcEngine/Functions/Statistical.cs @@ -209,12 +209,7 @@ // utility for tallying statistics static Tally GetTally(List p, bool numbersOnly) { - var tally = new Tally(numbersOnly); - foreach (Expression e in p) - { - tally.Add(e); - } - return tally; + return new Tally(p); } } } diff --git a/ClosedXML/ClosedXML/ClosedXML/Excel/CalcEngine/Functions/Subtotals/Average.cs b/ClosedXML/ClosedXML/ClosedXML/Excel/CalcEngine/Functions/Subtotals/Average.cs new file mode 100644 index 0000000..5d002fc --- /dev/null +++ b/ClosedXML/ClosedXML/ClosedXML/Excel/CalcEngine/Functions/Subtotals/Average.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace ClosedXML.Excel.CalcEngine.Functions.Subtotals +{ + class Average + { + public static Object GetSubtotal(List list) + { + var tally = new Tally(); + foreach (var e in list) + { + tally.Add(e); + } + return tally.Average(); + } + } +} diff --git a/ClosedXML/ClosedXML/ClosedXML/Excel/CalcEngine/Functions/Subtotals/Subtotal.cs b/ClosedXML/ClosedXML/ClosedXML/Excel/CalcEngine/Functions/Subtotals/Subtotal.cs new file mode 100644 index 0000000..2b7e6ee --- /dev/null +++ b/ClosedXML/ClosedXML/ClosedXML/Excel/CalcEngine/Functions/Subtotals/Subtotal.cs @@ -0,0 +1,30 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace ClosedXML.Excel.CalcEngine.Functions.Subtotals +{ + abstract class Subtotal + { + protected readonly List exprList; + + protected Subtotal(List list) + { + exprList = list; + } + public abstract Object Evaluate(); + + public static Object GetSubtotal(Int32 fId, List list) + { + + switch (fId) + { + case 1: + return Average.GetSubtotal(list); + default: + throw new ArgumentException("Function not supported."); + } + } + } +} diff --git a/ClosedXML/ClosedXML/ClosedXML/Excel/CalcEngine/Functions/Tally.cs b/ClosedXML/ClosedXML/ClosedXML/Excel/CalcEngine/Functions/Tally.cs index 989497a..c21be91 100644 --- a/ClosedXML/ClosedXML/ClosedXML/Excel/CalcEngine/Functions/Tally.cs +++ b/ClosedXML/ClosedXML/ClosedXML/Excel/CalcEngine/Functions/Tally.cs @@ -1,4 +1,6 @@ using System; +using System.Linq; +using System.Collections.Generic; using System.Net; using System.Collections; @@ -6,15 +8,15 @@ { internal class Tally { - double _sum, _sum2, _cnt, _min, _max; - bool _numbersOnly; + private readonly List _list = new List(); - public Tally(bool numbersOnly) + public Tally(){} + public Tally(IEnumerable p) { - _numbersOnly = numbersOnly; - } - public Tally() - { + foreach (var e in p) + { + Add(e); + } } public void Add(Expression e) @@ -25,85 +27,109 @@ { foreach (var value in ienum) { - AddValue(value); + _list.Add(value); } return; } // handle expressions - AddValue(e.Evaluate()); + _list.Add(e.Evaluate()); } - public void AddValue(object value) + + public void AddValue(Object v) { - // conversions - if (!_numbersOnly) - { - // arguments that contain text evaluate as 0 (zero). - // empty text ("") evaluates as 0 (zero). - if (value == null || value is string) - { - value = 0; - } - // arguments that contain TRUE evaluate as 1; - // arguments that contain FALSE evaluate as 0 (zero). - if (value is bool) - { - value = (bool)value ? 1 : 0; - } - } - - // convert all numeric values to doubles - if (value != null) - { - var typeCode = Type.GetTypeCode(value.GetType()); - if (typeCode >= TypeCode.Char && typeCode <= TypeCode.Decimal) - { - value = Convert.ChangeType(value, typeof(double), System.Globalization.CultureInfo.CurrentCulture); - } - } - - // tally - if (value is double) - { - var dbl = (double)value; - _sum += dbl; - _sum2 += dbl * dbl; - _cnt++; - if (_cnt == 1 || dbl < _min) - { - _min = dbl; - } - if (_cnt == 1 || dbl > _max) - { - _max = dbl; - } - } + _list.Add(v); } - public double Count() { return _cnt; } - public double Sum() { return _sum; } - public double Average() { return _sum / _cnt; } - public double Min() { return _min; } - public double Max() { return _max; } - public double Range() { return _max - _min; } + + public double Count() { return _list.Count; } + public double CountA() + { + Double cntA = 0; + foreach (var value in _list) + { + var strVal = value as String; + if (value != null && (strVal == null || !XLHelper.IsNullOrWhiteSpace(strVal))) + cntA++; + } + return cntA; + } + public List Numerics() + { + List retVal = new List(); + foreach (var value in _list) + { + Double tmp; + if (Double.TryParse(value.ToString(), out tmp)) + { + retVal.Add(tmp); + } + } + return retVal; + } + public double Product() + { + var nums = Numerics(); + if (nums.Count == 0) return 0; + + Double retVal = 1; + nums.ForEach(n => retVal *= n); + + return retVal; + } + public double Sum() { return Numerics().Sum(); } + public double Average() + { + return Numerics().Average(); + } + public double Min() { return Numerics().Min(); } + public double Max() { return Numerics().Max(); } + public double Range() + { + var nums = Numerics(); + return nums.Max() - nums.Min(); + } + + private double Sum2(List nums) + { + return nums.Sum(d => d * d); + } + public double VarP() { - var avg = Average(); - return _cnt <= 1 ? 0 : _sum2 / _cnt - avg * avg; + var nums = Numerics(); + var avg = nums.Average(); + var sum2 = nums.Sum(d => d * d); + return nums.Count <= 1 ? 0 : sum2 / nums.Count - avg * avg; } public double StdP() { - var avg = Average(); - return _cnt <= 1 ? 0 : Math.Sqrt(_sum2 / _cnt - avg * avg); + var nums = Numerics(); + var avg = nums.Average(); + var sum2 = nums.Sum(d => d * d); + return nums.Count <= 1 ? 0 : Math.Sqrt(sum2 / nums.Count - avg * avg); } public double Var() { - var avg = Average(); - return _cnt <= 1 ? 0 : (_sum2 / _cnt - avg * avg) * _cnt / (_cnt - 1); + var nums = Numerics(); + var avg = nums.Average(); + var sum2 = nums.Sum(d => d * d); + return nums.Count <= 1 ? 0 : (sum2 / nums.Count - avg * avg) * nums.Count / (nums.Count - 1); } public double Std() { - var avg = Average(); - return _cnt <= 1 ? 0 : Math.Sqrt((_sum2 / _cnt - avg * avg) * _cnt / (_cnt - 1)); + var values = Numerics(); + double ret = 0; + if (values.Count > 0) + { + //Compute the Average + double avg = values.Average(); + //Perform the Sum of (value-avg)_2_2 + double sum = values.Sum(d => Math.Pow(d - avg, 2)); + //Put it all together + ret = Math.Sqrt((sum) / (values.Count() - 1)); + } + return ret; + } } } diff --git a/ClosedXML/ClosedXML/ClosedXML_Tests/Excel/CalcEngine/FunctionsTests.cs b/ClosedXML/ClosedXML/ClosedXML_Tests/Excel/CalcEngine/FunctionsTests.cs index e0459f6..8812a53 100644 --- a/ClosedXML/ClosedXML/ClosedXML_Tests/Excel/CalcEngine/FunctionsTests.cs +++ b/ClosedXML/ClosedXML/ClosedXML_Tests/Excel/CalcEngine/FunctionsTests.cs @@ -295,5 +295,128 @@ actual = ws.Evaluate("SERIESSUM(A2,0,2,A3:A6)"); Assert.IsTrue(Math.Abs(0.70710321482284566 - (double)actual) < XLHelper.Epsilon); } + + [TestMethod] + public void SqrtPi() + { + var actual = XLWorkbook.EvaluateExpr("SqrtPi(1)"); + Assert.IsTrue(Math.Abs(1.7724538509055159 - (double)actual) < XLHelper.Epsilon); + + actual = XLWorkbook.EvaluateExpr("SqrtPi(2)"); + Assert.IsTrue(Math.Abs(2.5066282746310002 - (double)actual) < XLHelper.Epsilon); + } + + [TestMethod] + public void SubtotalAverage() + { + var actual = XLWorkbook.EvaluateExpr("Subtotal(1,2,3)"); + Assert.AreEqual(2.5, actual); + + actual = XLWorkbook.EvaluateExpr(@"Subtotal(1,""A"",3, 2)"); + Assert.AreEqual(2.5, actual); + } + + [TestMethod] + public void SubtotalCount() + { + var actual = XLWorkbook.EvaluateExpr("Subtotal(2,2,3)"); + Assert.AreEqual(2.0, actual); + + actual = XLWorkbook.EvaluateExpr(@"Subtotal(2,""A"",3)"); + Assert.AreEqual(2.0, actual); + } + + [TestMethod] + public void SubtotalCountA() + { + Object actual; + + actual = XLWorkbook.EvaluateExpr("Subtotal(3,2,3)"); + Assert.AreEqual(2.0, actual); + + actual = XLWorkbook.EvaluateExpr(@"Subtotal(3,"""",3)"); + Assert.AreEqual(1.0, actual); + } + + [TestMethod] + public void SubtotalMax() + { + Object actual; + + actual = XLWorkbook.EvaluateExpr(@"Subtotal(4,2,3,""A"")"); + Assert.AreEqual(3.0, actual); + } + + [TestMethod] + public void SubtotalMin() + { + Object actual; + + actual = XLWorkbook.EvaluateExpr(@"Subtotal(5,2,3,""A"")"); + Assert.AreEqual(2.0, actual); + } + + [TestMethod] + public void SubtotalProduct() + { + Object actual; + + actual = XLWorkbook.EvaluateExpr(@"Subtotal(6,2,3,""A"")"); + Assert.AreEqual(6.0, actual); + } + + [TestMethod] + public void SubtotalStDev() + { + Object actual; + + actual = XLWorkbook.EvaluateExpr(@"Subtotal(7,2,3,""A"")"); + Assert.IsTrue(Math.Abs(0.70710678118654757 - (double)actual) < XLHelper.Epsilon); + } + + [TestMethod] + public void SubtotalStDevP() + { + Object actual; + + actual = XLWorkbook.EvaluateExpr(@"Subtotal(8,2,3,""A"")"); + Assert.AreEqual(0.5, actual); + } + + [TestMethod] + public void SubtotalSum() + { + Object actual; + + actual = XLWorkbook.EvaluateExpr(@"Subtotal(9,2,3,""A"")"); + Assert.AreEqual(5.0, actual); + } + + [TestMethod] + public void SubtotalVar() + { + Object actual; + + actual = XLWorkbook.EvaluateExpr(@"Subtotal(10,2,3,""A"")"); + Assert.IsTrue(Math.Abs(0.5 - (double)actual) < XLHelper.Epsilon); + } + + [TestMethod] + public void SubtotalVarP() + { + Object actual; + + actual = XLWorkbook.EvaluateExpr(@"Subtotal(11,2,3,""A"")"); + Assert.AreEqual(0.25, actual); + } + + [TestMethod] + public void SumSq() + { + Object actual; + + actual = XLWorkbook.EvaluateExpr(@"SumSq(3,4)"); + Assert.AreEqual(25.0, actual); + } } }