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

Issue 38160 - MidpointRounding is buggy #38563

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 73 additions & 0 deletions src/libraries/System.Private.CoreLib/src/System/Math.cs
Original file line number Diff line number Diff line change
Expand Up @@ -939,12 +939,14 @@ public static double Round(double value, int digits)
return Round(value, digits, MidpointRounding.ToEven);
}

[Obsolete("MidpointRounding is buggy"]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static double Round(double value, MidpointRounding mode)
{
return Round(value, 0, mode);
}

[Obsolete("MidpointRounding is buggy"]
public static unsafe double Round(double value, int digits, MidpointRounding mode)
{
if ((digits < 0) || (digits > maxRoundingDigits))
Expand Down Expand Up @@ -1014,6 +1016,77 @@ public static unsafe double Round(double value, int digits, MidpointRounding mod

return value;
}

public static unsafe double Round(double value, int digits, RoundingMode rounding)
{
if ((digits < 0) || (digits > maxRoundingDigits))
{
throw new ArgumentOutOfRangeException(nameof(digits), SR.ArgumentOutOfRange_RoundingDigits);
}
if (digits == 0)
{
return Round(value, rounding);
}
double fraction = ModF(value, &value);
double power10 = roundPower10Double[digits];
fraction *= power10;
return value + Round(fraction, rounding) / power10;
}

public static unsafe double Round(double value, RoundingMode rounding)
{
if (rounding < RoundingMode.HalfToEven || rounding > RoundingMode.HalfTowardsZero)
{
throw new ArgumentException(SR.Format(SR.Argument_InvalidEnumValue, mode, nameof(RoundingMode)), nameof(mode));
}
if (Abs(value) < doubleRoundLimit)
{
if (rounding == RoundingMode.Down)
{
return Floor(value);
}
else if (rounding == RoundingMode.Up)
{
return Ceiling(value);
}
else if (rounding == RoundingMode.TowardsZero)
{
return Truncate(value);
}
else if (rounding == RoundingMode.AwayZero)
{
return Sign(value) < 0 ? Floor(value) : Ceiling(value);
}
else if (rounding == RoundingMode.HalfToEven)
{
return Round(value);
}
else
{
double fraction = ModF(value, &value);
double absoluteFrac = Abs(fraction);
if (absoluteFrac < 0.5D)
{
return value;
}
int signFrac = Sign(fraction);
switch (rounding)
{
case RoundingMode.HalfAwayZero:
return value + signFrac;
case RoundingMode.HalfDown:
return (signFrac < 0) ? value + signFrac : (absoluteFrac > 0.5D) ? value + signFrac : value;
case RoundingMode.HalfUp:
return (signFrac > 0) ? value + signFrac : (absoluteFrac > 0.5D) ? value + signFrac : value;
case RoundingMode.HalfTowardsZero:
return (absoluteFrac > 0.5D) ? value + signFrac : value;
default:
throw new ArgumentException(SR.Format(SR.Argument_InvalidEnumValue, mode, nameof(RoundingMode)), nameof(mode));
}
}
}
return value;
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int Sign(decimal value)
Expand Down
73 changes: 73 additions & 0 deletions src/libraries/System.Private.CoreLib/src/System/MathF.cs
Original file line number Diff line number Diff line change
Expand Up @@ -335,12 +335,14 @@ public static float Round(float x, int digits)
return Round(x, digits, MidpointRounding.ToEven);
}

[Obsolete("MidpointRounding is buggy"]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static float Round(float x, MidpointRounding mode)
{
return Round(x, 0, mode);
}

[Obsolete("MidpointRounding is buggy"]
public static unsafe float Round(float x, int digits, MidpointRounding mode)
{
if ((digits < 0) || (digits > maxRoundingDigits))
Expand Down Expand Up @@ -410,6 +412,77 @@ public static unsafe float Round(float x, int digits, MidpointRounding mode)

return x;
}

public static unsafe double Round(float value, int digits, RoundingMode rounding)
{
if ((digits < 0) || (digits > maxRoundingDigits))
{
throw new ArgumentOutOfRangeException(nameof(digits), SR.ArgumentOutOfRange_RoundingDigits);
}
if (digits == 0)
{
return Round(value, rounding);
}
float fraction = ModF(value, &value);
float power10 = roundPower10Single[digits];
fraction *= power10;
return value + Round(fraction, rounding) / power10;
}

public static unsafe double Round(float value, RoundingMode rounding)
{
if (rounding < RoundingMode.HalfToEven || rounding > RoundingMode.HalfTowardsZero)
{
throw new ArgumentException(SR.Format(SR.Argument_InvalidEnumValue, mode, nameof(RoundingMode)), nameof(mode));
}
if (Abs(value) < singleRoundLimit)
{
if (rounding == RoundingMode.Down)
{
return Floor(value);
}
else if (rounding == RoundingMode.Up)
{
return Ceiling(value);
}
else if (rounding == RoundingMode.TowardsZero)
{
return Truncate(value);
}
else if (rounding == RoundingMode.AwayZero)
{
return Sign(value) < 0 ? Floor(value) : Ceiling(value);
}
else if (rounding == RoundingMode.HalfToEven)
{
return Round(value);
}
else
{
float fraction = ModF(value, &value);
float absoluteFrac = Abs(fraction);
if (absoluteFrac < 0.5F)
{
return value;
}
int signFrac = Sign(fraction);
switch (rounding)
{
case RoundingMode.HalfAwayZero:
return value + signFrac;
case RoundingMode.HalfDown:
return (signFrac < 0) ? value + signFrac : (absoluteFrac > 0.5F) ? value + signFrac : value;
case RoundingMode.HalfUp:
return (signFrac > 0) ? value + signFrac : (absoluteFrac > 0.5F) ? value + signFrac : value;
case RoundingMode.HalfTowardsZero:
return (absoluteFrac > 0.5F) ? value + signFrac : value;
default:
throw new ArgumentException(SR.Format(SR.Argument_InvalidEnumValue, mode, nameof(RoundingMode)), nameof(mode));
}
}
}
return value;
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int Sign(float x)
Expand Down
126 changes: 126 additions & 0 deletions src/libraries/System.Private.CoreLib/src/System/RoundingMode
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
namespace System
{
public enum RoundingMode
{
/// <summary>
/// Directed round down (same as Floor).
/// </summary>
/// <remarks>
/// <code>Assert.AreEqual(-2D, -1.8D);</code>
/// <code>Assert.AreEqual(-2D, -1.5D);</code>
/// <code>Assert.AreEqual(-2D, -1.2D);</code>
/// <code>Assert.AreEqual(1D, +1.2D);</code>
/// <code>Assert.AreEqual(1D, +1.5D);</code>
/// <code>Assert.AreEqual(1D, +1.8D);</code>
/// </remarks>
Down = 3,

/// <summary>
/// Directed round up (same as Ceiling).
/// </summary>
/// <remarks>
/// <code>Assert.AreEqual(-1D, -1.8D);</code>
/// <code>Assert.AreEqual(-1D, -1.5D);</code>
/// <code>Assert.AreEqual(-1D, -1.2D);</code>
/// <code>Assert.AreEqual(2D, +1.2D);</code>
/// <code>Assert.AreEqual(2D, +1.5D);</code>
/// <code>Assert.AreEqual(2D, +1.8D);</code>
/// </remarks>
Up = 4,

/// <summary>
/// Directed round towards zero (same as Truncate).
/// </summary>
/// <remarks>
/// <code>Assert.AreEqual(-1D, -1.8D);</code>
/// <code>Assert.AreEqual(-1D, -1.5D);</code>
/// <code>Assert.AreEqual(-1D, -1.2D);</code>
/// <code>Assert.AreEqual(1D, +1.2D);</code>
/// <code>Assert.AreEqual(1D, +1.5D);</code>
/// <code>Assert.AreEqual(1D, +1.8D);</code>
/// </remarks>
TowardsZero = 2,

/// <summary>
/// Directed round away from zero
/// </summary>
/// <remarks>
/// <code>Assert.AreEqual(-2D, -1.8D);</code>
/// <code>Assert.AreEqual(-2D, -1.5D);</code>
/// <code>Assert.AreEqual(-2D, -1.2D);</code>
/// <code>Assert.AreEqual(2D, +1.2D);</code>
/// <code>Assert.AreEqual(2D, +1.5D);</code>
/// <code>Assert.AreEqual(2D, +1.8D);</code>
/// </remarks>
AwayZero = 5,

/// <summary>
/// Round to nearest integer and down when the number is halfway between two others.
/// </summary>
/// <remarks>
/// <code>Assert.AreEqual(-2D, -1.8D);</code>
/// <code>Assert.AreEqual(-2D, -1.5D);</code>
/// <code>Assert.AreEqual(-1D, -1.2D);</code>
/// <code>Assert.AreEqual(1D, +1.2D);</code>
/// <code>Assert.AreEqual(1D, +1.5D);</code>
/// <code>Assert.AreEqual(2D, +1.8D);</code>
/// <code>Assert.AreEqual(4503599627370496D, 4503599627370496D);
/// </remarks>
HalfDown = 6,

/// <summary>
/// Round to nearest integer and up when the number is halfway between two others.
/// </summary>
/// <remarks>
/// <code>Assert.AreEqual(-2D, -1.8D);</code>
/// <code>Assert.AreEqual(-1D, -1.5D);</code>
/// <code>Assert.AreEqual(-1D, -1.2D);</code>
/// <code>Assert.AreEqual(1D, +1.2D);</code>
/// <code>Assert.AreEqual(2D, +1.5D);</code>
/// <code>Assert.AreEqual(2D, +1.8D);</code>
/// <code>Assert.AreEqual(4503599627370496D, 4503599627370496D);
/// </remarks>
HalfUp = 7,

/// <summary>
/// Round to nearest integer and towards zero when the number is halfway between two others.
/// </summary>
/// <remarks>
/// <code>Assert.AreEqual(-2D, -1.8D);</code>
/// <code>Assert.AreEqual(-1D, -1.5D);</code>
/// <code>Assert.AreEqual(-1D, -1.2D);</code>
/// <code>Assert.AreEqual(1D, +1.2D);</code>
/// <code>Assert.AreEqual(1D, +1.5D);</code>
/// <code>Assert.AreEqual(2D, +1.8D);</code>
/// </remarks>
HalfTowardsZero = 8,

/// <summary>
/// Round to nearest integer and away from zero when the number is halfway between two others.
/// </summary>
/// <remarks>
/// <code>Assert.AreEqual(-2D, -1.8D);</code>
/// <code>Assert.AreEqual(-2D, -1.5D);</code>
/// <code>Assert.AreEqual(-1D, -1.2D);</code>
/// <code>Assert.AreEqual(1D, +1.2D);</code>
/// <code>Assert.AreEqual(2D, +1.5D);</code>
/// <code>Assert.AreEqual(2D, +1.8D);</code>
/// </remarks>
HalfAwayZero = 1,

/// <summary>
/// Round to nearest integer and to nearest even integer when the number is halfway between two others.
/// </summary>
/// <remarks>
/// <code>Assert.AreEqual(-2D, -1.8D);</code>
/// <code>Assert.AreEqual(-2D, -1.5D);</code>
/// <code>Assert.AreEqual(-1D, -1.2D);</code>
/// <code>Assert.AreEqual(0D, -0.5D);</code>
/// <code>Assert.AreEqual(0D, +0.5D);</code>
/// <code>Assert.AreEqual(1D, +1.2D);</code>
/// <code>Assert.AreEqual(2D, +1.5D);</code>
/// <code>Assert.AreEqual(2D, +1.8D);</code>
/// </remarks>
HalfToEven = 0
}
}