Числа со знаком и дополнительный код
Помимо того, что процессор работает с двоичными числами, эти числа могут быть со знаком или без знака. Если число без знака, то оно просто представляет собой результат перевода десятичного числа в двоичный вид. Все биты в таком числе являются информационными и оно может принимать только неотрицательные значения.
Для представления чисел со знаком используется специальное кодирование. Старший бит в этом случае обозначает знак числа. Если знаковый бит равен нулю, то число положительное, иначе — отрицательное. Понятно, что положительное число со знаком будет выглядеть точно так же, как и число без знака.
С отрицательными числами чуть сложнее. Исторически для представления отрицательных чисел в компьютерах использовались разные виды кодирования: прямой, обратный и дополнительный код. В настоящее время наиболее часто используется дополнительный код, в том числе и в процессорах 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 |
0 | 255 | -128 | 127 | |
слово | 00000000 00000000 | 11111111 11111111 | 10000000 00000000 | 01111111 11111111 |
0 | 65 535 | -32 768 | 32 767 | |
двойное слово |
0000…0000 | 1111…1111 | 1000…0000 | 0111…1111 |
0 | 4 294 967 295 | -2 147 483 648 | 2 147 483 647 | |
и т.д. | … | … | … | … |
Если результат какой-то операции выйдет за пределы диапазона представления чисел, то случится переполнение и результат будет некорректным. (Например, при сложении двух положительных чисел, можно получить отрицательное число!) Поэтому нужно быть внимательным при программировании и предусмотреть обработку таких ситуаций, если они могут возникнуть.
Следующая часть »
Статья основана на материале xrnd с сайта asmworld (из учебного курса по программированию на ассемблер 16-битного процессора 8086 под DOS).
Числа со знаком и дополнительный код
Помимо того, что процессор работает с двоичными числами, эти числа могут быть со знаком или без знака. Если число без знака, то оно просто представляет собой результат перевода десятичного числа в двоичный вид. Все биты в таком числе являются информационными и оно может принимать только неотрицательные значения.
Для представления чисел со знаком используется специальное кодирование. Старший бит в этом случае обозначает знак числа. Если знаковый бит равен нулю, то число положительное, иначе — отрицательное. Понятно, что положительное число со знаком будет выглядеть точно так же, как и число без знака.
С отрицательными числами чуть сложнее. Исторически для представления отрицательных чисел в компьютерах использовались разные виды кодирования: прямой, обратный и дополнительный код. В настоящее время наиболее часто используется дополнительный код, в том числе и в процессорах x86.
Чтобы сделать из положительного числа отрицательное, необходимо проинвертировать все его биты (0 заменяем на 1, а 1 заменяем на 0) и затем к младшему разряду прибавить единицу. Например, представим -5 в дополнительном коде:
В обратную сторону переводится точно также.
Синтаксис FASM
Для записи отрицательного числа в программе на ассемблере используется символ ‘-‘, например:
x db -5
Кстати, это работает и с числами в других системах счисления, и даже с символами ?
y db -25h z db -77o k db -101b s db -'a'
Со знаковыми и беззнаковыми числами нужно быть внимательным, потому что только вы знаете, какие числа используются в вашей программе! Процессору абсолютно по барабану, какие данные он обрабатывает, поэтому невнимательность может привести к ошибке. Один и тот же байт может интерпретироваться по-разному, в зависимости от того со знаком число или без. Например, числу со знаком -5 соответствует число без знака 251:
Диапазоны значений чисел со знаком и без
При программировании на ассемблере (как, впрочем, и на многих других языках) необходимо учитывать ещё один важный момент. А именно — ограничение диапазона представления чисел. Например, если размер беззнаковой переменной равен 1 байт, то она может принимать всего 256 различных значений. Это означает, что мы не сможем представить с её помощью число, больше 255 (11111111_2)
. Для такой же переменной со знаком максимальным значением будет 127 (01111111_2)
, а минимальным -128 (10000000_2)
. Аналогично определяется диапазон для 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 |
0 | 255 | -128 | 127 | |
слово | 00000000 00000000 | 11111111 11111111 | 10000000 00000000 | 01111111 11111111 |
0 | 65 535 | -32 768 | 32 767 | |
двойное слово |
0000…0000 | 1111…1111 | 1000…0000 | 0111…1111 |
0 | 4 294 967 295 | -2 147 483 648 | 2 147 483 647 | |
и т.д. | … | … | … | … |
Если результат какой-то операции выйдет за пределы диапазона представления чисел, то случится переполнение и результат будет некорректным. (Например, при сложении двух положительных чисел, можно получить отрицательное число!) Поэтому нужно быть внимательным при программировании и предусмотреть обработку таких ситуаций, если они могут возникнуть.
Online nasm with linux: http://www.tutorialspoint.com/compile_assembly_online.php
Example:
section .text
global _start ;must be declared for using gcc
_start: ;tell linker entry point
mov ax,-4 ; test value
doFewTests:
push eax
cmp ax,0
; test ax,ax
; test ax,0x8000
jl handleNegative
; handle non-negative
; fake conversion to ASCII for numbers 0-9
add al,'0'
mov ecx,strPositive
mov edx,lenPositive
mov [ecx+edx-2],al
jmp printMessage
handleNegative:
; fake conversion to ASCII for numbers -9 to -1
neg al
add al,'0'
mov ecx,strNegative
mov edx,lenNegative
mov [ecx+edx-2],al
printMessage:
mov ebx, 1 ;file descriptor (stdout)
mov eax, 4 ;system call number (sys_write)
int 0x80 ;call kernel
pop eax
inc ax
cmp ax,5
jl doFewTests ; do -4 to +4 demonstration loop
; exit
mov eax, 1 ;system call number (sys_exit)
int 0x80 ;call kernel
section .data
strPositive db 'Positive number: x', 10
lenPositive equ $ - strPositive
strNegative db 'Negative number: -x', 10
lenNegative equ $ - strNegative
BTW, there’s no point to work without debugger, and you obviously didn’t listen. This site is good only for demonstrating final example, it’s impossible to learn Assembly programming on it, because it doesn’t have debugger installed.
If you don’t listen and you just search for the easy way, you will never learn Assembly, because in Assembly there are only “proper” ways, no easy ones.
LAST TIME: use the debugger.
If you don’t know how, use the instructions at bottom of https://stackoverflow.com/tags/x86/info to get into gdb
, then find some other tutorial/docs how to use it.
Eventually try some graphics frontend like ddd
(but last time I tried, the experience was not very good, learning to control gdb
is long term investment in GNU world, as that debugger is used also by other high level languages, so don’t hesitate to spend DAYS on it.
(I’m using edb
debugger, but I had to compile it from sources, so that’s probably another “can of worms” for you).
Output of that fugly example:
sh-4.3$ nasm -f elf *.asm; ld -m elf_i386 -s -o demo *.o
sh-4.3$ demo
Negative number: -4
Negative number: -3
Negative number: -2
Negative number: -1
Positive number: 0
Positive number: 1
Positive number: 2
Positive number: 3
Positive number: 4
Со школьной скамьи нас учили, что у чисел бывают отрицательные значения. Дело выглядело просто, записываешь число на бумаге, рисуешь черточку слева и все – положительное число превратилось в отрицательное. Для арифметической операции нужно от положительного числа отнять модуль отрицательного числа. К сожалению, такой подход совершенно не приемлем для машинных вычислений, потому что процессор не разделяет числа на положительные и отрицательные, а к числу нельзя просто пририсовать черточку. Да и хранить её негде Разве что «спрятать» в самом старшем бите.
В программировании на ассемблере числа без знака – любые числа, не имеющие знака минус в привычном представлении. То есть любые числа от нуля и выше. Для байта это означает любые числа от нуля до 255, для двух байт от 0 до 65535.
Хранение чисел со знаком урезает верхнюю границу хранимого в байте, двух байтах, четырех байтах и т.д. значения в два раза, потому что теперь старший бит занят знаком числа. Если этот бит равен нулю, то число положительное, а если бит равен единице – отрицательное.
Казалось бы, для хранения чисел со знаком нужно просто отвести старший бит под знак и этого достаточно (прямое кодирование числа со знаком). Но в истории развития ЭВМ для цели хранения числа со знаком помимо прямого кода использовали обратный и дополнительный код. Именно дополнительный код наиболее распространен в наше время, также он входит в архитектуру процессоров x86.
Дополнительный код как способ хранения чисел со знаком
Рассмотрим число 7. В двоичном виде оно представлено как 00000111.Чтобы превратить его в отрицательное значение, понятное процессору x86, необходимо конвертировать его в дополнительный код. Для этого все биты инвертируются (1 меняются на 0, а 0 на 1), а к младшему биту прибавляется единица:
Процесс перевода отрицательных чисел в положительные одинаков.
Синтаксис FASM
Обозначать привычные нам десятичные числа в коде программы можно с использованием знака минус «-», например:
Однако не только десятичные числа конвертируются в отрицательные с прибавлением «минуса», также такой приём проходит с числами других систем счисления и со строчными символами:
y db –25h z db –77o k db –101b s db –‘a’ |
Как было написано выше, процессор не делит числа не положительные и отрицательное. Поэтому эта роль программиста определять, какими числами он оперирует в своей программе. Ведь от того интерпретируется байт как число без знака или как число со знаком, зависит его значение. Например, число со знаком -7 соответствует число без знака 249:
Диапазоны значений чисел со знаком и без
Числа без знака хранят в два раза большее положительные значения, чем это делают числа со знаком. Потому что числа со знаком также хранят отрицательные значения. Байт хранит 256 комбинаций из 8 бит, что соответствует 256 различным числам (включая 0). Граница верхнего хранимого числа в байте для числа без знака равна «255» (11111111b). Ну а для числа со знаком такой верхней границе соответствует число «127» (01111111b), а минимальному «-128» (10000000b). Схожим методом распределяется диапазон для переменных длинной в 2 байта и 4 байта.
В последующих уроках мы будем называть 16 битые переменные (2 байта) как слово, а 32 битые (4 байта) как двойное слово. Это общепринятые обозначения, которые относят нас к процессору Intel 8086 и его 16 битному такту, то есть к способности обработать 16 бит данных или одно слово (word) данных за такт. Следовательно, 32-битные переменные уже называются двойное слово (double word, dword). Ну а 32-битные процессоры просто унаследовали обозначения переменных от младшего брата. Отсюда же проистекают имена директив dw (Define Word) и dd (Define Dword). Ну а db – это Define Byte.
Для подкрепления урока приведу наглядную таблицу диапазонов чисел со знаком и без знака:
Размер переменной |
Число без знака | Число со знаком | ||
---|---|---|---|---|
min | max | min | max | |
байт | 00000000 | 11111111 | 10000000 | 01111111 |
0 | 255 | -128 | 127 | |
слово | 00000000 00000000 | 11111111 11111111 | 10000000 00000000 | 01111111 11111111 |
0 | 65 535 | -32 768 | 32 767 | |
двойное слово |
0000…0000 | 1111…1111 | 1000…0000 | 0111…1111 |
0 | 4 294 967 295 | -2 147 483 648 | 2 147 483 647 | |
и т.д. | … | … | … | … |
Главный подводный камень любых операций с числами это выход за пределы хранимого в регистре или памяти диапазона. К примеру два больших положительных числа при сложении выйдут за свой диапазон и произведут отрицательное число! Из-за чего изменятся соответствующие флаги процессора, поведение и значение которых мы еще не раз рассмотрим в следующих уроках. Для избегания таких ситуаций программист должен оптимизировать операции с числами резервируя достаточное пространство под результат и т.д.