This question already has answers here:
Is floating point math broken?
(31 answers)
Closed 8 years ago.
I'm having problems with a Rounding issue in c#. I would like to round the result of a calculation up to 4 decimals (awayfromzero). If I use Math.Round(variable,...) it rounds down, if I enter the result manually, it rounds up.. I have no idea why..
What am I doing wrong? The result of the below code is:
Rounded: 591.24575 591.2457 - 591.2458
double number1 = 1136.81;
double number2 = 4.00;
double number3 = 2182.257;
double result = (number1 * number2 - number3) / 4;
Console.WriteLine("Rounded: " +result+" " + Math.Round(result, 4, MidpointRounding.AwayFromZero) + " - " + Math.Round(591.24575, 4, MidpointRounding.AwayFromZero));
Console.ReadLine();
If you use my DoubleConverter, you can see the exact value of result:
Console.WriteLine(DoubleConverter.ToExactString(result));
That prints:
591.2457499999999299689079634845256805419921875
... which rounds to 591.2457 when rounded to 4 decimal places. When you just print result, it's printing it already rounded to a certain number of decimal places, which can affect the result if you then (mentally) rounded to 4 DP.
You can see this without any of the "normal" oddities of binary floating point. Consider this code:
decimal exact = 1.2345m;
decimal rounded2 = Math.Round(exact, 2, MidpointRounding.AwayFromZero);
decimal rounded3 = Math.Round(exact, 3, MidpointRounding.AwayFromZero);
decimal rounded3Then2 = Math.Round(rounded3, 2, MidpointRounding.AwayFromZero);
Console.WriteLine(rounded2); // 1.23
Console.WriteLine(rounded3); // 1.235
Console.WriteLine(rounded3Then2); // 1.24
In your code you weren't actually performing the "two roundings" - but you were mentally doing so by taking the printed value of result (591.24575) and assuming you could take that to be accurate in order to round it further.
result is not exactly 591.24575, it is some number that is very close to 591.24575 but it is just slightly smaller, something along the lines of 591.24574999999999, as a result of the fact that certain numbers that have a finite number of digits in base 10 cannot be represented with a finite number of digits in base 2. When you set the number exactly you are avoiding ever having a double set to one of those values as an intermediate value of your calculations.
If you are dealing with numbers that have a known fixed number of base 10 digits and it's important that you not have these precision errors than it may be appropriate to use Decimal in this context.
result doesn't have the value you think it does, it's being truncated by putting it in a string representation. As for why that is, read What Every Computer Scientist Should Read About Floating Point
Take a look while debugging:
result: 591.24574999999993
result.ToString(): "591,24575"
Related
Sorry for the daft question, but I get back this value from database
"7.545720553985866E+29"
I need to convert this value to a decimal, rounded to 6 digits. What is the best way to do that? I tried
var test = double.Parse("7.545720553985866E+29");
test = Math.Round(test, 6);
var test2 = Convert.ToDecimal(test);
but the value remains unchanged and the conversion crashes.
Math.Round rounds to N digits to the right of the decimal point. Your number has NO digits to the right of the decimal (it is equivalent to 754,572,055,398,586,600,000,000,000,000), so rounding it does not change the value.
If you want to round to N significant digits then look at some of the existing answers:
Round a double to x significant figures
Rounding the SIGNIFICANT digits in a double, not to decimal places
the conversion crashes.
That's because the value is too large for a decimal. The largest value a decimal can hold is 7.9228E+28 - your value is about 10 times larger than that.
Maybe you can substring it and then after, parse.
var test= "7.545720553985866E+29".Substring(0,8); // 7.545720
test = Math.Round(test, 6);
var test2 = Convert.ToDecimal(test);
You can use this to round to 6 significant digits:
round(test, 6 - int(math.log10(test)))
The resulting value from that is
7.545721e+29
This works by using log10 from the math module to get the power of 10 in test, rounds it down to get an integer, subtracts that from 6 then uses round to get the desired digits.
As noted by others, round works to the given number of decimal places. The log10 and the rest figures how many decimal places are needed to get the desired number of significant digits. If the decimal places are negative, round rounds to the left of the decimal point.
You should be aware that log10 is not perfectly accurate and taking the int of that may be off from the expected value by one. This happens rarely but it does happen. Also, even if the computed value is correct, converting the value to string (such as when you print it) may give a different-than-expected result. If you need perfect accuracy you would be better off working from the string representation of the value.
This question already has answers here:
Why does Math.Round(2.5) return 2 instead of 3?
(15 answers)
Closed 6 years ago.
I must be missing some subtlety of .NET rounding. So I am looking at this example:
decimal num = 2.5M;
var result = Math.Round(num);
Why is result = 2? (I would have expected 3 since it should round up)
See MSDN:
Decimal Math.Round(Decimald)
Rounds a decimal value to the nearest integer, and rounds midpoint
values to the nearest even number (example).
"Midpoint" here means .5; the even number in your case is 2. If you rounded 3.5 this way, it would result in 4.
If you want to use "away from zero" rounding instead, you can use the System.MidpointRounding.AwayFromZero enum:
decimal d = 2.5M;
decimal roundedD = Math.Round(d, MidpointRounding.AwayFromZero); // results in 3
Regarding why it uses midpoint rounding (AKA "banker's rounding") by default instead of "away from zero" rounding, see this answer. Supposedly it's a better algorithm (i.e. more efficient over many iterations).
If you want classic rounding use
decimal num = 2.5M;
var result = Math.Round(num,0, MidpointRounding.AwayFromZero);
Please see https://msdn.microsoft.com/en-us/library/system.math.round(v=vs.110).aspx for details
When I make a division in C#, it automaticaly rounds down. See this example:
double i;
i = 200 / 3;
Messagebox.Show(i.ToString());
This shows me a messagebox containing "66". 200 / 3 is actually 66.66666~ however.
Is there a way I can avoid this rounding down and keep a number like 66.6666667?
i = 200 / 3 is performing integer division.
Try either:
i = (double)200 / 3
or
i = 200.0 / 3
or
i = 200d / 3
Declaring one of the constants as a double will cause the double division operator to be used.
200/3 is integer division, resulting in an integer.
try 200.0/3.0
200 / 3 this is an integer division. Change to: 200.0 / 3 to make it a floating point division.
You can specify format string with the desired number of decimal ponits:
double i;
i = 200 / 3.0;
Messagebox.Show(i.ToString("F6"));
Though the answer is actually 66.666, what is happening is that 200 / 3 is being calculated resulting in an integer. The integer is then being placed in the float. The math itself is happening as integer math. To make it a float, use 200.0 / 3. The .0 will cause it to treat 200 as a float, resulting in floating point math.
Aside from the double vs int happening in that action, you're thinking of double as a precise unit. Try using the decimal datatype when you really care about accuracy.
More information at this answer:
decimal vs double! - Which one should I use and when?
double i = 200.0 / 3;
double i = ((double)200)/3;
What happens is the two integers perform an integer divide, and then the integer answer is assigned to the float. To avoid that, always cast one of the numbers as a double.
Try this
i = 200d/3d;
and it will not round.
200 and 3 are both integers, so the result will be an integer. Convert one of them to a decimal.
All given answers are wrong because they translate the integer division into one of kind double, which is cleanly not what was asked for (at least from a performance standpoint). The obvious answer is elementary school math, multiply by 10, add 5 and divide again, all integer.
i = (2000 / 3 + 5 ) / 10
You are catching a second division here, which is better than doing double conversions but still far from perfect. You could go even further and multiply by another factor and add other values than five, thus allowing you to use right shifting instead of dividing by 10. The exact formula for doing this is left as an exercise to the reader. (Just google "divisions with Multiply Shift")
Have a nice day.
var x = dr["NationalTotal"].ToString();
gives me 333333333
var xxx = Convert.ToSingle(dr["NationalTotal"].ToString());
gives me 333333344
Any ideas why?
This happens because Single does not have enough precision to store your full number.
Double has more precision.
The floating point specification says that the 32 bit representation of a floating point number is
Thus, the largest integer that can be represented without loss of (integer) accuracy is 16777216 (0x1000000). A simple test program to convince you that this is so:
#include <stdio.h>
int main(void) {
float x;
unsigned long j;
j = 0x00FFFFFC;
int i;
x = j;
for(i=0; i<10;i++) printf("%ld + %d = %f\n", j, i, x+i);
}
Output:
16777212 + 0 = 16777212.000000
16777212 + 1 = 16777213.000000
16777212 + 2 = 16777214.000000
16777212 + 3 = 16777215.000000
16777212 + 4 = 16777216.000000
16777212 + 5 = 16777216.000000 <<< from here on, adding one more doesn't give the right answer
16777212 + 6 = 16777218.000000
16777212 + 7 = 16777220.000000
16777212 + 8 = 16777220.000000
16777212 + 9 = 16777220.000000
EDIT Based on the comments underneath the question, we slowly converged on the fact that you had two questions, not one.
The first: "Why is this happening?" is answered with the above. A single float simply is not able to represent 333333333 exactly, so you get the nearest representable value, which is 333333344.
The second: "How do I fix it?" was initially answered by me in the comments - I will reprise my answer here:
Your database floating point number is not typically single precision - in fact, by default it is double precision. Thus, you solve your problem by converting the string to double, and assigning it to a double precision variable:
double xxx = Convert.ToDouble(dr["NationalTotal"]);
I would like to refer you to http://floating-point-gui.de/ - "what every programmer should know about floating point". There are actually many guides out there with similar names. Essential reading if you ever stray from using just integers (and most people will, at some point in their programming career).
From the docs for System.Single:
All floating-point numbers have a limited number of significant digits, which also determines how accurately a floating-point value approximates a real number. A Double value has up to 7 decimal digits of precision, although a maximum of 9 digits is maintained internally.
It actually means Single here rather than Double, but the point is that you're trying to use it for 9 significant digits, and that's not guaranteed to be available. You can see this very easily without any parsing:
float f = 333333333f;
Console.WriteLine("{0:r", f); // Prints 333333344
("r" is the "round-trip" format specifier.)
In other words, the closest float value to the exact decimal value of 333333333 is 333333344.
If you were to use Double instead, that would probably retain all the digits, but that doesn't mean it's the right approach necessarily. It depends on the value. Is it actually always an integer? Maybe you should be using long. Is it a non-integer, but financial data (or some other "artificial" value)? If so, consider using decimal.
Also, why are you having to convert this to a string and parse it? What's the execution time type of dr["NationalTotal"]? You may be able to just cast - avoid using string conversions where you don't need to.
When I make a division in C#, it automaticaly rounds down. See this example:
double i;
i = 200 / 3;
Messagebox.Show(i.ToString());
This shows me a messagebox containing "66". 200 / 3 is actually 66.66666~ however.
Is there a way I can avoid this rounding down and keep a number like 66.6666667?
i = 200 / 3 is performing integer division.
Try either:
i = (double)200 / 3
or
i = 200.0 / 3
or
i = 200d / 3
Declaring one of the constants as a double will cause the double division operator to be used.
200/3 is integer division, resulting in an integer.
try 200.0/3.0
200 / 3 this is an integer division. Change to: 200.0 / 3 to make it a floating point division.
You can specify format string with the desired number of decimal ponits:
double i;
i = 200 / 3.0;
Messagebox.Show(i.ToString("F6"));
Though the answer is actually 66.666, what is happening is that 200 / 3 is being calculated resulting in an integer. The integer is then being placed in the float. The math itself is happening as integer math. To make it a float, use 200.0 / 3. The .0 will cause it to treat 200 as a float, resulting in floating point math.
Aside from the double vs int happening in that action, you're thinking of double as a precise unit. Try using the decimal datatype when you really care about accuracy.
More information at this answer:
decimal vs double! - Which one should I use and when?
double i = 200.0 / 3;
double i = ((double)200)/3;
What happens is the two integers perform an integer divide, and then the integer answer is assigned to the float. To avoid that, always cast one of the numbers as a double.
Try this
i = 200d/3d;
and it will not round.
200 and 3 are both integers, so the result will be an integer. Convert one of them to a decimal.
All given answers are wrong because they translate the integer division into one of kind double, which is cleanly not what was asked for (at least from a performance standpoint). The obvious answer is elementary school math, multiply by 10, add 5 and divide again, all integer.
i = (2000 / 3 + 5 ) / 10
You are catching a second division here, which is better than doing double conversions but still far from perfect. You could go even further and multiply by another factor and add other values than five, thus allowing you to use right shifting instead of dividing by 10. The exact formula for doing this is left as an exercise to the reader. (Just google "divisions with Multiply Shift")
Have a nice day.