# `ints` e `floats`
Dalton Serey
Nem todos os números são representados da mesma forma dentro da máquina. Nesta lição veremos as diferenças da representação de números inteiros, abordados pelo tipo `int` em Python, e de números reais, abordados pelo tipo `float`. ## Inteiros Já dissemos que ao se deparar com um literal inteiro no texto do programa, Python representará o número internamente como um `int`. Por exemplo, o literal `19` deve ser interpretado como o número inteiro 19 e representado internamente como um `int` correspondente. A questão é como é essa representação interna? A resposta é simples. A máquina representa todo número inteiro (até certo limite) de forma binária. Isto é, o número será representado por uma sequência finita de bits (0s e 1s). Na prática, raramente é necessário converter representações decimais em binário. Contudo, saber fazê-lo é conhecimento fundamental para um engenheiro de software ou um cientista da computação. Portanto, vejamos como podemos fazê-lo. ### Conversão de inteiros em base 10 para base 2 (binária) A conversão de um inteiro decimal para binário se dá por divisões inteiras sucessivas. A ideia é simples: parte-se do número original, dividimos o valor por 2 e tomamos o resto da divisão como o bit menos significativo. Em seguida, repetimos o processo a partir do quociente, até que seja igual a 0. Retomemos nosso exemplo. Na sequência que se segue, faremos as divisões necessárias para obter a representação binária do número 19. Partiremos da divisão de 19 por 2. O resultado de cada divisão é representado na mesma linha pelos dois valores inteiros entre parênteses: o primeir número é o quociente e o segundo é o resto. A cada linha, repetimos o processo com o quociente da linha anterior. E fazemos isso até que o quociente obtido seja 0. A representação binária do número original será a sequência de restos em ordem inversa. Isto é, o primeiro resto será o bit menos significativo e o último resto o bit mais significativo. Em nosso exemplo, o valor 19 é, portanto, representado por `10011` na base 2. ``` 19 / 2 = (9, 1) 9 / 2 = (4, 1) 4 / 2 = (2, 0) 2 / 2 = (1, 0) 1 / 2 = (0, 1) ``` Como o número de bits reservado para cada inteiro é fixo, os demais bits menos significativos são _zerados_. Se assumirmos que a representação interna tem 16 bits (2 bytes), a representação final de `19` internamente será algo do tipo: ``` 0000000000010011 ``` **Um segundo exemplo**. Vejamos como converter o número 1641 para binário. Vejamos a sequência de divisões ao estilo do que fizemos acima. ``` 1641 / 2 = (820, 1) 820 / 2 = (410, 0) 410 / 2 = (205, 0) 205 / 2 = (102, 1) 102 / 2 = (51, 0) 51 / 2 = (25, 1) 25 / 2 = (12, 1) 12 / 2 = (6, 0) 6 / 2 = (3, 0) 3 / 2 = (1, 1) 1 / 2 = (0, 1) ``` Logo, a representação de 1641 em binário é `11001101001`. > Observação: a representação exata de cada valor na memória > varia não apenas entre linguagens, mas até mesmo entre > diferentes versões de interpretadores da mesma linguagem. > De fato, em Python, todo valor com que a linguagem lida é > um _objeto_ e consiste em vários campos de dados. Um > inteiro Python, portanto, consiste em mais dados do que a > simples sequência de bits acima. A representação a que nos > referimos acima é o campo que contém a informação central > do inteiro. E por isso nos concentraremos nela. ### Números negativos e complemento de dois Um dos problemas da representação direta apresentada acima é que ela só é conveniente para números positivos. Como na máquina não dispomos de “sinais” para adicionar aos inteiros, precisamos de uma outra abordagem para representar números negativos. Há várias formas de representar números negativos. Mas uma das mais populares nos dias atuais é a conhecida representação por [_complemento de dois_](https://pt.wikipedia.org/wiki/Complemento_para_dois) (de fato, é raro encontrar máquinas modernas que não usem complemento de dois). > A explicação dos motivos que levaram à escolha por essa > representação vai além do que trataremos aqui. Mas > certamente isso será tema de outras disciplinas do curso. Na representação por complemento de dois, as combinações de palavras que podemos formar com os bits são divididas ao meio. Metade é usada para representar números positivos e a outra metade para representar números negativos. Assuma, por simplicidade, que tivéssemos apenas 4 bits pra representar inteiros. As combinações possíveis dos quatro bits são: ``` 0000 0001 0010 0011 0100 0101 0110 0111 1000 1001 1010 1011 1100 1101 1110 1111 ``` Se usássemos a representação direta isso permitiria representar os números de 0 a 15. Não muito conveniente se precisarmos lidar com números negativos. Assim, a ideia é associarmos metade desses números a valores positivos e a outra metade a negativos. Esta é a ordem em que a chamada representação por _complemento de dois_ associa os valores: a primeira metade representa valores positivos (e o zero) e a segunda metade representa valores negativos. ``` 0000 0 0001 1 0010 2 0011 3 0100 4 0101 5 0110 6 0111 7 1000 -8 1001 -7 1010 -6 1011 -5 1100 -4 1101 -3 1110 -2 1111 -1 ``` Dessa forma, podemos representar, com 4 bits, os valores de -8 a +7. Bem mais conveniente que ter apenas valores positivos. Para se converter um número negativo qualquer para a representação em complemento de dois, pode-se usar o seguinte procedimento. Passo 1) subtraímos 1 do valor absoluto do número a representar. Passo 2) convertemos o valor encontrado em binário, usando a representação direta. Passo 3) invertemos todos os bits. Por exemplo, vejamos como representar -3, passo a passo: 1. Fazemos `|-3| - 1 = 2` 2. Convertemos 2 para a representação binária direta: `0010` 3. Invertemos todos os bits, obtendo: `1101`. E, de fato, `1101` é a representação de -3 em complemento de dois com 4 bits, como se pode confirmar pela listagem acima. Vejamos um segundo exemplo. Vamos converter agora o valor -6, também passo a passo: 1. Fazemos `|-6| - 1 = 5`. 2. Convertemos 5 para a representação binária direta: `0101` 3. Invertemos todos os bits, obtendo: `1010`. **Exemplo com 8 bits** O mesmo procedimento pode ser usado para representações com qualquer número de bits. Vejamos, por exemplo, como o número -19 pode ser representado em 8 bits. Façamos passo a passo, da mesma forma que os exemplos acima. 1. Fazemos `|-19| - 1 = 18`. 2. Convertemos 18 para a representação binária direta: `00010010` 3. Invertemos todos os bits, obtendo: `11101101`. Logo, a representação em complemento de dois de -19 em 8 bits será `11101101`. **Exercício 1** Determine as representações internas que teriam os literais `6`, `2`, `4`, `-55`, `-73`, se assumirmos que inteiros são armazenados 8 bits, com complemento de dois. ### Literais octais, hexadecimais e binários Python, assim como outras linguagens, permite usar literais numéricos em base 8 (octais), base 16 (hexadecimal) e em base 2 (binários). Para isso, os literais seguem regras específicas. Literais iniciados por `0o` são tratados como octais. Literais iniciados por `0x` são tratados como hexadecimais e literais iniciados por `0b` são considerados binários. > Atenção: em Python 2, octais podiam ser iniciados com um > simples 0. Assim, `025`, em Python 2, tem o valor 21 (= 2 x 8 + > 5). Como isso tendia a produzir bastante confusão, em Python 3 > literais iniciados apenas por 0 são considerados um erro > sintático (estritamente, é um erro léxico, já que o _token_ não > existe mais na linguagem). **Exercício** Analise o _repl_ abaixo. Verifique qual o valor da variável `soma` e entenda o motivo disso. ## Ponto Flutuante Literais que contêm um ponto decimal entre os digitos, tal como `0.5` não são considerados e tratados como inteiros. Estritamente, esse literal não se encaixa na regra léxica de inteiros que exige uma sequência ininterrupta de digitos. Alguns poderiam imaginar que se trata de dois inteiros `0` e `5`. Felizmente, Python reconhece o ponto decimal como um caractere válido no meio de digitos, para o literal de [_números de ponto flutuante_](https://pt.wikipedia.org/wiki/V%C3%ADrgula_flutuante). > Observe que Python e a maioria das linguagens de > programação usa o ponto (`.`) como o separador da parte > fracionária dos números e não a vírgula (`,`), como estamos > acostumados em português. Se você usar uma vírgula, verá > que Python não entenderá o que você digitou como um > literal, mas como par ordenado de números. Números de ponto flutuante são uma forma de representação aproximada de números reais usada por computadores (racionais seria mais apropriado). Trata-se de uma forma bastante adequada para representar e manipular um certo tipo de número dentro da máquina. Infelizmente, a representação implica em convivermos com erros de aproximação, para os quais precisamos nos manter sempre atentos. Assim, entender em detalhes o que é o tipo de ponto flutuante é outro conhecimento fundamental para compreender e utilizar qualquer linguagem de programação. ### Baseada em Notação Científica A representação de números de ponto flutuante é baseada na conhecida _notação científica_. Com uma particularidade. Por ser projetada para funcionar em máquinas digitais, em que tudo é representado na forma de bits, números de ponto flutuante são uma notação científica em base 2 e não em base 10 como estamos acostumados. A representação de 32 bits consiste em 3 partes, nesta ordem: i) um bit para representar o sinal; ii) 8 bits bits para representar o expoente (da base 2); e iii) 23 bits para o que se chama de _mantissa_. O número representado equivalerá a: ± 2_expoente_ × _mantissa_. Há ainda uma representação de 64 bits, chamada de número de ponto flutuante de precisão dupla, em que o expoente tem 11 bits e a mantissa, 52 bits. A conversão de um número decimal com parte fracionária em sua representação como número de ponto flutuante consiste em três passos: 1) converter o valor absoluto do número em sua versão binária convencional; 2) obtenção dos campos: sinal, expoente e mantissa a partir da representação binária obtida no primeiro passo. Vejamos como isso é feito, através de um exemplo. **Conversão de um decimal fracionário em binário**. Vehjamos, então, como podemos converter um número decimal com parte fracionária em sua representação de ponto flutuante. Tomemos, como exemplo, o número 22.375. Uma abordagem simples para a conversão é separar a parte inteira e a parte fracionária, convertê-las independentemente nas representações binárias e somá-las. Em termos de nosso exemplo, isso consiste em separar 22.375 em 22 + 0.375, converter cada um deles separadamente e depois somar as representações binárias. A conversão da parte inteira já foi coberta antes e resulta em `10110`. Vejamos como converter a parte fracionária. A conversão da parte fracionária é baseada em multiplicações sucessivas por 2. A cada multiplicação toma-se a parte inteira do resultado como um dos bits do resultado. O processo se repete com a parte fracionária (depois de se dispensar a parte inteira) até que este resultado seja 0.0 ou até que um número suficiente de bits tenha sido obtido. Abaixo, segue a sequência de multiplicações para converter o valor `0.375` que resulta nos bits `011` e, portanto, na representação `0.011`. ``` 0.375 * 2 = 0.75 = 0 + 0.75 0.75 * 2 = 1.5 = 1 + 0.5 0.5 * 2 = 1.0 = 1 + 0.0 ``` Observe os bits separados de cada produto em cada linha acima: `0`, `1` e `1`. Tomados na ordem direta, os bits correspondem aos bits que se seguem à vírgula na representação binária do número original. Assim, a representação binária de 0.375 é, portanto, `0.011`. Com a conversão das partes inteira e fracionária, podemos retornar à conversão do número original 22.375 que é a soma dos dois valores: 22 + 0.375 (em base 10) = `10110` + `0.011` (em base 2) = `10110.011` (em base 2). **Normalização** Depois de converter o valor absoluto em sua representação binária, é preciso _normalizar_ a representação para obtermos o expoente e o número do qual retiraremos a mantissa. A ideia é reescrever o valor binário no estilo notação científica, movendo a vírgula para deixar um único bit antes da vírgula. Obviamente, cada vez que movemos a vírgula uma posição à esquerda, precisamos compensar multiplicando o número por 2 (lembre que estamos na base 2). Da mesma forma, se movermos a vírgula para a direita, precisamos compensar dividindo o valor resultante por 2. É esse número de movimentações da vírgula, portanto, que irá determinar o expoente. Vejamos como fica nosso exemplo. A sequência abaixo consiste em relocar a vírgula para chegar à posição _normal_. Veja que em cada linha movemos a vírgula uma posição à esquerda. E que a cada linha aumentamos o expoente em 1. Na última linha chegamos à representação final, a que chamamos de _normal_. ``` 10110.011 = 1011.0011 * 2^1 = 101.10011 * 2^2 = 10.110011 * 2^3 = 1.0110011 * 2^4 ``` **O expoente** A última linha da sequência acima determina o expoente. Neste exemplo, o expoente encontrado é 4. Relembre que esse 4 indica que a vírgula foi movida quatro posições à esquerda. Indica também que o número final precisa ser multiplicado 4 vezes por 2 para que retornemos ao valor original. O expoente, contudo, ainda precisa ser representado apropriadamente em _excesso de 127_. Da mesma forma que os inteiros (`int`) , expoentes de números de ponto flutuante podem ser positivos ou negativos. Contudo, ao invés de usar uma representação por complemento de dois, o padrão IEEE 754 optou por usar uma representação por _excesso de 127_. Essa representação consiste simplesmente em somar 127 ao número antes de fazer a conversão para binário. Assim, por exemplo, o expoente 4 será representado por `10000011` que corresponde a 131 que é 4 + 127. **A mantissa** A _mantissa_ será formada pelos bits que se seguem à vírgula na representação normalizada do número. Os demais bits à direita serão iguais a `0`. Em nosso exemplo, a representação normalizada é `1.0110011`. Logo, a mantissa será iniciada por `0110011` e será complementada por 16 `0`s, de forma que a mantissa tenha 23 bits, no total, resultando em `01100110000000000000000`. > Observe que a mantissa não inclui o bit `1` que vem antes > da vírgula. Isso é possível porque esse bit é sempre igual > a `1`, exceto se o número representado for igual a 0.0. **Representação final** A representação final do número é a concatenação de todos os bits: - `0` para representar o sinal positivo - `10000011` para representar o expoente e - `01100110000000000000000` para representar a mantissa A representação final de 22.375 é, portanto: ``` 01000001101100110000000000000000 ``` ### Outro exemplo de conversão para `float` Vejamos como converter um segundo valor: `-110.59375`. Comecemos por converter o valor absoluto em sua versão binária. A parte inteira, 110, corresponde a `1101110` em base 2. A parte fracionária, 0.59375, corresponde a `.10011`. Logo, 110.59375 em binário é `1101110.10011`. O número normalizado corresponde a `1.10111010011` × 26. Logo, o expoente é 6 e será representado por 6 + 127 = 133 que em binário é `10000101`. A mantissa será dada pela parte fracionária do número normalizado (`10111010011`) complementado com `0s` à direita: `10111010011000000000000`. Finalmente, temos a representação final de `-110.59375` em ponto flutuante: sinal `1`, expoente `10000101` e mantissa `10111010011000000000000`: ``` 11000010110111010011000000000000 ``` **Importante** Agora você deve ser capaz de entender melhor por que os literais `1` e `1.0` têm significados profundamente diferentes em linguagens como Python. ## Erros Uma das coisas mais intrigantes ao lidarmos com computação é observarmos que o resultado de `0.1 + 0.2` **não é igual a** `0.3`. Embora seja verdade no mundo conceitual e matemático ao qual estamos acostumados, em linguagens de programação isso, em geral, não é verdade. O motivo para isso é justamente a representação na forma de números de ponto flutuante em binário. Enquanto esses números podem ser representados de forma absolutamente precisa em notação científica e, portanto, como números de ponto flutuante decimais, o mesmo não é verdade para sua representação em binário. O problema é que esses números resultam em representações periódicas em binário. Logo, precisaríamos de um número _infinito_ de bits para representar esses números com precisão absoluta. O resultado é que apenas uma aproximação desses números é possível, na prática. Veja o _repl_ e confira os resultados.