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
Related
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
I am working on an application that calculates ppm and checks if it is greater than a certain threshold. I recently found out the precision error of floating point calculation.
double threshold = 1000.0;
double Mass = 0.000814;
double PartMass = 0.814;
double IncorrectPPM = Mass/PartMass * 1000000;
double CorrectedPPM = (double)((decimal)IncorrectPPM);
Console.WriteLine("Mass = {0:R}", Mass);
Console.WriteLine("PartMass = {0:R}", PartMass);
Console.WriteLine("IncorrectPPM = {0:R}", IncorrectPPM);
Console.WriteLine("CorrectedPPM = {0:R}", CorrectedPPM);
Console.WriteLine("Is IncorrectPPM over threshold? " + (IncorrectPPM > threshold) );
Console.WriteLine("Is CorrectedPPM over threshold? " + (CorrectedPPM > threshold) );
The above codes would generate the following outputs:
Mass = 0.000814
PartMass = 0.814
IncorrectPPM = 1000.0000000000002
CorrectedPPM = 1000
Is IncorrectPPM over threshold? True
Is CorrectedPPM over threshold? False
As you could see, the calculated ppm 1000.0000000000002 has a trailing 2 which causes my application to falsely judge that the value is over the 1000 threshold. All inputs to the calculation are given to me as double values so I couldn't use decimal calculation. In addition, I couldn't round the calculated value since it could cause the threshold comparison to be incorrect.
I noticed that if I cast the calculated double number into decimal and then cast it back to double again the 1000.0000000000002 number got corrected into 1000.
Question:
Does anyone know how the computer know in this case that it should change the 1000.0000000000002 value to 1000 when casting to decimal?
Can I rely on this trick to avoid the precision issue of double calculation?
Does anyone know how the computer know in this case that it should change the 1000.0000000000002 value to 1000 when casting to decimal?
First of all the cast:
(decimal)IncorrectPPM
is equivalent to the constructor call, see here on SO:
new decimal(IncorrectPPM)
If you read on the MSDN page about the decimal constructor you will find the following remark:
This constructor rounds value to 15 significant digits using rounding to nearest. This is done even if the number has more than 15 digits and the less significant digits are zero.
That means
1000.0000000000002
^ ^
15th 17th significant digit
will be rounded to:
1000.00000000000
^
15th significant digit
Can I rely on this trick to avoid the precision issue of double calculation?
No, you can't imagine the following result when calculating IncorrectPPM, see online at ideone:
1000.000000000006
^
15th significant digit
will be rounded to:
1000.00000000001
^
15th significant digit
To resolve your issue about the comparison with your threshold, you have in general 2 possibilities.
Add a little epsilon to your threshold, e.g.:
double threshold = 1000.0001;
Change your cast of IncorrectPPM from:
double CorrectedPPM = (double)((decimal)IncorrectPPM);
to:
/* 1000.000000000006 will be rounded to 1000.0000 */
double CorrectedPPM = Math.Round(IncorrectPPM, 4);
with the Math.Round() function, but be careful Math.Round() means fractional not significant digits
Either your threshold is too small or you round up the result to a certain amount of decimals. The more decimals the more precise your evaluation.
double threshold = 1000.0;
double Mass = 0.000814;
double PartMass = 0.814;
double IncorrectPPM = Mass/PartMass * 1000000;
double CorrectedPPM = Math.Round(IncorrectPPM,4); // 1000.0000 will output 1000
You can be as precise as you want.
There is a fundamental difference in decimal and double in terms of precision and that is rooted in the way the number is stored:
decimal is a fixed point number, which means it provides a fixed number of decimals. When you do calculations with fixed point numbers, you need to perform rounding operations quite frequently. For example if you divide the number 100.0006 by 10, you have to round the value to 10.0001, given your decimal point is fixed with four decimals. This rounding error is usually fine in economical calculations, since you have enough decimals available and the point is fixed to decimal places (i.e. to the base of 10).
double is a floating point number, which is stored differently. It has a sign (defining if the number is positive or negative), a mantissa and an expontent. The mantissa is a number between 0 and 1 with a certain amount of significant digits (that is the actual precision we are talking about). The exponent defines where the decimal point is located in the mantissa. So the decimal point moves in the mantissa (hence floating point). Floating point numbers are great for calculating measured values and doing scientific calculations, since the precision stays the same through calculations and rounding errors can be minimized.
The fundamental problem that you are facing is that you can only rely on the value of a floating point number within its precision. That includes the actual precision of the mantissa and the accumulated rounding error through your calculation. Additionally, since the mantissa is stored in binary format and you convert it into a decimal number when you compare it with 1000, you have an additional imprecision through this conversion. You usually don't have this problem in a fixed point number, since the significant decimal digits are clearly defined (and you accept the rounding error during calculation).
In practice this means that when you compare floating point numbers you have to always be aware how many digits are significant. Note that means the total number of digits (i.e. the ones before and after the decimal point). Once you know the precision (or choose one that works for you and provides enough margin of error), you can decide to how many digits you need to round your value for your comparison. Let's say according to your data a precision of six decimal digits is appropriate, you can compare against your threshold like this:
bool isWithinThreshold = Math.Round(PPM, 6) > 1000D;
Note that you only round for the comparison, you don't round your value.
What you are doing with a conversion to decimal is to implicitly apply the precision of the decimal to the floating point number. That's nothing more than the preferred solution of rounding, just with less control over the precision and an additional performance impact. So no, a conversion to decimal is not reliable, especially with large numbers.
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
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(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.