Выполнив эту программу, получим следующий результат:
–25536 –727379968
Этого следовало ожидать. Здесь мы видим эффект переполнения. Целочисленные типы позволяют представить лишь относительно небольшие целые числа. Нам просто не хватит битов, чтобы точно представить каждое целое число, поэтому нам необходим способ, позволяющий выполнять эффективные вычисления. В данном случае двухбайтовое число типа
short
не может представить число 40 000,
а четырехбайтовое число типа
int
не может представить число 1 000 000 000 000. Точные размеры встроенных типов в языке C++ (см. раздел A.8) зависят от аппаратного обеспечения и компилятора; размер переменной
x
или типа
x
в байтах можно определить с помощью оператора
sizeof(x)
. По определению
sizeof(char)==1
. Это можно проиллюстрировать следующим образом.
Эти размеры характерны для операционной системы Windows и компилятора компании Microsoft. В языке С++ есть много способов представить целые числа и числа с плавающей точкой, используя разные размеры, но при отсутствии важных причин лучше придерживаться типов
char
,
int
и
double
. В большинстве программ (но, разумеется, не во всех) остальные типы целых чисел и чисел с плавающей точкой вызывают больше проблем, чем хотелось бы.
Целое число можно присвоить переменной, имеющей тип числа с плавающей точкой. Если целое число окажется больше, чем может представить тип числа с плавающей точкой, произойдет потеря точности. Рассмотрим пример.
cout << setprecision(15) << x << ' ' << f << '\n';
На нашем компьютере мы получили следующий результат:
Sizes: 4 4
2100000009 2.1e+009
2100000009 2100000000
Типы
float
и
int
занимают одинаковое количество памяти (4 байта). Тип
float
состоит из мантиссы (как правило, числа от нуля до единицы) и показателя степени (т.е. мантисса*10 показатель степени), поэтому он не может точно выразить самое большое число
int
. (Если бы мы попытались сделать это, то не смогли бы выделить достаточно памяти для мантиссы после размещения в памяти показателя степени.) Как и следовало ожидать, переменная
f
представляет число 2100000009 настолько точно, насколько это возможно. Однако последняя цифра
9
вносит слишком большую ошибку, — именно поэтому мы выбрали это число для иллюстрации.
С другой стороны, когда мы присваиваем число с плавающей точкой перемен- ной целочисленного типа, происходит усечение; иначе говоря, дробная часть — цифры после десятичной точки — просто отбрасываются. Рассмотрим пример.
float f = 2.8;
int x = f;
cout << x << ' ' << f << '\n';
Значение переменной
x
будет равно
2
. Оно не будет равным
3
, как вы могли подумать, если применили “правило округления 4/5”. В языке C++ преобразование типа
float
в тип
int
сопровождается усечением, а не округлением.
При вычислениях следует опасаться возможного переполнения и усечения.
Язык C++ не решит эту проблему за вас. Рассмотрим пример.
void f(int i, double fpd)
{
char c = i; // да: тип char действительно представляет
// очень маленькие целые числа
short s = i; // опасно: переменная типа int может
// не поместиться
// в памяти, выделенной для переменной
// типа short
i = i+1; // что, если число i станет максимальным?
long lg = i*i; // опасно: переменная типа long не может
// вместить результат
float fps = fpd; // опасно: большее число типа large может
// не поместиться в типе float
i = fpd; // усечение: например, 5.7 –> 5
fps = i; // можно потерять точность (при очень
// больших целых)
}
void g
{
char ch = 0;
for (int i = 0; i<500; ++i)
cout << int(ch++) << '\t';
}
Если сомневаетесь, поэкспериментируйте! Не следует отчаиваться и в то же время нельзя просто читать документацию. Без экспериментирования вы можете не понять содержание весьма сложной документации, связанной с числовыми типами.
ПОПРОБУЙТЕ
Выполните функцию
g
. Модифицируйте функцию
f
так, чтобы она выводила на печать переменные
c
,
s
,
i
и т.д. Протестируйте программу на разных значениях.
Представление целых чисел и их преобразование еще будет рассматриваться в разделе 25.5.3. По возможности ограничивайтесь немногими типами данных, чтобы минимизировать вероятность ошибок. Например, используя только тип
double
и избегая типа
float
, мы минимизируем вероятность возникновения проблем, связанных с преобразованием
double
—
float
. Например, мы предпочитаем использовать только типы
int
,
double
и
complex
(см. раздел 24.9) для вычислений,
char
— для символов и
bool
— для логических сущностей. Остальные арифметические типы мы используем только при крайней необходимости.
24.2.1. Пределы числовых диапазонов
Каждая реализация языка C++ определяет свойства встроенных типов в заголовках
<limits>
,
<climits>
и
<limits.h>
, чтобы программисты могли проверить пределы диапазонов, установить сигнальные метки и т.д. Эти значения перечислены в разделе Б.9.1. Они играют очень важную роль для создания низкоуровневых инструментов. Если они вам нужны, значит, вы работаете непосредственно с аппаратным обеспечением, хотя существуют и другие приложения. Например, довольно часто возникают вопросы о тонкостях реализации языка, например: “Насколько большим является тип
int
?” или “Имеет ли знак тип
char
?” Найти определенные и правильные ответы в системной документации бывает трудно, а в стандарте указаны только минимальные требования. Однако можно легко написать программу, находящую ответы на эти вопросы.