Операция дополнения двоих реализована на большинстве языков с помощью унарного оператора -
. Используется только со знаковыми целочисленными типами. Он может быть реализован в ALU как отдельная инструкция отрицания (например, NEG
) или свернут в другую операцию, например, когда вы используете инструкцию вычитания (например, SUB
) вместо инструкции сложения (например, ADD
) .
Ваш первый вопрос неясен, поскольку «последний бит» может относиться либо к старшему значащему биту (MSB), либо к младшему значащему биту (LSB). В целом числе со знаком MSB указывает знак; проверка отрицательного значения обычно реализуется в виде бита N
в регистре кода состояния, который обновляется на основе результата последней выполненной инструкции (хотя некоторые инструкции не изменяют регистр кода условия). Вычисление дополнения к двум, только если исходное число является отрицательным, является операцией абсолютного значения (например, ABS
). Проверка LSB просто говорит вам, является ли целое число четным или нечетным.
Числа с плавающей запятой используют отдельный знаковый бит, поэтому 0
и -0
являются разными значениями. Комплимент Two не работает со значениями с плавающей запятой; необходимо использовать другой подход.
РЕДАКТИРОВАТЬ: Пример. Рассмотрим следующий код C:
#include <stdlib.h>
int do_math(int a, int b)
{
return a - b;
}
int main(int argc, char* const argv[])
{
if(argc < 2)
return 0;
return do_math(atoi(argv[1]), atoi(argv[2]));
}
Это можно запустить с помощью:
$ gcc -O0 foo.c -o foo
$ ./foo 20 10; echo $?
10
На x86_64 функция do_math()
содержит следующий код:
_do_math:
pushq %rbp
movq %rsp, %rbp
movl %edi, -4(%rbp)
movl %esi, -8(%rbp)
movl -8(%rbp), %edx
movl -4(%rbp), %eax
subl %edx, %eax
leave
ret
Первые две строки - это преамбула, задающая стек для функции. Следующие четыре строки извлекают входные параметры из стека (так как оптимизация была отключена, параметры не были переданы в регистрах).
Затем ключевая инструкция: subl
, которая принимает второй параметр (%eax
, регистр Extended AX в x86, размер 32 бита) и вычитает его из первого параметра (%edx
, регистр Extended DX в x86) также размером 32 бита), сохраняя результат обратно в %edx
. В ALU инструкция subl
принимает первый параметр как есть и добавляет дополнение двух ко второму параметру. Он вычисляет дополнение двух, инвертируя биты второго параметра (аналогично оператору ~
в C), а затем используя специальный сумматор для добавления 1. Этот шаг может быть конвейерным, его можно оптимизировать, чтобы завершить и окончательное сложение за один цикл, или они могли бы пойти дальше и свернуть логику дополнения двух в цепочку сумматора АЛУ.
Последние две строки очищают стек и возвращаются. (Соглашения о вызовах x86 сохраняют результат в %edx
.
РЕДАКТИРОВАТЬ 2: Используйте параметр -S
для gcc
для создания файла сборки (то же имя, что и у входного файла, за исключением суффикса .c
, замененного на .s
). Например: gcc -O0 foo.c -S
(Если бы я не выключил оптимизатор с помощью -O0
, вся функция do_math()
могла бы быть встроена в main()
, что значительно усложняло бы ее видеть.)