double and decimal
C# stores whole numbers exactly, as long as they fit in the type. Fractional numbers are different, and you can see it as soon as you add two tenths.
Type 1 ÷ 3 on a calculator. The screen shows 0.33333333: eight threes, and there is no more room. A real third is an endless fraction, and the calculator keeps only its beginning.
The double type works in a similar way, except that it stores numbers in binary. Binary writes a number with only two digits, 0 and 1. In binary even 0.1 is an endless fraction, like a third in decimal. So double stores not 0.1 itself but the nearest number that fits in the type:
double sum = 0.1 + 0.2;
Console.WriteLine(sum); // 0.30000000000000004
Console.WriteLine(sum == 0.3); // FalseLet's go through what happened:
0.1and0.2already carry tiny errors, and the addition adds them up.- The sum prints as
0.30000000000000004: C# shows as many digits as it takes to tell this number from otherdoublevalues,0.3among them. - The comparison
sum == 0.3printsFalse:sumand the literal0.3hold different numbers.
As you remember from the theme on types, with Russian regional settings the first line looks different: 0,30000000000000004.
This is where the calculator analogy ends. A calculator shows where it cut the fraction, while double prints 0.1 as 0.1 and hides the error until it shows up after some arithmetic.
Interviewers like to ask this: why is 0.1 + 0.2 not equal to 0.3? The answer: double stores a binary fraction, and 0.1 and 0.2 have no exact binary form.
Comparing with a tolerance
Because of the error, double numbers are not compared with ==. You compare the difference between them instead: if it is smaller than a tiny tolerance, the numbers count as equal. The expression Math.Abs(sum - 0.3) < 1e-9 gives true. Let's take it apart:
sum - 0.3is the difference between the two numbers;Math.Absremoves the sign of the difference, since it may turn out negative;< 1e-9checks that the difference is smaller than the tolerance;1e-9is how you write the number0.000000001: one divided by ten to the ninth power.
decimal for money
In accounting, a sum must add up to the cent. That is why money is stored in the decimal type: it writes a number with decimal digits, and 0.1 is stored in it exactly.
decimal total = 0.1m + 0.2m;
Console.WriteLine(total); // 0.3
Console.WriteLine(total == 0.3m); // TrueThe letter m makes a literal a decimal: without it, 0.1 would be a double. How the two types differ:
double | decimal | |
|---|---|---|
| How it stores a number | in binary | with decimal digits |
| Significant digits | 15–17 | 28–29 |
| Arithmetic speed | higher | lower |
| What it is for | measurements, physics, graphics | money and exact accounting |
decimal is exact only for decimal fractions. A third cannot be written with decimal digits either, so 1m / 3 gives 0.3333333333333333333333333333, a fraction rounded to 28 digits after the point.
Common mistake
A tax rate is declared as a double, and a decimal price is multiplied by it:
decimal price = 19.99m;
double rate = 0.2;
decimal tax = price * rate; // ✗
Console.WriteLine(tax);error CS0019: Operator '*' cannot be applied to operands of type 'decimal' and 'double'The program does not compile. C# does not mix double and decimal in one operation: it never turns one of these types into the other on its own, in either direction. Declare the rate as decimal rate = 0.2m;, and tax becomes 3.998.
Key points
doublestores a binary fraction, so0.1 + 0.2does not equal0.3; compare such numbers with a tolerance.decimalstores decimal digits and counts money exactly; its literals take the suffixm.doubleanddecimaldo not mix in one operation.