Арифметические операции
Таблица 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 [PLAUGER[O.A.1] 92]).
Арифметика вещественных чисел создает еще одну проблему, связанную с округлением. Вещественное число представляется фиксированным количеством разрядов (разным для разных типов – float, double и long double), и точность значения зависит от используемого типа данных. Но даже самый точный тип long double не может устранить ошибку округления. Вещественная величина в любом случае представляется с некоторой ограниченной точностью. (См. [SHAMPINE97] о проблемах округления вещественных чисел.)
Упражнение 4.1
В чем разница между приведенными выражениями с операцией деления?
double dvall = 10.0, dva12 = 3.0; int ivall = 10, iva12 = 3; dvall / dva12; |
Упражнение 4.2
Напишите выражение, определяющее, четным или нечетным является данное целое число.
Упражнение 4.3
Найдите заголовочные файлы limits, climits и cfloat и посмотрите, что они содержат.