Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[POI-66083 & POI-66094] Support (FLOOR|CEILING).MATH & (VAR|STDEV).(P|S) functions #1135

Merged
merged 13 commits into from
Aug 29, 2023
Merged
1 change: 1 addition & 0 deletions main/NPOI.Core.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@

<ItemGroup>
<PackageReference Include="Enums.NET" Version="4.0.1" />
<PackageReference Include="ExtendedNumerics.BigDecimal" Version="2023.1000.0.230" />
<PackageReference Include="MathNet.Numerics.Signed" Version="4.15.0" />
<PackageReference Include="Microsoft.IO.RecyclableMemoryStream" Version="2.3.2" />
<PackageReference Include="BouncyCastle.Cryptography" Version="2.2.1" />
Expand Down
12 changes: 11 additions & 1 deletion main/SS/Formula/Atp/AnalysisToolPak.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -192,6 +193,15 @@ private static Dictionary<String, FreeRefFunction> 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);

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;
}

Expand Down
39 changes: 39 additions & 0 deletions main/SS/Formula/Functions/CeilingMath.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/*
* ====================================================================
* 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 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);
}
}
128 changes: 128 additions & 0 deletions main/SS/Formula/Functions/FloorCeilingMathBase.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
/*
* ====================================================================
* 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 ExtendedNumerics;
using NPOI.SS.Formula.Eval;
using NPOI.SS.Util;
using System;
using System.Collections.Generic;

namespace NPOI.SS.Formula.Functions
{
public abstract class FloorCeilingMathBase : FreeRefFunction
{
// Excel has an internal precision of 15 significant digits
private const int SignificantDigits = 15;
// Use high-precision decimal calculations or customized double workaround
private const bool UseHighPrecisionCalculation = true;
private const int SignificantDigitsForHighPrecision = SignificantDigits + 2;

public ValueEval Evaluate(ValueEval[] args, OperationEvaluationContext ec)
=> args.Length switch
{
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, ValueEval arg1, ValueEval arg2)
{
try
{
var number = NumericFunction.SingleOperandEvaluate(arg0, srcRowIndex, srcColumnIndex);
var 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;
}

double numberToTest = number / significance;

if (UseHighPrecisionCalculation)
{
BigDecimal.Precision = SignificantDigitsForHighPrecision;

var bigNumber = new BigDecimal(number);
var bigSignificance = new BigDecimal(significance);

BigDecimal bigNumberToTest = bigNumber / bigSignificance;

if (bigNumberToTest.IsIntegerWithDigitsDropped(SignificantDigits))
return number;

// High-precision number is only for integer determination. We don't need it later.
}
else
{
// Workaround without BigDecimal
if (numberToTest.IsIntegerWithDigitsDropped(SignificantDigits))
return number;
}

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;
}
}
}

protected abstract double EvaluateMajorDirection(double number);
protected abstract double EvaluateAlternativeDirection(double number);
}
}
40 changes: 40 additions & 0 deletions main/SS/Formula/Functions/FloorMath.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/*
* ====================================================================
* 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 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);
}
}
73 changes: 73 additions & 0 deletions main/SS/Formula/Functions/NumberListFuncBase.cs
Original file line number Diff line number Diff line change
@@ -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<double>();

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<double> list);
}
}
36 changes: 36 additions & 0 deletions main/SS/Formula/Functions/StdevP.cs
Original file line number Diff line number Diff line change
@@ -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<double> list)
=> Math.Sqrt(VarP.Instance.CalculateFromNumberList(list));
}
}
36 changes: 36 additions & 0 deletions main/SS/Formula/Functions/StdevS.cs
Original file line number Diff line number Diff line change
@@ -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<double> list)
=> Math.Sqrt(VarS.Instance.CalculateFromNumberList(list));
}
}
Loading
Loading