Does Math.Round in .NET round certain values incorrectly? - c#

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

Related

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 to specfic digits fails with this double-precision value

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.

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.

Decimal.Round default setting for MidpointRounding [duplicate]

This question already has answers here:
Why does .NET use banker's rounding as default?
(5 answers)
Closed 9 years ago.
The following applies:
var rounded = Decimal.Round(7.635m, 2);
//rounded: 7.63
This, to me, is wrong and unexpected behavior. I would assume the value of rounded to be 7.64.
To achieve this, I can do:
var rounded = Decimal.Round(7.635m, 2, MidpointRounding.AwayFromZero);
//rounded: 7.64
How can this not be the default behavior of Decimal.Round? Any good reason for this?
How can this not be the default behavior of Decimal.Round? Any good
reason for this?
If you look at the documentation of Decimal.Round Method (Decimal)
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.
From Math.Round(Decimal, Int32) Method
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.
This method is equivalent to calling the Round method with a mode
argument of MidpointRounding.ToEven. 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 reason is they implemented a method following IEEE Standard 754, section 4.
This is called rounding to nearest or sometimes bankers rounding.
It's just one of many ways to do rounding and they choose this one.
See: http://en.wikipedia.org/wiki/Bankers_rounding
And for more information: Why does .NET use banker's rounding as default?

Round-twice error in .NET's Double.ToString method

Mathematically, consider for this question the rational number
8725724278030350 / 2**48
where ** in the denominator denotes exponentiation, i.e. the denominator is 2 to the 48th power. (The fraction is not in lowest terms, reducible by 2.) This number is exactly representable as a System.Double. Its decimal expansion is
31.0000000000000'49'73799150320701301097869873046875 (exact)
where the apostrophes do not represent missing digits but merely mark the boudaries where rounding to 15 resp. 17 digits is to be performed.
Note the following: If this number is rounded to 15 digits, the result will be 31 (followed by thirteen 0s) because the next digits (49...) begin with a 4 (meaning round down). But if the number is first rounded to 17 digits and then rounded to 15 digits, the result could be 31.0000000000001. This is because the first rounding rounds up by increasing the 49... digits to 50 (terminates) (next digits were 73...), and the second rounding might then round up again (when the midpoint-rounding rule says "round away from zero").
(There are many more numbers with the above characteristics, of course.)
Now, it turns out that .NET's standard string representation of this number is "31.0000000000001". The question: Isn't this a bug? By standard string representation we mean the String produced by the parameterles Double.ToString() instance method which is of course identical to what is produced by ToString("G").
An interesting thing to note is that if you cast the above number to System.Decimal then you get a decimal that is 31 exactly! See this Stack Overflow question for a discussion of the surprising fact that casting a Double to Decimal involves first rounding to 15 digits. This means that casting to Decimal makes a correct round to 15 digits, whereas calling ToSting() makes an incorrect one.
To sum up, we have a floating-point number that, when output to the user, is 31.0000000000001, but when converted to Decimal (where 29 digits are available), becomes 31 exactly. This is unfortunate.
Here's some C# code for you to verify the problem:
static void Main()
{
const double evil = 31.0000000000000497;
string exactString = DoubleConverter.ToExactString(evil); // Jon Skeet, http://csharpindepth.com/Articles/General/FloatingPoint.aspx
Console.WriteLine("Exact value (Jon Skeet): {0}", exactString); // writes 31.00000000000004973799150320701301097869873046875
Console.WriteLine("General format (G): {0}", evil); // writes 31.0000000000001
Console.WriteLine("Round-trip format (R): {0:R}", evil); // writes 31.00000000000005
Console.WriteLine();
Console.WriteLine("Binary repr.: {0}", String.Join(", ", BitConverter.GetBytes(evil).Select(b => "0x" + b.ToString("X2"))));
Console.WriteLine();
decimal converted = (decimal)evil;
Console.WriteLine("Decimal version: {0}", converted); // writes 31
decimal preciseDecimal = decimal.Parse(exactString, CultureInfo.InvariantCulture);
Console.WriteLine("Better decimal: {0}", preciseDecimal); // writes 31.000000000000049737991503207
}
The above code uses Skeet's ToExactString method. If you don't want to use his stuff (can be found through the URL), just delete the code lines above dependent on exactString. You can still see how the Double in question (evil) is rounded and cast.
ADDITION:
OK, so I tested some more numbers, and here's a table:
exact value (truncated) "R" format "G" format decimal cast
------------------------- ------------------ ---------------- ------------
6.00000000000000'53'29... 6.0000000000000053 6.00000000000001 6
9.00000000000000'53'29... 9.0000000000000053 9.00000000000001 9
30.0000000000000'49'73... 30.00000000000005 30.0000000000001 30
50.0000000000000'49'73... 50.00000000000005 50.0000000000001 50
200.000000000000'51'15... 200.00000000000051 200.000000000001 200
500.000000000000'51'15... 500.00000000000051 500.000000000001 500
1020.00000000000'50'02... 1020.000000000005 1020.00000000001 1020
2000.00000000000'50'02... 2000.000000000005 2000.00000000001 2000
3000.00000000000'50'02... 3000.000000000005 3000.00000000001 3000
9000.00000000000'54'56... 9000.0000000000055 9000.00000000001 9000
20000.0000000000'50'93... 20000.000000000051 20000.0000000001 20000
50000.0000000000'50'93... 50000.000000000051 50000.0000000001 50000
500000.000000000'52'38... 500000.00000000052 500000.000000001 500000
1020000.00000000'50'05... 1020000.000000005 1020000.00000001 1020000
The first column gives the exact (though truncated) value that the Double represent. The second column gives the string representation from the "R" format string. The third column gives the usual string representation. And finally the fourth column gives the System.Decimal that results from converting this Double.
We conclude the following:
Round to 15 digits by ToString() and round to 15 digits by conversion to Decimal disagree in very many cases
Conversion to Decimal also rounds incorrectly in many cases, and the errors in these cases cannot be described as "round-twice" errors
In my cases, ToString() seems to yield a bigger number than Decimal conversion when they disagree (no matter which of the two rounds correctly)
I only experimented with cases like the above. I haven't checked if there are rounding errors with numbers of other "forms".
So from your experiments, it appears that Double.ToString doesn't do correct rounding.
That's rather unfortunate, but not particularly surprising: doing correct rounding for binary to decimal conversions is nontrivial, and also potentially quite slow, requiring multiprecision arithmetic in corner cases. See David Gay's dtoa.c code here for one example of what's involved in correctly-rounded double-to-string and string-to-double conversion. (Python currently uses a variant of this code for its float-to-string and string-to-float conversions.)
Even the current IEEE 754 standard for floating-point arithmetic recommends, but doesn't require that conversions from binary floating-point types to decimal strings are always correctly rounded. Here's a snippet, from section 5.12.2, "External decimal character sequences representing finite numbers".
There might be an implementation-defined limit on the number of
significant digits that can be converted with correct rounding to and
from supported binary formats. That limit, H, shall be such that H ≥
M+3 and it should be that H is unbounded.
Here M is defined as the maximum of Pmin(bf) over all supported binary formats bf, and since Pmin(float64) is defined as 17 and .NET supports the float64 format via the Double type, M should be at least 17 on .NET. In short, this means that if .NET were to follow the standard, it would be providing correctly rounded string conversions up to at least 20 significant digits. So it looks as though the .NET Double doesn't meet this standard.
In answer to the 'Is this a bug' question, much as I'd like it to be a bug, there really doesn't seem to be any claim of accuracy or IEEE 754 conformance anywhere that I can find in the number formatting documentation for .NET. So it might be considered undesirable, but I'd have a hard time calling it an actual bug.
EDIT: Jeppe Stig Nielsen points out that the System.Double page on MSDN states that
Double complies with the IEC 60559:1989 (IEEE 754) standard for binary
floating-point arithmetic.
It's not clear to me exactly what this statement of compliance is supposed to cover, but even for the older 1985 version of IEEE 754, the string conversion described seems to violate the binary-to-decimal requirements of that standard.
Given that, I'll happily upgrade my assessment to 'possible bug'.
First take a look at the bottom of this page which shows a very similar 'double rounding' problem.
Checking the binary / hex representation of the following floating point numbers shows that that the given range is stored as the same number in double format:
31.0000000000000480 = 0x403f00000000000e
31.0000000000000497 = 0x403f00000000000e
31.0000000000000515 = 0x403f00000000000e
As noted by several others, that is because the closest representable double has an exact value of 31.00000000000004973799150320701301097869873046875.
There are an additional two aspects to consider in the forward and reverse conversion of IEEE 754 to strings, especially in the .NET environment.
First (I cannot find a primary source) from Wikipedia we have:
If a decimal string with at most 15 significant decimal is converted
to IEEE 754 double precision and then converted back to the same
number of significant decimal, then the final string should match the
original; and if an IEEE 754 double precision is converted to a
decimal string with at least 17 significant decimal and then converted
back to double, then the final number must match the original.
Therefore, regarding compliance with the standard, converting a string 31.0000000000000497 to double will not necessarily be the same when converted back to string (too many decimal places given).
The second consideration is that unless the double to string conversion has 17 significant digits, it's rounding behavior is not explicitly defined in the standard either.
Furthermore, documentation on Double.ToString() shows that it is governed by numeric format specifier of the current culture settings.
Possible Complete Explanation:
I suspect the twice-rounding is occurring something like this: the initial decimal string is created to 16 or 17 significant digits because that is the required precision for "round trip" conversion giving an intermediate result of 31.00000000000005 or 31.000000000000050. Then due to default culture settings, the result is rounded to 15 significant digits, 31.00000000000001, because 15 decimal significant digits is the minimum precision for all doubles.
Doing an intermediate conversion to Decimal on the other hand, avoids this problem in a different way: it truncates to 15 significant digits directly.
The question: Isn't this a bug?
Yes. See this PR on GitHub. The reason of rounding twice AFAK is for "pretty" format output but it introduces a bug as you have already discovered here. We tried to fix it - remove the 15 digits precision converting, directly go to 17 digits precision converting. The bad news is it's a breaking change and will break things a lot. For example, one of the test case will break:
10:12:26 Assert.Equal() Failure
10:12:26 Expected: 1.1
10:12:26 Actual: 1.1000000000000001
The fix would impact a large set of existing libraries so finally this PR has been closed for now. However, .NET Core team is still looking for a chance to fix this bug. Welcome to join the discussion.
Truncation is the correct way to limit the precision of a number that will later be rounded, precisely to avoid the double rounding issue.
I have a simpler suspicion: The culprit is likely the pow operator => **;
While your number is exactly representable as a double, for convenience reasons
(the power operator needs much work to work right) the power is calculated
by the exponential function. This is one reason that you can optimize performance
by multiplying a number repeatedly instead of using pow() because pow() is very
expensive.
So it does not give you the correct 2^48, but something slightly incorrect and
therefore you have your rounding problems.
Please check out what 2^48 exactly returns.
EDIT: Sorry, I did only a scan on the problem and give a wrong suspicion. There is
a known issue with double rounding on the Intel processors. Older code use the
internal 80-bit format of the FPU instead of the SSE instructions which is likely
to cause the error. The value is written exactly to the 80bit register and then
rounded twice, so Jeppe has already found and neatly explained the problem.
Is it a bug ? Well, the processor is doing everything right, it is simply the
problem that the Intel FPU internally has more precision for floating-point
operations.
FURTHER EDIT AND INFORMATION:
The "double rounding" is a known issue and explicitly mentioned in "Handbook of Floating-Point Arithmetic" by Jean-Michel Muller et. al. in the chapter "The Need
for a Revision" under "3.3.1 A typical problem : 'double rounding'" at page 75:
The processor being used may offer an internal precision that is wider
than the precision of the variables of the program (a typical example
is the double-extended format available on Intel Platforms, when the
variables of the program are single- precision or double-precision
floating-point numbers). This may sometimes have strange side effects , as
we will see in this section. Consider the C program [...]
#include <stdio.h>
int main(void)
{
double a = 1848874847.0;
double b = 19954562207.0;
double c;
c = a * b;
printf("c = %20.19e\n", c);
return 0;
}
32bit:
GCC 4.1.2 20061115 on Linux/Debian
With Compilerswitch or with -mfpmath=387 (80bit-FPU): 3.6893488147419103232e+19
-march=pentium4 -mfpmath=sse (SSE) oder 64-bit : 3.6893488147419111424e+19
As explained in the book, the solution for the discrepancy is the double rounding with 80 bits and 53 bits.

Categories

Resources