Skip to content
This repository was archived by the owner on Jan 23, 2023. It is now read-only.

Add new rounding modes to System.Math, System.MathF #20815

Merged
merged 9 commits into from
Feb 12, 2019
Merged

Add new rounding modes to System.Math, System.MathF #20815

merged 9 commits into from
Feb 12, 2019

Conversation

hamish-rose
Copy link

resolves dotnet/corefx#31902

Adding new rounding modes to MidpointRounding for use with Math.Round and MathF.Round.

My unit tests are over on corefx at the moment. If I understand correctly I need my changes to be mirrored there by the bot and corefx be built with my coreclr version to run tests.

See https://github.com/hamish-rose/corefx/blob/MidpointRoundingmodes%2331902/src/System.Runtime.Extensions/tests/System/Math.cs#L1469

{
double fraction = ModF(value, &value);

if (Sign(fraction) == -1)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should only come into play for ties.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Decimal.DecCalc.InternalRound with Truncate mode supplied (AKA ToZero if i'm not mistaken) currently behaves like this aswell.

e.g. these scenarios all pass..

[InlineData(11.0, 11, MidpointRounding.ToZero)]
[InlineData(11.4, 11, MidpointRounding.ToZero)]
[InlineData(11.5, 11, MidpointRounding.ToZero)]
[InlineData(11.6, 11, MidpointRounding.ToZero)]
[InlineData(-11.0, -11, MidpointRounding.ToZero)]
[InlineData(-11.4, -11, MidpointRounding.ToZero)]
[InlineData(-11.5, -11, MidpointRounding.ToZero)]
[InlineData(-11.6, -11, MidpointRounding.ToZero)]
public static void Round_Decimal_Modes(decimal x, decimal expected, MidpointRounding mode)
{
    Assert.Equal(expected, Math.Round(x, 0, mode));
    Assert.Equal(expected, decimal.Round(x, 0, mode));       
}

Agreed its not really midpoint rounding though, perhaps Math.Round(decimal, ..) could be implemented in Math.cs ?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right. Sorry, I was thinking about the case where you are parsing a result.

Having comments explaining each case would be helpful. We should also just be able to use Ceiling, Floor, and Truncate (for ToPositiveInfinity, ToNegativeInfinity, and Truncate, respectively)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right. Sorry, I was thinking about the case where you are parsing a result.

Having comments explaining each case would be helpful. We should also just be able to use Ceiling, Floor, and Truncate (for ToPositiveInfinity, ToNegativeInfinity, and Truncate, respectively)

Done, added some comments and now using Floor, Ceiling and Truncate.

value--;
}
}
else if (mode is MidpointRounding.ToPositiveInfinity)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The result shall be the format’s floating-point number (possibly +∞) closest to and no less than the infinitely precise result.

value = Round(value);
value = Truncate(value);
}
else if (mode is MidpointRounding.ToNegativeInfinity)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The result shall be the format’s floating-point number (possibly −∞) closest to and no greater than the infinitely precise result


if (Abs(fraction) >= 0.5)
{
value += Sign(fraction);
}
}
else
else if (mode is MidpointRounding.ToZero)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The result shall be the format’s floating-point number closest to and no greater in magnitude than the infinitely precise result.

{
var fraction = ModF(value, &value);
value = Round(value);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The floating-point number nearest to the infinitely precise result shall be delivered; if the two nearest floating-point numbers bracketing an unrepresentable infinitely precise result are equally near, the one with an even least significant digit shall be delivered

}
else if (mode is MidpointRounding.AwayFromZero)
{
double fraction = ModF(value, &value);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The floating-point number nearest to the infinitely precise result shall be delivered; if the two nearest floating-point numbers bracketing an unrepresentable infinitely precise result are equally near, the one with larger magnitude shall be delivered.

@@ -183,18 +183,37 @@ public static unsafe float Round(float x, int digits, MidpointRounding mode)

x *= power10;

if (mode == MidpointRounding.AwayFromZero)
if (mode is MidpointRounding.ToEven)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

would a switch statement be clearer?

Copy link
Author

@hamish-rose hamish-rose Nov 7, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think so. I opted for if else here to be consistent with existing code such as Decimal.DecCalc.InternalRound.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've updated the implementation in Math and MathF with a switch statement

@danmoseley
Copy link
Member

@hamish-rose I see you have tests in your corefx fork. What we typically do is put up the corefx PR with the tests at the same time as the coreclr PR with the implementation. Of course the build or tests fail in the corefx PR initially but it means that we can review the tests concurrently and we know before we merge the implementation that satisfcatory tests on it are ready.

@hamish-rose
Copy link
Author

@hamish-rose I see you have tests in your corefx fork. What we typically do is put up the corefx PR with the tests at the same time as the coreclr PR with the implementation. Of course the build or tests fail in the corefx PR initially but it means that we can review the tests concurrently and we know before we merge the implementation that satisfcatory tests on it are ready.

@danmosemsft i've raised dotnet/corefx#33297

@tannergooding
Copy link
Member

test this please

{
value = Round(value);
// Directed rounding: Round toward zero, to nearest value above (positive numbers) or below (negative numbers)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the comment needs fixing, we won't be rounding above for positive numbers, but rather the nearest exact or the nearest just below (closer to zero)

Copy link
Member

@tannergooding tannergooding left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(sorry for the delay, this fell off my radar)

This looks correct to me (at least according to the current algorithm Math.Round(int digits) is doing).

CC. @ViktorHofer, @eerhardt as it would be useful to have a second pair of eyes.

@eerhardt
Copy link
Member

            DecCalc.InternalRound(ref AsMutable(ref d), (uint)scale, (DecCalc.RoundingMode)mode);

Would it make sense to get rid of DecCalc.RoundingMode completely now and just use public enum MidpointRounding instead? That way:

  1. We don't have 2 enums using different names to mean the same thing. It would aid in code readability.
  2. We don't accidently get the values out of sync (since the way it exists now, the values need to match up for this to work).

Refers to: src/System.Private.CoreLib/shared/System/Decimal.cs:625 in 072f5b8. [](commit_id = 072f5b8, deletion_comment = False)

}
else if (mode is MidpointRounding.ToNegativeInfinity)
{
// Directed Rounding: Round down to the next value, toward −∞
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we typically put non-standard symbols in source code, as some editors/viewers don't show these correctly. Can we just put negative infinity instead?

Copy link
Member

@eerhardt eerhardt left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

:shipit:

Thanks for the good work, @hamish-rose! Looks good.

@tannergooding
Copy link
Member

@hamish-rose, sorry for the delay, this slipped off my radar.

Could you please either merge or rebase this PR against the current head of master? Once that is done (and the jobs all pass), we should be good to merge.

new modes added to enum
implemented ToZero for double in Math.cs
now round inline with DecCalc internal round implementation
also replace var to make things obvious
add comments, update MathF with floor/ceil
 - fix comments
 - replace ifelse with switch
 - remove RoundingMode enum from DecCalc
@hamish-rose
Copy link
Author

@tannergooding I believe I've rebased onto master, working on the failing test now.

@tannergooding
Copy link
Member

I believe I've rebased onto master, working on the failing test now.

How did you rebase? I wouldn't expect quite so many commits. Many of them look like you may have rebased a merge commit...

@krwq
Copy link
Member

krwq commented Feb 4, 2019

you can fix/restore most of the stuff with git reflog assuming you didn't remove local copy of the repository (copy the whole coreclr folder before just in case)

@hamish-rose
Copy link
Author

On my local tracking branch I rebased it onto master, then when I pushed to my remote it 'merged' and now it a bit of a mess.

Sorry about that, I'll give reflog a try.

@hamish-rose
Copy link
Author

@tannergooding i've fixed the rebase now. The test that is failing is a corefx inner loop test that is obsolete now since we just have one enum for rounding mode. I think it can be removed once these changes are merged and mirrored in corefx

Round_InvalidMidpointRounding_ThrowsArgumentException

@tannergooding
Copy link
Member

For the failing CoreFX tests, we need to add an entry in the CoreFX.issues.json indicating it is outdated. You should just need to add the full name of the relevant test after the other Math test entries here: https://github.com/dotnet/coreclr/blob/master/tests/CoreFX/CoreFX.issues.json#L805

@tannergooding
Copy link
Member

Thanks for the work here @hamish-rose

@tannergooding tannergooding merged commit 3397472 into dotnet:master Feb 12, 2019
Dotnet-GitSync-Bot pushed a commit to Dotnet-GitSync-Bot/corefx that referenced this pull request Feb 12, 2019
…0815)

* add new rounding modes to MidpointRounding.cs

new modes added to enum
implemented ToZero for double in Math.cs

* ToZero implementation

* implement double and float rounding modes

* updating rounding implementation

now round inline with DecCalc internal round implementation

* small bug fix

also replace var to make things obvious

* update implementation - floor/ceil

code review feedback

* review feedback

add comments, update MathF with floor/ceil

* code review feedback

 - fix comments
 - replace ifelse with switch
 - remove RoundingMode enum from DecCalc

* exclude outdated corefx test

Signed-off-by: dotnet-bot <dotnet-bot@microsoft.com>
Dotnet-GitSync-Bot pushed a commit to Dotnet-GitSync-Bot/mono that referenced this pull request Feb 12, 2019
…0815)

* add new rounding modes to MidpointRounding.cs

new modes added to enum
implemented ToZero for double in Math.cs

* ToZero implementation

* implement double and float rounding modes

* updating rounding implementation

now round inline with DecCalc internal round implementation

* small bug fix

also replace var to make things obvious

* update implementation - floor/ceil

code review feedback

* review feedback

add comments, update MathF with floor/ceil

* code review feedback

 - fix comments
 - replace ifelse with switch
 - remove RoundingMode enum from DecCalc

* exclude outdated corefx test

Signed-off-by: dotnet-bot <dotnet-bot@microsoft.com>
stephentoub pushed a commit to dotnet/corefx that referenced this pull request Feb 12, 2019
…0815)

* add new rounding modes to MidpointRounding.cs

new modes added to enum
implemented ToZero for double in Math.cs

* ToZero implementation

* implement double and float rounding modes

* updating rounding implementation

now round inline with DecCalc internal round implementation

* small bug fix

also replace var to make things obvious

* update implementation - floor/ceil

code review feedback

* review feedback

add comments, update MathF with floor/ceil

* code review feedback

 - fix comments
 - replace ifelse with switch
 - remove RoundingMode enum from DecCalc

* exclude outdated corefx test

Signed-off-by: dotnet-bot <dotnet-bot@microsoft.com>
Copy link

@pentp pentp left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unless I'm completely misunderstanding something, then this change incorrectly conflates midpoint rounding modes with truncation/ceiling/floor which apply to any rounded value, not just to midpoint!

@pentp
Copy link

pentp commented Feb 12, 2019

OK, the IEEE 754 spec actually talks about directed rounding (truncate/floor/ceiling), but then this API naming is definitely wrong - it's not midpoint rounding.

marek-safar pushed a commit to mono/mono that referenced this pull request Feb 12, 2019
…0815)

* add new rounding modes to MidpointRounding.cs

new modes added to enum
implemented ToZero for double in Math.cs

* ToZero implementation

* implement double and float rounding modes

* updating rounding implementation

now round inline with DecCalc internal round implementation

* small bug fix

also replace var to make things obvious

* update implementation - floor/ceil

code review feedback

* review feedback

add comments, update MathF with floor/ceil

* code review feedback

 - fix comments
 - replace ifelse with switch
 - remove RoundingMode enum from DecCalc

* exclude outdated corefx test

Signed-off-by: dotnet-bot <dotnet-bot@microsoft.com>
@tannergooding
Copy link
Member

@pentp, I've commented about your concerns on the original API proposal: https://github.com/dotnet/corefx/issues/31902.

We should continue the discussion there, to help keep it in one location.

@hamish-rose hamish-rose deleted the midpointrounding-modes-#31902 branch February 12, 2019 19:22
Dotnet-GitSync-Bot pushed a commit to Dotnet-GitSync-Bot/corert that referenced this pull request Feb 12, 2019
…0815)

* add new rounding modes to MidpointRounding.cs

new modes added to enum
implemented ToZero for double in Math.cs

* ToZero implementation

* implement double and float rounding modes

* updating rounding implementation

now round inline with DecCalc internal round implementation

* small bug fix

also replace var to make things obvious

* update implementation - floor/ceil

code review feedback

* review feedback

add comments, update MathF with floor/ceil

* code review feedback

 - fix comments
 - replace ifelse with switch
 - remove RoundingMode enum from DecCalc

* exclude outdated corefx test

Signed-off-by: dotnet-bot <dotnet-bot@microsoft.com>
jkotas pushed a commit to dotnet/corert that referenced this pull request Feb 13, 2019
…0815)

* add new rounding modes to MidpointRounding.cs

new modes added to enum
implemented ToZero for double in Math.cs

* ToZero implementation

* implement double and float rounding modes

* updating rounding implementation

now round inline with DecCalc internal round implementation

* small bug fix

also replace var to make things obvious

* update implementation - floor/ceil

code review feedback

* review feedback

add comments, update MathF with floor/ceil

* code review feedback

 - fix comments
 - replace ifelse with switch
 - remove RoundingMode enum from DecCalc

* exclude outdated corefx test

Signed-off-by: dotnet-bot <dotnet-bot@microsoft.com>
tannergooding pushed a commit to dotnet/corefx that referenced this pull request Mar 4, 2019
#33297)

* add tests for new midpointrounding modes

new tests added for Math.cs double Round methods

* add tests for float, decimal

convert to theory

* Update tests

tests now reflect correct rounding behavior

* add tests for existing modes

* code review feedback

- refactor tests to use member data
- move MathF tests to MathF.netcoreapp.cs
- add tests for new modes with digits and special cases

* move math tests to mathtests.netcoreapp.cs

netcoreapp specific tests moved
picenka21 pushed a commit to picenka21/runtime that referenced this pull request Feb 18, 2022
…0815)

* add new rounding modes to MidpointRounding.cs

new modes added to enum
implemented ToZero for double in Math.cs

* ToZero implementation

* implement double and float rounding modes

* updating rounding implementation

now round inline with DecCalc internal round implementation

* small bug fix

also replace var to make things obvious

* update implementation - floor/ceil

code review feedback

* review feedback

add comments, update MathF with floor/ceil

* code review feedback

 - fix comments
 - replace ifelse with switch
 - remove RoundingMode enum from DecCalc

* exclude outdated corefx test


Commit migrated from dotnet/coreclr@3397472
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[Proposal] Expose additional MidpointRounding modes
6 participants