Skip to content

Commit 506a1c8

Browse files
hackf5slozier
authored andcommitted
Round of an integer should return an integer 505 (#219)
* Added round to even methods to support the new rounding model in Python 3. https://bugs.python.org/issue4707 * Added sanity tests for the RoundToEven methods. These tests don't numerically stress the functions, but provided a level of confidence above zero that the methods are doing what they claim to do. * Made RoundAwayFromZero obsolete. Removed RoundToEven. Replaced with multipurpose Round method. * Added more tests to cover AwayFromZero.
1 parent 5310277 commit 506a1c8

File tree

2 files changed

+103
-7
lines changed

2 files changed

+103
-7
lines changed

Src/Microsoft.Dynamic/Utils/MathUtils.cs

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -149,8 +149,9 @@ public static long FloorRemainder(long x, long y) {
149149
/// Behaves like Math.Round(value, MidpointRounding.AwayFromZero)
150150
/// Needed because CoreCLR doesn't support this particular overload of Math.Round
151151
/// </summary>
152+
[Obsolete("The method has been deprecated. Call MathUtils.Round(value, 0, MidpointRounding.AwayFromZero) instead.")]
152153
public static double RoundAwayFromZero(double value) {
153-
return Math.Round(value, MidpointRounding.AwayFromZero);
154+
return RoundAwayFromZero(value, 0);
154155
}
155156

156157
private static readonly double[] _RoundPowersOfTens = new double[] { 1E0, 1E1, 1E2, 1E3, 1E4, 1E5, 1E6, 1E7, 1E8, 1E9, 1E10, 1E11, 1E12, 1E13, 1E14, 1E15 };
@@ -166,7 +167,16 @@ private static double GetPowerOf10(int precision) {
166167
///
167168
/// (This function is also needed because CoreCLR lacks this overload.)
168169
/// </summary>
170+
[Obsolete("The method has been deprecated. Call MathUtils.Round(value, precision, MidpointRounding.AwayFromZero) instead.")]
169171
public static double RoundAwayFromZero(double value, int precision) {
172+
return Round(value, precision, MidpointRounding.AwayFromZero);
173+
}
174+
175+
public static bool IsNegativeZero(double self) {
176+
return (self == 0.0 && 1.0 / self < 0);
177+
}
178+
179+
public static double Round(double value, int precision, MidpointRounding mode) {
170180
if (double.IsInfinity(value) || double.IsNaN(value)) {
171181
return value;
172182
}
@@ -177,25 +187,21 @@ public static double RoundAwayFromZero(double value, int precision) {
177187
}
178188

179189
double num = GetPowerOf10(precision);
180-
return RoundAwayFromZero(value * num) / num;
190+
return Math.Round(value * num, mode) / num;
181191
}
182192

183193
if (precision >= -308) {
184194
// Note: this code path could be merged with the precision >= 0 path,
185195
// (by extending the cache to negative powers of 10)
186196
// but the results seem to be more precise if we do it this way
187197
double num = GetPowerOf10(-precision);
188-
return RoundAwayFromZero(value / num) * num;
198+
return Math.Round(value / num, mode) * num;
189199
}
190200

191201
// Preserve the sign of the input, including +/-0.0
192202
return value < 0.0 || 1.0 / value < 0.0 ? -0.0 : 0.0;
193203
}
194204

195-
public static bool IsNegativeZero(double self) {
196-
return (self == 0.0 && 1.0 / self < 0);
197-
}
198-
199205
#region Special Functions
200206

201207
public static double Erf(double v0) {

Tests/Microsoft.Dynamic.Test/Utils/MathUtilsTest.cs

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,5 +60,95 @@ public double HypotTest(double x, double y) {
6060
public double LogGammaTest(double v0) {
6161
return Scripting.Utils.MathUtils.LogGamma(v0);
6262
}
63+
64+
[TestCase(double.NegativeInfinity, 3 , ExpectedResult = double.NegativeInfinity)]
65+
[TestCase(double.NaN, 3 , ExpectedResult = double.NaN)]
66+
[TestCase(3.2934232, 309 , ExpectedResult = 3.2934232)]
67+
[TestCase(3.2934232, -309 , ExpectedResult = 0.0)]
68+
[TestCase(-3.2934232, -309 , ExpectedResult = -0.0)]
69+
[TestCase(-0.0, 1, ExpectedResult = -0.0)]
70+
[TestCase(0.0, 1, ExpectedResult = 0.0)]
71+
[TestCase(2.5, 0, ExpectedResult = 2.0)]
72+
[TestCase(2.5 + 1.0E-15, 0, ExpectedResult = 3.0)]
73+
[TestCase(2.5 - 1.0E-15, 0, ExpectedResult = 2.0)]
74+
[TestCase(25.0, -1, ExpectedResult = 20.0)]
75+
[TestCase(3.5, 0, ExpectedResult = 4.0)]
76+
[TestCase(35.0, -1, ExpectedResult = 40.0)]
77+
[TestCase(-35.0, -1, ExpectedResult = -40.0)]
78+
[TestCase(-25.0, -1, ExpectedResult = -20.0)]
79+
public double RoundToEvenTest(double number, int precision) {
80+
return Scripting.Utils.MathUtils.Round(number, precision, MidpointRounding.ToEven);
81+
}
82+
83+
[TestCase(double.NegativeInfinity, ExpectedResult = double.NegativeInfinity)]
84+
[TestCase(double.NaN, ExpectedResult = double.NaN)]
85+
[TestCase(-0.0, ExpectedResult = -0.0)]
86+
[TestCase(0.0, ExpectedResult = 0.0)]
87+
[TestCase(2.5, ExpectedResult = 2.0)]
88+
[TestCase(2.5 + 1.0E-15, ExpectedResult = 3.0)]
89+
[TestCase(2.5 - 1.0E-15, ExpectedResult = 2.0)]
90+
[TestCase(3.5, ExpectedResult = 4.0)]
91+
92+
public double RoundToEvenTest(double number) {
93+
return Scripting.Utils.MathUtils.Round(number, 0, MidpointRounding.ToEven);
94+
}
95+
96+
[TestCase(2.45, 1, 2.4, 1E-15)]
97+
public void RoundToEvenWithToleranceTest(double number, int precision, double expected, double delta) {
98+
var actual = Scripting.Utils.MathUtils.Round(number, precision, MidpointRounding.ToEven);
99+
Assert.AreEqual(expected, actual, delta);
100+
}
101+
102+
[TestCase(double.NegativeInfinity, 3, ExpectedResult = double.NegativeInfinity)]
103+
[TestCase(double.NaN, 3, ExpectedResult = double.NaN)]
104+
[TestCase(3.2934232, 309, ExpectedResult = 3.2934232)]
105+
[TestCase(3.2934232, -309, ExpectedResult = 0.0)]
106+
[TestCase(-3.2934232, -309, ExpectedResult = -0.0)]
107+
[TestCase(-0.0, 1, ExpectedResult = -0.0)]
108+
[TestCase(0.0, 1, ExpectedResult = 0.0)]
109+
[TestCase(2.5, 0, ExpectedResult = 3.0)]
110+
[TestCase(2.5 + 1.0E-15, 0, ExpectedResult = 3.0)]
111+
[TestCase(2.5 - 1.0E-15, 0, ExpectedResult = 2.0)]
112+
[TestCase(25.0, -1, ExpectedResult = 30.0)]
113+
[TestCase(3.5, 0, ExpectedResult = 4.0)]
114+
[TestCase(35.0, -1, ExpectedResult = 40.0)]
115+
[TestCase(-35.0, -1, ExpectedResult = -40.0)]
116+
[TestCase(-25.0, -1, ExpectedResult = -30.0)]
117+
public double RoundAwayFromZeroTest(double number, int precision) {
118+
#pragma warning disable 618
119+
var obsolete = Scripting.Utils.MathUtils.RoundAwayFromZero(number, precision);
120+
#pragma warning restore 618
121+
var actual = Scripting.Utils.MathUtils.Round(number, precision, MidpointRounding.AwayFromZero);
122+
Assert.AreEqual(actual, obsolete);
123+
return actual;
124+
}
125+
126+
[TestCase(double.NegativeInfinity, ExpectedResult = double.NegativeInfinity)]
127+
[TestCase(double.NaN, ExpectedResult = double.NaN)]
128+
[TestCase(-0.0, ExpectedResult = -0.0)]
129+
[TestCase(0.0, ExpectedResult = 0.0)]
130+
[TestCase(2.5, ExpectedResult = 3.0)]
131+
[TestCase(2.5 + 1.0E-15, ExpectedResult = 3.0)]
132+
[TestCase(2.5 - 1.0E-15, ExpectedResult = 2.0)]
133+
[TestCase(3.5, ExpectedResult = 4.0)]
134+
135+
public double RoundAwayFromZeroTest(double number) {
136+
#pragma warning disable 618
137+
var obsolete = Scripting.Utils.MathUtils.RoundAwayFromZero(number);
138+
#pragma warning restore 618
139+
var actual = Scripting.Utils.MathUtils.Round(number, 0, MidpointRounding.AwayFromZero);
140+
Assert.AreEqual(actual, obsolete);
141+
return actual;
142+
}
143+
144+
[TestCase(2.45, 1, 2.5, 1E-15)]
145+
public void RoundAwayFromZeroWithToleranceTest(double number, int precision, double expected, double delta) {
146+
#pragma warning disable 618
147+
var obsolete = Scripting.Utils.MathUtils.RoundAwayFromZero(number, precision);
148+
#pragma warning restore 618
149+
var actual = Scripting.Utils.MathUtils.Round(number, precision, MidpointRounding.AwayFromZero);
150+
Assert.AreEqual(actual, obsolete);
151+
Assert.AreEqual(expected, actual, delta);
152+
}
63153
}
64154
}

0 commit comments

Comments
 (0)