| | | |

Matemática numérica I

Ajude a manter o site livre, gratuito e sem propagandas. Colabore!

1.2 Representação de números em máquina

Usualmente, manipulamos números em máquina por meio de suas representações em registros com n bits. Ao longo desta seção, usamos a seguinte notação

[b1⁢b2⁢b3⁢⋯⁢bn], (1.23)

para representar um registro de n-bits bi∈{0,1}, i=1,2,…,n.

Na sequência, fazemos uma breve discussão sobre as formas comumente usadas para a manipulação de números em computadores.

1.2.1 Números inteiros

Usamos o sistema de complemento de 2 para manipular números inteiros em computadores. Nessa representação, um registro de n bits

[d1⁢d2⁢d3⁢⋯⁢dn], (1.24)

representa o número inteiro

x=(dn−1⁢…⁢d2⁢d1)2−dn⁢2n−1. (1.25)
Exemplo 1.2.1.

O registro de 8 bits2228 bits = 1 byte [B].

[1 1 0 0 0 0 0 0] (1.26)

representa o número

x=−d8⋅28−1+(d7⁢d6⁢…⁢d1)2 (1.27)
=−0⋯27+(06050403021110)2 (1.28)
=21+20=3. (1.29)

Podemos implementar um conversor de registro para número inteiro como segue

Código 1: packbits8.py
1def packBitsInt8(dd):
2 x = -dd[7] * 2**7
3 for i, d in enumerate(dd[:7]):
4 x += d * 2**(i)
5 return x

Com essa função, convertemos uma lista de bits (registro) no inteiro correspondente à representação em complemento de 2.

1packBitsInt8([1,1,0,0,0,0,0,0])
3

Na representação de complemento de 2 com n bits, o menor e o maior números inteiros são obtidos com os registros

−2n−1∼[0 0 0 0⁢⋯⁢ 1], (1.30)
2n−1−1∼[1 1 1⁢⋯⁢ 1 0], (1.31)

respectivamente. Já o zero é obtido com o registro

0∼[0 0 0 0 0 0 0 0]. (1.32)
Exemplo 1.2.2.

Com um registro de 8-bits, temos que o menor e o maior números inteiros que podem ser representados são

[0 0 0 0 0 0 0 1]
∼−27+(0000000)2=−128, (1.33)

e

[1 1 1 1 1 1 1 0]
∼−0⋅27+(1111111)2=127, (1.34)

respectivamente.

Usando o Código 1, temos

1packBitsInt8([0,0,0,0,0,0,0,1])
-128
1packBitsInt8([1,1,1,1,1,1,1,0])
127
1packBitsInt8([0,0,0,0,0,0,0,0])
0
Observação 1.2.1.

No NumPy, o dtype=numpy.int8 corresponde a inteiros de 8 bits.

1import numpy as np
2np.array([-127, 0, 3, 128, 129], dtype=np.int8)
array([-127,    0,    3, -128, -127], dtype=int8)

Podemos consultar a lista de tipos básicos do NumPy em NumPy:Data types.

A adição de números inteiros na representação de complemento de 2 pode ser feita de maneira simples. Por exemplo, consideremos a soma 3+9 usando registros de 8 bits. Temos

3 ∼[1 1 0 0 0 0 0 0] (1.35)
9 ∼[1 0 0 1 0 0 0 0]+ (1.36)
− −⁣−⁣−⁣−⁣−⁣−⁣−⁣− (1.37)
12 ∼[0 0 1 1 0 0 0 0] (1.38)

No sistema de complemento de 2, a representação de um número negativo −x pode ser obtida da representação de x, invertendo seus bits e somando 1. Por exemplo, a representação de −3 pode ser obtida da representação de 3, como segue

3∼[1 1 0 0 0 0 0 0]. (1.39)

Invertendo seus bits e somando 1, obtemos

−3∼[1 0 1 1 1 1 1 1]. (1.40)

A subtração de números inteiros usando a representação de complemento de 2 fica, então, tanto simples quanto a adição. Por exemplo:

3 ∼[1 1 0 0 0 0 0 0] (1.41)
−9 ∼[1 1 1 0 1 1 1 1]+ (1.42)
− −⁣−⁣−⁣−⁣−⁣−⁣−⁣− (1.43)
−6 ∼[0 1 0 1 1 1 1 1] (1.44)

1.2.2 Ponto flutuante

Em computadores, manipulamos números reais comumente por meio da representação de ponto flutuante de 64 bits333Padrão IEEE 754.. Nela, um registro de 64 bits

[s⁢|c10⁢c9⁢…⁢c0|⁢m1⁢m2⁢…⁢m52] (1.45)

representa o número

x=(−1)s⁢M⋅2c−1023, (1.46)

em que chamamos M de mantissa e c de característica, definidas por

M :=(1,m1⁢m2⁢m3⁢…⁢m52)2, (1.47)
c :=(c10⁢…⁢c2⁢c1⁢c0)2. (1.48)
Exemplo 1.2.3.

Por exemplo, na representação em ponto flutuante de 64 bits, temos que o registro

[1⁢| 1 0⁢…⁢ 0 0|⁢ 1 0 1 0 0⁢…⁢ 0] (1.49)

representa o número −3.25.

A seguinte função converte uma lista de 64 bits no número decimal correspondente, supondo que o registro represente um número normalizado finito.

Código 2: packBitsDouble.py
1def packBitsDouble(ld):
2 s = ld[0]
3 c = 0
4 for i, d in enumerate(ld[1:12]):
5 c += d * 2**(10-i)
6 m = 1.
7 for i, d in enumerate(ld[12:]):
8 m += d * 2**(-(i+1))
9 x = m * 2**(c - 1023)
10 return -x if s else x

Por exemplo, usando-a para o registro acima, obtemos

1ld = [0]*64
2ld[0]=1
3ld[1]=1
4ld[12]=1
5ld[14]=1
6packBitsDouble(ld)
-3.25

1.2.3 Erro de arredondamento

Dado um número real x, sua representação f⁢l⁢(x) em ponto flutuante é o registro que representa o número mais próximo de x. Este procedimento é chamado de arredondamento por proximidade.

Podemos usar a função a seguir para obter a representação em ponto flutuante de 64 bits de um dado número x444Esta função não é precisa e pode fornecer registros errados devido a erros de arredondamento. Uma alternativa melhor é apresentada na Observação 1.2.2..

Código 3: unpackBitsDouble.py
1import numpy as np
2
3def unpackBitsDouble(x):
4 ld = 64*[0]
5 if ( x == 0):
6 return ld
7 elif (x < 0):
8 ld [0] = 1
9 x = np.fabs(x)
10 c = int(np.log2(x) + 1023)
11 m = x/2**(c - 1023)
12 for i in range(11):
13 ld [11 - i] = c % 2
14 c //= 2
15 m -= 1
16 for i in range(52):
17 m *= 2
18 ld [12+ i] = int(m)
19 m %= 1
20 return ld

Por exemplo, x=1.1 é representado pelo registro

1ld = unpackBitsDouble(1.1)
2ld
[0, 0, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 0, 0, 0, 1,
1, 0, 0, 1, 1, 0, 0, 1,
1, 0, 0, 1, 1, 0, 0, 1,
1, 0, 0, 1, 1, 0, 0, 1,
1, 0, 0, 1, 1, 0, 0, 1,
1, 0, 0, 1, 1, 0, 0, 1,
1, 0, 0, 1, 1, 0, 1, 0]

que corresponde ao número

1flx = packBitsDouble(ld)
2print(f'{flx:1.51f}')
1.10000000000000008881784197
0012523233890533447265625

O erro de arredondamento é |x−f⁢l⁢(x)|≈8.9×10−17.

Observação 1.2.2.

O seguinte código é uma solução mais pythônica para obter-se o registro em ponto flutuante de 64 bits de x=1.1.

1''.join(f'{c:08b}' for c in struct.pack('!d', 1.1))
’0011111111110001
1001100110011001
1001100110011001
1001100110011010’

Recomendamos consultar [4] para mais informações sobre a conversão eficiente de números decimais em pontos flutuantes.

Observamos que o erro de arredondamento varia conforme o número dado e pode ser zero quando x=f⁢l⁢(x). Para caracterizar a precisão do sistema, usamos o épsilon de máquina, definido como a distância entre o número 1 e seu primeiro sucessor em ponto flutuante. Portanto, ele não representa o erro de arredondamento de todo número. Temos

1ld = unpackBitsDouble(1)
2ld
[0, 0, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0]
1ld[63] = 1
2x = packBitsDouble(ld)
3x-1
2.220446049250313e-16

Ou seja, o épsilon de máquina é

eps:=2−52≈2.22×10−16. (1.50)
Observação 1.2.3.

O método numpy.finfo pode ser usado para obtermos várias informações sobre o sistema de números em ponto flutuante. Por exemplo, temos

1import numpy as np
2finfo = np.finfo(np.double)
3finfo.eps
2.220446049250313e-16
1finfo.min
-1.7976931348623157e+308
1finfo.max
1.7976931348623157e+308

A aritmética em ponto flutuante requer arredondamentos sucessivos de números. Por exemplo, a computação da soma de dois números dados x e y é feita a partir de suas representações em ponto flutuante f⁢l⁢(x) e f⁢l⁢(y). Então, computa-se z=f⁢l⁢(x)+f⁢l⁢(y) e o resultado é f⁢l⁢(z). Observe, inclusive que f⁢l⁢(x+y) pode ser diferente de f⁢l⁢(f⁢l⁢(x)+f⁢l⁢(y)). Por exemplo

10.1 + 0.2 == 0.3
False

1.2.4 Exercícios resolvidos

ER 1.2.1.

Vamos fornecer, no sistema de complemento de 2 de 8 bits, os registros que representam os seguintes números inteiros:

  1. a)

    1

  2. b)

    -1

  3. c)

    15

  4. d)

    -15

Resolução.

Com a função a seguir, obtemos o registro em complemento de 2 de 8 bits que representa um dado número inteiro x.

1def unpackBitsInt8(x):
2 ld = 8*[0]
3 if (x < 0):
4 ld[7] = 1
5 x += 2**(7)
6 for i in range(7):
7 ld[i] = x % 2
8 x //= 2
9 return ld

Usando-a, obtemos os seguintes resultados:

1# a) 1
2unpackBitsInt8(1)
[1, 0, 0, 0, 0, 0, 0, 0]
1# b) -1
2unpackBitsInt8(-1)
[1, 1, 1, 1, 1, 1, 1, 1]
1# c) 15
2unpackBitsInt8(15)
[1, 1, 1, 1, 0, 0, 0, 0]
1unpackBitsInt8(-15)
[1, 0, 0, 0, 1, 1, 1, 1]
ER 1.2.2.

Vamos determinar o menor número positivo representável em ponto flutuante de 64 bits e fornecer seu registro.

Resolução.

Um registro em ponto flutuante de 64-bits tem a forma

[s⁢|c10⁢c9⁢…⁢c0|⁢m1⁢m2⁢…⁢m52] (1.51)

e representa o número

x=(−1)s⁢M⋅2c−1023, (1.52)

onde M é chamada de mantissa e c da característica, as quais são definidas por

M :=(1,m1⁢m2⁢m3⁢…⁢m52)2, (1.53)
c :=(c10⁢…⁢c2⁢c1⁢c0)2. (1.54)

A expressão acima descreve números normalizados. No padrão IEEE 754, também representamos números subnormais: para característica c=0 e mantissa M=(0,m1⁢m2⁢…⁢m52)2, temos x=(−1)s⁢M⋅2−1023. O registro totalmente nulo representa o zero.

Assim, o menor número positivo representável é subnormal: usamos s=0, c=0 e mantissa M=2−52. Logo,

x=2−52⋅20−1023=2−1075 (1.55)
≈4.9406564584124654×10−324 (1.56)

Seu registro é

[0⁢| 0 0⁢…⁢ 0|⁢ 0 0⁢…⁢ 1] (1.57)

O resultado pode ser verificado com os seguintes comandos:

1import numpy as np
2import struct
3x = np.nextafter(0., 1.); x
5e-324
1''.join(f'{c:08b}' \
2 for c in struct.pack('!d', x))
’0000000000000000
0000000000000000
0000000000000000
’0000000000000001’
ER 1.2.3.

Em aplicações que não necessitam de muita precisão, a representação de números decimais no sistema de ponto flutuante de 32 bits é mais eficiente (no sentido de velocidade de processamento computacional). Neste sistema, um registro de 32-bits

[s⁢|c7⁢c6⁢…⁢c0|⁢m1⁢m2⁢…⁢m23] (1.58)

representa o número

x=(−1)s⋅M⋅2c−127 (1.59)

onde,

M=(1,m1⁢m2⁢…⁢m23)2 (1.60)
c=(c7⁢c6⁢…⁢c0)2 (1.61)
  1. a)

    Forneça o registro do ponto flutuante de 32-bits que representa o número 42.5.

  2. b)

    Qual é o sucessor de 1 em ponto flutuante de 32-bits. Forneça, também, o épsilon de máquina deste sistema.

Resolução.
  1. a)

    O registro do ponto flutuante de 32-bits que representa o número 42.5 pode ser computado com o seguinte código:

    1x = 42.5
    2ld = 32*[0]
    3c = int(np.log2(x) + 127)
    4m = x/2**(c-127)
    5for i in range(8):
    6 ld[8-i] = c % 2
    7 c //= 2
    8m -= 1
    9for i in range(23):
    10 m *= 2
    11 ld[9+i] = int(m)
    12 m %= 1
    13ld
    [0, 1, 0, 0, 0, 0, 1, 0,
    0, 0, 1, 0, 1, 0, 1, 0,
    0, 0, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0]
    

    Alternativamente, pode-se obter o registro como segue:

    1''.join(f'{c:08b}' \
    2for c in struct.pack('!f', 42.5))
    ’0100001000101010
    0000000000000000’
    
  2. b)

    No sistema de ponto flutuante de 32-bits, o sucessor de 1 tem o registro

    [0⁢| 0 1 1⁢…⁢ 1|⁢ 0 0⁢…⁢ 0 1] (1.62)

    donde, sua mantissa é m=1+2−23, característica c=127 e corresponde ao número decimal

    x=(−1)0⋅(1+2−23)⋅2127−127 (1.63)
    x=1+2−23 (1.64)

    Portanto, o épsilon de máquina neste sistema é

    eps=x−1 (1.65)
    =2−23 (1.66)
    1np.float32(2**-23)
    1.1920929e-07
    

1.2.5 Exercícios

E. 1.2.1.

Considerando a representação de complemento de 2 de números inteiros, obtenha os registros de 8 bits dos seguintes números:

  1. a)

    17

  2. b)

    −17

  3. c)

    32

  4. d)

    −32


a) [10001000]; b) [11110111]
c) [00000100]; d) [00000111]

E. 1.2.2.

Considerando a representação de complemento de 2 de números inteiros, obtenha os registros de 16-bits dos seguintes números:

  1. a)

    1024

  2. b)

    −1024


a) [0000000000100000];
b) [0000000000111111];

E. 1.2.3.

Considerando a representação de complemento de 2 de números inteiros, qual é o maior número que pode ser representado por um registro de 32-bits da forma

[1 0⁢b2⁢b3⁢b4⁢⋯⁢b30⁢ 1], (1.67)

onde bi∈{0,1}, i=2,3,4,⋯⁢,30.


[10111⁢…⁢11]∼−3

E. 1.2.4.

Obtenha os registros em ponto flutuante de 64-bits dos seguintes números:

  1. a)

    −1.25

  2. b)

    3


a) [1⁢| 0 1 1⁢…⁢ 1|⁢ 1 0 1 0 0⁢…⁢ 0];
b) [0⁢| 1 0 0⁢…⁢ 0|⁢ 1 0 0⁢…⁢ 0]

E. 1.2.5.

Assumindo o sistema de ponto flutuante de 32-bits, obtenha o registro e o erro de arredondamento na representação dos seguintes números decimais:

  1. a)

    0.1

  2. b)

    10.1

  3. c)

    100.1


  1. a)

      [0, 0, 1, 1, 1, 1, 0, 1,
      1, 1, 0, 0, 1, 1, 0, 0,
      1, 1, 0, 0, 1, 1, 0, 0,
      1, 1, 0, 0, 1, 1, 0, 1]
    

    |0.1−f⁢l⁢(0.1)|≈1.5⁢e−9

  2. b)

    [0, 1, 0, 0, 0, 0, 0, 1,
    0, 0, 1, 0, 0, 0, 0, 1,
    1, 0, 0, 1, 1, 0, 0, 1,
    1, 0, 0, 1, 1, 0, 1, 0]
    

    |10.1−f⁢l⁢(10.1)|≈3.8⁢e−7

  3. c)

    [0, 1, 0, 0, 0, 0, 1, 0,
    1, 1, 0, 0, 1, 0, 0, 0,
    0, 0, 1, 1, 0, 0, 1, 1,
    0, 0, 1, 1, 0, 0, 1, 1]
    

    |100.1−f⁢l⁢(100.1)|≈1.5⁢e−6


Envie seu comentário

Aproveito para agradecer a todas/os que de forma assídua ou esporádica contribuem enviando correções, sugestões e críticas!

Opcional. Preencha seu nome para que eu possa lhe contatar.
Opcional. Preencha seu e-mail para que eu possa lhe contatar.
As informações preenchidas são enviadas por e-mail para o desenvolvedor do site e tratadas de forma privada. Consulte a política de uso de dados para mais informações.

Licença Creative Commons
Este texto é disponibilizado nos termos da Licença Creative Commons Atribuição-CompartilhaIgual 4.0 Internacional. Ícones e elementos gráficos podem estar sujeitos a condições adicionais.

Pedro H A Konzen
| | | |