Math.Round Midpointrounding.RoundToEven to 2dp why is it rounding to odd - c#

Math.Round(35.035, 2, MidpointRounding.ToEven) // returns: 35.03
The above as I understand it should round the number to two decimal places. The number is halfway between two numbers 35.03 and 35.04.
So I specified round to even so as far as I understand it this means the last decimal place should be even so I was expecting it to round to the nearest even being 35.04.
Could someone please explain to me why it's rounding to an odd on the last decimal place?

You need to m or M when you are representing decimals otherwise it will lead to approximation errors.
Try Math.Round(35.035m, 2, MidpointRounding.ToEven)
Suffixes for different data types
float f = 1.2f;
double d = 1.2d;
uint u = 2u;
long l = 2L;
ulong ul = 2UL;
decimal m = 2m;

Please see, http://msdn.microsoft.com/en-us/library/f5898377.aspx
there you find the following:
Notes to Callers
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, MidpointRounding) method may not appear to round midpoint values as specified by the mode parameter. 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.
Using Math.Round with decimal as first value solves the problem, but for the cost of type conversion.

Related

C#: Math Round() results different values for different decimals

I am getting a different value when using Math.Round with different decimal points, can someone correct me where am I going wrong.
double r = Math.Round(1.235, 2,MidpointRounding.AwayFromZero);
double t = Math.Round(19.185, 2,MidpointRounding.AwayFromZero);
r results in 1.24 and whereas t results in 19.18, the expected result for t is 19.19.
Accroding to Math.Round, Notes to Callers section
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, MidpointRounding) method may not appear to round midpoint
values as specified by the mode parameter. This is illustrated in the
following example, where 2.135 is rounded to 2.13 instead of 2.14.
This sounds like your exact case, due the loss of precision 19.185 is rounded to 19.18 instead of 19.19. You can display values using the G17 format specifier to see all significant digits of precision
Console.WriteLine(1.235.ToString("G17"));
Console.WriteLine(19.185.ToString("G17"));
The output will be something like that
1.2350000000000001
19.184999999999999
As possible workaround, you can use decimal values with better precision
var r = Math.Round(1.235m, 2, MidpointRounding.AwayFromZero);
var t = Math.Round(19.185m, 2, MidpointRounding.AwayFromZero);
The result will be expected

Weird behaviour of decimal.Round

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

Math.Round bug 81.725 MidpointRounding.AwayFromZero = 81.72

I have a bug with Math.Round with no explanation.
When I make
Math.Round(81.725, 2, MidpointRounding.AwayFromZero)
The result is 81,72 but when I make the same with Decimal.Round
Decimal.Round(81.725M, 2, MidpointRounding.AwayFromZero)
The result is 81,73
I d'ont understand why, have you an solution to use Math.Round systematically ?
You shouldn't be talking about bugs before understanding how double works, and the differences with decimal that explain the behavior you are seeing.
A double is the best approximation of a real number with the following structure:
number = sign * mantissa * 2 ^ exponent
Therefore, the number 81.725, when represented as a double, is really:
1 * 2875442808959795 * 2^-45 = 81,724999999999994315658113919199
Now you should understand why Math.Round(81.725, 2) resolves to 81.72 and not 81.73.
This doesn't happen with decimal because decimal, contrary to double, can exactly represent 81.725. This is due to the fact that the scaling factor in decimal is a power of 10.
This added precision obviously comes at a cost, in speed, space and range. When to choose one type or the other is well explained in a link to another SO question provided in comments.
The M suffix on second assignment below determines data type:
Math.Round(81.725, 2, MidpointRounding.AwayFromZero); // double, returns 81.72
Math.Round(81.725M, 2, MidpointRounding.AwayFromZero); // decimal, returns 81.73
Decimal.Round(81.725M, 2, MidpointRounding.AwayFromZero); // decimal, returns 81.73
By using that suffix, the given data type on second assignment treated as decimal instead of double, makes the rounding more precise.
The MSDN documentation of Math.Round already mention this difference:
Notes to Callers:
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)
method may not appear to round midpoint values to the nearest even
integer.
And there is further explanation for Decimal.Round:
The behavior of this method follows IEEE Standard 754, section 4. This
kind of rounding is sometimes called round half to even or banker's
rounding. It minimizes rounding errors that result from consistently
rounding a midpoint value in a single direction. It is equivalent to
calling the Round(Decimal, MidpointRounding) method with a mode
argument of MidpointRounding.ToEven.
In short, decimal uses IEEE-754 which uses consistent rounding for midpoint value, while double (and float) uses floating-point which has no finite binary representation.
Other reference:
Declaration suffix for decimal type

Rounding the SIGNIFICANT digits in a double, not to decimal places [duplicate]

This question already has answers here:
Round a double to x significant figures
(17 answers)
Closed 7 years ago.
I need to round significant digits of doubles. Example
Round(1.2E-20, 0) should become 1.0E-20
I cannot use Math.Round(1.2E-20, 0), which returns 0, because Math.Round() doesn't round significant digits in a float, but to decimal digits, i.e. doubles where E is 0.
Of course, I could do something like this:
double d = 1.29E-20;
d *= 1E+20;
d = Math.Round(d, 1);
d /= 1E+20;
Which actually works. But this doesn't:
d = 1.29E-10;
d *= 1E+10;
d = Math.Round(d, 1);
d /= 1E+10;
In this case, d is 0.00000000013000000000000002. The problem is that double stores internally fractions of 2, which cannot match exactly fractions of 10. In the first case, it seems C# is dealing just with the exponent for the * and /, but in the second case it makes an actual * or / operation, which then leads to problems.
Of course I need a formula which always gives the proper result, not only sometimes.
Meaning I should not use any double operation after the rounding, because double arithmetic cannot deal exactly with decimal fractions.
Another problem with the calculation above is that there is no double function returning the exponent of a double. Of course one could use the Math library to calculate it, but it might be difficult to guarantee that this has always precisely the same result as the double internal code.
In my desperation, I considered to convert a double to a string, find the significant digits, do the rounding and convert the rounded number back into a string and then finally convert that one to a double. Ugly, right ? Might also not work properly in all case :-(
Is there any library or any suggestion how to round the significant digits of a double properly ?
PS: Before declaring that this is a duplicate question, please make sure that you understand the difference between SIGNIFICANT digits and decimal places
The problem is that double stores internally fractions of 2, which cannot match exactly fractions of 10
That is a problem, yes. If it matters in your scenario, you need to use a numeric type that stores numbers as decimal, not binary. In .NET, that numeric type is decimal.
Note that for many computational tasks (but not currency, for example), the double type is fine. The fact that you don't get exactly the value you are looking for is no more of a problem than any of the other rounding error that exists when using double.
Note also that if the only purpose is for displaying the number, you don't even need to do the rounding yourself. You can use a custom numeric format to accomplish the same. For example:
double value = 1.29e-10d;
Console.WriteLine(value.ToString("0.0E+0"));
That will display the string 1.3E-10;
Another problem with the calculation above is that there is no double function returning the exponent of a double
I'm not sure what you mean here. The Math.Log10() method does exactly that. Of course, it returns the exact exponent of a given number, base 10. For your needs, you'd actually prefer Math.Floor(Math.Log10(value)), which gives you the exponent value that would be displayed in scientific notation.
it might be difficult to guarantee that this has always precisely the same result as the double internal code
Since the internal storage of a double uses an IEEE binary format, where the exponent and mantissa are both stored as binary numbers, the displayed exponent base 10 is never "precisely the same as the double internal code" anyway. Granted, the exponent, being an integer, can be expressed exactly. But it's not like a decimal value is being stored in the first place.
In any case, Math.Log10() will always return a useful value.
Is there any library or any suggestion how to round the significant digits of a double properly ?
If you only need to round for the purpose of display, don't do any math at all. Just use a custom numeric format string (as I described above) to format the value the way you want.
If you actually need to do the rounding yourself, then I think the following method should work given your description:
static double RoundSignificant(double value, int digits)
{
int log10 = (int)Math.Floor(Math.Log10(value));
double exp = Math.Pow(10, log10);
value /= exp;
value = Math.Round(value, digits);
value *= exp;
return value;
}

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

Categories

Resources