Skip to content

Commit 53b4820

Browse files
authored
Fixes issue 2043-Round function precision (#2044)
1 parent f05644e commit 53b4820

5 files changed

Lines changed: 36 additions & 10 deletions

File tree

src/EPPlus/FormulaParsing/Excel/Functions/MathFunctions/Round.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,9 @@ public override CompileResult Execute(IList<FunctionArgument> arguments, Parsing
4242
{
4343
return CreateResult(System.Math.Round(number / System.Math.Pow(10, positivDigits), 0, MidpointRounding.AwayFromZero) * System.Math.Pow(10, positivDigits), DataType.Integer);
4444
}
45-
return CreateResult(System.Math.Round(number, nDigits, MidpointRounding.AwayFromZero), DataType.Decimal);
45+
//We cast to decimal here to avoid issues where the double is rounded incorrectly due to floating-point problems
46+
//. For example 39.285 is incorrectly rounded down
47+
return CreateResult((double)System.Math.Round((decimal)number, nDigits, MidpointRounding.AwayFromZero), DataType.Decimal);
4648
}
4749
public override int ArgumentMinLength => 2;
4850
}

src/EPPlus/FormulaParsing/Excel/Functions/MathFunctions/Rounddown.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ internal class Rounddown : ExcelFunction
3131
public override CompileResult Execute(IList<FunctionArgument> arguments, ParsingContext context)
3232
{
3333
if (arguments[0].Value == null) return CreateResult(0d, DataType.Decimal);
34-
var number = ArgToDecimal(arguments, 0, out ExcelErrorValue e1, context.Configuration.PrecisionAndRoundingStrategy);
34+
var number = (decimal)ArgToDecimal(arguments, 0, out ExcelErrorValue e1, context.Configuration.PrecisionAndRoundingStrategy);
3535
if (e1 != null) return CompileResult.GetErrorResult(e1.Type);
3636

3737
var nDecimals = ArgToInt(arguments, 1, out ExcelErrorValue e2);
@@ -50,12 +50,12 @@ public override CompileResult Execute(IList<FunctionArgument> arguments, Parsing
5050
result = (long)Math.Floor(number);
5151
result = result - (result % System.Math.Pow(10, (nDecimals*-1)));
5252
}
53-
return CreateResult(result * nFactor, DataType.Decimal);
53+
return CreateResult((double)result * nFactor, DataType.Decimal);
5454
}
5555

56-
private static double RoundDownDecimalNumber(double number, int nDecimals)
56+
private static double RoundDownDecimalNumber(decimal number, int nDecimals)
5757
{
58-
long integerRepresentation = (long)Math.Floor(number * Math.Pow(10d, nDecimals));
58+
long integerRepresentation = (long)Math.Floor(number * (decimal)Math.Pow(10d, nDecimals));
5959
var result = integerRepresentation / Math.Pow(10d, nDecimals);
6060
return result;
6161
}

src/EPPlus/FormulaParsing/Excel/Functions/MathFunctions/Roundup.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,13 +32,13 @@ internal class Roundup : ExcelFunction
3232
public override CompileResult Execute(IList<FunctionArgument> arguments, ParsingContext context)
3333
{
3434
if (arguments[0].Value == null) return CreateResult(0d, DataType.Decimal);
35-
var number = ArgToDecimal(arguments, 0, out ExcelErrorValue e1, context.Configuration.PrecisionAndRoundingStrategy);
35+
var number = (decimal)ArgToDecimal(arguments, 0, out ExcelErrorValue e1, context.Configuration.PrecisionAndRoundingStrategy);
3636
if (e1 != null) return CompileResult.GetErrorResult(e1.Type);
3737
var nDigits = ArgToInt(arguments, 1, out ExcelErrorValue e2);
3838
if (e2 != null) return CompileResult.GetErrorResult(e2.Type);
39-
double result = (number >= 0)
40-
? System.Math.Ceiling(number * System.Math.Pow(10, nDigits)) / System.Math.Pow(10, nDigits)
41-
: System.Math.Floor(number * System.Math.Pow(10, nDigits)) / System.Math.Pow(10, nDigits);
39+
double result = (double)((number >= 0)
40+
? System.Math.Ceiling(number * (decimal)Math.Pow(10, nDigits)) / (decimal)Math.Pow(10, nDigits)
41+
: System.Math.Floor(number * (decimal)Math.Pow(10, nDigits)) / (decimal)Math.Pow(10, nDigits));
4242
return CreateResult(result, DataType.Decimal);
4343
}
4444
}

src/EPPlusTest/FormulaParsing/Excel/Functions/MathFunctions/RoundTests.cs

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -211,7 +211,7 @@ public void RoundShouldUseFifteenSignificantFigures()
211211
sheet.Calculate(opt => opt.PrecisionAndRoundingStrategy = PrecisionAndRoundingStrategy.Excel);
212212
Assert.AreEqual(120253.88, sheet.Cells["A2"].Value);
213213
sheet.Calculate(opt => opt.PrecisionAndRoundingStrategy = PrecisionAndRoundingStrategy.DotNet);
214-
Assert.AreEqual(120253.87, sheet.Cells["A2"].Value);
214+
Assert.AreEqual(120253.88, sheet.Cells["A2"].Value); //Changed from 120253.87, 2025-06-26, when changed to type-cast the number to decimal in the ROUND function.
215215
}
216216
}
217217

@@ -314,5 +314,16 @@ public void RoundDownShouldNotChangeCorrectValues()
314314
Assert.AreEqual(17.38, sheet.Cells["A2"].Value);
315315
}
316316
}
317+
[TestMethod]
318+
public void RoundTypeCastToDecimalShouldAvoidFloatingPointError()
319+
{
320+
using (var package = new ExcelPackage())
321+
{
322+
var ws = package.Workbook.Worksheets.Add("Sheet1");
323+
ws.Cells["A1"].Formula = "Round(39.285,2)";
324+
ws.Calculate();
325+
Assert.AreEqual(39.29, ws.Cells["A1"].Value);
326+
}
327+
}
317328
}
318329
}

src/EPPlusTest/Issues/FormulaCalculationIssues.cs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1152,6 +1152,19 @@ public void s875()
11521152
Assert.AreEqual(-1083371.20, (double)ws.Cells["P13"].Value, 0.0001);
11531153
}
11541154
}
1155+
[TestMethod]
1156+
public void s868()
1157+
{
1158+
using (var package = OpenTemplatePackage("s868.xlsx"))
1159+
{
1160+
var wb = package.Workbook;
1161+
var ws = wb.Worksheets["aliss"];
1162+
wb.Worksheets.Add("AlissCopy", ws);
1163+
ws.Cells["CW151"].Calculate();
1164+
Assert.AreEqual(39.29, ws.Cells["CW151"].Value);
1165+
SaveAndCleanup(package);
1166+
}
1167+
}
11551168
}
11561169
}
11571170

0 commit comments

Comments
 (0)