|
C++
для начинающих |
4.2. Арифметические операции
Таблица 4.1. Арифметические операции
Символ операции |
Значение |
Использование |
* |
Умножение |
expr*expr |
/ |
Деление |
expr / expr |
% |
Остаток от деления |
expr % expr |
+ |
Сложение |
expr + expr |
- |
Вычитание |
expr – expr |
Деление целых чисел дает в результате целое число. Дробная часть результата,
если она есть, отбрасывается:
int ivall = 21 / 6;
int iva12 = 21 / 7;
И ival1, и ival2 в итоге получат значение 3.
Операция остаток (%), называемая также делением по модулю, возвращает остаток
от деления первого операнда на второй, но применяется только к операндам целого
типа (char, short, int, long). Результат положителен, если оба операнда положительны.
Если же один или оба операнда отрицательны, результат зависит от реализации,
то есть машинно-зависим. Вот примеры правильного и неправильного использования
деления по модулю:
3.14 % 3; // ошибка: операнд типа double
21 % 6; // правильно: 3
21 % 7; // правильно: 0
21 % -5; // машинно-зависимо: -1 или 1
int iva1 = 1024;
double dval = 3.14159;
iva1 % 12; // правильно:
iva1 % dval; // ошибка: операнд типа double
Иногда результат вычисления арифметического выражения может быть неправильным
либо не определенным. В этих случаях говорят об арифметических исключениях (хотя
они не вызывают возбуждения исключения в программе). Арифметические исключения
могут иметь чисто математическую природу (скажем, деление на 0) или происходить
от представления чисел в компьютере – как переполнение (когда значение превышает
величину, которая может быть выражена объектом данного типа). Например, тип
char содержит 8 бит и способен хранить значения от 0 до 255 либо от -128 до
127 в зависимости от того, знаковый он или беззнаковый. В следующем примере
попытка присвоить объекту типа char значение 256 вызывает переполнение:
#include <iostream>
int main() { char byte_value = 32; int ival = 8;
// переполнение памяти, отведенной под byte_value byte_value = ival * byte_value;
cout << "byte_value: " <<static_cast<int>(byte_value) << endl; }
Для представления числа 256 необходимы 9 бит. Переменная byte_value получает
некоторое неопределенное (машинно-зависимое) значение. Допустим, на нашей рабочей
станции SGI мы получили 0. Первая попытка напечатать это значение с помощью:
cout << "byte_va1ue: " << byte_va1ue << endl;
привела к результату:
byte_value:
После некоторого замешательства мы поняли, что значение 0 – это нулевой символ
ASCII, который не имеет представления при печати. Чтобы напечатать не представление
символа, а его значение, нам пришлось использовать весьма странно выглядящее
выражение:
static_cast<int>(byte_value)
которое называется явным приведением типа. Оно преобразует тип объекта или выражения
в другой тип, явно заданный программистом. В нашем случае мы изменили byte_value
на int. Теперь программа выдает более осмысленный результат:
byte_value: 0
На самом деле нужно было изменить не значение, соответствующее byte_value,
а поведение операции вывода, которая действует по-разному для разных типов.
Объекты типа char представляются ASCII-символами (а не кодами), в то время как
для объектов типа int мы увидим содержащиеся в них значения. (Преобразования
типов рассмотрены в разделе 4.14.)
Это небольшое отступление от темы – обсуждение проблем преобразования типов
– вызвано обнаруженной нами погрешностью в работе нашей программы и в каком-то
смысле напоминает реальный процесс программирования, когда аномальное поведение
программы заставляет на время забыть о том, ради достижения какой, собственно,
цели она пишется, и сосредоточиться на несущественных, казалось бы, деталях.
Такая мелочь, как недостаточно продуманный выбор типа данных, приводящий к переполнению,
может стать причиной трудно обнаруживаемой ошибки: из соображений эффективности
проверка на переполнение не производится во время выполнения программы.
Стандартная библиотека С++ имеет заголовочный файл limits, содержащий различную
информацию о встроенных типах данных, в том числе и диапазоны значений для каждого
типа. Заголовочные файлы climits и cfloat также содержат эту информацию. (Об
использовании этих заголовочных файлов для того, чтобы избежать переполнения
и потери значимости, см. главы 4 и 6
[PLAUGER92]).
Арифметика вещественных чисел создает еще одну проблему, связанную с округлением.
Вещественное число представляется фиксированным количеством разрядов (разным
для разных типов – float, double и long double), и точность значения зависит
от используемого типа данных. Но даже самый точный тип long double не может
устранить ошибку округления. Вещественная величина в любом случае представляется
с некоторой ограниченной точностью. (См. [SHAMPINE97] о проблемах округления
вещественных чисел.)
Упражнение 4.1
В чем разница между приведенными выражениями с операцией деления?
double dvall = 10.0, dva12 = 3.0;
int ivall = 10, iva12 = 3;
dvall / dva12;
ivall / iva12;
Упражнение 4.2
Напишите выражение, определяющее, четным или нечетным является данное целое
число.
Упражнение 4.3
Найдите заголовочные файлы limits, climits и cfloat и посмотрите, что они содержат.
Назад Вперед
|