Double
This floating-point datatype stores 64-bit floating-point values. Many of the math-functions in .NET (e.g.: Sqrt() ) uses doubles for it's calculations. Numbers ranging from negative 1.79769313486232e308 to positive 1.79769313486232e308 can be stored in a double and it also has the ability to represent positive and negative infinity. Converting a double to a decimal might result in an exception (OverflowException) when the value can not be represented as a decimal.
| Type | Approximate Range | Precision | .NET Framework Type |
| double | ±5.0 × 10−324 to ±1.7 × 10308 | 15-16 digits | System.Double |
(source: MSDN)
Since a double holds less precision it's not a good idea to compare this to another double. Chances are that because of rounding, you might get errors. For "greater than" or "smaller than" comparisons a double does just fine.
The behavior of a double isn't always what you would expect it to be. When dividing a decimal by zero you get a DivideByZero-exception. A double does not. Instead it returns values such as "infinity":
1: double f = 3.965478d;
2: Console.WriteLine("divide by zero: " + (f / 0));
Also, different ways to lead to a certain result, don't always lead to the same result but rather to something near it. To illustrate this I got a great example from Steve McConnel's book "Code Complete, 2nd Edition". The idea is very simple: create a variable and add 0.1 ten times in a row (which results to 1.0) then compare it with an expected result: 1.0.
1: double sum = 0.0d;
2: double expected = 1.0d;
3:
4: for (int i = 0; i < 10; i++)
5: {
6: sum += 0.1d;
7: Console.WriteLine(sum);
8: }
9:
10: if (sum == expected)
11: {
12: Console.WriteLine("sum is equal to expected");
13: }
14: else
15: {
16: Console.WriteLine("sum is different from expected");
17: }
Of course, this gives "Sum is different from expected". To see what values are used for the comparisson I set a breakpoint on line 10 and this is what you see:
To solve this, you can set a range of accuracy that is acceptable and then use a boolean to evaluate whether the values are close enough. And of course, since you know that working with doubles can result in rounding errors, this can be anticipated.
Decimal
The decimal is a 128-bit data type which, due to it's relatively large size, has great precision. This precision makes a decimal perfect for calculations where this degree of precision is required (science, finance). It can represent decimal numbers ranging from positive 79,228,162,514,264,337,593,543,950,335 to negative 79,228,162,514,264,337,593,543,950,335. Converting a decimal to another data type will result in a loss of information. You might notice rounding errors or even exceptions when the result of the conversion is not representable in the requested data type. Converting a decimal to a double might also result in rounding errors, but you will not lose any information as to the magnitude of the value.
| Type |
Approximate Range |
Precision |
.NET Framework Type |
| decimal |
±1.0 × 10−28 to ±7.9 × 1028
|
28-29 significant digits
|
System.Decimal
|
(source: MSDN)
The decimal is build up out of four integers (4 * 32 bit = 128 bit). Thes first three represent the mantissa and the fourth integer holds the information concerning the sign and exponent of the decimal. You can get these values as an array of integers (int) by using the GetBits(decimal) command:
1: decimal[] decimalArray = {5M, decimal.MaxValue, decimal.MinValue, 0.598M, 3.14159265M, 10000000000M};
2: int[] bitvalues;
3:
4: foreach (decimal dc in decimalArray)
5: {
6: Console.WriteLine("Showing results for decimal: " + dc.ToString() );
7: bitvalues = decimal.GetBits(dc);
8:
9: for (int i = 0; i < 4; i++)
10: {
11: Console.WriteLine("Bit " + (i+1) + ": " + bitvalues[i]);
12: }
13: }
This code gives the following output:
In contrast to C# the CLR doesn't see a decimal as a primitive type. The .NET Framework SDK documentation tells us that it has public static methods called Add, Subtract, Multiply, Divide,... And, in addition, there are operator overload methods for +,-,*,/,... When compiling, the compiler generates the code to call the members to perform the actual operation since there are no IL instructions available for manipulating values of type Decimal.
Double vs Decimal
Of course, both these types have their pro's and con's. When comparing performance you might expect the decimal being a lot slower than the double since it's actually not a primitive and so the compiler has to do a lot of other stuff before doing the actual calculation. And this assumption is correct, the following example is only to show how much slower the decimal is compared to the double. The conclusion comes down to this: it's performance versus correctness. The double is way faster but has the rounding problem. The decimal is slower but has more precission. For financial institutions or any form of monetary transaction for that matter, it's obvious to choose a decimal over a double.
I've created two loops: one with a double, one with a decimal. These loops will count to one million and add 1 to the double and the decimal (which have been instantiated to 0). This two loops are timed and when the loop is done, the results are displayed (the decimal itself, the elapsed ticks and the elapsed milliseconds).
1: static void Main(string[] args)
2: {
3: Stopwatch stopwatch = new Stopwatch();
4: double doubleResult = 0.0d;
5: decimal decimalResult = 0.0M;
6:
7: stopwatch.Start();
8: for (int i = 0; i < 1000000; i++)
9: {
10: doubleResult += 1;
11: }
12: stopwatch.Stop();
13: Console.WriteLine("The doubleResult is: " + doubleResult);
14: Console.WriteLine("Elapsed Ticks: " + stopwatch.ElapsedTicks);
15: Console.WriteLine("Elapsed Miliseconds: " + stopwatch.ElapsedMilliseconds);
16:
17: stopwatch.Reset();
18:
19: stopwatch.Start();
20: for (int i = 0; i < 1000000; i++)
21: {
22: decimalResult += 1;
23: }
24: stopwatch.Stop();
25: Console.WriteLine("The decimalResult is: " + decimalResult);
26: Console.WriteLine("Elapsed Ticks: " + stopwatch.ElapsedTicks);
27: Console.WriteLine("Elapsed Miliseconds: " + stopwatch.ElapsedMilliseconds);
28:
29: Console.ReadLine();
30: }
The result:
Now I execute the same program, but instead of adding 1, I add 0.1. Here we see another example of the possible rounding errors you get when working with doubles. The result:
