Перейти к содержанию

Калькулятор двоичной арифметики с флагами процессора

Сложите, вычтите, умножьте или разделите два целых числа в двоичном виде — 8, 16, 32 или 64 бита, в дополнительном коде или без знака. Калькулятор покажет сложение столбиком с переносами, флаги переноса, переполнения, знака и нуля так, как их выставляют AVR и Cortex-M, деление и умножение по шагам, сдвиги, отрицание и расширение знака.

Исходные данные

Числа

−128 … 127; 0x… и 0b… — биты слова

−128 … 127

Результат

Результат
−106
1001 0110 = 0x96
Точное значение
150
Со знаком / без знака
−106 / 150те же биты в двух прочтениях

Результат не помещается в 8 бит со знаком: переполнение, V = 1. Биты те же, что у 150 по модулю 2^8.

ФлагAVRADDCortex-Mпо определению
C перенос / заём00
V переполнение со знаком11
N знак результата11
Z ноль00
S N ⊕ V (только AVR)0
H перенос из бита 3 (только AVR)0

У Cortex-M регистры 32-битные, команд ADDS для 8 бит нет: столбец — флаги AddWithCarry() по определению Arm для 8-битного слова.

Сложение столбиком

В строке переносов — перенос в каждый разряд; крайний левый — перенос из старшего разряда, флаг C.

переносы·11······
A01100100
B00110010
сумма010010110

Операнды в двоичном виде

Дополнительный код: отрицательное число −x хранится как 2^n − x, то есть биты x инвертируются и к ним прибавляется 1.

A
0110 01000x64; со знаком 100, без знака 100
B
0011 00100x32; со знаком 50, без знака 50
−A: инвертировать
1001 1011
−A: прибавить 1
1001 1100−100 со знаком
A → 16 бит со знаком
0000 0000 0110 0100старший бит копируется влево
A → 16 бит без знака
0000 0000 0110 0100слева дописываются нули

Расчёт выполнен по формулам и данным из источников, приведённых на этой странице, и не заменяет проверку: параметры компонентов у разных производителей и даже партий различаются. Перед применением сверяйте результат с документацией на конкретные детали.

Двоичная арифметика — сложение, вычитание, умножение и деление чисел, записанных нулями и единицами. Процессор складывает биты одинаково для чисел со знаком и без знака: разницу между ними видно только по флагам, которые выставляет команда, и по тому, какие из них проверяет программа. Калькулятор считает в словах 8, 16, 32 и 64 бита и показывает флаги так, как их ставят 8-битные AVR и 32-битные Cortex-M.

Как пользоваться

  1. Выберите разрядность слова и операцию: сложение, вычитание, умножение, деление или один из трёх сдвигов.
  2. Выберите, как читать числа: со знаком — в дополнительном коде, без знака. Ввод десятичных чисел проверяется по диапазону выбранного режима; запись с 0x или 0b задаёт биты слова напрямую.
  3. Введите A и B. Для сдвигов вместо B — число разрядов.

Под результатом — таблица флагов двух архитектур, ниже — сложение столбиком с переносами, умножение и деление по шагам (для слов до 16 бит), операнды в двоичном виде, отрицание A и его расширение до вдвое более широкого слова.

Дополнительный код

Слово из n бит без знака хранит числа от 0 до 2ⁿ − 1. В дополнительном коде старший бит имеет вес −2ⁿ⁻¹, и диапазон становится от −2ⁿ⁻¹ до 2ⁿ⁻¹ − 1:

РазрядностьБез знакаСо знаком
8 бит0 … 255−128 … 127
16 бит0 … 65 535−32 768 … 32 767
32 бита0 … 4 294 967 295−2 147 483 648 … 2 147 483 647
64 бита0 … 18 446 744 073 709 551 615−9 223 372 036 854 775 808 … 9 223 372 036 854 775 807

Отрицательное число −x записывается как 2ⁿ − x: биты x инвертируются и к ним прибавляется 1. Удобство этого кода в том, что сложение и вычитание выполняются одной и той же схемой для обоих прочтений. Подробнее — в словаре на странице «Дополнительный код».

Флаги C, V, N, Z

ФлагНазваниеКогда ставитсяДля каких чисел
Cпереносперенос из старшего разряда при сложении; при вычитании — заём (AVR) или его отсутствие (ARM)без знака
Vпереполнениерезультат со знаком не помещается в словосо знаком
Nзнакстарший бит результата равен 1со знаком
Zнольвсе биты результата равны 0любых

Руководство AVR задаёт флаги логическими формулами от старших бит операндов Rd7, Rr7 и результата R7. Для сложения V = 1, если оба слагаемых одного знака, а результат — другого: Rd7 · Rr7 · ¬R7 + ¬Rd7 · ¬Rr7 · R7. У AVR есть ещё два флага: H — перенос из бита 3 в бит 4 (полуперенос), и S = N ⊕ V — истинный знак результата со знаком, по которому работают BRLT и BRGE.

Arm описывает те же флаги функцией AddWithCarry(): результат складывается дважды — как числа без знака и как числа со знаком, — и C или V ставится, если сумма не равна тому, что поместилось в слово. Обе записи для сложения дают одинаковые N, Z, C и V — это проверяет тест калькулятора на всех 65 536 парах 8-битных чисел.

Вычитание и обратный флаг переноса

Вычитание процессор выполняет сложением: A − B = A + НЕ B + 1. Разница между архитектурами — в том, что попадает во флаг C:

  • AVR (SUB, SBC, CP): C = 1, если был заём — вычитаемое без знака больше уменьшаемого. Формула руководства: C = ¬Rd7 · Rr7 + Rr7 · R7 + R7 · ¬Rd7.
  • Cortex-M (SUBS, SBCS, CMP): во флаг C пишется перенос сложения A + НЕ B + 1, то есть C = 1, если заёма не было (A ≥ B). Руководство Arm прямо называет этот флаг «NOT borrow».

Например, 3 − 5: на AVR C = 1, на Cortex-M C = 0; 5 − 3 — наоборот. Поэтому одинаковые по смыслу условные переходы смотрят на противоположные значения C: «без знака больше или равно» — BRSH на AVR (переход при C = 0) и BHS на ARM (при C = 1). Для одной команды над словом флаги N, Z и V у двух архитектур совпадают; в многобайтных цепочках флаг Z различается — об этом в следующем разделе.

Многобайтные числа и флаг Z

AVR складывает 16- и 32-битные числа цепочкой: ADD для младшего байта, затем ADC для каждого следующего. ADC ставит Z только по своему байту, поэтому после цепочки Z = 1 означает лишь, что нулевой старший байт: 0x0001 + 0x0000 даёт Z = 1. SBC и CPC устроены иначе — они сохраняют Z предыдущего байта, если их байт нулевой, и после цепочки SUB + SBC флаг Z верен для всего числа. Именно поэтому многобайтное сравнение на AVR пишут как CP + CPC… и проверяют Z.

Cortex-M складывает 64-битные числа парой ADDS + ADCS, вычитает парой SUBS + SBCS, и обе вторые команды ставят Z только по старшему слову. Калькулятор показывает флаги именно этих цепочек. Для 8- и 16-битных чисел у Cortex-M нет команд с флагами: регистры 32-битные, и столбец Cortex-M в этом случае — флаги AddWithCarry() для слова выбранной разрядности, а не результат конкретной команды.

Пример: 100 + 50 в 8 битах

переносы  0 1 1 0 0 0 0 0 0
A           0 1 1 0 0 1 0 0    100
B           0 0 1 1 0 0 1 0     50
сумма       1 0 0 1 0 1 1 0    0x96

Без знака 0x96 = 150 — верный результат, переноса из старшего разряда нет, C = 0. Со знаком оба слагаемых положительные, а старший бит суммы 1: это −106, V = 1, N = 1, S = N ⊕ V = 0 — истинный знак суммы положительный. Этот пример открыт в калькуляторе по умолчанию.

Умножение и деление

Произведение двух n-битных чисел занимает до 2n бит. Команда MUL у AVR умножает 8 × 8 бит и кладёт 16-битный результат в пару R1:R0; флаг C в ней — старший бит произведения, Z — признак нуля всех 16 бит. MULS делает то же для чисел со знаком. Более широкие числа AVR умножает подпрограммой. Cortex-M командой MULS оставляет только младшие 32 бита и ставит по ним N и Z, а C и V не меняет — поэтому переполнение умножения по флагам не определить.

Столбиком двоичное умножение проще десятичного: для каждой единицы множителя копия множимого сдвигается на номер разряда, и эти строки складываются. Деление — обратная процедура: разряды делимого сносятся по одному, и если остаток не меньше делителя, из него вычитается делитель, а в частное записывается 1.

Частное округляется к нулю, остаток имеет знак делимого: −7 / 2 = −3, остаток −1. Так делят язык C и команды SDIV и UDIV Cortex-M. Деление на ноль Cortex-M по умолчанию возвращает 0; если в регистре CCR установлен бит DIV_0_TRP, возникает исключение UsageFault. Единственный случай переполнения при делении — наименьшее отрицательное число на −1: у SDIV, которая работает только с 32-битными регистрами, 0x80000000 / −1 даёт 0x80000000 без всякого признака. В C такое деление в типе int — неопределённое поведение. Операнды int8_t и int16_t перед делением продвигаются до int, поэтому −128 / −1 даёт 128 без ошибки, а при записи результата обратно в узкий тип значение определяется реализацией. Команды деления у AVR нет.

Сдвиги и расширение знака

  • Логический сдвиг влево (LSL) умножает на 2ᵏ по модулю 2ⁿ; во флаг C уходит последний выдвинутый бит.
  • Логический сдвиг вправо (LSR) делит число без знака на 2ᵏ, отбрасывая остаток; слева вдвигаются нули.
  • Арифметический сдвиг вправо (ASR) сохраняет знак — слева вдвигаются копии старшего бита — и делит число со знаком на 2ᵏ с округлением вниз: −5 >> 1 = −3, а не −2, как при делении.

Команды сдвига AVR сдвигают на один разряд; после них V = N ⊕ C. У Cortex-M сдвиг на любое число разрядов — одна команда, и V она не меняет.

При переходе к более широкому слову число со знаком расширяют знаком: старший бит копируется во все новые разряды, и 0xFC (−4) в 16 битах становится 0xFFFC. Число без знака дополняют нулями: 0xFC → 0x00FC. Ошибка в выборе способа — частая причина неверных результатов после приведения типов в C.

Ограничения и типичные ошибки

  • Неверный флаг для проверки. Для чисел без знака проверяют C, для чисел со знаком — V или S. Проверка C после сложения чисел со знаком ничего не говорит о переполнении.
  • Перенос AVR-кода на ARM и обратно. Условия «выше», «ниже» после вычитания у двух архитектур читают C с противоположным значением. При переносе ассемблерного кода переходы без знака нужно переписать, а не скопировать.
  • Z после цепочки ADC. На AVR после сложения многобайтных чисел Z относится только к старшему байту.
  • Сдвиг вместо деления. Для отрицательных чисел x >> 1 и x / 2 в C дают разные результаты при нечётном x; кроме того, сдвиг отрицательного числа вправо в C определяется реализацией.

Перевести число между системами счисления и посмотреть его в виде float поможет конвертер систем счисления, разобрать поля регистра — калькулятор битовых масок. Флаги и условные переходы подробно разобраны в справочниках по системе команд AVR и системе команд Thumb-2.

Частые вопросы

Как сложить двоичные числа столбиком?

Как десятичные, только цифр две: 0 + 0 = 0, 0 + 1 = 1, 1 + 1 = 10 — пишем 0 и переносим 1 в следующий разряд, 1 + 1 + 1 = 11 — пишем 1 и переносим 1. Складывают справа налево. Перенос из старшего разряда в процессоре попадает во флаг C: для чисел без знака он означает, что сумма не поместилась в слово.

Чем флаг переноса C отличается от флага переполнения V?

C говорит о результате для чисел без знака, V — для чисел со знаком в дополнительном коде. Процессор складывает биты одинаково и ставит оба флага, а какой из них проверять, решает программа. В 8 битах 100 + 50 = 150: без знака это правильный результат, C = 0, а со знаком 150 больше 127, получается −106 и V = 1. Обратный пример: 200 + 100 без знака переполняет слово, C = 1, а со знаком это −56 + 100 = 44, V = 0.

Почему флаг C после вычитания на AVR и ARM разный?

AVR после SUB и CP ставит C = 1, когда был заём, то есть вычитаемое без знака больше уменьшаемого. Cortex-M вычитает как A + НЕ B + 1 и пишет во флаг C перенос этого сложения: C = 1, когда заёма не было (A ≥ B). Поэтому условие «без знака больше или равно» у AVR — BRSH, переход при C = 0, а у ARM — BHS, переход при C = 1. Флаги N, Z и V для одной команды над словом совпадают, но в многобайтных цепочках Z у двух архитектур ставится по-разному.

Как получить отрицательное число в дополнительном коде?

Инвертировать все биты положительного числа и прибавить 1. В 8 битах 5 = 00000101, инверсия — 11111010, плюс 1 — 11111011 = 0xFB, это −5. У самого отрицательного числа пары нет: −(−128) в 8 битах снова даёт 10000000. Команда NEG у AVR в этом случае оставляет 0x80 и ставит V = 1.

Почему сдвиг вправо отрицательного числа даёт не то, что деление?

Арифметический сдвиг вправо округляет вниз, к минус бесконечности, а деление в C — к нулю. −5 / 2 = −2, а −5 >> 1 = −3 (11111011 → 11111101). Для неотрицательных чисел результаты совпадают. В руководстве AVR пример ASR: 0xFC (−4) превращается в 0xFE (−2) — здесь деление точное, поэтому разницы нет.

Источники

  1. Microchip. AVR Instruction Set Manual, DS40002198B, 2021 — 6.1 ADC и 6.2 ADD (с. 24–25), 6.119 SUB и 6.93 SBC (с. 116–117, 143–144) — логические формулы флагов H, V, N, Z, C, Z команд ADC и SBC в цепочке; 6.77 MUL и 6.78 MULS (с. 100–101); 6.6 ASR, 6.73 LSL, 6.74 LSR (с. 29, 96–98) — у LSL H = Rd3, пример ASR 0xFC → −2; 6.80 NEG (с. 103); 6.45 CP и 6.46 CPC (с. 65–67) — Z в цепочке сравнения; 6.20 BRLO и 6.25 BRSH (с. 42, 47); 6.15 BRGE и 6.21 BRLT (с. 37, 43) — переходы по флагу S
  2. Arm. Armv7-M Architecture Reference Manual, ARM DDI 0403E.e — A2.2.1 — функция AddWithCarry(), перенос как «не заём» при вычитании, сдвиги LSL_C, LSR_C, ASR_C (с. A2-26 — A2-28); таблица A7-1 условий (с. A7-178); A7.7.2 ADC, A7.7.4 ADD, A7.7.84 MUL, A7.7.125 SBC, A7.7.127 SDIV (только 32-битные регистры), A7.7.175 SUB, A7.7.195 UDIV; A7.7.68 LSL (immediate) — сдвиг 0–31, #0 — это MOV, A7.7.70 LSR (immediate) — 1–32; A4.4.6 — деление на ноль и бит DIV_0_TRP
  3. ISO/IEC 9899:201x (C11), Committee Draft N1570, 2011 — 6.3.1.1 — целочисленное продвижение int8_t и int16_t до int; 6.3.1.3p3 — запись значения, не помещающегося в знаковый тип, определяется реализацией; 6.5.5p5–6 — деление на ноль и частное, не помещающееся в тип, — неопределённое поведение, дробная часть отбрасывается; 6.5.7p5 — сдвиг вправо отрицательного числа определяется реализацией

Где пригодится

Встроить калькулятор на свой сайт

Скопируйте код и вставьте его в HTML страницы, статьи или вики. Виджет бесплатный, без рекламы, счётчиков и cookie. Условие одно — ссылка на GAW под рамкой остаётся на месте. Подробнее о виджетах

<iframe src="https://gaw.ru/embed/binary-arithmetic/" title="Калькулятор двоичной арифметики с флагами процессора" width="100%" height="640" style="border:1px solid #e3e5e9;border-radius:8px" loading="lazy" allow="clipboard-write"></iframe>
<p style="margin:6px 0 0;font-size:14px">Калькулятор: <a href="https://gaw.ru/tools/binary-arithmetic/">Калькулятор двоичной арифметики с флагами процессора</a> — GAW.ru</p>
Автоподстройка высоты рамки

Если сайт разрешает скрипты, добавьте этот код один раз в любом месте страницы: рамка будет менять высоту вместе с результатом расчёта.

<script>window.addEventListener("message",function(e){if(e.origin!=="https://gaw.ru"||!e.data||e.data.type!=="gaw:embed-height")return;var f=document.querySelectorAll('iframe[src^="https://gaw.ru/embed/"]');for(var i=0;i<f.length;i++)if(f[i].contentWindow===e.source)f[i].style.height=e.data.height+"px"})</script>

Обновлено .

Нашли ошибку в расчёте?