Escolar Documentos
Profissional Documentos
Cultura Documentos
Resumo
A matemática por detrás de representações em ponto flutuante. Neste
texto mostro apenas representações normalizadas. Ao final do texto há
observações sobre representação sub normal e NaN s.
estendida", respectivamente.
1
A equação genérica que descreve o valor armaze-
nado
Dados os bits de S, E e F , a equação que descreve o valor armazenado, n,
em qualquer uma das precisões é:
S F
n = (−1) · 1 + p−1 · 2E−Ebias (1)
2
A constante p é a precisão total, em bits, do tipo. Isso significa que a
quantidade de bits de F é p − 1. Como visto na figura 1, para um float p = 24;
num double p = 53 e com um long double p = 64.
S
O primeiro termo da equação, (−1) , é o que dá o sinal a n. O segundo
termo, 1 + 2p−1 , também conhecido como mantissa, nos dá sempre um valor
F
entre 1 e 2, ou:
F
1 6 1 + p−1 < 2 (2)
2
Pode-se entender esse termo como uma sequência de bits que resulta no valor
fracionário 0b1.F . O terceiro termo, 2E−Ebias , é chamado de fator de escala.
Ele muda a escala do segundo termo para qualquer outro valor. Pode-se pensar
naquilo que vai armazenado na estrutura de um ponto flutuante como sendo
partições da faixa entre 1 e 2 escalonadas.
Já que todos esses valores contidos na estrutura são sempre positivo, consegue-
se um expoente negativo no fator de escala subtraíndo-o pela metade da faixa
do mesmo. Esse valor é outra constante chamada de "polarização"ou Ebias :
e = E − Ebias (3)
2 Com 8 bits temos o valor biário máximo de 0b11111111. Deslocando esses 8 bits para a
2
Também para tornar as equações menos complicadas, usarei a equação ge-
nérica abaixo, desconsiderando o termo do sinal:
F
n = 1 + p−1 · 2e (4)
2
Como já explicado, outra forma de entender a equação acima é observar
que o fator de escala, 2e , determina uma faixa para a fração. Isso pode ser
desenhado assim:
Onde f = 1 + 2p−1 F
. Isso nos dá a posição dentro do intervalo dado pelo
fator de escala: [2e , 2e+1 ). Pode-se pensar na mantissa como um offset e o fator
de escala como a base da faixa escolhida, como se fosse um ponteiro associado
ao um índice, mas no domínio dos números racionais (Q).
e = blog2 nc (5)
E como a mantissa segue a inequação 2, temos que:
2e 6 n < 2e+1
Já temos e e só precisamos achar F . Basta manipular a equação 4 e temos:
F
n ≈ 1 + p−1 · 2e
2
F ≈ n · 2−e − 1 · 2p−1 (6)
3
Repare que o termo (n · 2−e − 1) sempre será um valor entre 0 e 1, desde
que desconsideremos a possibilidade de n ser zero3 . É bom reparar também
que a equação 6 precisa tanto de n quanto de e, que calculamos previamente na
equação 5.
Como exemplo, consideremos n = 1.25. Usando as equações 5 e 6 temos:
e = blog2 1.25c = 0
F ≈ 1.25 · 20 − 1 · 2p−1
e = blog2 0.1c = −4
F ≈ 0.1 · 24 − 1 · 2p−1
4
Isso quer dizer que se o valor n encontra-se na faixa entre [1, 2), será o
incremento mínimo possível dentro dessa faixa. Essa é a definição de "epsi-
lon"(letra grega ) e é apenas dependente da precisão p, em bits, do tipo usado.
Citando o exemplo do float, p = 24, então = 2−23 , ou seja, ele é igual a:
= 1.1920928955078125 · 10−7
Isso faz sentido se observarmos que F , tendo p − 1 bits de tamanho tem
seu msb correspondente a 2−1 e, caminhando em direção ao bit de baixa ordem
temos o lsb correspondente a 21−p . Por exemplo, considerando e = 0 o valor
binário da mantissa 0b1.1000 · · · corresponde a 1 + 2−1 = 1.5, já 0b1.01 · · ·
a 1.25 – e se observarmos o primeiro exemplo de encontrarmos os valores e
e F , lá em cima, veremos que achamos um F = 2097152 ou, em binário,
0b01000000000000000000000 (23 bits).
O só se aplica a faixa entre [1, 2), ou seja, quando e = 0. Precisamos
multiplicá-lo pelo fator de escala, 2e , para obtermos o menor incremento possível
para a escala em uso. Esse menor valor possível de acordo com a escala é
chamado de ulp ou "Unit at Last Position"4 . A ulp é, então, calculada com
base na precisão p e no expoente e:
5
especial de e, a seguinte equação descreve o valor final de n:
S F
n = (−1) · · 21−Ebias
2p−1
Aqui vou desconsiderar o bit de sinal, como no caso da equação 4:
F
n= · 21−Ebias (8)
2p−1
Esses valores são chamados de sub normais porque valores normalizados têm
aquele 1 somado ao termo da fração. No caso de valores sub normais a mantissa
fica, em binário, como sendo 0b0.F .
Sub normais nos dão valores entre zero (para F = 0) e o valor máximo
dependerá da precisão p. Para um floats o fator de escala será 2−126 , o que
nos dá, para Fmax = 223 − 1:
223 − 1
nmax = · 2−126 = 2−126 − 2−149 ≈
223
1.1754942106924... · 10−38
E uma ulp também é fixa para valores sub normais, de acordo com a precisão,
já que estamos lidando com uma faixa entre [21−Ebias , 22−Ebias ). No caso de
um float, uma ulp será 2−149 ou, aproximadamente, 1.40129846432... · 10−45 .
Esse é o menor valor possível, diferente de zero, que pode ser armazenado num
float. A especificação ISO 9899 cita um valor diferente, mas lá é levado em
conta o menor valor possível normalizado, que é obtido com E = 1 e F = 0,
ou seja, aproximadamente, 1.17549435... · 10−38 .
Até o momento não podíamos representar o valor zero. O único jeito é usando
uma representação "sub normal", que não é tratada como tal. A representação
de zero é feita com E = 0 e F = 0, da equação 8:
0
n= · 21−Ebias = 0.0
2p−1
O que leva a outro problema: O bit de sinal continua valendo e temos agora
+0 e -0. Outra coisa: Exceto pelo zero, todos valores sub normais exigem pro-
cessamento extra para que operações aritméticas sejam computadas, causando
um pequeno atraso.
Felizmente a faixa dos sub normais é apenas uma dentre todas as Emax − 1
faixas possíveis. A ideia dessa faixa, além de possibilitar a representação de
zero, é oferecer uma alternativa elegante aos underflow s. No caso de operações
resultarem em menos que 1 ulp de um sub normal o processador pode sinalizar
uma exceção de "inexatidão"5 , mas a reposta será zero.
5 Exceções em ponto flutunte não constituem, necessariamente, em "interrupções"do fluxo
do programa. Pense nelas mais como uma espécie de flags, mantidos pelo hardware.
6
Valores que não são números
Diferente dos sub normais, valores onde E = Emax são chamados de NaN s –
acrônimo de Not a Number. Existem duas classes de NaN s possíveis: Infinitos
e valores inválidos, estranhamente também chamados de NaN s.
Infinitos ocorrem como consequência de overflows e também em operações
como divisões por zero. Embora isso seja matematicamente estranho, se divi-
dirmos um valor numérico diferente de zero pelo próprio zero, obtemos infinitos
com o sinal de acordo com a divisão (considerando o sinal do zero também).
Mas, se dividirmos zero por zero, obteremos um NaN. Existe também a exceção
de "divisão por zero"para indicar essa tentativa.
Outro exemplo de obtenção de NaN como resultado é a tentativa de extrair
a raiz quadrada de valores negativos ou obter o logaritmo, em qualquer base,
do valor zero.
Uma propriedade dos NaN s, mas não dos infinitos, é que qualquer compara-
ção com eles resulta sempre em falso, não importa o sinal, exceto a comparação
por diferença, Nenhum valor válido é obviamente igual a um NaN e nem mesmo
um NaN é igual a outro NaN. Afinal, um NaN não é um número e não deveria
ser comparável a um ou a algo que não é um número:
1 /* test . c */
2 # include < stdio .h >
3 # include < math .h >
4
7
Embora você possa testar se um objeto contenha um NaN ou não usando
o operador ==, essa não é a melhor maneira porque o campo F em um NaN,
mesmo sem significado, pode ser qualquer um. A maneira correta de determinar
se um valor é ou não um NaN é usando a função/macro isnan(), definida em
math.h. Isso não acontece com infinitos porque, no caso deles, F = 0, mas
também existe uma função/macro isinf().
• E se esse bit estiver zerado e 0 < E < Emax ? Ele é ou não "normalizado"?
8
• E se esse bit estiver setado e E = 0? Teríamos um valor sub normal ?
De forma geral, o tipo long double deve ser evitado. Lidar com essas duas
perguntas acima pode levar a erros "esquisitos"de cálculo somente porque os
tipos normalizados e sub normais podem ser "corrompidos"e, além do mais,
essa representação não é, geralmente, suportada em SIMD6 .
1 __float128 x = 3.15 Q ;
Listagem 2: Exemplo de inicialização de __float128.
9
decimais garantidamente "precisos"? Se consideramos que estamos trabalhando
com binários, e temos precisão de p bits, então podemos inferir que:
2p = 10k
Para encontrarmos o expoente k que seja uma representação exata do valor
em base 2. Acontece que p é um valor inteiro (expresso em bits) e só podemos
obter um k inteiro, expresso em algarismos. Isso nos dá:
k = blog10 2p c
k = bp · log10 2c
Assim, um float tem precisão mínima decimal de 7 algarismos, double tem
15 e long double, 19.
Note que estou usando o termo "precisão"no mesmo sentido anterior. No
caso, a "precisão binária"nos dá a quantidade de bits de F mais 1. No caso
da "precisão decimal"isso nos dá a menor quantidade de algarismos que cor-
responde a um valor "exato". Por exemplo, considere um float cujo valor é
0.1. Esse valor, como já vimos, é impossível de ser representado exatamente
em ponto flutuante. A mantissa, em binário, é 0b1.10011001100 · · · , ad inifini-
tum, e portanto ela tem que ser arredondada. Se usarmos apenas 7 algarismos
decimais o valor que obtivemos no exemplo, lá em cima, será arredondado cor-
retamente para 0.1000000. À partir do 8º algarismo começamos a obter valores
"não exatos", por causa da ulp.
É bom notar que a função printf() da biblioteca padrão de C não faz
arredondamentos, ela trunca o valor impresso com base na "precisão decimal
depois da ’vírgula’"e que "precisão"para o printf() aplica-se à parte fracionária
do valor. Por default printf() usa uma "precisão"de 6 algarismos7 na parte
fracionária.
7 Você pode usar quantos algarismos "depois da vírgula"quiser com printf. Trata-se de
uma rotina que imprime um ponto flutuante em uma string decimal. Não estamos, de fato,
lidando com valores decimais.
10