Робимо математику

Робимо математику

Усі обчислення відбуваються між регістрами. Арифметична інструкція називає спочатку призначення, а потім два джерела, які вона читає: означає 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.