Módulo 02: Representação de Dados e Aritmética Computacional — Resumo
Este resumo condensa o Módulo 02 em uma leitura de revisão. Para o aprofundamento completo, com demonstrações, exemplos adicionais e contexto histórico estendido, recorra a 02_material.qmd e a 02_livro.qmd. Use estas páginas para repassar os conceitos na véspera da prova, antes da tutoria do Projeto Integrador ou durante deslocamentos.
O Computador É Uma Máquina Aritmética Imperfeita
Abra um console e digite 0.1 + 0.2. A resposta 0.30000000000000004 não é bug do navegador nem da minha máquina: é o padrão IEEE 754 funcionando como foi escrito em 1985. Por que esse 4 aparece em qualquer linguagem, em qualquer arquitetura, inclusive no seu PIC18F4550? E, no extremo oposto da curva, como um chip de 8 bits soma inteiros de 16 bits sem ter uma instrução nativa para essa largura? Eu quero que você guarde essas duas perguntas; ao final deste resumo elas terão respostas precisas. O caminho passa pelos sistemas posicionais, atravessa o complemento de dois, mergulha no IEEE 754 e desemboca nos cinco flags do registrador STATUS do PIC18.
flowchart TD
A["Sistemas de<br/>Numeração"] --> B["Inteiros<br/>sem Sinal"]
B --> C["Inteiros<br/>com Sinal"]
C --> D["Ponto Flutuante<br/>IEEE 754"]
D --> E["Circuitos<br/>Aritméticos"]
E --> F["Aritmética no<br/>PIC18F4550"]
F --> G["Projeto<br/>Integrador"]
Os blocos conceituais do módulo, da base teórica ao Projeto Integrador.
Sistemas Posicionais e Conversões Entre Bases
A base 2 não venceu por elegância matemática — projetos ternários como o SETUN soviético chegaram a existir, e a base e é teoricamente ótima — mas por robustez tecnológica: transistores em corte ou saturação distinguem dois níveis de tensão com folga. Um sistema posicional em base b representa o inteiro não negativo N pela soma N = \sum_{i=0}^{n-1} d_i \cdot b^i, com cada d_i \in \{0, \ldots, b-1\}. A unicidade dessa decomposição é o que torna o algoritmo de divisões sucessivas pela base bem definido.
O hexadecimal (b = 16) sobrevive por uma propriedade preciosa: 16 = 2^4, então cada dígito corresponde a um quarteto de bits e a conversão binário-hexadecimal é mero agrupamento. Daí datasheets, listagens do MPLAB X e dumps preferirem \texttt{A35C}_{16} a \texttt{1010 0011 0101 1100}_2. O octal sobrevive nas permissões Unix e nas constantes C com prefixo 0 — atenção: 010 em C vale decimal 8, fonte recorrente de bugs sutis.
flowchart LR
DEC["173 base 10"] -->|"divisões<br/>sucessivas por 2"| BIN["10101101 base 2"]
BIN -->|"agrupar 4 bits"| HEX["AD base 16"]
HEX -->|"expandir nibble"| BIN
BIN -->|"soma posicional"| DEC
Fluxos de conversão entre bases que precisam virar automáticos.
Da base b para decimal, somam-se as contribuições posicionais. De decimal para base b, faz-se divisões sucessivas lendo os restos do último ao primeiro. Para frações, multiplicações sucessivas, lendo a parte inteira a cada passo. Aqui aparece o fato que sustenta metade dos mistérios do ponto flutuante: 0{,}1_{10} em binário é a dízima 0{,}0\overline{0011}_2. O número que você escreve com despreocupação em C não tem representação binária exata.
Inteiros sem Sinal e Aritmética Modular
Quando o processador soma dois inteiros sem sinal de n bits em um registrador de n bits, ele opera no anel \mathbb{Z}/2^n\mathbb{Z}. Some \texttt{0xFF} com \texttt{0x01} na ULA de 8 bits do PIC18: o resultado matemático é 256, não cabe, o registrador recebe \texttt{0x00} e o flag C do STATUS é setado. A faixa representável é [0, 2^n - 1].
Como o PIC18 tem ULA de 8 bits, declarar uint8_t em C emite uma instrução por operação; uint16_t encadeia duas, propagando carry; uint32_t quatro; uint64_t oito. O custo cresce linearmente com a largura, e essa é uma das primeiras decisões de eficiência conscientes do código embarcado. Prefira sempre tipos de largura explícita (uint8_t, int16_t) a int ou long, que mudam de tamanho entre o PIC e o seu notebook.
Voltando à primeira pergunta de abertura: o compilador soma uint16_t no PIC18 emitindo ADDWF para os bytes baixos (que atualiza C) seguida de ADDWFC para os bytes altos, incluindo o flag C herdado. O carry propagado manualmente entre as duas instruções constrói, em duas etapas, o equivalente a uma soma de 16 bits. Essa é a aritmética de múltipla precisão: o mesmo flag C que detectava transbordo estende indefinidamente a largura, byte a byte, sem alterar um único transistor.
Ponto Flutuante e o Padrão IEEE 754
Inteiros, mesmo em 64 bits, são insuficientes para problemas científicos: a massa de uma molécula é 10^{-26} kg e a do Sol é 10^{30} kg. A solução é a notação científica em base 2: três campos — sinal s, mantissa m \in [1, 2) e expoente e — com N = (-1)^s \cdot m \cdot 2^e. Variar e desloca a janela de precisão; m codifica o número com aproximadamente o mesmo número de algarismos significativos.
flowchart LR
subgraph FP32["IEEE 754 binário32"]
S["s<br/>1 bit"] --- E["E<br/>8 bits<br/>bias 127"] --- F["F<br/>23 bits<br/>fração"]
end
S --> V["valor = (-1)^s × (1 + F·2^-23) × 2^(E-127)"]
E --> V
F --> V
Layout do formato binário32 do IEEE 754.
No formato binário32, uma palavra de 32 bits aloca 1 bit para o sinal, 8 bits para o expoente armazenado E (com bias 127) e 23 bits para a fração F. Para números normais (1 \le E \le 254), o valor é N = (-1)^s \cdot (1 + F \cdot 2^{-23}) \cdot 2^{E - 127}, com o bit implícito 1 à esquerda da vírgula. O binário64 usa 11 bits de expoente (bias 1023) e 52 bits de fração, atingindo precisão relativa 2^{-52} \approx 2{,}2 \times 10^{-16} contra 2^{-23} \approx 1{,}2 \times 10^{-7} da precisão simples.
Três decisões do padrão merecem atenção. O expoente com bias em vez de complemento de dois preserva ordenação por comparação inteira — William Kahan defendeu o esquema porque um comparador único para inteiros e floats valia mais que a simetria estética. O bit implícito da mantissa evita armazenar o 1 inicial obrigatório da forma normalizada, ganhando um bit de precisão; a exceção são os denormais (E = 0), que usam bit implícito 0 e permitem perda gradual de precisão perto de zero. Os valores especiais completam o quadro: E = 0 e F = 0 codificam zero com sinal; E = 255 e F = 0, \pm\infty; E = 255 e F \neq 0, NaN, que propaga em qualquer operação e satisfaz \text{NaN} \neq \text{NaN} por especificação — daí if (x != x) ser a forma canônica de detectá-lo em C.
Os números representáveis não estão uniformemente distribuídos na reta real: a distância entre dois floats consecutivos em torno de 1{,}0 é \approx 1{,}19 \times 10^{-7}; em torno de 10^6, perto de 0{,}12; em torno de 10^{30}, da ordem de 10^{23}. Precisão relativa constante, precisão absoluta variável — exatamente o que ponto flutuante promete entregar. Como 0{,}1 é dízima em binário, o IEEE 754 arredonda para o representável mais próximo, e a soma de dois arredondados acumula erro. Por isso 0{,}1 + 0{,}2 \neq 0{,}3: o terceiro padrão de bits difere do esperado por um único bit no fim da mantissa.
Nunca compare floats por igualdade; compare por tolerância, fabs(a-b) < eps. E reorganize algoritmos numéricos para evitar cancelamento subtrativo, em que dois números próximos em magnitude perdem todos os algarismos significativos ao serem subtraídos.
Repito o conselho que sempre dou: ponto flutuante não é a representação certa para tudo. Para valores monetários é desastroso; sistemas financeiros sérios usam inteiros em centavos. No PIC18, sem FPU, cada soma em float pode consumir 200 ciclos da biblioteca do XC8 — em código crítico, represente grandezas em escalas fixas (centésimos de grau, décimos de milímetro) e converta para exibição só na hora de mostrar no LCD.
Circuitos Aritméticos e a ULA do PIC18F4550
Toda essa aritmética é realizada, no silício, por arranjos simples de portas lógicas. O somador completo de um bit tem três entradas (a, b, c_{in}) e duas saídas dadas por s = a \oplus b \oplus c_{in} e c_{out} = (a \cdot b) + (c_{in} \cdot (a \oplus b)) — cerca de doze transistores em CMOS. Conectar n desses em cadeia, com carry-out alimentando carry-in do próximo, produz o somador ripple-carry.
flowchart LR
A0["a0,b0"] --> FA0["FA"]
A1["a1,b1"] --> FA1["FA"]
A2["a2,b2"] --> FA2["FA"]
A3["a3,b3"] --> FA3["FA"]
CIN["cin=0"] --> FA0
FA0 -->|"carry"| FA1
FA1 -->|"carry"| FA2
FA2 -->|"carry"| FA3
FA3 --> COUT["carry-out<br/>flag C"]
FA0 --> S0["s0"]
FA1 --> S1["s1"]
FA2 --> S2["s2"]
FA3 --> S3["s3"]
Somador ripple-carry — padrão da ULA do PIC18.
A virtude do ripple-carry é a simplicidade; o vício é a latência sequencial. Processadores rápidos adotam carry-lookahead, que calcula todos os carries em paralelo com latência logarítmica ao custo de área. Para o PIC18, com somador de 8 bits e clock até 12 MHz internos, o ripple-carry basta. A subtração reaproveita o mesmo somador: inverte-se o subtraendo e força-se c_{in} = 1, implementando “inverte e soma um” sem hardware adicional. A multiplicação elementar é shift-and-add, mas o PIC18 traz MULWF, que executa multiplicação 8 \times 8 em um ciclo via matriz combinacional, com resultado em PRODH:PRODL. Para arquiteturas maiores, o algoritmo de Booth examina três bits do multiplicador por vez, unificando o tratamento de unsigned e complemento de dois. A divisão é a operação mais cara; o PIC18 não a tem em hardware, o XC8 implementa em software por subtrações iterativas.
A ULA do PIC18 atualiza cinco flags do STATUS que merecem ser memorizados. C (carry-out do bit 7), DC (carry-out do bit 3, útil para BCD), Z (resultado zero), N (bit 7 do resultado, sinal em complemento de dois) e OV (overflow em complemento de dois). A combinação desses flags com BC/BNC/BZ/BNZ/BN/BNN/BOV/BNOV implementa toda a lógica de controle baseada em comparações.
flowchart TB
OP["Operação ALU<br/>ADDWF / SUBWF / ..."] --> ST["Registrador STATUS"]
ST --> C["C: carry-out<br/>unsigned"]
ST --> DC["DC: digit carry<br/>nibble (BCD)"]
ST --> Z["Z: resultado zero"]
ST --> N["N: bit 7<br/>(negativo c2)"]
ST --> OV["OV: overflow<br/>complemento de 2"]
Os cinco flags do STATUS atualizados pelas operações aritméticas.
Uma armadilha clássica: no PIC18, C = 1 após SUBWF significa sem empréstimo, oposto do x86. Ao escrever assembly com comparações, lembre-se de que BC após SUBWF salta quando minuendo \geq subtraendo. Erros de off-by-one em laços derivam frequentemente dessa inversão.
Pergunta para o intervalo: em um sistema de dosagem de medicamentos em décimos de miligrama, em que representação você armazenaria a dose acumulada — float, int32_t em décimos de mg, ou outra? Justifique pensando em faixa dinâmica, precisão relativa e custo computacional no PIC18.
Aplicação no Projeto Integrador
Três tarefas materializam, no kit ACEPIC PRO V8.2 com o LCD Sunstar 2004A, os blocos teóricos do módulo. A primeira pede que você implemente uma rotina de conversão binário-decimal para exibição no LCD, via divisões sucessivas por 10, com cuidado especial para INT16_MIN (-32\,768), cuja negação direta produziria overflow — a solução é converter para uint16_t, manipular a magnitude como unsigned e prefixar o sinal ao final.
flowchart TB
N["valor uint16_t"] --> D1["N mod 10 → dígito 0<br/>N := N / 10"]
D1 --> D2["N mod 10 → dígito 1<br/>N := N / 10"]
D2 --> D3["... até N = 0"]
D3 --> INV["lê dígitos<br/>na ordem inversa"]
INV --> LCD["string para o LCD<br/>Sunstar 2004A"]
Conversão binário-decimal via divisões sucessivas por 10.
A segunda tarefa é a soma de 16 bits com detecção de overflow já apresentada. A entrega pedagógica não está no código, mas no relatório: execute o programa com pares que exercitem os quatro casos da tabela abaixo e confronte C com OV. É nessa tabela que o complemento de dois finalmente se torna operacional na sua cabeça.
0xFFFF + 0x0001 |
65535 + 1 |
-1 + 1 = 0 |
1 |
0 |
carry sem overflow |
0x7FFF + 0x0001 |
32767 + 1 |
+32767 + 1 erra |
0 |
1 |
overflow sem carry |
0x8000 + 0xFFFF |
32768 + 65535 |
-32768 + (-1) erra |
1 |
1 |
ambos os flags |
0x1000 + 0x2000 |
4096 + 8192 |
+4096 + 8192 |
0 |
0 |
sem condição excepcional |
A terceira tarefa é experimental: executar 0{,}1 + 0{,}2 e (1 + \epsilon) - 1 no próprio PIC18, exibindo o resultado no LCD e registrando no relatório a representação hexadecimal dos quatro valores envolvidos (0{,}1, 0{,}2, 0{,}3 e a soma) como sequências de 4 bytes em IEEE 754 binário32, inspecionadas no simulador do MPLAB X. Você verá com os próprios olhos que os padrões de 0{,}1 e 0{,}2 não correspondem aos racionais exatos e que sua soma difere do padrão de 0{,}3 por um único bit no fim da mantissa.
flowchart TB
L1["ADDWF<br/>(byte baixo)"] -->|"propaga<br/>flag C"| L2["ADDWFC<br/>(byte alto)"]
L2 --> R["Resultado 16 bits"]
L2 --> FLAGS["C: carry final<br/>OV: overflow 16 bits"]
Aritmética de múltipla precisão — soma de 16 bits a partir de duas instruções de 8 bits.
Síntese
flowchart LR
A["Aritmética<br/>modular"] --> B["Complemento<br/>de Dois"]
A --> C["Unsigned<br/>n bits"]
B --> D["Somador único<br/>(ADDWF + sinal)"]
C --> D
D --> E["Aritmética de<br/>múltipla precisão<br/>(ADDWFC)"]
E --> F["Módulo 03:<br/>conjunto de<br/>instruções"]
G["IEEE 754"] --> H["Software<br/>no PIC18"]
H --> I["Ponto fixo<br/>preferível"]
Os três blocos conceituais e suas conexões com o Módulo 03.
As duas perguntas de abertura agora se respondem com precisão. O 4 no fim de 0{,}1 + 0{,}2 aparece porque 0{,}1 e 0{,}2 são dízimas periódicas em binário, arredondadas para o float mais próximo; a soma desses arredondados difere do float que melhor aproxima 0{,}3 por um bit no fim da mantissa, e o IEEE 754 entrega esse bit sem dó. O PIC18 soma uint16_t em duas instruções — ADDWF e ADDWFC — com o flag C propagando o carry entre os bytes, técnica que se estende a qualquer largura sem alterar uma porta lógica do silício. Três blocos ficam consolidados: a aritmética modular unifica unsigned e complemento de dois e cristaliza “inverte e soma um” como mecanismo único de negação; o IEEE 754 emerge como decisão de engenharia motivada pela comparação via inteiros, pelo bit implícito e pelos valores especiais; o somador completo, o ripple-carry e a ULA do PIC18 com seus cinco flags fecham o circuito entre teoria e silício. No Módulo 03 abrimos o conjunto de instruções do PIC18, os modos de endereçamento e a controvérsia RISC versus CISC — e as instruções de soma e subtração que usaremos lá são exatamente aquelas cujos flags discutimos esta semana.