2016-12-14 15 views
0

двойка дополнение означает, что просто переворачивая все биты числа я получаю меня -i-1:Кто определяет шаблон инверсии знака для целых чисел?

~ 0 -1

~ 01000001 является 10111110

~ 65 -66

и т. Д. Чтобы переключить знак целого числа, я должен использовать фактический знак минус.

int i = 65; int j = -i; 
cout << j; // -65 

Где это определенно фактическое поведение, и чья обязанность заключается в том, что для обеспечения двоек шаблона дополнения (сделать ряд негативных инвертировать все биты и добавить 1) следует? Я даже не знаю, является ли это аппаратной или компиляционной операцией.

+5

Это математическая истина. Аппаратное обеспечение выполняет побитовое отрицание и приращение/декрементирование; для этих операций обычно предусмотрены инструкции по единой сборке. Хотя я бы поспорил, что современные процессоры также имеют инструкции более высокого уровня, которые компиляторы воспользуются ... – qxz

+0

Это не «ответственность», это всего лишь следствие представления двухкомпонента. Битовый шаблон '00000000 ... 0' означает' 0', бит-шаблон '11111111 ... 1' означает' -1'. – Barmar

+0

Часть процессора, реализующего арифметику, знает, как работает представление. Когда вы умножаете число на '-1', это приводит к дополнению + add1. – Barmar

ответ

4

Обычно это делается аппаратным обеспечением центрального процессора.

Некоторые процессоры имеют инструкцию для вычисления отрицательного числа. В архитектуре x86 это инструкция NEG.

Если нет, это может быть сделано с использованием оператора умножения, умножая его на -1. Но многие программистов воспользоваться идентичностями, что вы обнаружили, и дополнить число, а затем добавить 1. См

How to convert a positive number to negative in assembly

3

причина этого проста: последовательность с 0 и сложением.

Вы хотите прибавление работать так же для положительных и отрицательных чисел без особых случаях ... в частичном, увеличивающиеся -1 на 1 должен уступить 0.

Единственная последовательность битов, где классическое приращение перелива выдает значение 0, это последовательность в 1 бит. Если вы увеличиваете на 1, вы получаете все нули. Так что ваши -1: все 1s, то есть побитовое отрицание 0. Теперь мы (предполагая, что 8-битовые целые числа, увеличивающиеся на 1 в каждой строке)

-2: 11111110 = ~1 
-1: 11111111 = ~0 
0: 00000000 = ~-1 
+1: 00000001 = ~-2 

Если вам не нравится такое поведение, вам нужно кроме того, для обработки особых случаев, и вы будете иметь +0 и -0. Скорее всего, такой процессор будет намного медленнее.

Если ваш вопрос как

int i = -j; 

реализован, это зависит от компилятора и процессора и оптимизации. Обычно он будет оптимизирован вместе с другими указанными вами операциями. Но не удивляйтесь, если это заканчивается тем, что были выполнены в

int i = 0 - j; 

Так как это, вероятно, занимает 1-2 процессора клещей для вычисления (например, в качестве одного XOR или регистра на себя, чтобы получить 0, то SUB операцию вычислить 0-j), это едва ли станет узким местом. Загрузка j и сохранение результата i где-то в памяти будет намного дороже. На самом деле, некоторые процессоры (MIPS?) Даже имеют встроенный регистр, который всегда равен нулю. Тогда вам не нужна специальная инструкция для отрицания, вы просто вычитаете j из $zero обычно в 1 тик. Говорят, что нынешние процессоры Intel распознают такие операторы xor и делают их в 0 тиках, с оптимизацией переименования регистров (т. Е. Они позволяют следующей инструкции использовать новый регистр, который равен нулю). У вас есть neg на amd64, но быстрый xor rax,rax полезен и в других ситуациях.

2

C арифметика определяется в терминах значений. Когда код:

int i = 65; 
int j = -i; 

компилятор будет выдавать то, что CPU инструкции должны дать j значение -65, независимо от битового представления.

Исторически, не все системы использовали дополнение 2. Компилятор C выберет систему отрицательных чисел, которая приведет к наиболее эффективному выходу для целевого ЦП на основе возможностей ЦП.

Однако дополнение 2 является очень распространенным выбором, поскольку оно приводит к простейшим алгоритмам для выполнения арифметики. Например, одну и ту же инструкцию можно использовать для +-* с целыми числами без знака.

+1

Боковая панель: реализация умножения 2s-комплемента обычно требует больше ресурсов, чем умножение на 1s-дополнение при умножении операндов смешанной ширины. Это правда, делаете ли вы это на аппаратном или программном обеспечении. Например. умножение 8-разрядного знакового значения на 32-разрядное значение в 2s-дополнение требует, чтобы подписать расширение 8-битного операнда и использовать более широкое расширенное значение для умножения. В 1s-дополнении это не проблема: сначала вы извлекаете знаки, затем умножаете значения на неподписанные числа и затем вставляете знак результата. –