Знаковые целые числа

FasmWorld Программирование на ассемблере FASM для начинающих и не только

Учебный курс. Часть 8. Числа со знаком и без

Автор: xrnd | Рубрика: Учебный курс | 27-03-2010 | Распечатать запись

Числа со знаком и дополнительный код

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

Для представления чисел со знаком используется специальное кодирование. Старший бит в этом случае обозначает знак числа. Если знаковый бит равен нулю, то число положительное, иначе – отрицательное. Понятно, что положительное число со знаком будет выглядеть точно так же, как и число без знака.

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

Чтобы сделать из положительного числа отрицательное, необходимо проинвертировать все его биты (0 заменяем на 1, а 1 заменяем на 0) и затем к младшему разряду прибавить единицу. Например, представим -5 в дополнительном коде:

В обратную сторону переводится точно также

Синтаксис FASM

Для записи отрицательного числа в программе на ассемблере используется символ ‘-‘, например:

Кстати, это работает и с числами в других системах счисления, и даже с символами

y db -25h z db -77o k db -101b s db -‘a’

Со знаковыми и беззнаковыми числами нужно быть внимательным, потому что только вы знаете, какие числа используются в вашей программе! Процессору абсолютно по барабану, какие данные он обрабатывает, поэтому невнимательность может привести к ошибке. Один и тот же байт может интерпретироваться по-разному, в зависимости от того со знаком число или без. Например, числу со знаком -5 соответствует число без знака 251:

Диапазоны значений чисел со знаком и без

При программировании на ассемблере (как, впрочем, и на многих других языках) необходимо учитывать ещё один важный момент. А именно – ограничение диапазона представления чисел. Например, если размер беззнаковой переменной равен 1 байт, то она может принимать всего 256 различных значений. Это означает, что мы не сможем представить с её помощью число, больше 255 (111111112). Для такой же переменной со знаком максимальным значением будет 127 (011111112), а минимальным -128 (100000002). Аналогично определяется диапазон для 2- и 4-байтных переменных.

Кстати, так как процессор Intel 8086 был 16-битным и обрабатывал за одну команду 16-бит, то 16-битная переменная называется слово (word), а 32-битная – двойное слово (double word, dword). Эти названия сохранились в ассемблере даже для 32-битных процессоров (и в WIN32 API, например). И от них же происходят названия директив dw (Define Word) и dd (Define Dword). Ну а db – это Define Byte.

Для наглядности вот табличка диапазонов чисел:

Размер
переменной
Число без знака Число со знаком
min max min max
байт 00000000 11111111 10000000 01111111
255 -128 127
слово 00000000 00000000 11111111 11111111 10000000 00000000 01111111 11111111
65 535 -32 768 32 767
двойное
слово
0000…0000 1111…1111 1000…0000 0111…1111
4 294 967 295 -2 147 483 648 2 147 483 647
и т.д.

Если результат какой-то операции выйдет за пределы диапазона представления чисел, то случится переполнение и результат будет некорректным. (Например, при сложении двух положительных чисел, можно получить отрицательное число!) Поэтому нужно быть внимательным при программировании и предусмотреть обработку таких ситуаций, если они могут возникнуть.

Комментарии:

Спасибо за этот учебный курс, читаю с самого начала, занимался ассемблером лет 6 назад, забылось многое, сейчас вот читаю и вспоминаю =) очень понятно изложен материал. С числами со знаками не отложилось и в прошлый раз..или просто не предал этому значения, но в процессе отладки встречаются знаковые и беззнаковые операторы перехода, и тут приходиться анализировать аргументы. Материал помог разложить мне у себя в мозгу всё по полочкам =) ..Спасибо.

Спасибо. Такие комментарии мне нравятся

Только не операторы перехода, а операнды сравнения.

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

Приветствую, xrnd и всех. Такой вот вопрос замучил: почему при сложении 106+25=131 возникает переполнение, а в случае 2+129=131 нет ? Складываю в байтовом регистре, т.е. в обоих случаях получаю одно и то же отрицат. число.

Тут надо быть внимательным с флагами.
Дело в том, что числа со знаком и числа без знака складываются и вычитаются совершенно одинаково. Разница только в интерпретации флагов и знакового бита.

Флаг переполнения OF имеет смысл только для чисел со знаком.
Переполнение возникает в следующих случаях:
1. положительное + положительное = отрицательное
2. отрицательное + отрицательное = положительное

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

Если записать твои примеры в виде чисел со знаком, то всё становится понятным:
106+25 = -125 (переполнение!)
2 + (-127) = -125 (нет переполнения)

Для чисел без знака проверять переполнение нужно по флагу переноса CF. Если был перенос из старшего разряда, то результат вышел за допустимые пределы.

Значит, если подразумевается, что числа со знаком, поднятый of будет означать неверный результат, если же числа беззнаковые, на of можно не обращать внимания, а смотреть на cf. Спасибо, теперь понятно.

я что то не понял при записи 101b=5 но при 010b получается 2. Так как при инвертации нельзя записать число с начальным нулем получается 10b
А при -5 получаеться 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1011b
я что то не понимаю вас

Имеются два числа: 11111011 и 11111011. Как ассемеблер понимает что нужно выводить отрицательное число или положительное?

Мне тоже интересно. Вопрос еще актуален, ответьте пожалуйста, о, гуру ассемблера

test ax,ax
во флаги будут помещены конкретные значения свойств числа, в том числе и s флаг, который и говорит о знаковом бите.

По флагу жеж. Если есть флаг SF=1 то данное число отрицательное.

Ассемблер никак не понимает, для него это одно и то же самое значение. Интерпретация ложится на программиста который это значение использует.
Например при сравнении двух чисел, записаных в регистры процессоа, с использованием команды cmp:
cmp ax, bx
процессор вычтет одно число из другого, и установит по результату вычитания значение флагов
ZF. SF, OF CF. Если надо интерпритировать сравнение как сравнение знаковых чисел, то проверяются одни флаги (ZF, OF), если как беззнаковые, то другие флаги (ZF, CF). Для удобства в ассемблере используется отдельные команды сравнения знаковых чисел (jl,jle, jg,jge) и беззнаковых (jb,jbe,ja,jae)

Тут дело вообще не в ассемблере. Сложение/вычитание выполняется одинаково, но в завимисости от результата выставляются флаги SF(знаковый),CF(переноса),OF(переполнения).
Некоторые операторы учитывают знаки(переходы ja,jb), в противовес им jg,jl -считают числа беззнаковыми (http://asmworld.ru/spravochnik-komand/jcond/).
В остальных случаях флаги нужно учитывать самому.

Всё зависит от алгоритма вывода. Последний бит — бит знака. Если его не считать, то получается число без знака, имеющее обычно диапазон [0.255]. Иначе, нужно выводить знак и остальные семь бит выводить в десятичной системе счисления (как я понимаю).
Подробнее разобрано тут: http://asmworld.ru/uchebnyj-kurs/022-vyvod-chisel-na-konsol/

Ассемблер – никак… Это задача программиста на ассемблере…
А вот отдельные команды, IDIV (целочисленное деление со знаком) например, по наличию “1” в старшем (знаковом) разряде своих операндов.

Знаковые целые числа

Целые числа являются простейшими числовыми данными, с которыми оперирует ЭВМ. Для целых чисел существуют два представления: беззнаковое (только для неотрицательных целых чисел) и со знаком. Очевидно, что отрицательные числа можно представлять только в знаковом виде. Целые числа в компьютере хранятся в формате с фиксированной запятой.

Представление целых чисел в беззнаковых целых типах.

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

Прямой код числа.

Дополнительный код числа.


Дополнительный код положительного числа равен прямому коду этого числа. Дополнительный код отрицательного числа m равен 2 k -|m|, где k — количество разрядов в ячейке.
Как уже было сказано, при представлении неотрицательных чисел в беззнаковом формате все разряды ячейки отводятся под само число. Например, запись числа 243=11110011 в одном байте при беззнаковом представлении будет выглядеть следующим образом:

1 1 1 1 1 1

При представлении целых чисел со знаком старший (левый) разряд отводится под знак числа, и под собственно число остаётся на один разряд меньше. Поэтому, если приведённое выше состояние ячейки рассматривать как запись целого числа со знаком, то для компьютера в этой ячейке записано число -13 (243+13=256=28).
Но если это же отрицательное число записать в ячейку из 16-ти разрядов, то содержимое ячейки будет следующим:

1 1 1 1 1 1 1 1 1 1 1 1 1 1

Знаковый разряд
Возникает вопрос: с какой целью отрицательные числа записываются в виде дополнительного кода и как получить дополнительный код отрицательного числа?
Дополнительный код используется для упрощения выполнения арифметических операций. Если бы вычислительная машина работала с прямыми кодами положительных и отрицательных чисел, то при выполнении арифметических операций следовало бы выполнять ряд дополнительных действий. Например, при сложении нужно было бы проверять знаки обоих операндов и определять знак результата. Если знаки одинаковые, то вычисляется сумма операндов и ей присваивается тот же знак. Если знаки разные, то из большего по абсолютной величине числа вычитается меньшее и результату присваивается знак большего числа. То есть при таком представлении чисел (в виде только прямого кода) операция сложения реализуется через достаточно сложный алгоритм. Если же отрицательные числа представлять в виде дополнительного кода, то операция сложения, в том числе и разного знака, сводится к из поразрядному сложению.

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

Алгоритм получения дополнительного кода отрицательного числа.


Для получения дополнительного k-разрядного кода отрицательного числа необходимо

  1. модуль отрицательного числа представить прямым кодом в k двоичных разрядах;
  2. значение всех бит инвертировать:все нули заменить на единицы, а единицы на нули(таким образом, получается k-разрядный обратный код исходного числа);
  3. к полученному обратному коду прибавить единицу.
Читайте также  Счётчик электрической энергии

Пример:
Получим 8-разрядный дополнительный код числа -52:

Можно заметить, что представление целого числа не очень удобно изображать в двоичной системе, поэтому часто используют шестнадцатеричное представление:

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

Для представления вещественных чисел в современных компьютерах принят способ представления с плавающей запятой. Этот способ представления опирается на нормализованную (экспоненциальную) запись действительных чисел.
Как и для целых чисел, при представлении действительных чисел в компьютере чаще всего используется двоичная система, следовательно, предварительно десятичное число должно быть переведено двоичную систему.

Нормализованная запись числа.


Нормализованная запись отличного от нуля действительного числа — это запись вида a= m*P q , где q — целое число (положительное, отрицательное или ноль), а m — правильная P-ичная дробь, у которой первая цифра после запятой не равна нулю, то есть . При этом m называется мантиссой числа, q — порядком числа.

Примеры:

  1. 3,1415926 = 0, 31415926 * 10 1 ;
  2. 1000=0,1 * 10 4 ;
  3. 0,123456789 = 0,123456789 * 10 0 ;
  4. 0,00001078 = 0,1078 * 8 -4 ; (порядок записан в 10-й системе)
  5. 1000,00012 = 0, 100000012 * 2 4 .

Так как число ноль не может быть записано в нормализованной форме в том виде, в каком она была определена, то считаем, что нормализованная запись нуля в 10-й системе будет такой:
0 = 0,0 * 10 0 .

Нормализованная экспоненциальная запись числа — это запись вида a= m*P q , где q — целое число (положительное, отрицательное или ноль), а m — P-ичная дробь, у которой целая часть состоит из одной цифры. При этом (m-целая часть) называется мантиссой числа, q — порядком числа.

Представление чисел с плавающей запятой.


При представлении чисел с плавающей запятой часть разрядов ячейки отводится для записи порядка числа, остальные разряды — для записи мантиссы. По одному разряду в каждой группе отводится для изображения знака порядка и знака мантиссы. Для того, чтобы не хранить знак порядка, был придуман так называемый смещённый порядок, который рассчитывается по формуле 2 a-1 +ИП, где a — количество разрядов, отводимых под порядок.

Пример:
Если истинный порядок равен -5, тогда смещённый порядок для 4-байтового числа будет равен 127-5=122.

Алгоритм представления числа с плавающей запятой.

  1. Перевести число из p-ичной системы счисления в двоичную;
  2. представить двоичное число в нормализованной экспоненциальной форме;
  3. рассчитать смещённый порядок числа;
  4. разместить знак, порядок и мантиссу в соответствующие разряды сетки.

Пример:
Представить число -25,625 в машинном виде с использованием 4 байтового представления (где 1 бит отводится под знак числа, 8 бит — под смещённый порядок, остальные биты — под мантиссу).

2510=1000112
0,62510=0,1012
-25,62510= -100011,1012
2. -100011,1012 = -1,000111012 * 2 4
3. СП=127+4=131
4.

Можно заметить, что представление действительного числа не очень удобно изображать в двоичной системе, поэтому часто используют шестнадцатеричное представление:


Окончательный ответ: C1CD0000.

Числа

Цель лекции

Получение знаний по работе с числами всех типов, преобразование чисел в другие типы данных, закрепление материала на практике.

Целые числа

В Lazarus (а точнее, в Free Pascal ), как и в любом другом языке программирования, числа играют довольно важную роль. Трудно представить себе программу, в которой не использовались бы числа. Даже когда вы просто установите какой-то компонент на форму, автоматически начинают действовать множество настроек. Left , Top , Height , Width — все эти свойства есть в любом визуальном компоненте, и они содержат числа. Числа бывают целые и вещественные, знаковые и беззнаковые.

В этом разделе поговорим о целых числах, как знаковых, так и беззнаковых. Что такое целое число? Это число без запятой, то есть, без десятичной части. Знаковым называют число со знаком: -1, например. Беззнаковое число — это число от нуля и больше.

В программировании базовым целым числом является integer , который мы уже не раз использовали. Но вы, вероятно, догадались, что это не единственный возможный целый тип? Есть разные типы целых чисел, они могут быть со знаком и без него, имеют разный диапазон возможных значений и, соответственно, занимают разный размер оперативной памяти. Разберем эти типы:

Таблица 8.1. Целые числа

Тип Диапазон Размер в байтах
Byte 0…255 1
ShortInt -128…127 1
Word 0…65 535 2
Smallint -32 768…32 767 2
LongWord 0…4 294 967 295 4
Cardinal 0…4 294 967 295 4
LongInt -2 147 483 648…2 147 483 647 4
Integer -2 147 483 648…2 147 483 647 4
Int64 8

Обратите внимание, здесь диапазон и размер Integer совпадает с LongInt . Вообще-то, это зависит от режима компилятора FPC. Проект можно скомпилировать в разных режимах, с поддержкой Delphi, например, или TP (Turbo Pascal ). По умолчанию, выставлен режим Object Pascal, это можно проверить, выполнив в среде Lazarus команду меню Проект -> Параметры проекта, затем в разделе Параметры компилятора выбрать Обработка. В верхней части там указан Режим синтаксиса, по умолчанию это Object Pascal, но при необходимости его можно и поменять.

Так вот, если там выставлен режим Object Pascal или Delphi, тогда Integer имеет размер 32 бита, или 4 байта. Если же выставлен старый режим Turbo Pascal или Free Pascal, то Integer будет иметь размер в 16 бит или 2 байта, и будет соответствовать типу Smallint .

Зачем нужно такое разнообразие целых типов? В былые времена оперативная память была довольно маленькой. Если сейчас у меня на ПК установлена оперативная память 4 Гб, то когда-то давно я был вынужден обходиться компьютером с процессором 486 и оперативкой аж в 16 мегабайт , и можете поверить, это был далеко не самый худший компьютер ! В те времена программисты сражались за каждый байт памяти, переписывая и минимизируя код, выбирая самые маленькие из возможных типы данных. Это называлось оптимизацией кода. Допустим, вам нужно выполнить какой-то цикл 10 раз. Для подсчета шагов цикла вам придется создать переменную целого типа. Но зачем использовать переменную Integer в 4 байта, когда вполне можно обойтись однобайтовым Byte ? Сейчас конечно, это не играет такой большой роли, как прежде, но всё равно, оптимизация кода — это признак хорошего программиста, это хороший тон в программировании. Так что старайтесь не тратить понапрасну лишнюю память .

Рекомендации тут следующие: если вы знаете, что число будет без знака, то и выбирайте беззнаковые типы. Если вы точно знаете, что максимальное число в переменной будет маленьким, выбирайте типы поменьше. Если вам неизвестно, какого размера число попадет в переменную, то выбирайте Integer — это универсальный тип, годный для большинства случаев. Ну а если вы уверены, что число будет очень большим, то используйте 4-х или даже 8-ми байтовые типы.

Вещественные числа

Вещественными называются числа с дробной частью, причем, если дробная часть равна нулю, её все равно нужно указать. Например:

Такие числа еще называют числами с плавающей точкой, поскольку количество цифр после точки может быть различным. Записываются вещественные числа по определенным правилам. Если в математике мы дробную часть отделяем запятой, в Lazarus для этого используют точку. При указании очень большого числа можно выбрать сокращенную форму. Если в математике для этого число умножают на десятичную степень, например,

то в Lazarus вместо 10 указывают букву E (от англ. exponent — показатель степени):

Степени могут быть и отрицательными:

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

Таблица 8.2. Вещественные числа

Тип Диапазон Количество значащих цифр Размер в байтах
Single 1.5E-45…3.4E38 7-8 4
Real 5.0E-324…1.7E308 15-16 8
Double 5.0E-324…1.7E308 15-16 8
Comp -2E64+1…2E63-1 19-20 8
Currency -922 337 203 685 477.5808 … 922 337 203 685 477.5807 19-20 8
Extended 1.9E-4932…1.1E4932 19-20 10

Как видите, вещественные числа куда больше целых, процессорного времени на обработку таких чисел тратится тоже больше. Поэтому вещественные числа имеет смысл применять только по необходимости, когда целыми числами явно не обойтись. Не слушайте тех, кто предлагает на все случаи жизни использовать тип Real — и для целых, и для вещественных чисел.

Рекомендации тут такие же, как и для целых чисел — выбирайте типы по необходимости. Особо выделю тип Currency — его создали специально для финансовых расчетов, поэтому для всякого рода бухгалтерских расчетов лучше выбирать именно этот тип, как наиболее точный. Но чаще всего обходятся типом Real (или Double ).

Операции над целыми и вещественными числами

Целые числа можно складывать (+), отнимать (-) и умножать (*) друг на друга. С делением дело обстоит сложней. Допустим, нам нужно 10 разделить на 3. Получится 3,33333…, а это уже не целое число . Поэтому для целых чисел в Паскале предусмотрено деление нацело. Операция div обеспечивает деление нацело, и возвращает целую часть, отбрасывая дробную. Например, 10 разделить на 8 будет равно 1,25. Если применить целочисленное деление , то 10 div 8 = 1 . Чтобы узнать остаток от такого деления, применяют операцию mod. 10 mod 8 = 2 .

Арифметика над вещественными числами еще проще, здесь применяют следующие стандартные операции : + ( сложение ), — ( вычитание ), * ( умножение ), / ( деление ).

Кроме того, как целые, так и вещественные числа можно сравнивать между собой, используя для этого логические операторы : = (равно), <> (не равно), > (больше), (меньше), >= (больше или равно), (меньше или равно).

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

Что попадет в переменную r ? Если вы ответили 11, то вы правы. Чтобы сначала выполнить сложение , его нужно поместить в скобки, которые имеют высший приоритет:

В этом случае, в переменную r попадет число 14.

Целые числа: общее представление

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

Целые числа. Определение, примеры

Вначале вспомним про натуральные числа ℕ . Само название говорит о том, что это такие числа, которые естественно использовались для счета с незапамятных времен. Для того, чтобы охватить понятие целых чисел, нам нужно расширить определение натуральных чисел.

Определение 1. Целые числа

Целые числа — это натуральные числа, числа, противоположные им, и число нуль.

Читайте также  Простая система радиооповещения

Множество целых чисел обозначается буквой ℤ .

Множество натуральных чисел ℕ — подмножество целых чисел ℤ . Любое натуральное число является целым, но не любое целое число является натуральным.

Из определения следует, что целым является любое из чисел 1 , 2 , 3 . . , число 0 , а также числа — 1 , — 2 , — 3 , . .

В соответствии с этим, приведем примеры. Числа 39 , — 589 , 10000000 , — 1596 , 0 являются целыми числами.

Целые числа и координатная прямая

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

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

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

Положительные и отрицательные целые числа

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

Определение 2. Положительные целые числа

Положительные целые числа — это целые числа со знаком «плюс».

Например, число 7 — целое число со знаком плюс, то есть положительное целое число. На координатной прямой это число лежит справа от точки отсчета, за которую принято число 0 . Другие примеры положительных целых чисел: 12 , 502 , 42 , 33 , 100500 .

Определение 3. Отрицательные целые числа

Отрицательные целые числа — это целые числа со знаком «минус».

Примеры целых отрицательных чисел: — 528 , — 2568 , — 1 .

Число 0 разделяет положительные и отрицательные целые числа и само не является ни положительным, ни отрицательным.

Любое число, противоположное положительному целому числу, в силу определения, является отрицательным целым числом. Справедливо и обратное. Число, обратное любому отрицательному целому числу, есть положительное целое число.

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

Определение 4. Положительные целые числа

Положительные целые числа — это целые числа, которые больше нуля.

Отрицательные целые числа — это целые числа, которые меньше нуля.

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

Ранее мы уже говорили, что натуральные числа — это подмножество целых. Уточним этот момент. Множество натуральных чисел составляют целые положительные числа. В свою очередь, множество отрицательных целых чисел является множеством чисел, противоположных натуральным.

Любое натуральное число можно назвать целым, но любое целое число нельзя назвать натуральным. Отвечая на вопрос, являются ли являются ли отрицательные числа натуральными, нужно смело говорить — нет, не являются.

Неположительные и неотрицательные целые числа

Определение 6. Неотрицательные целые числа

Неотрицательные целые числа — это положительные целые числа и число нуль.

Неположительные целые числа — это отрицательные целые числа и число нуль.

Как видим, число нуль не является ни положительным, ни отрицательным.

Примеры неотрицательных целых чисел: 52 , 128 , 0 .

Примеры неположительных целых чисел: — 52 , — 128 , 0 .

Неотрицательное число — это число, большее или равное нулю. Соответственно, неположительное целое число — это число, меньшее или равное нулю.

Термины «неположительное число» и «неотрицательное число» используются для краткости. Например, вместо того, чтобы говорить, что число a — целое число, которое больше или равно нулю, можно сказать: a — целое неотрицательное число.

Использование целых чисел при описании изменения величин

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

Пусть на складе хранится какое-то количество коленвалов. Если на склад привезут еще 500 коленвалов, то их количество увеличится. Число 500 как раз и выражает изменение (увеличение) количества деталей. Если потом со склада увезут 200 деталей, то это число также будет характеризовать изменение количества коленвалов. На этот раз, в сторону уменьшения.

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

Очевидное удобство использования целых чисел в отличие от натуральных в том, что их знак явно указывает на направление изменения величины (увеличение или убывание).

Понижение температуры на 30 градусов можно охарактеризовать отрицательным числом — 30 , а увеличение на 2 градуса — положительным целым числом 2 .

Приведем еще один пример с использованием целых чисел. На этот раз, представим, что мы должны отдать кому-то 5 монет. Тогда, можно сказать, что мы обладаем — 5 монетами. Число 5 описывает размер долга, а знак «минус» говорит о том, что мы должны отдать монеты.

Если мы должны 2 монеты одному человеку, а 3 — другому, то общий долг ( 5 монет) можно вычислить по правилу сложения отрицательных чисел:

Свод правил по работе с целыми числами в C/C++

В основу статьи легли мои собственные выработанные нелегким путем знания о принципах работы и правильном использовании целых чисел в C/C++. Помимо самих правил, я решил привести список распространенных заблуждений и сделать небольшое сравнение системы целочисленных типов в нескольких передовых языках. Все изложение строилось вокруг баланса между краткостью и полноценностью, чтобы не усложнять восприятие и при этом отчетливо передать важные детали.

Всякий раз, когда я читаю или пишу код на C/C++, мне приходится вспоминать и применять эти правила в тех или иных ситуациях, например при выборе подходящего типа для локальной переменной/элемента массива/поля структуры, при преобразовании типов, а также в любых арифметических операциях или сравнениях. Обратите внимание, что типы чисел с плавающей запятой мы затрагивать не будем, так как это большей частью относится к анализу и обработке ошибок аппроксимации, вызванных округлением. В противоположность этому, математика целых чисел лежит в основе как программирования, так и компьютерной науки в целом, и в теории вычисления здесь всегда точны (не считая проблем реализации вроде переполнения).

Типы данных

Базовые целочисленные типы

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

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

  • char : минимум 8 бит в ширину;
  • short : минимум 16 бит и при этом не меньше char ;
  • int : минимум 16 бит и при этом не меньше short ;
  • long : минимум 32 бит и при этом не меньше int ;
  • long long : минимум 64 бит и при этом не меньше long .

Наличие знака

Дополнительные правила

Типы из стандартных библиотек

  • size_t (определен в stddef.h) является беззнаковым и содержит не менее 16 бит. При этом не гарантируется, что его ширина будет как минимум равна int .
  • ptrdiff_t (определен в stddef.h) является целочисленным типом со знаком. Вычитание двух указателей будет давать этот тип. При этом не стоит ожидать, что вычитание двух указателей даст int .
  • В stdint.h определена конкретная ширина типов: uint8_t , int8_t , 16 , 32 и 64 . Будьте внимательны к операциям, подразумевающим продвижение типов. Например, uint8_t + uint8_t даст int (со знаком и шириной не менее 16 бит), а не uint8_t , как можно было предположить.

Преобразования

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

Как происходит преобразование?

Главный принцип в том, что, если целевой тип может содержать значение исходного типа, то это значение семантически сохраняется.

  • Когда исходный тип расширяется до целевого типа с аналогичной знаковой характеристикой (например, signed char -> int или unsigned short -> unsigned long ), каждое исходное значение после преобразования сохраняется.
  • Даже если исходный и целевой типы имеют разные диапазоны, все значения в их пересекающейся части будут сохранены. Например, int , содержащий значение в диапазоне [0, 255] , будет без потерь преобразован в unsigned char .

В более точной форме эти правила звучат так:

  • При преобразовании в беззнаковый тип новое значение равняется старому значению по модулю 2 целевая ширина в битах . Объяснение:
    • Если исходный тип беззнаковый и шире целевого, тогда старшие биты отбрасываются.
    • Если исходный тип имеет знак, тогда в процессе преобразования берется исходное значение, и из него/к нему вычитается/прибавляется 2 целевая ширина в битах до тех пор, пока новое значение не впишется в диапазон целевого типа. Более того, если число со знаком представлено в дополнительном коде, то в процессе преобразования старшие биты отбрасываются, как и в случае с беззнаковыми числами.

  • В случае преобразования в тип со знаком случаи могут быть такими:
    • Если исходное значение вписывается в диапазон целевого типа, тогда процесс преобразования (например, расширение знака) производит целевое значение, семантически равное исходному.
    • Если же оно не вписывается, тогда поведение будет определяться реализацией и может вызвать исключение (к примеру, прерывание из-за переполнения).

Арифметика

Продвижение/преобразование

Неопределенное поведение

Знаковое переполнение:

  • При выполнении арифметических операций над целочисленным типом переполнение считается неопределенным поведением (UB). Такое поведение может вызывать верные, несогласованные и/или неверные действия как сразу, так и в дальнейшем.
  • При выполнении арифметики над беззнаковым целым (после продвижений и преобразований) любое переполнение гарантированно вызовет оборот значения. Например, UINT_MAX + 1 == 0 .
  • Выполнение арифметики над беззнаковыми целыми фиксированного размера может привести к едва уловимым ошибкам. Например:
    • Пусть uint16_t = unsigned short , и int равен 32-битам. Тогда uint16_t x=0xFFFF , y=0xFFFF , z=x*y ; x и y будут продвинуты до int , и x * y приведет к переполнению int , вызвав неопределенное поведение.
    • Пусть uint32_t = unsigned char , и int равен 33-битам. Тогда uint32_t x=0xFFFFFFFF , y=0xFFFFFFFF , z=x+y ; x и y будут продвинуты до int , и x + y приведет к переполнению int , то есть неопределенному поведению.
    • Чтобы обеспечить безопасную арифметику с беззнаковыми целыми, нужно либо прибавить 0U , либо умножить на 1U в качестве пустой операции. Например: 0U + x + y или 1U * x * y . Это гарантирует, что операнды будут продвинуты как минимум до ранга int и при этом останутся без знаков.

Деление/остаток:

  • Деление на нуль и остаток с делителем нуля также относятся к неопределенному поведению.
  • Беззнаковое деление/остаток не имеют других особых случаев.
  • Деление со знаком может вызывать переполнение, например INT_MIN / -1 .
  • Остаток со знаком при отрицательных операндах может вызывать сложности, так как некоторые части являются однообразными, в то время как другие определяются реализацией.

Битовые сдвиги:

  • Неопределенным поведением считается битовый сдвиг ( >) на размер, который либо отрицателен, либо равен или больше битовой ширины.
  • Левый сдвиг беззнакового операнда (после продвижения/преобразования) считается определенным правильно и отклонений в поведении не вызывает.
  • Левый сдвиг операнда со знаком, содержащего неотрицательное значение, вследствие которого 1 бит переходит в знаковый бит, является неопределенным поведением.
  • Левый сдвиг отрицательного значения относится к неопределенному поведению.
  • Правый сдвиг неотрицательного значения (в типе операнда без знака или со знаком) считается определенным правильно и отклонений в поведении не вызывает.
  • Правый сдвиг отрицательного значения определяется реализацией.

Счетчик цикла

Выбор типа

Предположим, что у нас есть массив, в котором нужно обработать каждый элемент последовательно. Длина массива хранится в переменной len типа T0 . Как нужно объявить переменную счетчика цикла i типа T1 ?

  • Самым простым решением будет использовать тот же тип, что и у переменной длины. Например:

  • Говоря обобщенно, переменная счетчика типа T1 будет работать верно, если диапазон T1 будет являться (не строго) надмножетсвом диапазона T0 . Например, если len имеет тип uint16_t , тогда отсчет с использованием signed long (не менее 32 бит) сработает.
  • Говоря же более конкретно, счетчик цикла должен просто покрывать всю фактическую длину. Например, если len типа int гарантированно будет иметь значение в диапазоне [3,50] (обусловленное логикой приложения), тогда допустимо отсчитывать цикл, используя char без знака или со знаком (в котором однозначно можно представить диапазон [0,127] ).
  • Нежелательно использовать переменную длины и переменную счетчика с разной знаковостью. В этом случае сравнение вызовет неявное сложное преобразование, сопровождаемое характерными для платформы проблемами. К примеру, не стоит писать такой код:

Отсчет вниз

Для циклов, ведущих отсчет вниз, более естественным будет использовать счетчик со знаком, потому что тогда можно написать:

При этом для беззнакового счетчика код будет таким:

Примечание: сравнение i >= 0 имеет смысл только, когда i является числом со знаком, но всегда будет давать true , если оно будет беззнаковым. Поэтому, когда это выражение встречается в беззнаковом контексте, значит, автор кода скорее всего допустил ошибку в логике.

Заблуждения

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

  • char всегда равен 8 битам. int всегда равен 32 битам.
  • sizeof(T) представляет число из 8-битных байтов (октетов), необходимых для хранения переменной типа T . (Это утверждение ложно, потому что если, скажем, char равняется 32 битам, тогда sizeof(T) измеряется в 32-битных словах).
  • Можно использовать int в любой части программы и игнорировать более точные типы вроде size_t , uint32_t и т.д.
  • Знаковое переполнение гарантированно вызовет оборот значения. (например, INT_MAX + 1 == INT_MIN ).
  • Символьные литералы равны их значениям в коде ASCII, например ‘A’ == 65 . (Согласно EBCDIC это утверждение ложно).
  • Преобразование указателя в int и обратно в указатель происходит без потерь.
  • Преобразование <указателя на один целочисленный тип>в <указатель на другой целочисленный тип>безопасно. Например, int *p (…); long *q = (long*)p; . (см. каламбур типизации и строгий алиасинг).
  • Когда все операнд(ы) арифметического оператора (унарного или бинарного) имеют беззнаковые типы, арифметическая операция выполняется в беззнаковом режиме, никогда не вызывая неопределенного поведения, и в результате получается беззнаковый тип. Например: предположим, что uint8_t x; uint8_t y; uint32_t z; , тогда операция x + y должна дать тип вроде uint8_t , беззнаковый int , или другой разумный вариант, а +z по-прежнему будет uint32_t . (Это не так, потому что при продвижении типов предпочтение отдается типам со знаком).

Целочисленные двоичные коды

Беззнаковые двоичные коды

Первый вид двоичных кодов, который мы рассмотрим — это целые беззнаковые коды. Для определённости примем длину слова процессора равной восьми битам. В этих кодах каждый двоичныйразряд представляет собой степень цифры 2:

При этом минимально возможное число, которое можно записать таким двоичным кодом, равно 0. Максимально возможное число, которое можно записать таким двоичным кодом, можно определить как:

Этими двумя числами полностью можно определить диапазон, чисел которые можно представить таким двоичным кодом. В случае двоичного восьмиразрядного беззнакового целого числа диапазон будет: диапазон чисел, которые можно записать таким кодом: 0 . 255. Для шестнадцатиразрядного кода этот 0 . 65535. В восьмиразрядном процессоре для хранения такого числа используется две ячейки памяти, расположенные в соседних адресах. Для работы с такими числами используются специальные команды.

Прямые знаковые двоичные коды

Второй вид двоичных кодов, который мы рассмотрим — это прямые целые знаковые коды. В этих кодах старший разряд в слове используется для представления знака числа. В прямом знаковом коде нулем обозначается знак ‘+’, а единицей — знак ‘−’. В результате введения знакового разряда диапазон чисел смещается в сторону отрицательных чисел:

В случае двоичного восьмиразрядного знакового целого числа диапазон чисел, которые можно записать таким кодом: −127 . +127. Для шестнадцатиразрядного кода этот диапазон будет: −32767 . +32767. В восьмиразрядном процессоре для хранения такого числа тоже используется две ячейки памяти, расположенные в соседних адресах.

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

Знаковые обратные двоичные коды

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

Тем не менее при работе с обратными кодами требуется специальный алгоритм распознавания знака, вычисления абсолютного значения числа, восстановления знака результата числа. Кроме того, в прямом и обратном коде числа для запоминания числа 0 используется два кода, тогда как известно, что число 0 положительное и отрицательным не может быть никогда.

Знаковые дополнительные двоичные коды

От перечисленных недостатков свободны дополнительные коды. Эти коды позволяют непосредственно суммировать положительные и отрицательные числа не анализируя знаковый разряд и при этом получать правильный результат. Все это становится возможным благодаря тому, что дополнительные числа являются естественным кольцом чисел, а не исскуственным образованием как прямые и обратные коды. Кроме того немаловажным является то, что вычислять дополнение в двоичном коде чрезвычайно легко. Для этого достаточно к обратному коду добавить 1:

Диапазон чисел, которые можно записать таким кодом: −128 .. +127. Для шестнадцатиразрядного кода этот диапазон будет: −32768 .. +32767. В восьмиразрядном процессоре для хранения такого числа используется две ячейки памяти, расположенные в соседних адресах.

В обратных и дополнительных кодах наблюдается интересный эффект, который называется эффект распространения знака. Он заключается в том, что при преобразовании однобайтного числа в двухбайтное достаточно всем битам старшего байта присвоить значение знакового бита младшего байта. То есть для хранения знака числа можно использовать сколько угодно старших бит. При этом значение кода совершенно не изменяется.

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

1) Просуммируем числа 12 и 5

В этом примере видно, что в результате суммирования получается правильный результат. Это можно проконтролировать по флагу переноса C, который совпадает со знаком результата (действует эффект распространения знака).

2) Просуммируем два отрицательных числа −12 и −5

В этом примере флаг переноса C тоже совпадает со знаком результата, то есть переполнения не произошло и в этом случае

3) Просуммируем положительное и отрицательное число −12 и +5

В этом примере при суммировании положительного и отрицательного числа автоматически получается правильный знак результата. В данном случае знак результата отрицательный. Флаг переноса совпадает со знаком результата, поэтому переполнения не было (мы можем убедиться в этом непосредственными вычислениями на бумаге или на калькуляторе).

4) Просуммируем положительное и отрицательное число +12 и −5

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

5)Просуммируем числа 100 и 31

В этом примере видно, что в результате суммирования произошло переполнение восьмибитовой переменной, т.к. в результате операции над положительными числами получился отрицательный результат. Однако если рассмотреть флаг переноса, то он не совпадает со знаком результата. Эта ситуации является признаком переполнения результата и легко обнаруживается при помощи операции «исключающее ИЛИ» над старшим битом результата и флагом переноса C. Большинство процессоров осуществляют эту операцию аппаратно и помещают результат во флаг переполнения OV.

В этом примере результате операции над отрицательными числами в результате суммирования произошло переполнение восьмибитовой переменной, т.к. получился положительный результат. И в этом случае если рассмотреть флаг переноса C, то он не совпадает со знаком результата. Отличие от предыдущего случая только в комбинации этих бит. В примере 5 говорят о переполнении результата (комбинация 01), а в примере 6 об антипереполнении результата (комбинация 10).

Понравился материал? Поделись с друзьями!

  1. Микушин А.В. Занимательно о микроконтроллерах. СПб, БХВ-Петербург, 2006.
  2. Микушин А.В., Сажнев А.М., Сединин В.И. Цифровые устройства и микропроцессоры. СПб, БХВ-Петербург, 2010.
  3. http://matlab.exponenta.ru К.Г.Жуков «Справочное руководство пользователя Fixed-Point Blockset»
  4. http://static.dstu.edu.ru/informatics/mtdss/part2.html Описание данных. Типы данных и переменные
  5. http://khpi-iip.mipk.kharkiv.edu ПРОСТЫЕ СТРУКТУРЫ ДАННЫХ

Другие виды двоичных кодов:

Двоично-десятичный код Иногда бывает удобно хранить числа в памяти процессора в десятичном виде
https://digteh.ru/proc/DecCod.php

Представление чисел в двоичном коде с плавающей запятой Стандартные форматы чисел с плавающей запятой для компьютеров и микроконтроллеров
https://digteh.ru/proc/float/

Запись текстов двоичным кодом Представление текстов в памяти компьютеров и микроконтроллеров
https://digteh.ru/proc/text.php

Системы счисления В настоящее время и в технике и в быту широко используются как позиционные, так и непозиционные системы счисления.
https://digteh.ru/digital/SysSchis.php

Автор Микушин А. В. All rights reserved. 2001 . 2020

Предыдущие версии сайта:
http://neic.nsk.su/

Об авторе:
к.т.н., доц., Александр Владимирович Микушин

Кандидат технических наук, доцент кафедры САПР СибГУТИ. Выпускник факультета радиосвязи и радиовещания (1982) Новосибирского электротехнического института связи (НЭИС).

А.В.Микушин длительное время проработал ведущим инженером в научно исследовательском секторе НЭИС, конструкторско технологическом центре «Сигнал», Научно производственной фирме «Булат». В процессе этой деятельности он внёс вклад в разработку систем радионавигации, радиосвязи и транкинговой связи.

Научные исследования внедрены в аппаратуре радинавигационной системы Loran-C, комплексов мобильной и транкинговой связи «Сигнал-201», авиационной системы передачи данных «Орлан-СТД», отечественном развитии системы SmarTrunkII и радиостанций специального назначения.