Двойная целочисленная точность - это особый случай произвольной точности. На уровне ассемблера, как это реализовано, зависит от ISA. (Даже если вы спрашивали конкретно о x86, я предоставлю некоторую дополнительную информацию.)
Для сложения и вычитания многие ISA (например, ARM, Power, x86) предоставляют бит переноса (заимствование для вычитания) и специальное сложение с переносом и вычитанием с инструкциями заимствования.
ADD r1, r2, r3; // r1 = r2 + r3, set or clear carry bit
ADDC r4, r5, r6; // r4 = r5 + r6 + carry bit (also sets/clears carry)
Некоторые ISA (например, Alpha [который всегда был 64-битным], MIPS) не предоставляют бит переноса и специальные инструкции сложения / вычитания. Они могут использовать инструкцию set-on-less-than (установить регистр в ноль, если не меньше, и в один, если меньше чем), чтобы проверить, меньше ли результат одного из операндов (который определяет, сгенерировал ли результат перенос ).
ADD r1, r2, r3; // r1 = r2 + r3
ADD r4, r5, r6; // r4 = r5 + r6
SLT r7, r1, r2; // r7 = (r1 < r2)? 1 : 0 (determining carry)
ADD r4, r4, r7; // r4 = r4 + r7 (adding in carry)
(Разработчики ISA, которые решили не предоставлять бит переноса, сделали это, потому что операция многократной точности не является распространенной, один бит переноса вводит зависимость данных, которая препятствует суперскалярной операции, а специальные биты делают переименование регистров для выполнения вне порядка более сложным .)
Для умножения удвоенной точности может быть недостаточно, чтобы сделать сложные алгоритмы более эффективными, поэтому может быть предпочтителен простой метод генерации частичных произведений и суммирования результатов. Здесь используется формула (где a и c - верхняя половина значения удвоенной точности):
(a + b) * (c + d) = a*c + a*d + b*c + b*d.
(Для простоты в следующем предполагается, что результат с удвоенной точностью будет игнорироваться. Это означает, что термин продукта a * c полностью игнорируется.)
Для ISA (например, MIPS, x86), который обеспечивает как высокий, так и низкий результат от одного умножения, операция достаточно проста. Ниже является грубым приближением для x86 (я не знаком с деталями):
MUL r2:r0, r3, r7; // multiply a*d
MOV r1, r0; // preserve low result of a*d
MUL r2:r0, r5, r6; // multiply b*c
MOV [temp], R0; // preserve low result on stack at temp
MUL r2:r0, r5, r7; // multiply b*d
ADD r2, r1; // add high b*d and low b*c for part of
// higher result
ADD r2, [temp]; // add partial higher result and low a*d
// doubled precision product is now in r2:r0
Для ISA (например, Alpha), который обеспечивает раздельное умножение для высокого результата и кратное для команд с низким результатом (умножение, естественно, дает значение удвоенной точности), эта операция в некоторой степени похожа:
// a*c result is beyond doubled precision range
MULL r16, r1, r4; // low multiply result (a*d)
// high a*d result is beyond doubled precision range
MULL r17, r2, r3; // low multiply result (b*c)
// high b*c result is beyond doubled precision range
MULL r18, r2, r4; // low multiply result (b*d)
MULH r19, r2, r4; // high multiply result (b*d)
ADD r20, r19, r17; // sum high (b*d) and low (b*c)
// for part of higher result
ADD r20, r20, r16; // sum partial result and low (a*d)
// double precision result is in r20:r16
(Некоторые МСА не предоставляют средства для получения более высокого результата умножения. Для таких ISA операнды могут быть разбиты на половинные единицы, и может быть выполнено умножение в четыре раза, хотя, возможно, существует более сложный алгоритм. ISA с инструкциями умножения суммирования, очевидно, могут объединять сложения и два умножения в умножения сложения.)
Приведенный выше код псевдосборки предполагает, что переполнение не является проблемой (и не оптимизировано для производительности). Фактическая реализация будет обрабатывать такие правильно.
Арифметическая библиотека множественной точности GNU, библиотека с открытым исходным кодом для арифметики произвольной точности, предоставит подробную информацию о реальной реализации.
Ограничения адресного пространства
Причина того, что 32-разрядный процессор может (в простом смысле) использовать только 4 ГБ памяти, заключается в том, что он использует 32-разрядные указатели / адреса и использует байтовую адресацию (поэтому доступно 2 32 адреса). Традиционно, даже в системах с виртуальной памятью поддержка физического адресного пространства (кстати, включающего в себя адреса ввода-вывода с отображением в памяти) больше 4 ГиБ считалась ненужной. (Физическое адресное пространство 4 ГиБ также удобно для 32-битных записей таблицы страниц, что позволяет использовать 10 бит для определения достоверности, разрешений и других метаданных при использовании страниц 4 КиБ. Использование записей в 64-битных таблицах страниц будет иметь тенденцию использовать избыточную память для небольших систем, а исходные 32-битные системы были небольшими.)
Чтобы продлить срок службы 32-разрядных ISA, некоторые ISA (например, ARM, x86) добавили расширения физических адресов, которые используют запись таблицы страниц 64-разрядных, чтобы позволить больший физический адрес. Этот обходной путь неудобен, особенно для ОС (которая должна управлять всей физической памятью).
Некоторые ISA (например, HP PA-RISC) предоставили систему сегментации, которая позволяла программам уровня пользователя адресовать большее адресное пространство, добавляя регистры сегментов специального назначения в начало адреса. (Power [PC] также использует сегменты, но значения не могут быть изменены непривилегированным [т.е. прикладным] программным обеспечением.)
(Теоретически, ISA может использовать пару регистров для расширения возможностей адресации памяти, как некоторые 8-битные процессоры. Однако к этому моменту переход на 64-битную ISA, как правило, имеет больше смысла.)