Почему существуют разные машинные инструкции для подписанного mul / div?
Я всегда думал, что знаковые умножения и деления требуют иной логики, чем соответствующие операции без знака. На другом форуме кто-то сказал, что неподписанные ALU могут использоваться без каких-либо изменений для подписанных параметров и давать действительные результаты.
Я только что проверил это с небольшой программой
#include <stdio.h>
void main()
{
short a, b;
long long c;
c = 0;
a = -32768;
do
{
b = -32768;
do
if( (int)((unsigned)a * (unsigned)b) != ((int)a * b) )
++c;
while( ++b != -32768 );
} while( ++a != -32768 );
printf( "%.0lf\n", (double)c );
}
Это печатает ноль, т.е. умножения работают как вышеупомянутое предположение. Так почему существуют разные инструкции для умножения со знаком и без знака и как это представлено в схемотехнике?
И я не говорил о разделениях. Правильно ли я предположить, что мое более раннее предположение о том, что для делений нужна другая логика, по крайней мере, другая схема?
2 ответа
Это зависит от размера параметров. если вы умножите два 8-битных значения, то для сохранения результата в худшем случае потребуется 16 бит, верно? 0xFF * 0xFF = 0xFE01. 255*255 = 65025.
Из начальной школы, как мы делаем умножение вручную? У меня есть два, два битных числа
ab
* cd
======
Допустим, оба CD позволяют понять, что происходит.
ab
* 11
======
0ab
+ ab
======
Если мы считаем их числами без знака, то биты слева от ab и cd дополняются нулями. но если это числа со знаком, то эти биты расширяются знаком
000ab
* 000cd
========
xxxab
* yyycd
=======
Если я возьму двухбитовые операнды и двухразрядный результат, это не имеет значения
ab
* 11
======
ab
+ b
======
биты в суммировании не заботятся. но если я хочу двухбитные операнды и 4-битный результат, это имеет значение.
Деление, наоборот, вы хотите начать с 64-разрядного числителя, если вы хотите иметь 32-разрядный знаменатель, поэтому 32-разрядное деление на 32 бита - это числитель со знаком или без знака? Когда вы дополняете его до 64 битов, чтобы выполнить деление, вы подписываете его, расширяете или обнуляете его (со знаком или без знака). результат варьируется.
В некоторых наборах инструкций нет двух к одному, все операнды имеют одинаковый размер, поэтому вам "просто" нужно синтезировать это, но вы делаете это так, как вы бы делали это, основываясь на знаниях начальной школы.
сложение и вычитание не заботятся о знаковых и неподписанных, они не знают, вычитание использует сумматор, прелесть дополнения к двум, вы инвертируете и добавляете один, так что вы инвертируете второй операнд, а затем вводите 1 в перенос на lsbit, а затем добавляете, превращая сумматор в вычитатель. Дополнение позаботится обо всем остальном, результаты просто работают в любом случае. переполнение без знака и переполнение со знаком имеют значение, и если оба вычисляются, то ваш сумматор делает оба. Переполнение без знака - это бит переноса из мсбита, со знаком - сравнение переноса и выведения из мсбита, которые также могут быть определены из мсбит ввода и результата.
На другом форуме кто-то сказал, что неподписанные ALU могут использоваться без каких-либо изменений для подписанных параметров и давать действительные результаты.
Чтобы понять это противоречие, мы должны помнить, что число (здесь десятичное число) имеет "неявные" цифры слева и справа:
12345 = 00...0000000012345.000000000...00
То же самое верно для положительных и беззнаковых двоичных чисел; неявные цифры равны 0 в этом случае:
01110100 = 00...0000000001110100.0000...00
Для отрицательных чисел, хранимых как дополнение к двум, неявные цифры на левой стороне, однако, являются единицами, а не нулями:
10110100 = 11...1111111110110100.0000...00
Всякий раз, когда одна из "неявных" цифр на левой стороне влияет на результат операции, нам нужен неподписанный и подписанный вариант операции:
сдвиг
Сдвиг влево неявной цифры на правой стороне (после десятичной точки) повлияет на результат; это всегда 0.
Однако смещение вправо первой неявной цифры на левой стороне будет влиять на результат; и действительно, есть две операции "сдвиг вправо": "SRL" (без знака) и "SRA" (с подписью).
Сложение, вычитание и умножение
В этих случаях результаты также будут отличаться:
1111 + 0011 = 11111 + 00011 = 00010 (signed)
1111 + 0011 = 01111 + 00011 = 10010 (unsigned)
Однако большинство процессоров предоставляют операции, которые будут возвращать столько битов, сколько было у входных данных: если вы добавите два 8-битных числа, вы получите только младшие 8 битов результата, а не целые 9 битов. И младшие 8 бит не отличаются между подписанной и неподписанной операциями.
Если есть процессор, который может добавить два 8-битных числа, возвращающих 16-битный результат, этот процессор требует инструкции "без знака добавления" и "со знаком добавления"!
То же самое верно для умножения. Однако многие (большинство) процессоров работают следующим образом: они умножают два 32-битных числа, и в результате получаются не 32 младших бита, а полные 64 бита, поэтому требуются инструкция "умножение со знаком" и "умножение без знака".
Однако есть несколько процессоров, которые умножают два 32-битных числа, возвращая только младшие 32 бита результата. Этим процессорам потребуется только одна команда "умножения".
разделение
Деление на 8 - это та же операция, что и сдвиг вправо на три бита.
Это означает, что три неявные цифры с левой стороны будут смещены, что означает, что неявные цифры с левой стороны влияют на результат.
Поэтому вам всегда понадобится операция без знака и операция со знаком.