Как найти отрицательное число в ассемблере

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

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

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

С отрицательными числами чуть сложнее. Исторически для представления отрицательных чисел в компьютерах использовались разные виды кодирования: прямой, обратный и дополнительный код. В настоящее время наиболее часто используется дополнительный код, в том числе и в процессорах 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), а к младшему биту прибавляется единица:

Преобразование числа 7 в дополнительный код

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

Синтаксис 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
и т.д.

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

Добавить комментарий