Types, values and auto
The five types you will use every day, what auto really means, and the one place where whole numbers and fractions disagree.
The types you will use daily
int holds a whole number up to about two billion, long holds a wider one, double holds a fraction, bool holds true or false, char holds a single character in single quotes, and string holds text in double quotes. A declaration names the type, then the variable, then a value: int likes = 12;.
D is statically typed: every variable has one type, fixed when the program is compiled, and the compiler refuses an operation that does not fit it. That check is the reason a D program that compiles tends to do what the types say, long before it is run.
A declaration can also leave the value out, in which case the variable starts at the default for its type: zero for a number, false for a bool, an empty string for text. Reading it before assigning is legal, which is why an uninitialised counter is a zero rather than an error — and why a forgotten assignment looks like a working program.
auto names the type for you
auto means "use the type of the value on the right". It is not a dynamic type and nothing is decided while the program runs: auto count = likes * 2; makes a variable of type int exactly as if you had written int count. The gain is brevity in long expressions, and the price is that the type is no longer visible at the declaration.
typeid asks the compiler what it settled on. It is a good way to check a guess, and it also explains a D detail: a string reports its type as immutable(char)[], because a string really is an array of characters that cannot be modified.
Whole numbers and fractions
Dividing two whole numbers in D throws the fraction away: likes / 5 on likes of 12 gives 2 rather than 2.4. One operand written as a fraction — likes / 5.0 — moves the whole division into the fractional world and gives 2.4. This is the single most common surprise in a first week with the language.
The rule is about the types of the operands, not about the value stored. 7 / 2 is 3 even when the answer is later assigned to a double, because the division happens first and has already thrown the half away. Cast one operand before the division, not after it.
writeln prints a double with as few digits as it can: a value of 85.0 prints as 85. When a number has to look like money, ask for the digits with writefln("%.1f", value) rather than trusting the default.
What the bits actually hold
An int is 32 bits, so it counts from about minus two billion to plus two billion, and a long doubles that width. A double is 64 bits split into sign, exponent and fraction, which is why it can hold 0.1 only approximately: the value stored is the nearest fraction those bits can express.
When an int passes its limit the value wraps around rather than raising an error, because D defines the result instead of leaving it undefined. int.max + 1 is int.min. That is fast and predictable, and it is also a bug waiting in a counter nobody expected to reach two billion.
One expression, several types
When an int meets a double in an expression, the int is converted to double and the arithmetic happens in fractions, so 1 + 2.5 is 3.5. When two ints meet, the answer stays an int, which is the rule behind 7 / 2 == 3.
cast(int) is the explicit way to ask for the other type. It truncates toward zero, so cast(int) 4.9 is 4 and cast(int)(-4.9) is -4. Write the cast when you mean it rather than hoping an assignment will do it: D refuses to put a double into an int without one.
Four values, four types, inferred
import std.stdio;
void main() {
auto count = 3;
auto price = 2.5;
auto name = "tea";
auto ok = count > 0;
writeln(count, " ", price, " ", name, " ", ok);
}Five types and three guesses
Run it and match each printed line to a declaration. Then change likes to 7 and watch count and share change with it, and add a variable of your own with auto and print its typeid.
Writing 5 instead of 5.0 in a division changes the answer without changing the look of the line. When a fraction matters, make one side a double and print it with a format such as %.1f so the digits you meant are the digits on screen.
Where the whole numbers stop
Run it and find the wrapped value. Then change `top += 1` to `top += 2`, predict the new number, and add a line that prints `cast(int)(-price)` to see truncation toward zero.
Three traps in this chapter: int share = 7 / 2; stores 3; int price = 2.5; does not compile because a fraction does not narrow silently; and "total: " + 4 does not compile either, because + adds numbers and ~ joins text. Name the type you want and convert once, in the open.
Try these next
- Print
int.maxandint.min, then add 1 to the maximum and read the wrapped value. - Compute an average two ways, once as
total / countand once astotal / cast(double) count, and compare the digits. - Declare a
longabove the range ofintand print it, then try to assign it to anintand read the compiler message.
Check what you learned
One question at a time. A wrong answer explains itself — take another go.
Part 1 of 5
What does auto do in a declaration?
