Деление 64-битного числа
Поскольку MIPS, эмулированный MARS, не поддерживает 64 ÷ 32 ⇒ 64 деления1, нам нужно реализовать собственное разделение на несколько слов.
Книга < em>Хакерское восхищение как глава, основанная в основном на Искусстве компьютерного программирования Кнута.
Идея в принципе очень проста: представьте 64-битное число как двузначное число, каждая цифра 32-битная (так что основание этого числа равно 232) и выполните оценку. школьное цифровое деление.
Давайте рассмотрим эту идею на примере с основанием 10: рассмотрим 53 / 2.
Мы можем вычислить результат, разделив 5 на 2, что даст результат m1 от 2 и остаток r1 от 1.
m1 — первая цифра результата. (наиболее значимое).
Затем мы "опускаем" 3 из 53, чтобы получить число 13, это r1 * 10 + 3.
Снова выполняем 13/2, чтобы получить m0 = 6 и r0 = 1.
Таким образом, результат равен 26 (с остатком 1).
Мы используем тот же подход, с той лишь разницей, что мы имеем дело с цифрами в диапазоне от 0 до 232 - 1.
Таким образом, 64-битное число, подобное тому, которое возвращает системный вызов time , рассматривается как двузначное число: наименее значащая (крайняя правая) находится в $a0
, другая — в $a1
.
Например, число 0x15EF18933B1 рассматривается как
Digit 1 Digit 0
0x15E 0xF18933B1
Алгоритм, который мы ищем, представляет собой просто цикл, в котором мы «прибавляем» последний остаток к текущей цифре, делим на делитель, чтобы получить текущую цифру результата, а остаток используем на следующем шаге.
Обратите внимание, что под «добавить» мы подразумеваем «добавление с весом», остаток ri не добавляется к текущей цифре ni sub>, он масштабируется по базе, а затем прибавляется (именно так мы делали ранее, мы делали 1*10+3, а не 1+3). Я не буду подробно объяснять алгоритм, так как он широко доступен в Интернете.
Важно отметить, что на самом деле мы никуда не двигаемся.
Из-за масштабирования текущего остатка нам все равно нужно выполнить деление 64 ÷ 32 ⇒ 64 (как и в десятичном случае, 13/2 равно не легче, чем 53/2!).
Разница в том, что мы знаем, что число состоит не более чем из двух цифр.
Чтобы распутать этот циклический аргумент, нам нужно уменьшить масштаб до 32 ÷ 16 ⇒ 32.
MIPS поддерживает 32 ÷ 32 ⇒ 32, ограничивая наш делитель не более чем 65 535, мы получаем то, что хотели.
Итак, Алгоритм работает с полусловом из 16 бит, тогда 64-битное число воспринимается как 4-значное число.
Код
#Input
# a0:a1 = N = DCBA
# a2 = K (16-bit)
#Output
# a0:a1 = quotient
# hi = reminder
div64x16:
subu $sp, $sp, 16
sw $a0, ($sp)
sw $a1, 4($sp)
add $t0, $sp, 8 # Pointer to digits (N)
add $t3, $sp, 16 # Pointer to result (M)
xor $t1, $t1, $t1 # Remainder
loop:
subu $t3, $t3, 2
subu $t0, $t0, 2
sll $t1, $t1, 16 # t1 = R * 65536
lhu $t2, ($t0) # t2 = N[i]
addu $t2, $t2, $t1 # t2 = N[i] + R * 65536
div $t2, $a2
mflo $t1 # t1 = (N[i] + R * 65536) / K
sh $t1, ($t3) # M[i] = (N[i] + R * 65536) / K
mfhi $t1 # t1 = (N[i] + R * 65536) % K
bne $t0, $sp, loop
mthi $t1
lw $a0, 8($sp)
lw $a1, 12($sp)
addu $sp, $sp, 16
jr $ra
Входные аргументы представлены в виде a0:a1
(64-битное делимое, младшее слово в a0
) и a2
(делитель).
Результатом является a0:a1
(частное) и hi
(остаток).
Обратите внимание, что существует ограничение на делитель: он должен иметь размер 16 бит.
Это значительно упрощает алгоритм деления, но требует некоторого обходного пути при вычислении времени суток.
Вычисление времени суток
Учитывая мс эпохи Unix (1 января 1970 г.), чтобы получить время суток, нужно разделить на 1000 * 3600 * 24 и взять остаток.
Однако 1000 * 3600 * 24 не соответствует 16 битам. Мы можем сделать это с тремя делениями, но тогда нам нужно будет объединить остатки.
Есть немного более простой и интуитивно понятный подход.
Во-первых, мы избавляемся от ms. Нам не нужна точность мс по времени суток, поэтому мы можем вообще отбросить остаток.
li $v0, 30
syscall
#a0:a1 = ms since epoch
li $a2, 1000
jal div64x16
#a0:a1 = seconds since epoch
Теперь нам нужно разделить на 3600 * 24 = 86400, но мы не можем.
Хороший трюк — разделить на 3600 * 12 = 43200 (что соответствует 16 битам), это дает нам количество полдня (назовем его чч), а остаток дает нам количество секунд далеко за полдня (назовем его чч).
Поскольку в половине дня 43200 секунд, время может быть не более 11:59:59.
Мы не знаем, является ли 2:0:0 14:00 или 2:00, нам нужно проверить, hh нечетное или четное, чтобы узнать, если hh четное, то мы находимся в первой половине дня и время правильное, в противном случае мы находимся во второй половине дня и мы прибавляем 43200 (секунды за полдня) к hs.
Это переводит секунды за полдня в секунды за дни.
#a0:a1 = seconds since epoch
li $a2, 43200
jal div64x16
#a0:a1 = half-days since epoch
#hi = seconds in half-day
mfhi $s0 #Seconds in the half-day
andi $a0, $a0, 1 #a1 = 1 if odd half-day number (otherwise 0)
ror $a0, $a0, 1 #a1 < 0 if odd half-day number (otherwise 0)
sra $a0, $a0, 31 #a1 = 0xffffffff if odd half-day number (otherwise 0)
andi $a0, $a0, 43200 #a1 = 43200 if odd half-day number (otherwise 0)
add $s0, $s0, $a0 #s0 = seconds in the day
Как только у вас есть секунды в дне (32-битное число), остальное легко.
li $t0, 3600
div $s0, $t0
mflo $s0 #s0 = Hour
mfhi $t1
li $t0, 60
div $t1, $t0
mflo $s1 #s1 = Minute
mfhi $s2 #s2 = Second
#Print the time
li $v0, 1
move $a0, $s0
syscall
li $v0, 4
la $a0, sep
syscall
li $v0, 1
move $a0, $s1
syscall
li $v0, 4
la $a0, sep
syscall
li $v0, 1
move $a0, $s2
syscall
#Exit
li $v0, 10
syscall
Примечание. Системный вызов времени использует значение Java new Date().getTime()
, это текущее время в часовом поясе по Гринвичу. Если вы не живете в этом часовом поясе, часы будут другими.
1 Это обозначение обозначает 64-битное делимое, 32-битный делитель и 64-битный результат.
person
Margaret Bloom
schedule
06.10.2017
int div_by_1000(long long millis) { return millis / 1000; }
: godbolt.org/g/MR6h6d; мог бы посмотреть на его src.) - person Peter Cordes   schedule 06.10.2017