C++ comes with a wide variety of built in numeric types. There are signed and unsigned integers in five different sizes, as well as three different sizes of floating-point (or real) numbers. In addition, the standard library contains a complex number class, and it is easy to create your own custom numeric types.
Integers are whole numbers; the name is Latin, meaning "whole". Mathematical integers are infinite, but the C++ varieties are finite; each stored in a fixed region of memory.
The sizes for C++ integers are: short, int, long, and long long. C++ does not specify an exact range or representation for the integers. Both are implementation dependent. Here are the rules:
- Size cannot decrease as you move from short to int to long to long long.
- int must use at least 2 bytes (16 bits), long must use at least 4 bytes (32 bits), and long long must use at least 8 bytes of storage (64 bits).
On our platform, int is 32 bits, long and long long are both 64 bits. Other platforms have different limits. For instance, on the current version of Visual C++, long is 32 bits, just like the int.
Unlike Java, C++ integers come in two "flavors": signed and unsigned. Unsigned variables offer twice the range of positive numbers, but cannot store negative numbers. For example, a 32-bit int has a maximum value of 2,147,483,647, while the maximum unsigned int is 4,294,967,295. C++ allows unsigned int to be abbreviated as unsigned.
Since integers use a fixed amount of memory, what happens if you exceed their range? Unsigned numbers will "wrap around". For instance, try this.
unsigned n = 0;
cout << n - 1 << endl;
As you can see, the output wraps around from zero to the largest possible unsigned value.
Signed Overflow
This is not necessarily the case with signed numbers, however. Overflow and underflow on signed numbers is undefined behavior. Consider this code:
int n = 2147483647; // max size of 32-bit int
cout << "one larger is " << n + 1 << endl;
On many modern compilers (including ours), if you add the compiler flag
-fsanitize=undefined, you will get a runtime error, like that
shown here.
2147483647 + 1 cannot be represented in type 'int'
If you leave off that flag, most compilers wrap around just as with signed numbers, and it will print this:
Explicit values like 235 or -75 are called literals. An integer literal is a sequence of decimal digits, with no spaces or commas allowed, preceded by an optional (+/-) sign. It is stored as a signed int.
- Change the representation from signed to unsigned by add a U to the end.
- Change the storage from int to long, or to long long by adding an L or an LL.
Here are some examples:
auto a = 15; // a is stored as a signed decimal int
auto b = 15L; // b is stored as a signed decimal long
auto c = 15LL; // c is stored as a signed decimal long long
auto d = 15UL; // d is an unsigned decimal long
Using auto instead of an explicit type to create the variables a, b, c, and d, allows the compiler to infer or deduce their types from their initializers. This type inference is a new feature of C++11.
You can also write literals in base 8 (octal), base 16 (hexadecimal) and base 2 (binary).
auto oct32 = 040; // 4 8s and no 0s
auto hex32 = 0x20; // 2 16s and no 0s
auto bin32 = 0b10'0000; // 1 32 and no 16s, 8s, 4s, 2s or 1s
Starting in C++14 you can use the apostrophe as a visual separator, as I've done here to separate the digits in bin32 into groups of 4.
Numbers with a decimal fraction are called floating-point numbers. They are used to model real numbers from mathematics. C++ has three different floating-point types: float, double, and long double.
Floating-point literals in C++ are written in two ways:
- Using fixed-point notation (2.0). The value is stored as a double.
- Using scientific or exponential notation. For instance, you can write (2.9979E+8 to represent the speed of light, instead of writing it as 299790000.) The exponent can be positive (for large numbers) or negative (for very small numbers), and you can use an uppercase or lowercase "E".
You can change the storage of your literals by appending an F for type float and an L for a type long double.
Here are some examples of floating-point literals:
auto a = 3.14159; // fixed notation, type double
auto b = 2.997E8; // scientific notation, type double
auto c = 299'792'458L; // fixed notation, type long double
auto d = 3.5F; // fixed notation, type float
Generally, use double, not float or long double.
The C++ output objects display floating-point numbers by choosing the representation that is most compact, limiting the default number of digits to 6.
Often, this is not what you want. To explicitly set the output format involves 3 steps, but you only need to do it once in your program:
- Add #include <iomanip> to the list of libraries you are using.
- Send the fixed manipulator to the stream before printing.
- Specify the number of decimal places to be displayed, using the setprecision(n) manipulator.
Here's an example, displaying the double
variable cost with two digits of precision:
cout << fixed << setprecision(2) << cost;
When printing numbers, you may want to line up the decimal points correctly, so that the output is easier to read.
- Use setw(width) where width is the width of the column that you want to display.
- Unlike setprecision(), setw() only applies to one output object.
cout << fixed << setprecision(2); // once (persistent)
cout << "Widget cost: " << setw(10) << cost << endl;
cout << "Sales price: " << setw(10) << price << endl;
To perform calculations, you write expressions to calculate the answer in a form similar to that used in mathematics. Consider the quadratic equation:
This equation has two solutions given by the quadratic formula:
To solve this in C++, you write an expression which uses + in place of the ± symbol, to calculate one of the roots, like this:
(-b + sqrt(b * b - 4 * a * c)) / (2 * a)
An Expression Vocabulary
An expression is any combination of operators and operands which, when evaluated, yields a value.
- An operand indicates a value. Operands include:
- Literals:which represent a value
- Variables:a storage location containing a value
- Function calls:which can produce a value
- Sub-expressions:which yeild a value
- An operator is a symbol which performs an operation on one or more operands and, subsequently, produces a value. Operators have three characteristics:
- Arity: the number of operands required. Unary operators require a single operand, while binary operators require two.
- Precedence: determines which operands "bind to" the operator. Those with higher precedence "stick to" their adjacent operands more closely.
- Associativity: determines whether operations, at the same level of precedence, should proceed from right-to-left, (called right-associative), or from left-to-right, (called left-associative).
This linked table shows the precedence and associativity for all of the C++ operators.
When operators and operands are evaluated, each operator is applied to its operands, and a temporary value is calculated. This is the result of the expression.
Let's see how this expression is evaluated:
int a = 3 + 7 * 5 / 2 - 4;
- There are six operands: the variable as and five int literals, along with five operators: the assignment operator, multiplication, division, addition and subtraction.
-
Multiplication and division have higher precedence than addition or
subtraction. However, the * and / operators are
tied when it comes to dealing with the 5. That means
we have to fall back on associativity, going left-to-right,
performing the multiplication before the division.
Using parentheses we can represent the expression at this stage like this. Evaluating those subexpressions, we end up with:a = 3 + ((7 * 5) / 2) - 4;
a = 3 + ((35) / 2) - 4; // multiplication a = 3 + 17 - 4; // division
-
Now we have three operands and two operators at the same precedence. Again,
we fall back on associativity (left to right) and evaluate addition (on the
left) and then subtraction (on the right).
a = (3 + 17) - 4; // addition a = 20 - 4; // subtraction
- The assignment operator has the lowest precedence of all, so we finish up by copying 16 into the variable a (this is the side effect of the expression) and returning 16 as its value.
In C and C++, the order of operation (specified by precedence and associativity) and the order of evaluation are not identical. Here's a simple example:
x = a() * b() + c();
Order of operation guarantees that the results of (a() * b()) will be calculated before the addition of c(). However no guarantees are made about the order in which the functions will be called: c() could be called first, or a() could be called first.
If functions have no side effects (idempotent functions) this doesn't make a difference. If functions have side effects, such as printing, the result is undefined.