Чтобы предоставить, возможно, более ясный пример, на x86_64, скомпилированном с -O
флагом, функция
pub fn leet(a : i128) -> i128 {
a + 1337
}
компилируется в
example::leet:
mov rdx, rsi
mov rax, rdi
add rax, 1337
adc rdx, 0
ret
(В моем исходном сообщении было больше u128
, чем то i128
, о чем вы спрашивали. Функция компилирует один и тот же код в любом случае, хорошая демонстрация того, что подписанное и неподписанное сложение одинаково на современном процессоре.)
Другой листинг выдал неоптимизированный код. В отладчике безопасно переходить по шагам, потому что он гарантирует, что вы можете поставить точку останова в любом месте и проверить состояние любой переменной в любой строке программы. Это медленнее и труднее читать. Оптимизированная версия намного ближе к коду, который действительно будет запускаться в производственной среде.
Параметр a
этой функции передается в паре 64-битных регистров rsi: rdi. Результат возвращается в другой паре регистров, rdx: rax. Первые две строки кода инициализируют сумму a
.
Третья строка добавляет 1337 к младшему слову ввода. Если это переполнение, он несет 1 в флаге переноса ЦП. В четвертой строке к старшему слову ввода добавляется ноль - плюс 1, если оно было перенесено.
Вы можете думать об этом как о простом добавлении однозначного числа к двузначному числу.
a b
+ 0 7
______
но в базе 18,446,744,073,709,551,616. Вы по-прежнему сначала добавляете самую низкую «цифру», возможно, переносите 1 в следующий столбец, а затем добавляете следующую цифру плюс перенос. Вычитание очень похоже.
При умножении необходимо использовать тождество (2⁶⁴a + b) (2⁶⁴c + d) = 2¹²⁸ac + 2⁶⁴ (ad + bc) + bd, где каждое из этих умножений возвращает верхнюю половину произведения в одном регистре и нижнюю половину произведения в другой. Некоторые из этих терминов будут отброшены, потому что биты выше 128-го не помещаются в a u128
и отбрасываются. Даже в этом случае для этого требуется ряд машинных инструкций. Разделение также проходит в несколько этапов. Для значения со знаком умножение и деление дополнительно потребуют преобразования знаков операндов и результата. Эти операции вообще не очень эффективны.
На других архитектурах становится легче или сложнее. RISC-V определяет 128-битное расширение набора команд, хотя, насколько мне известно, никто не реализовал его в кремнии. Без этого расширения руководство по архитектуре RISC-V рекомендует условный переход:addi t0, t1, +imm; blt t0, t1, overflow
В SPARC есть управляющие коды, такие как управляющие флаги x86, но для их установки вам нужно использовать специальную инструкцию add,cc
. MIPS, с другой стороны, требует , чтобы вы проверяли, действительно ли сумма двух целых чисел без знака меньше одного из операндов. Если да, то добавление вышло за край. По крайней мере, вы можете установить в другом регистре значение бита переноса без условного перехода.