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
Related
For example,
double a = 2.55;
double b = Math.Round(a, 1); // expected result is 2.5
Console.WriteLine(b); // 2.6
The reason we expect 2.5 there is that the closest 64-bit IEEE 754 float to 2.55 is exactly 2.54999999999999982236431605997495353221893310546875, so whether we're using MidpointRounding.ToEven or MidpointRounding.AwayFromZero the value should round down to 2.5.
On the other hand, the "F" format specifier seems to handle the rounding correctly.
double a = 2.55;
Console.WriteLine($"{a:F1}"); // 2.5
Edit: It looks like the .NET team is tracking basically the same issue with Math.Round here. According to this, the issue might be addressed in the upcoming .NET 7 but it's not certain.
The answer to your actual question "Does Math.Round in .NET round certain values incorrectly?" is: Yes. (Well, Microsoft would probably argue that this behaviour is defined, and is therefore correct.)
The reason for this is described in the documentation for Math.Round():
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
10^digits, and the multiplication operation in this case suffers from a
loss of precision.
We can test this:
double a = 2.55;
double c = a * Math.Pow(10, 1); // "a * 10.0" gives the same result.
Console.WriteLine(a.ToString("f16"));
Console.WriteLine(c.ToString("f16"));
The output is:
2.5499999999999998
25.5000000000000000
You can see that the value after multiplication by 10^1 is 25.5, which will be rounded up in the next step of the rounding algorithm.
You can look at the actual implementation here.
It's a bit fiddly, but the answer is really "something something rounding something" ;)
The default implementation for rounding in .NET is "Round half to even" which is "Bankers Rounding". This means that mid-point values are rounded towards the nearest even number.
Math.Round(value,decimal) works as expected. You define your value as 2.55 and thought you are rounding 2.55 aka 2.54999999... in IEEE 754 but this is false. Rounding with decimal apply a power of 10 to the rounding. So your 2.55 with 1 decimal is apply a single power of 10 so it become 25.5 which is perfectly represented as 25.5 in IEEE 754. Then a rounding become 26.0 then it divide by the factor back to 2.6 hence your results.
Because 2.55 is a midway value, it uses the convention when rounding. Default this is "To Even". So in this case it rounds to the closest even, 2.6.
If you use AwayFromZero it will do the same in this case, if you rounded 2.65 it would give different results.
Strange, I get the same result with the string formatter... ? Maybe it's a cultural setting. See also Convert Double to String: Culture specific
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
I'm attempting to truncate a series of double-precision values in C#. The following value fails no matter what rounding method I use. What is wrong with this value that causes both of these methods to fail? Why does even Math.Round fail to correctly truncate the number? What method can be used instead to correctly truncate such values?
The value :
double value = 0.61740451388888251;
Method 1:
return Math.Round(value, digits);
Method 2:
double multiplier = Math.Pow(10, decimals)
return Math.Round(value * multiplier) / multiplier;
Fails even in VS watch window!
Double is a floating binary point type. They are represented in binary system (like 11010.00110). When double is presented in decimal system it is only an approximation as not all binary numbers have exact representation in decimal system. Try for example this operation:
double d = 3.65d + 0.05d;
It will not result in 3.7 but in 3.6999999999999997. It is because the variable contains a closest available double.
The same happens in your case. Your variable contains closest available double.
For precise operations double/float is not the most fortunate choice.
Use double/float when you need fast performance or you want to operate on larger range of numbers, but where high precision is not required. For instance, it is perfect type for calculations in physics.
For precise decimal operations use, well, decimal.
Here is an article about float/decimal: http://csharpindepth.com/Articles/General/FloatingPoint.aspx
If you need a more exact representation of the number you might have to use the decimal type, which has more precision but smaller range (it's usually used financial calculations).
More info on when to use each here: https://stackoverflow.com/a/618596/1373170
According to this online tool which gives the binary representation of doubles, the two closest double values to 0.62 are:
6.19999999999999995559107901499E-1 or 0x3FE3D70A3D70A3D7
link
6.20000000000000106581410364015E-1 or 0x3FE3D70A3D70A3D8
link
I'm not sure why neither of these agree with your value exactly, but like the others said, it is likely a floating point representation issue.
I think you are running up against the binary limit of a double-precision float (64 bits). From http://en.wikipedia.org/wiki/Double-precision_floating-point_format, a double only gives between 15-17 significant digits.
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
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.