Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
CSBasicCourse2ndedPodbelsky / CSBasicCourse2ndedPodbelsky.rtf
Скачиваний:
27
Добавлен:
22.03.2016
Размер:
11.9 Mб
Скачать

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

Рассматривая

поразрядные

операции ,

мы

ограничились

операндами

беззнакового типа byte, так как использование знаковых типов требует знакомства с

правилами кодирования отрицательных целых чисел. Переменные и константы типа

byte могут иметь значения от 0 до 255. Соответствующие двоичные коды - 00000000

(все нули) и 11111111 (все единицы). В то же время для знакового типа sbyte

установлены пределы от -128 до +127.

Это связано с принятым на аппаратном уровне правилом кодирования

знаковых целых чисел. Для их внутреннего представления используется так

называемый дополнительный код "дополнительный код" . Если k – количество

разрядов, отведенное для представления числа x (для sbyte k равно 8), то

дополнительный код определяет выражение:

x, если x >=0,

доп (x) =

2k - |x|, если x<0

В битовом представлении чисел с использованием дополнительного кода у

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

Минимальное число типа sbyte равно -128. Его двоичный код 10000000. Число

-1 представлено кодом 11111111. Представление нуля 00000000, код единицы

00000001.

Зная правила двоичного кодирования отрицательных целых чисел, легко

понять, как меняется значение переменной знакового типа при поразрядных

операциях. Например, применяя к положительному числу операцию поразрядного

инвертирования ~, мы меняем знак числа и на 1 увеличиваем его абсолютно

е

значение. При поразрядном инвертировании отрицательного числа результат равен

уменьшенному

на

1

его

абсолютному

значению.

Следующая

программа

иллюстрирует применение операции:

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

using System;

class Program

{

static void Main()

{

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=(byte)(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). При её отсутствии результат суммирования x+y

автоматически приводится к типу int. Попытка присвоить значение типа int

переменной z, имеющей тип sbyte, воспринимается как ошибка, и компиляция

завершается аварийно.

Приведенные

иллюстрации

переполнений

разрядной

сетки

при

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

быть распространены и на целые типы с большим количеством разрядов (эти типы с

указанием разрядностей приведены в Табл. 2.1).

Основным типом для представления целочисленных данных в C# является ти

п

int. Для представления целочисленных значений типа int используются 32-

разрядные участки памяти. Тем самым предельные значения для значения типа int

таковы:

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

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

В следующей программе результаты умножений переменной типа int на саму

себя выходят за пределы разрядной сетки.

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

using System;

class Program

{

static void Main()

{

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 становится

100200110, после второго результат выходит за разрядную сетку из 32-х битов.

Левые лишние разряды отбрасываются, однако, оставшийся самый левый 32-й бит

оказывается равным 1, и код воспринимается как представление отрицательного

числа. После следующего умножения 32-й бит оказывается равным 0, и

арифметически неверный результат воспринимается как код положительного числа.

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

результата за разрядную сетку, и программисту нужно самостоятельно следить за

возможностью появления таких неверных результатов.

В рассмотренных программах с переменными типов byte и sbyte мы несколько

раз применили операцию преобразования (иначе приведения "приведение типов" )

типов "операция:операция преобразования типов" \y "операция" . Например, были

использованы конструкции:

(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.

Контрольные вопросы

Перечислите группы (категории) операций языка C#.

Перечислите названия групп операций в порядке возрастания их приоритетов

(рангов).

Знаки каких бинарных операций могут использоваться в составных операциях

присваивания?

В чём отличия префиксных форм операций декремента и инкремента от

постфиксных.

К каким операциям применимы операции ++ и --?

К каким операндам применима операция %?

К каким операндам применима операция ^?

В чём особенность операции деления целочисленных операндов?

Назовите правила выполнения операций %.

Какому действию эквивалентен сдвиг влево разрядов битового представления

целого числа?

Получите дополнительный код отрицательного числа типа sbyte, модуль которого

не превышает 127.

Объясните механизм возникновения переполнения при вычислениях с

целочисленными операндами.

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