Weird behaviour of decimal.Round - c#

When using decimal, why the behaviour of rounding is always the same?
No matter if I use MidpointRounding.AwayFromZero or not it always gives 1.04. In the first case, shouldn't the output be 1.03?
Console.WriteLine(decimal.Round(1.035m, 2));
Console.WriteLine(decimal.Round(1.035m, 2, MidpointRounding.AwayFromZero));
https://github.com/dotnet/corefx/blob/664d98b3dc83a56e1e6454591c585cc6a8e19b78/src/Common/src/CoreLib/System/Decimal.cs#L612
https://github.com/dotnet/corefx/blob/61d792e202d039c304c4f04ad816a57688f32fd4/src/Common/src/CoreLib/System/Decimal.DecCalc.cs#L2429-L2444

No:
This method [Round(decimal d, int decimals)] is equivalent to calling the Round(Decimal, Int32, MidpointRounding) method with a mode argument of MidpointRounding.ToEven.
When d is exactly halfway between two rounded values, the result is the rounded value that has an even digit in the far right decimal position. For example, when rounded to two decimals, the value 2.345 becomes 2.34 and the value 2.355 becomes 2.36.
So when rounding 1.035 to even, it becomes 1.04 because 4 is even and 3 is not.

The default rounding method is MidpointRounding.ToEven, so when choosing whether to round to either 1.03 or 1.04, it chooses the one with the even number at the end, 1.04.

As the MSDN said:
public static decimal Round(
decimal d,
int decimals
)
decimals : A value from 0 to 28 that specifies the number of decimal places to
round to.
You want to round at 2 places, it must 1.04

This is seems expected behavior to me when rounding up a decimal.
Ex:
1.035 => 1.040 produce with two decimal place 1.04
1.033 => 1.030 produce with two decimal place 1.03

Isn't the default rounding to round up? in this case the 5 gets rounded up and carried over...
By default, the Round method uses the rounding to nearest convention. The following table lists the overloads of the Round method and the rounding convention that each uses.
https://learn.microsoft.com/en-us/dotnet/api/system.math.round?view=netframework-4.7.2

Related

Math.Round returning a rounded up for odd values but rounds down for even

I am trying to found a float using math round
I found the following
0.5 --> 0
1.5 --> 2
2.5 --> 2
3.5 --> 4
and so on.
I believe this is due to floating point error, but not quite sure how.
How can I get around this so even numbers round properly?
From documentation;
The integer nearest a. If the fractional component of a is halfway
between two integers, one of which is even and the other odd, then the
even number is returned. Note that this method returns a Double
instead of an integral type.
Math.Round method has some overloads that takes MidpointRounding as a parameter which you can specify the rounding value if it is midway between two numbers.
AwayFromZero
When a number is halfway between two others, it is rounded toward the
nearest number that is away from zero.
ToEven
When a number is halfway between two others, it is rounded toward the
nearest even number.
You could use this one, to overcome that you have stated:
Math.Round(value, MidpointRounding.AwayFromZero);
Using the above:
When a number is halfway between two others, it is rounded toward the
nearest number that is away from zero.
For further documentation about the MidpointRounding enumeration, please have a look here.
You may try like this
Math.Round(value, MidpointRounding.AwayFromZero);
From MSDN
If the fractional component of a is halfway between two integers, one
of which is even and the other odd, then the even number is returned.
Also to mention one important point which I think is good to mention is that Microsoft has followed the IEEE 754 standard. This is also mentioned in MSDN for Math.Round under Remarks which says:
Round to nearest, ties to even – rounds to the nearest value; if the number falls midway it is rounded to the nearest value with an
even (zero) least significant bit, which occurs 50% of the time; this
is the default for binary floating-point and the recommended default
for decimal.
Round to nearest, ties away from zero – rounds to the nearest value; if the number falls midway it is rounded to the nearest value
above (for positive numbers) or below (for negative numbers); this is
intended as an option for decimal floating point.
This is known as bankers rounding (round to even). You can read more about it here. This is a .NET Framework feature and is working as designed.

Math.Round number with 3 decimal

Could someone explain to me why Math.Round(1.565,2) = 1.56 and not 1.57?
Math.Round() uses the Banker's rounding algorithm. Basically it rounds anything ending in 5 towards the even number. So 1.565 rounds down (towards the 6) but 1.575 rounds up (towards the 8). This avoids the rounding errors from accumulating if you add many rounded numbers.
See: Math.Round Method (Double, Int32)
Because of the loss of precision that can result from representing
decimal values as floating-point numbers or performing arithmetic
operations on floating-point values, in some cases the Round(Double,
Int32) method may not appear to round midpoint values to the nearest
even value in the digits decimal position. This is illustrated in the
following example, where 2.135 is rounded to 2.13 instead of 2.14.
This occurs because internally the method multiplies value by
10digits, and the multiplication operation in this case suffers from a
loss of precision.
And the example is:
public static void Main()
{
double[] values = { 2.125, 2.135, 2.145, 3.125, 3.135, 3.145 };
foreach (double value in values)
Console.WriteLine("{0} --> {1}", value, Math.Round(value, 2));
}
The double representation of 1.565 is not exact, and is slightly less than that amount - 1.564999999999999946709294817992486059665679931640625 or thereabouts. So when rounded to only two decimal places, it gets rounded downwards.
Even if you were to use a decimal (which represents decimal fractions exactly) and try Math.round(1.565M,2), this would also get rounded down, as Math.round(number, decimalPlaces) rounds values halfway between one value and the next towards the one whose last digit is even - this is sometimes called banker's rounding. So Math.round(1.575M,2), for example, would round upwards.
This occurs because internally the method multiplies value by 10digits, and the multiplication operation in this case suffers from a loss of precision. http://msdn.microsoft.com/en-us/library/system.math.round.aspx#Round5_Example and
http://msdn.microsoft.com/en-us/library/75ks3aby.aspx

Is C# Decimal Rounding Inconsistent?

I've been fighting decimal precision in C# coming from a SQL Decimal (38,30) and I've finally made it all the way to a rounding oddity. I know I'm probably overlooking the obvious here, but I need a little insight.
The problem I'm having is that C# doesn't produce what I would consider to be consistent output.
decimal a = 0.387518769125m;
decimal b = 0.3875187691250002636113061835m;
Console.WriteLine(Math.Round(a, 11));
Console.WriteLine(Math.Round(b, 11));
Console.WriteLine(Math.Round(a, 11) == Math.Round(b, 11));
Yields
0.38751876912
0.38751876913
False
Uhh, 0.38751876913? Really? What am I missing here?
From MSDN:
If the digit in the decimals position is odd, it is changed to an even digit. Otherwise, it is left unchanged.
Why am I seeing inconsistent results? The additional precision isn't changing the 'digit in the decimals position'...
From MSDN:
If there is a single non-zero digit in d to the right of the decimals decimal position and its value is 5, the digit in the decimals position is rounded up if it is odd, or left unchanged if it is even. If d has fewer fractional digits than decimals, dis returned unchanged.
In your first case
decimal a = 0.387518769125m;
Console.WriteLine(Math.Round(a, 11));
there is a single digit to the right of the 11th place, and that number is 5. Therefore, since position 11 is even, it is left unchanged. Thus, you get
0.38751876912
In your second case
decimal b = 0.3875187691250002636113061835m;
Console.WriteLine(Math.Round(b, 11));
there is not a single digit to the right of the 11th place. Therefore, this is straight up grade-school rounding; you round up if the next digit is greater than 4, otherwise you round down. Since the digit to the right of the 11th place is more than 4 (it's a 5), we round up so you see
0.38751876913
Why am I seeing inconsistent results?
You're not. The results are completely consistent with the documentation.
From MSDN - Math.Round Method (Decimal, Int32):
If there is a single non-zero digit in d to the right of the decimals decimal position and its value is 5, the digit in the decimals position is rounded up if it is odd, or left unchanged if it is even. If d has fewer fractional digits than decimals, d is returned unchanged.
The behavior of this method follows IEEE Standard 754, section 4. This kind of rounding is sometimes called rounding to nearest, or banker's rounding. It minimizes rounding errors that result from consistently rounding a midpoint value in a single direction.
Note the use of single non-zero digit. This corresponds to your first examples, but not the second.
And:
To control the type of rounding used by the Round(Decimal, Int32) method, call the Decimal.Round(Decimal, Int32, MidpointRounding) overload.
The part "single non-zero digit in d to the right of the decimals decimal position and its value is 5" explains the result. Only when the part to round is exactly 0,5 the rounding rule comes into play.
Let's shift both numbers over 11 digits to the left:
38751876912.5
38751876912.50002636113061835
Using banker's rounding, we round the first one down. Under every midpoint-rounding system, we round the second number up (because it is not at the midpoint).
.Net is doing exactly what we'd expect it to.

Math.Round vs String.Format

I need double value to be rounded to 2 digits.
What is preferrable?
String.Format("{0:0.00}", 123.4567); // "123.46"
Math.Round(123.4567, 2) // "123.46"
Math.Round(double,digits) with digits>0 is conceptually very unclean. But I think it should never be used. double is a binary floating point number and thus has no well-defined concept of decimal digits.
I recommend using string.Format, or just ToString("0.00") when you only need to round for decimal display purposes, and decimal.Round if you need to round the actual number(for example using it in further calculations).
Note: With decimal.Round you can specify a MidpointRounding mode. It's common to want AwayFromZero rounding, not ToEven rounding.
With ToEven rounding 0.005m gets rounded to 0.00 and 0.015 gets rounded to 0.02. That's not what most people expect.
Comparisons:
ToEven: 3.75 rounds to 3.8
ToEven: 3.85 rounds to 3.8 (That's not what most people expect)
AwayFromZero: 3.75 rounds to 3.8
AwayFromZero: 3.85 rounds to 3.9
for more information see: https://msdn.microsoft.com/en-us/library/system.math.round.aspx
They are different functions, if you need the output to be displayed, use the first one (that also forces decimals to appear). You will avoid the overhead of the inevitable .ToString() that will occur if the variable is of type double.
Note that the second one rounds the number but if it's an integer result, you will get just the integer (ie: 7 vs 7.00)
That depends on what you want to do with it.
String.Format will return a string, Math.Round(double) will return a double.
the former outputs a string, the latter a double. What's your use of the result ? The answer of this will give the answer of your question.
If you want to return this value as a string then String.Format is better and if you want to return this value as a double in that case Math.Round is better. It totally depends on your requirement.
Math.Round will not add any decimal places if there aren't any to begin with. String.Format will.
e.g.: Math.Round(2) returns 2;
String.Format("{0:0.00}",2) returns 2.00;

Rounding in C# to 2 decimal places from higher precision number

How would I round the following number in C# to obtain the following results.
Value : 500.0349999999
After Rounding to 2 digits after decimal : 500.04
I have tried Math.Round(Value,2, MidpointRounding.AwayFromZero); //but it returns the value 500.03 instead of 500.04
You're asking for non-standard rounding rules. The value 500.03499999999 rounded to the nearest hundredth should be 500.03. Since the thousandths digit is less than 5, the hundredths digit remains unchanged.
One way I can see to achieve your desired result is to round the number to the decimal place one smaller than what you ultimately want. Then round that result to the precision you want.
In your example, you would round the value to 3 decimal places resulting in 500.035. You would then round that to 2 decimal places which should result in 500.04 (assuming you're using MidpointRounding.AwayFromZero.
Hope that helps.
You could make it needlessly complex and wrap a variable Round(Decimal, Int32) function in a for loop. Decrement the Int32 to work it's way back to the decimal precision needed. It's a bit of work but like Eric said, you are asking for non-standard rounding rules.
Extra details: http://msdn.microsoft.com/en-us/library/ms131274.aspx

Categories

Resources