С++ для начинающих

       

Арифметические операции


Таблица 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 и посмотрите, что они содержат.


Содержание раздела