Скачиваний:
66
Добавлен:
24.03.2015
Размер:
181.25 Кб
Скачать

3.6. Переполнения при операциях с целыми

Рассматривая поразрядные операции, мы ограничились операндами беззнакового типа byte, так как использование знаковых типов требует знакомства с правилами кодирования отрицательных целых чисел. Переменные и константы типа byte могут иметь значения от 0 до 255. Соответствующие двоичные коды - 00000000 (все нули) и 11111111 (все единицы). В то же время для знакового типа sbyte установлены пределы от

-128 до +127. Это связано с принятым на аппаратном уровне правилом кодирования знаковых целых чисел. Для их внутреннего представления используется так называемый дополнительный код. Если к - количество разрядов, отведенное для представления числа х (для sbyte k равно 8), то дополнительный код определяет выражение: |х|, если х >=0, доп (х) = 2к - |х|, если х<0

В битовом представлении чисел с использованием дополнительного кода у всех положительных чисел самый левый бит равен 0, а у отрицательных -единице.

Минимальное число типа sbyte равно -128. Его двоичный код 10000000. Число -1 представлено кодом 11111111. Представление нуля 00000000, код единицы 00000001.

Зная правила двоичного кодирования отрицательных целых чисел, легко понять, как меняется значение переменной знакового типа при поразрядных операциях. Например, применяя к положительному числу операцию поразрядного инвертирования ~, мы меняем знак числа и на 1 увеличиваем его абсолютное значение. При поразрядном инвертировании отрицательного числа результат равен уменьшенному на 1 его абсолютному значению. Следующая программа иллюстрирует применение операции:

//03_05.cs – поразрядное инвертирование знаковых чисел!!!

static void Main05()

{

sbyte sb = 9;

sbyte nb = 3;

Console.WriteLine("~sb = " + ~sb);

Console.WriteLine("~sb+sb = " + (~sb + sb));

Console.WriteLine("~nb = " + ~nb);

Console.WriteLine("~nb+nb = " + (~nb + nb));

}

Результат выполнения программы:

~sb = -10

~sb+sb = -1

~nb = -4

~nb+nb = -1

Поразрядный сдвиг влево << целочисленного аргумента знакового типа может не только изменить его абсолютное значение, но и зачастую изменяет его знак. Приводить примеры здесь нет необходимости. Гораздо важнее рассмотреть особенности выполнения традиционных арифметических операций над беззнаковыми и знаковыми операндами с ограниченным количеством разрядов.

Начнем с беззнаковых целочисленных типов. В результате выполнения следующего фрагмента программы:

byte b=255, c=1, d;

d = b+c;

Значением переменной d будет 0. Обоснованность такого результата иллюстрирует следующее двоичное представление:

11111111 = 255

+

00000001 = 1

100000000 = 0 (нуль за счет отбрасывания левого разряда)

Теперь обратимся к операндам знаковых типов, например, типа sbyte.

Если просуммировать числа -1 (с поразрядным представлением 11111111) и 1 (с кодом 00000001), то получим девятиразрядное число с битовым представлением 100000000. Для внутреннего представления чисел типа sbyte отводится 8 разрядов. Девятиразрядное число в эти рамки не помещается, и левая (старшая) единица отбрасывается. Тем самым результатом суммирования становится код нуля 00000000. Все совершенно верно - выражение (-1 + 1) должно быть равно нулю! Однако так правильно завершаются вычисления не при всех значениях целочисленных операндов.

За счет ограниченной разрядности внутреннего представления значений целых типов при вычислении выражений с целочисленными операндами существует опасность аварийного выхода результата за пределы разрядной сетки. Например, после выполнения следующего фрагмента программы:

sbyte x=127, y=127, z;

z=(sbyte) (x+y);

Значением переменной z будет -2

В этом легко убедиться, представив выполнение операции суммирования в двоичном виде:

01111111 = 127

+ 01111111 = 127

11111110 = -2 (в дополнительном коде).

Примечание: В операторе z=(sbyte)(x+y); использована операция приведения типов (sbyte). При её отсутствии результат суммирования х+у автоматически приводится к типу int. Попытка присвоить значение типа int переменной z, имеющей тип sbyte, воспринимается как ошибка и компиляция завершается аварийно.

Приведенные иллюстрации переполнений разрядной сетки при арифметических операциях с восьмиразрядными целыми (типов byte, sbyte) могут быть распространены и на целые типы с большим количеством разрядов (эти типы с указанием разрядностей приведены в Табл. 2.1).

Основным типом для представления целочисленных данных в С# является тип int. Для представления целочисленных значений типа int используются 32-разрядные участки памяти. Тем самым предельные значения для значения типа int таковы:

положительные от 0 до 231-1;

отрицательные от -1 до -231.

В следующей программе результаты умножений переменной типа int на саму себя выходят за пределы разрядной сетки.

// 03_06.cs - переполнение при целочисленных операндах

static void Main06()

{

int m = 1001;

Console.WriteLine("m = " + m);

Console.WriteLine("m = " + (m = m * m));

Console.WriteLine("m = " + (m = m * m));

Console.WriteLine("m = " + (m = m * m));

}

В программе значение целочисленной переменной вначале равной 1001 последовательно умножается само на себя.

Результат выполнения программы:

m = 1001

m = 1002001

m = -1016343263

m = 554036801

После первого умножения m*m значением переменной m становится 1002001, после второго результат выходит за разрядную сетку из 32-х битов. Левые лишние разряды отбрасываются, однако, оставшийся самый левый 32-й бит оказывается равным 1, и код воспринимается как представление отрицательного числа. После следующего умножения 32-й бит оказывается равным 0, и арифметически неверный результат воспринимается как код положительного числа.

Особо отметим, что исполняющая система никак не реагирует на выход результата за разрядную сетку, и программисту нужно самостоятельно следить за возможностью появления таких неверных результатов.

В рассмотренных программах с переменными типов byte и sbyte мы несколько раз применили операцию преобразования (иначе приведения) типов. Например, были использованы конструкции:

(byte)(bb&dd)

z=(sbyte)(x+y);

В следующей главе приведение типов будет рассмотрено подробно, а сейчас покажем его роль в некоторых выражениях с целочисленными операндами.

Поместим в программу операторы:

short dd = 15, nn=24;

dd=(dd+nn)/dd;

При компиляции программы будет выведено сообщение об ошибке: Cannot implicitly convert type ‘int' to 'short'. Невозможно неявное преобразование типа int в short.

Несмотря на то, что в операторах использованы переменные только одного типа short, в сообщении компилятора указано, что появилось значение типа int! Компилятор не ошибся - при вычислении выражений с целочисленными операндами, отличными от типа long, они автоматически приводятся к типу int. Поэтому результат вычисления (dd+nn)/dd имеет тип int. Для значений типа short (см. табл. 2.1) выделяется два байта (16 разрядов), значение типа int занимает 4 байта. Попытка присвоить переменной dd с типом short значения типа int воспринимается компилятором как потенциальный источник ошибки за счёт потери 16-ти старших разрядов числа. Именно поэтому выдано сообщение об ошибке.

Программист может «успокоить» компилятор, применив следующим образом операцию приведения типов:

dd=(short)((dd+nn)/dd);

При таком присваивании программист берет на себя ответственность за правильность вычислений.

Обратите внимание на необходимость дополнительных скобок. Если записать (short)(dd+nn)/dd, то в соответствии с рангами операций к типу short будет приведено значение (dd+nn), а результат его деления на dd получит тип int.

1

Соседние файлы в папке Lekc_C#