Ir para o conteúdo principal

Aritmética de Computadores: Soma e Subtração em Binário, Octal e Hexadecimal

Autor
Francisco Bustamante
Químico, cientista de dados e programador Python.
Tabela de conteúdos
Do Zero ao Float - Este artigo faz parte de uma série de artigos.
Parte 2: Esse Artigo

No artigo anterior, aprendemos como ler e escrever números em diferentes bases, como o binário e o hexadecimal. Agora, vamos dar vida a esses números. Afinal, computadores não apenas armazenam dados; eles os processam.

A boa notícia é que você não precisa reaprender matemática. As regras de soma e subtração que você aprendeu no ensino fundamental para a base decimal (base 10) são universais. O conceito de “vai-um” (carry) ou de “pegar emprestado” funciona exatamente da mesma maneira, quer você esteja contando maçãs (decimal) ou pulsos elétricos (binário).

Neste artigo, vamos revisitar essas operações fundamentais na base 10 para isolar o algoritmo e, em seguida, aplicar exatamente a mesma lógica para realizar cálculos como um computador.

Base decimal
#

A base decimal, também conhecida como base 10, é o sistema numérico mais utilizado no dia a dia. Ele é composto pelos dígitos de 0 a 9 e utiliza um sistema posicional, onde o valor de cada dígito depende de sua posição dentro do número.

Soma na base decimal - regra prática
#

A soma de números decimais segue um processo posicional, onde os dígitos são somados coluna por coluna, começando pela unidade (direita) e avançando para a esquerda.

O “segredo” aqui é generalizar a regra: o “vai-um” ocorre sempre que a soma atinge ou ultrapassa o valor da Base. No caso decimal, se deu 10 ou mais, vai um.

Exemplo: Soma \(67 + 84\) na base 10

$$ \begin{array}{r r r}  & {\scriptstyle 1} & \\  & 6 & 7\\ +& 8 & 4\\ \hline 1& 5 & 1 \end{array} $$

Passo a passo:

  1. Somamos a coluna das unidades: \(7 + 4 = 11\). Como o resultado tem dois dígitos (passou da base), colocamos o 1 na posição das unidades e levamos o outro 1 para a próxima coluna (chamado de “vai-um”).
  2. Somamos a coluna das dezenas: \(6 + 8 = 14\) e somamos o “vai-um”, ficando \(14 + 1 = 15\). Como não há mais colunas, o 15 é colocado como resultado final.

Portanto, o resultado da soma é 151.

O Movimento Vai-Um (Carry)
#

O “vai-um” ocorre quando a soma de dois dígitos resulta em um valor maior que 9, já que 10 é a base do sistema decimal e o “vai-um” ocorre sempre que a soma atinge ou ultrapassa a base, como já vimos. Nesse caso, mantemos o dígito das unidades na coluna correspondente e levamos o dígito das dezenas para a próxima coluna.

Exemplo: Soma \(489 + 276\) na base 10

$$ \begin{array}{r r r}  {\scriptstyle 1} & {\scriptstyle 1} &\\  4 & 8 & 9\\ +2 & 7 & 6\\ \hline  7 & 6 & 5 \end{array} $$
  1. Unidades: \(9 + 6 = 15\), escrevemos o 5 e levamos 1 para a coluna das dezenas.
  2. Dezenas: \(8 + 7 = 15\), somamos o “vai-um” (\(15 + 1 = 16\)), escrevemos 6 e levamos 1 para a próxima coluna.
  3. Centenas: \(4 + 2 = 6\), somamos o “vai-um” (\(6 + 1 = 7\)), resultando em 7.

O resultado final é 765.

Soma na base decimal - algoritmo formal
#

A soma de números decimais segue um processo posicional, como vimos. Para entender melhor como isso funcionará em outras bases, vamos analisar o algoritmo formal: somamos a coluna e verificamos o resultado.

Se o valor for maior ou igual à base (neste caso, 10), não podemos representá-lo com um único dígito. Por isso, subtraímos o valor da base (10 no caso) para encontrar o que fica na coluna atual e, em troca, enviamos 1 para a próxima coluna (o carry ou “vai-um”).

Vamos repetir o exemplo anterior, mas agora detalhando o algoritmo formalmente.

Exemplo: Soma \(67 + 84\) na base 10 (Algoritmo Formal)

$$ \begin{array}{r r r l} & {\scriptstyle 1} & & \\ & 6 & 7 & \\ +& 8 & 4 & \\ \hline & (15) & (11) & \text{somas parciais} \\ -& 10 & 10 & \text{subtraindo a base (10)} \\ \hline 1& 5 & 1 & \end{array} $$

Passo a passo pelo algoritmo:

  1. Unidades: \(7 + 4 = 11\). Como \(11 \ge 10\), fazemos \(11 - 10 = 1\). O 1 fica e geramos um “vai-um”.
  2. Dezenas: \(6 + 8 = 14\). Somamos o “vai-um” (\(15\)). Como \(15 \ge 10\), fazemos \(15 - 10 = 5\). O 5 fica e geramos um “vai-um” para a próxima coluna (centenas).

Subtração na Base Decimal - Regra Prática
#

A subtração também segue um processo posicional. Neste artigo, adotaremos o método conhecido como “carry sub” (ou método da compensação/adições iguais).

Sempre que for preciso “emprestar”, ao invés de diminuir o valor do topo, nós adicionamos 1 na casa imediatamente superior do subtraendo (o número de baixo). Isso mantém a diferença correta, mas deixa o visual da conta mais limpo.

Nota sobre métodos

Talvez você tenha aprendido na escola o método de “pedir emprestado” riscando o número de cima (ex: o 7 vira 6 para emprestar 1 para o 5). Ambos os métodos são matematicamente equivalentes. Usaremos o método da compensação aqui porque ele facilita a organização visual em algoritmos computacionais. Ele tem história própria e uma ligação direta com o próximo artigo desta série: é uma variação do método dos complementos, a mesma ideia que o hardware usa para não precisar subtrair.

Exemplo: Subtração \(75 - 38\) (carry sub)

$$ \begin{array}{r r r} &7 & {\scriptstyle 1}5\\ -&{\scriptstyle 1+}3 & 8\\ \hline &3 & 7 \end{array} $$

Passo a passo:

  1. Unidades: \(5 - 8\) não é possível. Para contornar isso, somamos 10 ao minuendo na casa das unidades (o \(5\) vira 15) e acrescentamos 1 à casa das dezenas do subtraendo (o \(3\) vira 4). Assim, a diferença permanece inalterada.
  2. Agora subtraímos as unidades: \(15 - 8 = 7\).
  3. Dezenas: após o ajuste (o 3 virou 4), subtraímos \(7 - 4 = 3\).

No final, obtemos \(37\) como resultado.

O Processo de Pegar Emprestado - carry sub
#

No método “carry sub”, sempre que a casa superior não for suficiente para a subtração, adicionamos 10 (valor da base) ao algarismo do minuendo nessa casa e, simultaneamente, acrescentamos 1 à casa imediata do subtraendo.

Exemplo: Subtração \(402 - 189\) (carry sub)

$$ \begin{array}{r r r r} &4 & {\scriptstyle 1}0 & {\scriptstyle 1}2\\ -&{\scriptstyle 1+}1 & {\scriptstyle 1+}8 & 9\\ \hline &2 & 1 & 3 \end{array} $$
  1. Unidades: precisamos fazer \(2 - 9\). Como \(2 < 9\), somamos 10 ao 2 (fica 12) e adicionamos 1 à casa das dezenas do subtraendo (8 vira 9). Agora subtraímos: \(12 - 9 = 3\).
  2. Dezenas: temos agora \(0 - 9\). Novamente não é possível sem “carregar”: somamos 10 ao 0 (vira 10) e acrescentamos 1 à casa das centenas do subtraendo (1 vira 2). Então fazemos \(10 - 9 = 1\).
  3. Centenas: restou \(4 - 2\), que é \(2\).

Dessa forma, obtemos o resultado \(213\).

Subtração na Base Decimal - Algoritmo Formal
#

A subtração também segue um processo posicional. No método de compensação (carry sub), a lógica algorítmica é clara: se o dígito de cima é menor que o de baixo, somamos o valor da base (10 no caso) ao dígito de cima para tornar a subtração possível.

Para manter a equação equilibrada, devemos compensar adicionando 1 à próxima coluna do número de baixo (subtraendo).

Exemplo: Subtração \(75 - 38\) na base 10 (Algoritmo Formal)

$$ \begin{array}{r r r} & 7 & 5^{\scriptscriptstyle+10} \\ -& 3^{\scriptscriptstyle+1} & 8 \\ \hline & 3 & 7 \end{array} $$

Passo a passo pelo algoritmo:

  1. Unidades: \(5 - 8\) não é possível. Somamos a base ao 5 (\(5 + 10 = 15\)). Agora fazemos \(15 - 8 = 7\). Compensamos adicionando \(+1\) ao vizinho de baixo (o 3).
  2. Dezenas: Agora temos \(7\) em cima e \(3 + 1 = 4\) em baixo. Fazemos \(7 - 4 = 3\).

Outras bases
#

Soma em diferentes bases
#

A soma em bases diferentes segue o mesmo princípio da base decimal, o “vai-um” ocorre sempre que o resultado de uma soma ultrapassa o maior valor permitido na base. Agora, o algoritmo formal fica mais evidente: se o valor da soma é maior ou igual à base, subtraímos o valor da base para encontrar o que fica na coluna atual e enviamos 1 para a próxima coluna.

Vamos aos exemplos práticos:

Exemplo: Soma na base 9

A base 9 tem dígitos de 0 a 8. Sempre que a soma é maior ou igual à base (9), o “vai-um” é adicionado à próxima coluna.

$$ \begin{array}{c c c c}     {\scriptscriptstyle 1} & \scriptscriptstyle 1 &  & {} \\     {} & 6 & 7 & {} \\     {} + & 8 & 4 & {} \\ \hline     1 & (15) & (11) & {} \\     {} - & 9 & 9 & {\text{ subtraindo a base}} \\ \hline     1 & 6 & 2 & {} \end{array} $$

Passo a passo:

  1. Unidades: \(7 + 4 = 11\). Escrevemos 2, resultado de \(11 - 9\) (base), e     levamos 1 para a próxima coluna.
  2. Dezenas: \(6 + 8 = 14\). Somamos o “vai-um” e temos \(14 + 1 = 15\). O     resultado é 6 e levamos 1 para a próxima coluna.

Portanto, o resultado da soma \(67 + 84\) na base 9 é \(162_9\).

Exemplo: Soma na base 16

A base 16 tem dígitos de 0 a F (representando 15). Sempre que a soma é maior ou igual à base (16), o “vai-um” é adicionado à próxima coluna.

$$ \begin{array}{c c c c c}     {\scriptscriptstyle 1} &  & \scriptscriptstyle 1 &  & {} \\     {} & F & 1 & A & {} \\     {} + & E & 0 & 9 & {} \\ \hline     1 & (29) & 2 & (19) & {} \\     {} - & 16 &  & 16 & {\text{ subtraindo a base}} \\ \hline     1 & D & 2 & 3 & {} \end{array} $$

Resultado: \(\mathtt{F1A} + \mathtt{E09} = \mathtt{1D23_{16}}\).

Antes de ver os exemplos em base 2, vale a pena ter em mente a “tabuada” básica da soma binária, que é onde a maioria das confusões acontece:

  • \(0 + 0 = 0\)
  • \(0 + 1 = 1\)
  • \(1 + 0 = 1\)
  • \(1 + 1 = 10_2\) (Fica 0 e vai 1)
  • \(1 + 1 + 1 = 11_2\) (Fica 1 e vai 1)
Exemplo: Soma na base 2

A base 2 tem dígitos 0 e 1. Sempre que a soma é maior ou igual à base (2), o “vai-um” é adicionado à próxima coluna.

$$ \begin{array}{c c c c c c c}     \scriptscriptstyle 1 & \scriptscriptstyle 1 & \scriptscriptstyle 1 & \scriptscriptstyle 1 & \scriptscriptstyle 1 & & {} \\     {} & 1 & 0 & 1 & 1 & 1 & {} \\     {} + & 1 & 1 & 0 & 1 & 1 & {} \\ \hline        & (3) & (2) & (2) & (3) & (2) & {} \\     {} - & 2 & 2 & 2 & 2 & 2 & {\text{ subtraindo a base}} \\ \hline     1 & 1 & 0 & 0 & 1 & 0 & {} \end{array} $$

Resultado: \(10111_2 + 11011_2 = 110010_2\).

Na base 2, vimos que \(1_2 + 1_2 = 10_2\) e \(1_2 + 1_2 + 1_2 = 11_2\). Assim, pode ser mais fácil somar números binários se lembrarmos dessas contas de cabeça.

Exemplo: Soma na base 2 usando conhecimento dos valores de 2 e 3 na base

$$ \begin{array}{c c c c c c c}     \scriptscriptstyle 1 & \scriptscriptstyle 1 & \scriptscriptstyle 1 & & &  & {} \\     {} & 1 & 0 & 1 & 0 & 0 & {} \\     {} + & 1 & 1 & 1 & 0 & 1 & {} \\ \hline     1 & 1 & 0 & 0 & 0 & 1 & {} \end{array} $$

Três casos que testam o entendimento
#

Os exemplos acima têm, no máximo, um “vai-um” isolado por coluna. Três situações fogem desse padrão e valem a pena, porque é nelas que a conta costuma descarrilar.

Dois dígitos máximos na mesma coluna. Em base 8, o maior dígito é 7. Somar \(77_8 + 77_8\) coloca o algoritmo no pior caso possível em todas as colunas:

$$ \begin{array}{c c c c} \scriptscriptstyle 1 & \scriptscriptstyle 1 & & {} \\ {} & 7 & 7 & {} \\ {} + & 7 & 7 & {} \\ \hline & (15) & (14) & {} \\ {} - & 8 & 8 & {\text{ subtraindo a base}} \\ \hline 1 & 7 & 6 & {} \end{array} $$

Resultado: \(77_8 + 77_8 = 176_8\). Confira em decimal: \(77_8 = 63\), e \(63 + 63 = 126 = 176_8\) (\(1 \times 64 + 7 \times 8 + 6\)).

Vai-um em cadeia. Em \(2\mathtt{FF}_{16} + 1\mathtt{FE}_{16}\), o “vai-um” não fica preso a uma coluna: ele nasce nas unidades e atravessa a conta inteira.

$$ \begin{array}{c c c c c} {} & \scriptscriptstyle 1 & \scriptscriptstyle 1 & & {} \\ {} & 2 & F & F & {} \\ {} + & 1 & F & E & {} \\ \hline {} & 4 & (31) & (29) & {} \\ {} - & {} & 16 & 16 & {\text{ subtraindo a base}} \\ \hline {} & 4 & F & D & {} \end{array} $$

Resultado: \(2\mathtt{FF}_{16} + 1\mathtt{FE}_{16} = 4\mathtt{FD}_{16}\). Em decimal: \(767 + 510 = 1277\), e \(4 \times 256 + 15 \times 16 + 13 = 1277\).

Em binário esse efeito de cadeia fica ainda mais nítido, e vale ver desenhado. A figura abaixo acompanha o pior caso possível de uma soma de 8 bits, \(01111111_2 + 00000001_2\) — ou seja, \(127 + 1\). O “vai-um” nasce na coluna mais à direita e precisa atravessar, uma a uma, todas as sete colunas seguintes antes que o resultado esteja pronto. Os três painéis de baixo destacam os três momentos dessa travessia:

Soma binária de 8 bits entre 01111111 e 00000001, resultando em 10000000. Acima das colunas, sete vai-uns marcados em vermelho, ligados por setas que apontam da direita para a esquerda, mostrando o vai-um atravessando toda a conta. Abaixo, três painéis menores: no primeiro, 1 mais 1 resulta em 0 e vai 1, quando o vai-um nasce; no segundo, o vai-um que chega somado a 1 e 0 resulta em 0 e vai 1, quando ele propaga; no terceiro, o vai-um que chega somado a 0 e 0 resulta em 1 e a propagação acaba.
O pior caso de uma soma de 8 bits: o vai-um nasce à direita e precisa percorrer todas as colunas até a esquerda.

É daqui que vem o nome ripple carry (“vai-um em ondulação”): cada coluna só consegue terminar depois que a anterior entregou o seu transporte. No papel isso é só trabalho; no hardware, é tempo — e é por isso que processadores modernos usam somadores mais espertos, que tentam adivinhar o vai-um em vez de esperar por ele.

Somar três números de uma vez. Aqui mora a armadilha mais bonita do algoritmo. Todo mundo aprende “vai-um” — mas o vai-um só vale um porque estamos somando dois números. Com três parcelas, uma coluna pode gerar transporte 2.

$$ \begin{array}{c c c c c c c c} {} & {} & \scriptscriptstyle 2 & \scriptscriptstyle 1 & \scriptscriptstyle 1 & \scriptscriptstyle 1 & \scriptscriptstyle 1 & {} \\ {} & {} & {} & 1 & 0 & 1 & 0 & 0 \\ {} & {} & {} & 1 & 1 & 1 & 0 & 1 \\ {} + & {} & {} & 1 & 1 & 0 & 1 & 1 \\ \hline {} & 1 & 0 & 0 & 1 & 1 & 0 & 0 \end{array} $$

Acompanhe a coluna que estoura: na quinta posição temos \(1 + 1 + 1 = 3\), mais o transporte 1 que chegou da coluna anterior, dá \(4\). Como \(4 = 100_2\), fica \(0\) na coluna e vão 2 para a próxima. Resultado: \(10100_2 + 11101_2 + 11011_2 = 1001100_2\), ou \(20 + 29 + 27 = 76\).

O “vai-um” nem sempre vai um

A regra geral não é “escreva o resto e leve 1”, e sim: escreva o resto da divisão pela base e leve o quociente. Com duas parcelas o quociente nunca passa de 1, e a regra simplificada funciona. Com três ou mais, ele pode valer 2 ou mais — e é exatamente aí que a conta feita no automático erra.

Subtração em diferentes bases
#

A subtração em bases diferentes segue o mesmo princípio que vimos na base decimal (carry sub/compensação): se o dígito de cima for menor que o de baixo, somamos a base ao de cima e compensamos somando 1 ao vizinho de baixo.

Novamente, o algoritmo formal fica mais evidente: se o valor da subtração é negativo, somamos o valor da base ao dígito de cima para tornar a subtração possível e adicionamos 1 à próxima coluna do número de baixo (subtraendo) para manter a equação equilibrada.

Exemplo: Subtração na base 8

Na base 8, os dígitos vão de 0 a 7. Sempre que a subtração resulta em um número negativo, emprestamos 1 da próxima coluna.

$$ \begin{array}{c c c} {} & 5 & 2^{\scriptscriptstyle+8} \\ {} - & 1^{\scriptscriptstyle+1} & 7 \\ \hline {} & 3 & 3 \end{array} $$

Passo a passo:

  1. Unidades: \(2 - 7\) não é possível. Somamos a base (8) ao dígito de cima — o \(2\) passa a valer \(10\) — e compensamos com \(+1\) na próxima coluna do subtraendo (o \(1\) vira \(2\)). Agora \(10 - 7 = 3\).
  2. Próxima coluna: \(5 - 2 = 3\).

Resultado: \(52_8 - 17_8 = 33_8\).

Dica de verificação: \(52_8 = 42\) e \(17_8 = 15\); \(42 - 15 = 27\), que em base 8 é \(3 \times 8 + 3 = 33_8\).

Exemplo: Subtração na base 16

Na base 16, os dígitos vão de 0 a F. Sempre que a subtração resulta em um número negativo, emprestamos 1 da próxima coluna.

$$ \begin{array}{c c c} {} & 5 & 2^{\scriptscriptstyle+16} \\ {} - & 1^{\scriptscriptstyle+1} & 7 \\ \hline {} & 3 & B \end{array} $$

Passo a passo:

  1. Unidades: \(2 - 7\) não é possível. Somamos a base (16) ao dígito de cima — o \(2\) passa a valer \(18\) — e compensamos com \(+1\) na próxima coluna do subtraendo (o \(1\) vira \(2\)). Agora \(18 - 7 = 11\), que em hexadecimal é o dígito \(\mathtt{B}\).
  2. Próxima coluna: \(5 - 2 = 3\).

Resultado: \(52_{16} - 17_{16} = 3\mathtt{B}_{16}\).

Dica de verificação: \(52_{16} = 82\) e \(17_{16} = 23\); \(82 - 23 = 59\), que em base 16 é \(3 \times 16 + 11 = 3\mathtt{B}_{16}\).

Repare que a conta é a mesma dos dois exemplos anteriores — muda só o valor emprestado, que é sempre a base.

Na base 2, podemos usar o conhecimento já adquirido da “tabuada” para facilitar a subtração.

Exemplo: Subtração na base 2

$$ \begin{array}{c c c c c c c} {} & 1 & 1 & {\scriptscriptstyle1}0 & {\scriptscriptstyle1}0 & 1 & {\scriptscriptstyle1}0 \\ {} - & 1 & 0^{\scriptscriptstyle+1} & 1^{\scriptscriptstyle+1} & 1 & 0^{\scriptscriptstyle+1} & 1 \\ \hline & 0 & 0 & 0 & 1 & 0 & 1 \end{array} $$

Passo a passo, da direita para a esquerda:

  1. \(0 - 1\) não dá. Somamos a base (2) ao topo, que vira \(10_2\), e compensamos \(+1\) na próxima coluna de baixo. \(2 - 1 = 1\).
  2. Topo \(1\), baixo \(0 + 1 = 1\). \(1 - 1 = 0\), sem compensação.
  3. Topo \(0\), baixo \(1\). Somamos a base: \(2 - 1 = 1\), e compensamos.
  4. Topo \(0\), baixo \(1 + 1 = 2\). Somamos a base: \(2 - 2 = 0\), e compensamos.
  5. Topo \(1\), baixo \(0 + 1 = 1\). \(1 - 1 = 0\).
  6. Topo \(1\), baixo \(1\). \(1 - 1 = 0\).

Resultado: \(110010_2 - 101101_2 = 101_2\).

Dica de verificação: \(110010_2 = 50\) e \(101101_2 = 45\); \(50 - 45 = 5\), que em binário é \(101_2\).

Algoritmos Formais e Implementação
#

Para um cientista da computação, entender a aritmética não é apenas saber fazer a conta no papel, mas entender o algoritmo que pode ser programado. Abaixo, apresentamos a lógica generalizada para qualquer Base \(B\), primeiro em pseudocódigo e depois em Python.

Algoritmo de Soma (Generalizado)
#

A lógica central depende de duas operações inteiras: o resto da divisão (%) determina o dígito que fica, e a divisão inteira (//) determina o “vai-um”.

Para começar, vamos ver em pseudocógido, que é uma forma de escrever algoritmos de maneira independente de linguagem de programação. Talvez facilite a compreensão antes de vermos o código Python.

Pseudocódigo:

FUNÇÃO Somar(Numero_A, Numero_B, BASE):
    vai_um = 0
    resultado = ""

    PARA cada posição i (da direita para a esquerda):
        soma_parcial = digito_a + digito_b + vai_um

        # O dígito que fica é o resto da divisão pela base
        digito_que_fica = soma_parcial MOD BASE

        # O "vai-um" é a divisão inteira pela base
        vai_um = soma_parcial DIV BASE

        ADICIONAR digito_que_fica À ESQUERDA de resultado

    SE vai_um > 0: ADICIONAR vai_um À ESQUERDA de resultado
    RETORNAR resultado

Implementação em Python:

Para os códigos Python, usaremos uma string auxiliar para mapear dígitos maiores que 9: DIGITOS = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"

DIGITOS = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"

def somar_bases(n1: str, n2: str, base: int) -> str:
    # Inverte as strings para facilitar a iteração da direita para esquerda
    n1, n2 = n1[::-1].upper(), n2[::-1].upper()
    
    # Iguala o tamanho das strings com zeros à direita (que são à esquerda no número real)
    tam_max = max(len(n1), len(n2))
    n1 = n1.ljust(tam_max, '0')
    n2 = n2.ljust(tam_max, '0')
    
    vai_um = 0
    resultado = []
    
    for i in range(tam_max):
        # Converte caractere para valor numérico (Ex: 'A' -> 10)
        v1 = DIGITOS.index(n1[i])
        v2 = DIGITOS.index(n2[i])
        
        soma_parcial = v1 + v2 + vai_um
        
        # Lógica da Base
        digito_fica = soma_parcial % base
        vai_um = soma_parcial // base
        
        resultado.append(DIGITOS[digito_fica])
        
    if vai_um > 0:
        resultado.append(DIGITOS[vai_um])
        
    # Inverte de volta para retornar a string correta
    return "".join(resultado[::-1])

# --- Exemplos de Execução (Baseados nos exemplos do artigo) ---

# Base 10: 67 + 84
print(f"Base 10: {somar_bases('67', '84', 10)}")
# Resultado: 151

# Base 9: 67 + 84
print(f"Base 9:  {somar_bases('67', '84', 9)}") 
# Resultado: 162 (Note que 7+4=11, que é 12 na base 9)

# Base 16: F1A + E09
print(f"Base 16: {somar_bases('F1A', 'E09', 16)}") 
# Resultado: 1D23

# Base 2: 10111 + 11011
print(f"Base 2:  {somar_bases('10111', '11011', 2)}") 
# Resultado: 110010

Algoritmo de Subtração (Método Carry Sub)
#

Neste método, quando o dígito de cima é menor, somamos a BASE a ele e marcamos uma compensacao (+1) para ser adicionada ao dígito de baixo na próxima coluna.

Pseudocódigo:

FUNÇÃO Subtrair(Minuendo, Subtraendo, BASE):
    compensacao = 0 
    resultado = ""

    PARA cada posição i (da direita para a esquerda):
        digito_baixo = Subtraendo[i] + compensacao
        digito_cima = Minuendo[i]

        SE digito_cima < digito_baixo:
            # "Pega emprestado" da base (soma a base ao topo)
            digito_cima = digito_cima + BASE
            compensacao = 1
        SENÃO:
            compensacao = 0

        diferenca = digito_cima - digito_baixo
        ADICIONAR diferenca À ESQUERDA de resultado

    RETORNAR resultado

Implementação em Python:

def subtrair_bases(n1: str, n2: str, base: int) -> str:
    # Este algoritmo só sabe produzir resultados não negativos. Sem a checagem
    # abaixo, subtrair_bases('38', '75', 10) devolveria '63' em silêncio.
    # Comparar numerais da mesma base não exige convertê-los: basta olhar o
    # comprimento (sem zeros à esquerda) e, em caso de empate, comparar dígito a
    # dígito — os símbolos de DIGITOS já estão em ordem crescente.
    a = n1.upper().lstrip('0') or '0'
    b = n2.upper().lstrip('0') or '0'
    if (len(a), a) < (len(b), b):
        raise ValueError(f"{n1} < {n2} na base {base}: resultado seria negativo")

    n1, n2 = n1[::-1].upper(), n2[::-1].upper()
    tam_max = max(len(n1), len(n2))
    n1 = n1.ljust(tam_max, '0')
    n2 = n2.ljust(tam_max, '0')
    
    compensacao = 0
    resultado = []
    
    for i in range(tam_max):
        v_cima = DIGITOS.index(n1[i])
        v_baixo = DIGITOS.index(n2[i]) + compensacao
        
        if v_cima < v_baixo:
            # Aplica a lógica do Carry Sub (soma a base em cima)
            v_cima += base
            compensacao = 1
        else:
            compensacao = 0
            
        diferenca = v_cima - v_baixo
        resultado.append(DIGITOS[diferenca])
        
    # Remove zeros à esquerda resultantes (ex: 037 -> 37)
    return "".join(resultado[::-1]).lstrip('0') or "0"

# --- Exemplos de Execução ---

# Base 10: 75 - 38
print(f"Base 10: {subtrair_bases('75', '38', 10)}")
# Resultado: 37

# Base 8: 52 - 17 (Exemplo do artigo: pegamos emprestado 8)
print(f"Base 8:  {subtrair_bases('52', '17', 8)}")
# Resultado: 33 (Pois 2 virou 10 (2+8), 10-7=3. O 1 virou 2, 5-2=3)

# Base 16: 52 - 17
print(f"Base 16: {subtrair_bases('52', '17', 16)}")
# Resultado: 3B (Pois 2 virou 18 (2+16), 18-7=11(B). O 1 virou 2, 5-2=3)

# Base 2: 1101010 - 101101 (Exemplo adaptado)
print(f"Base 2:  {subtrair_bases('1101010', '101101', 2)}")
# Resultado: 111101

Tabela-Resumo
#

Situação Regra Exemplo do artigo
Soma, coluna a coluna escreva \(s \bmod b\) e leve \(s \div b\) \(67_9 + 84_9 = 162_9\)
Vai-um com duas parcelas o transporte nunca passa de 1 \(1_2 + 1_2 = 10_2\)
Vai-um com três parcelas o transporte pode valer 2 ou mais \(10100_2 + 11101_2 + 11011_2 = 1001100_2\)
Pior caso da soma dois dígitos máximos na mesma coluna \(77_8 + 77_8 = 176_8\)
Vai-um em cadeia o transporte atravessa várias colunas \(2\mathtt{FF}_{16} + 1\mathtt{FE}_{16} = 4\mathtt{FD}_{16}\)
Subtração (carry sub) se o de cima for menor, some a base em cima e \(+1\) na próxima coluna de baixo \(52_8 - 17_8 = 33_8\)
Quanto se empresta sempre o valor da base 8 na base 8, 16 na base 16, 2 na base 2
Verificação converta para decimal e refaça a conta \(42 - 15 = 27 = 33_8\)
Soma em código digito = soma % base, vai_um = soma // base somar_bases('F1A','E09',16)
Subtração em código if v_cima < v_baixo: v_cima += base subtrair_bases('52','17',16)
Limite do algoritmo só resultados não negativos subtrair_bases('38','75',10) levanta ValueError

Conclusão e Próximos Passos
#

Como vimos, a aritmética posicional é consistente: não importa se temos 2 símbolos (binário) ou 16 (hexadecimal), o algoritmo é o mesmo. A única coisa que muda é o valor que dispara o “vai-um” ou o “empréstimo”.

Dominar essas operações no papel é excelente para entender a lógica, mas levanta uma questão importante: será que o hardware do computador realiza subtrações pedindo emprestado bits vizinhos?

Na prática, não — e o motivo talvez surpreenda. Não é que um subtrator seja caro: um subtrator completo custa praticamente o mesmo que um somador completo, porque ambos são feitos das mesmas portas lógicas. O problema é o desperdício.

Vale a pena ver de perto o circuito de que estamos falando. O somador completo é a peça que executa, em silício, exatamente uma coluna da conta que fizemos no papel: ele recebe dois bits e o vai-um que chegou da coluna anterior, e devolve o bit da soma e o vai-um para a coluna seguinte. São cinco portas lógicas, só isso:

Diagrama do circuito de um somador completo de 1 bit. As entradas A e B alimentam uma porta XOR, cuja saída, rotulada A xor B, vai para uma segunda porta XOR junto com a entrada vai-um, produzindo o bit da soma S. As mesmas entradas A e B alimentam uma porta AND, e a saída A xor B alimenta outra porta AND junto com o vai-um. As saídas das duas portas AND entram numa porta OR, que produz o vai-um de saída.
Um somador completo de 1 bit: duas portas XOR produzem o bit da soma, e duas portas AND mais uma OR decidem se há vai-um.

Ligando oito dessas peças em fila, com o vai-um de uma entrando na seguinte, temos exatamente a soma de 8 bits que desenhamos mais acima — inclusive a espera em cadeia. E um subtrator completo tem esse mesmo porte: as mesmas cinco portas, ligadas de outro jeito. É aí que está o desperdício.

Manter dois circuitos separados, um para somar e outro para subtrair, gasta área do processador fazendo duas vezes um trabalho quase idêntico — e, pior, obriga o projeto a decidir qual dos dois usar a cada operação.

A saída da computação moderna é elegante: transformar toda subtração em soma, de modo que um único somador dê conta das duas operações. É o conceito de Complemento de Dois e de números com sinal, e é exatamente sobre essa representação de números negativos que falaremos no próximo artigo.

Até lá!

Do Zero ao Float - Este artigo faz parte de uma série de artigos.
Parte 2: Esse Artigo

Relacionados

Lógica para programadores

·4116 palavras·20 minutos
Em programação, a palavra “lógica” pode se referir ou ao processo de elaboração de um algoritmo, ou a estruturas condicionais da linguagem, ou a operadores lógicos como E e OU. Nos dois últimos casos, estamos dentro da chamada lógica matemática.