Resumo
Para o processador, um inteiro é uma fileira de bits, e C++ oferece operadores que atuam sobre essa fileira coluna por coluna: &, |, ^ e ~, e os deslocamentos << e >>. Esta aula ensina a calculá-los à mão, a usá-los para guardar várias flags num só inteiro com a técnica das máscaras, e a reconhecer os deslocamentos que o padrão não define. Explica depois por que 2 ^ 3 vale 1 e não 8, e por que os operadores bit a bit exigem parênteses. A segunda metade apresenta a biblioteca matemática: as funções de <cmath>, as constantes de <numbers>, o máximo divisor comum e o mínimo múltiplo comum de <numeric> e as funções de bits de <bit>, com um princípio que vale em qualquer linguagem: o que a biblioteca oferece correto e testado não se reescreve.
Objetivos de aprendizagem
- Calcular à mão o resultado de
&,|,^,~e dos deslocamentos sobre inteiros pequenos. - Distinguir os deslocamentos definidos (também os de negativos, desde C++20) dos que são comportamento indefinido, e prever o resultado dos primeiros.
- Usar máscaras para consultar, ligar, desligar e alternar um bit.
- Explicar por que
2 ^ 3vale 1 e não 8, e escolher entrestd::powe um deslocamento conforme o caso. - Usar funções de
<cmath>(std::sqrt,std::pow,std::abs,std::hypot, as trigonométricas) e as constantes de<numbers>, comostd::numbers::pi, em fórmulas.
Operadores bit a bit
A aula Os inteiros e sua representação mostrou que um int é uma fileira de bits, 32 nos sistemas do curso, e que seu valor é a soma dos pesos das posições onde há um 1. Até aqui, porém, o programa só tratou o inteiro como um número: somou-o, multiplicou-o e dividiu-o. Ora, a mesma fileira admite outra leitura, como uma coleção de 32 respostas de sim ou não, e para essa leitura C++ tem quatro operadores que trabalham sobre os bits um a um, sem transporte entre posições.
Os bits são numerados a partir da direita, começando por 0: o bit é o de peso . Assim, em , que em binário é 1100, estão ligados os bits 2 e 3, e desligados os bits 0 e 1.
Os operadores são estes. O E bit a bit, a & b, tem um 1 em cada posição onde a e b têm ambos um 1. O OU bit a bit, a | b, tem um 1 onde ao menos um dos dois o tem. O OU exclusivo, a ^ b, tem um 1 onde exatamente um dos dois o tem, isto é, onde os bits diferem. A negação bit a bit, ~a, tem um só operando e inverte cada bit. As tabelas dos três primeiros, para um par de bits e , são as da conjunção, da disjunção e da disjunção exclusiva:
Quem tiver estudado o curso Lógica Matemática I reconhecerá aqui os conectivos proposicionais, com 1 por verdadeiro e 0 por falso: um operador bit a bit não é outra coisa senão um conectivo aplicado a todas as colunas ao mesmo tempo. Daí se segue o método de cálculo à mão: escrevem-se os dois números em binário, um sobre o outro, alinhados pela direita, e aplica-se a tabela em cada coluna, sem se ocupar das vizinhas.
Os números 1100 e 1010 têm a propriedade de que suas quatro colunas percorrem as quatro combinações de dois bits; por isso servem para ver as três tabelas ao mesmo tempo.
Demonstração
Os dados:
1100é 12 e1010é 10. As colunas, da esquerda para a direita, têm os pares , , e .Primeira coluna de
12 & 10: ambos os bits são 1, e o E dá 1.Segunda coluna:
1e0. Basta um 0 para que o E dê 0.Terceira coluna:
0e1; o E dá 0 outra vez.Quarta coluna:
0e0dão 0. A fileira resultado,1000, é 8:12 & 10vale 8.O OU liga cada coluna que tenha ao menos um 1:
1110, isto é,12 | 10vale 14.O OU exclusivo liga só as colunas cujos bits diferem:
0110, e12 ^ 10vale 6.
O programa seguinte comprova a figura. A especificação de formato {:04b}, apresentada na aula sobre os inteiros, escreve um valor em binário com quatro algarismos, completados com zeros.
#include <print>int main(){ const int a{0b1100}; const int b{0b1010}; std::println("a = {:04b} = {}", a, a); std::println("b = {:04b} = {}", b, b); std::println("a & b = {:04b} = {}", a & b, a & b); std::println("a | b = {:04b} = {}", a | b, a | b); std::println("a ^ b = {:04b} = {}", a ^ b, a ^ b); std::println("~a = {}", ~a);}a = 1100 = 12b = 1010 = 10a & b = 1000 = 8a | b = 1110 = 14a ^ b = 0110 = 6~a = -13A última linha merece uma explicação. A negação inverte os 32 bits de a, não só os quatro que se escrevem: os 28 zeros da esquerda tornam-se uns, e um int que começa com 1 é negativo em complemento de dois. Ora, em complemento de dois o oposto de um número obtém-se invertendo seus bits e somando 1, isto é, ; portanto, , e ~12 vale . A negação bit a bit sobre um inteiro com sinal dá, então, um número que raramente interessa como número: interessa como configuração, e por isso os bits se manipulam de preferência em tipos sem sinal, como se verá na seção das máscaras.
Uma advertência sobre os tipos pequenos. Antes de operar, C++ converte para int todo operando de um tipo inteiro mais estreito, como std::uint8_t; é a promoção que a aula Aritmética descreveu para as operações aritméticas, e vale também aqui. Daí se segue que ~flags, com flags do tipo std::uint8_t e valor 5, não é o byte 11111010, mas o int , cujos 32 bits incluem 24 uns acrescentados à esquerda; só ao guardá-lo de novo num std::uint8_t ele é recortado a oito bits e se recupera o byte esperado, 250.
Não se devem confundir os operadores bit a bit com os operadores lógicos da aula Relações e lógica. 6 && 3 é true: ambos os operandos são diferentes de zero, e o resultado é um bool. 6 & 3, por sua vez, é um inteiro, o que formam as colunas onde ambos têm um 1. Além disso, && é avaliado em curto-circuito e & avalia sempre ambos os operandos.
Carregando o conteúdo…