{$R- – отключить проверку диапазонов }
Этот вариант программы не выдаст сообщений об ошибке, но результат ошеломит вас – это будет ноль! Вот так чаша! Числовая переменная оказалась необычной посудой, – лишняя капля полностью опустошила её! И теперь можно вновь заполнять пустую чашу. Убедитесь в этом, поменяв единицу на другое слагаемое, например 5, – в результате сложения получится 4. Открытое нами явление называют переполнением (по-английски – OVERFLOW).
В следующем опыте запустим такую программу.
{$R- – отключить проверку диапазонов }
var N : byte;
begin
N:= 0; { 0 – минимальное значение для байта }
N:= N-1;
Writeln(N); Readln;
end.
Результат ещё удивительней: теперь программа напечатает число 255! То есть, удалив из пустой чаши несуществующую каплю, мы наполнили её доверху! Этот фокус называют антипереполнением, то есть переполнением наоборот.
Проделав опыты с переменными других числовых типов, вы убедитесь, что переполнение и антипереполнение может постигнуть любую из них. Так, добавление единицы к положительному числу 32767 в переменной типа INTEGER дает отрицательный результат -32768. Отсюда следует общее правило: добавление единицы к максимальному значению для числового типа дает минимальное значение. И наоборот: вычитание единицы из минимального значения дает максимальное. Рис. 75 наглядно показывает это.
Такая вот чудная арифметика! Причина переполнений и антипереполнений кроется в устройстве регистров процессора, — в свое время мы узнаем о них больше при изучении двоичной системы счисления. Или вспомните одометр — прибор для подсчёта пробега автомобиля: по достижении предельного количества километров (99999) одометр сбрасывается в ноль.
Сейчас важно понять, что присвоение переменной некоторого выражения не гарантирует правильного результата, – он будет верным лишь при отсутствии переполнений и антипереполнений. Когда в вычислении участвуют переменные разных типов, оно выполняется в самом емком формате, то есть в Longint, а затем результат «обрубается» в соответствии с типом принимающей переменной, например:
{ $R- }
var B: Byte; S: ShortInt; W: Word; N: Integer;
...
N:= B + S + W;
Здесь даже при положительных значениях всех суммируемых операндов, результат в переменной N может оказаться отрицательным! Если вам не по нраву такое поведение программы, включайте директиву проверки диапазонов $R+.
Угадайте, что чаще всего делают с целыми переменными? — прибавляют и вычитают единицу. Потому в процессорах стараются ускорить эти операции. Паскаль не обошел вниманием эту особенность программ, и предлагает вам две процедуры, объявленные так:
procedure Inc (var N : longint); { прибавление единицы к переменной N }
procedure Dec (var N : longint); { вычитание единицы из переменной N }
Хотя параметр N в процедурах объявлен как LONGINT, в действительности здесь может стоять переменная любого порядкового типа: INTEGER, WORD, BYTE, CHAR и даже BOOLEAN.
var B: byte; N: integer; C: char;
...
Inc(B); { B:= B+1 }
Dec(N); { N:= N–1 }
C:= ‘A‘; Inc(C); { ‘B‘}
Процедуры инкремента и декремента – так их называют – выполняются быстрее операторов присваивания N:=N+1 и N:=N-1.
Работающим в IDE Borland Pascal, следует учесть, что здесь процедуры инкремента и декремента не подвластны директиве $R+ (в отличие от сложения и вычитания). То есть, переполнения и антипереполнения не вызывают аварий.
Контроль переполнений директивой $R+ повышает надежность программ. Но порой нужны более сильные ограничения. Предположим, некая переменная M по смыслу является порядковым номером месяца в году. Стало быть, её значения должны быть ограничены диапазоном от 1 до 12. Программист может указать это компилятору, объявив переменную как диапазон, и явно задав допустимые пределы её изменения:
var M : 1..12;
Диапазон выражается двумя константами: минимальным и максимальным значениями, разделенными двумя точками. Теперь, при включенной директиве $R+, будет выдано сообщение об ошибке при попытке присвоить этой переменной любое значение за пределами 1…12. Во всем прочем диапазон – это обычный целочисленный тип (в данном случае – однобайтовый).
Рассмотрим ещё пример.
var M : 1..12; { месяцы }
D : 1..7; { дни недели }
…
M:= D; { здесь возможна смысловая ошибка }