procedure TForm1.Button1Click(Sender: TObject);
var
X: Byte;
Y: Word;
begin
Y:= 1618;
X:= Y;
Label1.Caption:= IntToStr(X)
end;
На экране появится число 82. Разберемся, почему это происходит. Число 1618 в двоичной записи равно 00000110 01010010. При присваивании этого значения переменной X
старшие восемь битов "некуда девать", поэтому они просто игнорируются. В результате в Х
записывается число 01010010, т. е. 82.
Разумеется, при включенной опции Range checking и в этом случае произойдет исключение ERangeError
.
Приведенные примеры показывают два основных источника неожиданностей, возникающих при присваивании значения целой переменной:
1. При смешении знаковых и беззнаковых чисел значение меняется из-за того, что старший бит интерпретируется то как знак числа, то как старший разряд.
2. При присваивании переменной значения, требующего большего числа разрядов, "лишние" разряды просто игнорируются.
Все проблемы при присваивании сводятся к одному из этих случаев или к их комбинации.
Все эти ситуации при выключенной опции Range checking приводят к ошибкам, которые бывает очень трудно обнаружить. Из-за этого рекомендуется включать эту опцию хотя бы на этапе отладки.
В некоторых случаях возможность присваивания значений, выходящих за пределы диапазона переменной, может быть необходимой (например, для реализации "хитрых" алгоритмов или при сопряжении сторонних библиотек, одна из которых использует знаковые типы, другая — беззнаковые). Чтобы включение ERangeError
не возникало, следует предусмотреть явное приведение типа. Например, следующий код работает без исключений при включенной опции Range checking (листинг 3.3).
procedure TForm1.Button1Click(Sender: TObject);
var
X: Byte;
Y: ShortInt;
begin
Y:= -1;
X:= Byte(Y);
Label1.Caption:= IntToStr(X)
end;
В результате его выполнения переменная X
получает значение 255.
3.1.3. Переполнение при арифметических операциях
procedure TForm1.Button1Click(Sender: TObject);
var X: Byte;
begin
X:= 0;
X:= X — 1;
Label1.Caption:= IntToStr(X)
end;
Переменная X
получит значение 255, поскольку при вычитании получается -1, что в беззнаковом формате соответствует 255. В принципе, этот пример практически эквивалентен примеру Assignment1, за исключением того, что значение -1 появляется в результате арифметических операций.
Немного изменим этот пример — заменим оператор вычитания функцией Dec
(листинг 3.5, пример Overflow2 на компакт-диске).
{$R+}
procedure TForm1.Button1Click(Sender: TObject);
var X: Byte;
begin
X:= 0;
Dec(X);
Label1.Caption:= IntToStr(X);
end;
Результат получается тот же (X получает значение 255), но обратите внимание: несмотря на то, что опция Range checking включена, исключение не возникает. Этим пример Overflow2 отличается от Overflow1 — там исключение возникнет. Связано это с тем, что переполнение при использовании Dec
и подобных ей функций контролируется другой опцией — Overflow checking (в коде программы включается директивой {$Q+}
или {$OVERFLOWCHECKS ON}
). Эта опция по умолчанию тоже отключена и ее также рекомендуется включать при отладке. При ее включении в данном примере возникнет исключение EIntOverflow
.
3.1.4. Сравнение знакового и беззнакового числа
Посмотрим, что произойдет, если мы попытаемся сравнить знаковое и беззнаковое число (листинг 3.6, пример Compare1 на компакт-диске).
procedure TForm1.Button1Click(Sender: TObject);
var
X: Byte;
Y: ShortInt;
begin
Y:= -1;
X:= Y;
if X = Y then Label1.Caption:= 'Равно';
else Label1.Caption:= 'He равно';
end;
В окне появится надпись Не равно, хотя последовательность битов в переменных X
и Y
будет, как мы уже знаем, одинаковая. Надпись соответствует действительности — X
(255) действительно не равно Y
(-1). Разберемся, почему так происходит.