Заниматься математикой

Заниматься математикой

Все вычисления происходят между регистрами. Арифметическая инструкция называет сначала приёмник, а затем два источника, которые она читает: означает dest = src1 + src2.

Арифметическая операция
t2, t0, t1←add
t0 = 7t1 = 5+t2 = 12

add помещает t0 + t1 в t2.

Две линии, входящие слева, — это t0 и t1, всегда в этом порядке. Они входят в одну и ту же форму независимо от выполняемой операции: сложение, вычитание, умножение, деление или остаток. Входят два значения, происходит одна операция, и одно значение выходит в t2 справа. называет t2 первым, потому что именно туда попадает результат, что соответствует единственному правилу порядка, которому следует каждая арифметическая инструкция в этом уроке.

Сложение и вычитание

берёт числа, хранящиеся в двух регистрах, и записывает их сумму в третий. делает то же самое для вычитания: вычисляет t0 - t1. Обе инструкции читают только из регистров.

Регистрация арифметики
add t2, t0, t1 # t2 = t0 + t1
sub t2, t0, t1 # t2 = t0 - t1

Одна вещь, которую стоит знать: регистр хранит только 32 бита, поэтому существует потолок. Если результат слишком велик, чтобы поместиться, ошибка не возникает. Значение тихо заворачивается к очень малым числам, и выполнение продолжается. Обычные значения не достигнут этого предела, но о потолке стоит знать.

Чтобы использовать фиксированное число вместо второго регистра, добавьте букву i, которая означает непосредственный: прибавляет константу 1. Инструкции subi нет, поэтому чтобы вычесть константу, прибавляют отрицательную: .

💡
прибавляет константу. складывает два регистра. Путать эти две — самая распространённая ошибка новичков.

Умножение и деление

умножает два регистра. делит их как целые числа и отбрасывает дробную часть, поэтому 7 / 2 даёт 3. даёт остаток, поэтому 7 % 2 даёт 1. Деление никогда не вызывает сбоя. Деление на ноль возвращает -1.

Умножение, деление, остаток
mul t2, t0, t1 # t2 = t0 * t1
div t2, t0, t1 # t2 = t0 / t1 (целое)
rem t2, t0, t1 # t2 = t0 % t1 (остаток)

Сборка

Сложить два числа и вывести результат
li t0, 7
li t1, 5
add t1, t0, t1 # t1 = 7 + 5 = 12
mv a0, t1 # сервис вывода читает a0
li a7, 1
ecall
li a7, 10
ecall

загружает 7 в t0. загружает 5 в t1. складывает их и записывает 12 в t1. копирует результат в a0, потому что служба печати всегда читает своё значение из a0. Затем выбирает службу печати целого числа, и запускает её. Наконец, и завершают программу.

Бонус: умножение с помощью сдвигов

Когда множитель является степенью двойки, существует более быстрый метод, чем . (логический сдвиг влево) перемещает биты регистра влево, и каждая позиция сдвига удваивает значение: сдвиньте на 1, чтобы умножить на 2, на 2 — для 4, на 3 — для 8. Сдвиг стоит меньше, чем умножение, поэтому компиляторы часто его используют.

Умножение на 8 с помощью сдвигов
li t0, 5 # начать с 5
# сдвиг влево на 3 бита (5 * 2^3 = 5 * 8)
sll a0, t0, 3 # a0 = 40
li a7, 1 # вывести целое число
ecall
li a7, 10
ecall

помещает 5 в t0. сдвигает его биты на три позиции влево. Каждая позиция удваивает значение, поэтому 5 удваивается три раза (5, 10, 20, 40), давая 40 в a0. Остальное следует шаблону печати из предыдущего. Попробуйте изменить 3 на 1 или 2 и предскажите результат, прежде чем запускать.

Умножение сдвигом
5sll a0, t0, 0
×2 →
10sll a0, t0, 1
×2 →
20sll a0, t0, 2
×2 →
40sll a0, t0, 3
0
0
0
0
0
1
0
1

5 сдвинуто влево на 0 бит — результат остаётся 5.

Восемь ячеек — это двоичная форма числа над ними, по одному биту в каждой, читаемая слева направо, как обычно. Сдвиг всей строки на одну позицию влево — та же операция, что и умножение на 2, потому что перемещение цифры на одну позицию влево в любой системе счисления умножает её значение на основание этой системы. В двоичной системе всего две цифры, поэтому множитель равен 2. Сдвиг три раза складывается в умножение на 8, что соответствует тому, что делает с числом 5.