C# from the ground up

Values, var and the built-in types

The types C# ships with, what var really means, integer division, and why money is a decimal and never a double.

Part 2 of 13

Beginner15 min readBack to Courses

A type is the kind of value in the box

A variable is a name for a value, and every value has a type. The type decides what the value can hold, what you may do with it, and how much room it takes. int is a whole number, long a wider whole number, double a fast approximate number, decimal an exact money-shaped number, bool a true or false, char a single character, and string text.

Writing the type out is normal: int visits = 3;. A whole number divided by a whole number stays whole, so 7 / 2 is 3 and the leftover is thrown away. When you want the fraction, at least one side has to be a number with decimal places: 7.0 / 2 is 3.5. The percent sign gives the leftover instead, so 7 % 2 is 1.

var is not "any type"

var is a shortcut that is resolved while compiling. var name = "Ada"; means exactly the same as string name = "Ada"; — the compiler looks at the right side and decides the type there and then, and the variable keeps it for good. There is no box that can hold anything later, so assigning a number to that name would be an error.

Use var when the type is obvious from the value. Write the type out when the fact matters to the reader: a price is a decimal, and saying so is worth the extra word.

Why money is a decimal

A double stores numbers as binary fractions, and most decimal fractions cannot be written exactly that way. A tenth becomes a value that is a hair above or below a tenth, so in a program 0.1 + 0.2 is not equal to 0.3, even when printing hides the difference by rounding. A tenth of a cent left over from a rounding error is a real bug in an invoice, and thousands of additions make it grow.

decimal is a base-ten type built for exactly this. Ten cents plus twenty cents is thirty cents, every time, and a comparison says so. The letter m marks a decimal literal — 12.50m — and two decimal places come out with the format F2: price.ToString("F2") or, inside an interpolated string, {price:F2}. Formatting is display only; the value behind it is untouched.

The built-in types at a glance

using System;

class Program
{
    static void Main()
    {
        int visits = 3;            // a whole number
        long stars = 9000000000L;  // a wider whole number
        double rate = 0.15;        // fast, approximate
        decimal price = 19.99m;    // exact decimals: money
        bool ok = true;            // true or false
        char grade = 'A';          // one character
        string name = "Ada";       // text

        // var takes its type from the right side.
        var total = price * visits;

        // ints divide whole; decimals keep the fraction.
        Console.WriteLine(7 / 2);                  // 3
        Console.WriteLine(7.0 / 2);                // 3.5
        Console.WriteLine(7 % 2);                  // 1
        Console.WriteLine(total.ToString("F2"));   // 59.97
    }
}

Whole numbers, decimals and the difference

Run it and compare the last two lines. Then add a line that multiplies two decimals and formats the answer with F2, and one that divides two integer variables.

Output
Click "Run" to execute your code

Never hold money in a double. It looks right when printed and is wrong when compared, and a report built on it can end a cent away from the bank. Use decimal for prices, totals and tax.

What a conversion costs

C# converts between number types, but only when it can do so without losing information. An int fits inside a long, and a long fits inside a decimal, so those conversions happen quietly. Going the other way needs a cast, and a cast is you telling the compiler that you know what will be lost.

(int)9.99 is 9, not 10: a cast from a fractional type truncates toward zero, so (int)-9.99 is -9. That is a conversion of the value, not a rounding of it, and it is the reason a price should be formatted rather than cast when it is printed.

The edges of a type

Every integer type has a range. An int holds roughly two billion in each direction; past that, the arithmetic wraps around rather than reporting anything. A long is the type for counts that might grow past that, and decimal for money that must stay exact.

A char is a number too, just one that prints as a character: (int)'A' is 65, and (char)66 is B. That is occasionally useful and rarely needed, but it explains why a char and an int can be converted with a cast while a string and an int cannot.

Three type mistakes: a decimal literal without the m is a double, so decimal price = 12.5; will not compile; integer division drops the fraction before you notice it, so total / count is whole when both are int; and an int that grows past its range at run time wraps instead of stopping.

Try these next

  • Divide two integers, then change one of them to 7.5 and compare the two answers.
  • Cast 9.99 to int, print it, and explain the answer to yourself before running it.
  • Store a price in a decimal, add ten percent, and print it with F2; then try the same with a double and compare an equality test.

Casts, widths and an exact price

Run it and compare the two divisions and the two prices. Then add a line that casts a negative decimal to int, and one that stores a whole number in a long.

Output
Click "Run" to execute your code

Check what you learned

One question at a time. A wrong answer explains itself — take another go.

Part 1 of 5

What is 7 / 2 when both sides are int values?