Skip to content
Theory · Operators · lesson 6 of 10

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:

C#
double sum = 0.1 + 0.2;
Console.WriteLine(sum); // 0.30000000000000004
Console.WriteLine(sum == 0.3); // False

Let's go through what happened:

  1. 0.1 and 0.2 already carry tiny errors, and the addition adds them up.
  2. The sum prints as 0.30000000000000004: C# shows as many digits as it takes to tell this number from other double values, 0.3 among them.
  3. The comparison sum == 0.3 prints False: sum and the literal 0.3 hold 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.3 is the difference between the two numbers;
  • Math.Abs removes the sign of the difference, since it may turn out negative;
  • < 1e-9 checks that the difference is smaller than the tolerance;
  • 1e-9 is how you write the number 0.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.

C#
decimal total = 0.1m + 0.2m;
Console.WriteLine(total); // 0.3
Console.WriteLine(total == 0.3m); // True

The letter m makes a literal a decimal: without it, 0.1 would be a double. How the two types differ:

doubledecimal
How it stores a numberin binarywith decimal digits
Significant digits15–1728–29
Arithmetic speedhigherlower
What it is formeasurements, physics, graphicsmoney 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:

C#
decimal price = 19.99m;
double rate = 0.2;
decimal tax = price * rate; // ✗
Console.WriteLine(tax);
TEXT
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

  • double stores a binary fraction, so 0.1 + 0.2 does not equal 0.3; compare such numbers with a tolerance.
  • decimal stores decimal digits and counts money exactly; its literals take the suffix m.
  • double and decimal do not mix in one operation.