MAC2166 - Introdução à Computação

20/06/2017 - Aula 22

Representação de Números Reais

Número reais são representados na memória do computador de forma análoga à notação científica, feita em ponto fluante, como mostrado a seguir:

$$0.x_1x_2x_3...x_k \times B^e$$

onde $x_1x_2x_3...x_k$ é a mantissa (os $k$ dígitos mais significativos do número), $B$ é a base e $e$ é o expoente (que determina a posição correta do dígito mais significativo do número em ponto flutuante).

Exemplos de números reais representados em ponto flutuante:

Número Notação Científica Mantissa Base Expoente
$1000000000$ $0.1 \times 10^{10}$ ou $1$E$9$ $1$ $10$ $10$
$123000$ $0.123 \times 10^{6}$ ou $1.23$E$5$ $123$ $10$ $6$
$456.78$ $0.45678 \times 10^{3}$ ou $4.5678$E$2$ $45678$ $10$ $3$
$0.00123$ $0.123 \times 10^{-2}$ ou $1.23$E$-3$ $123$ $10$ $-2$

Observe que os valores para a mantissa e o expoente são números inteiros. Portanto, se temos uma certa quantidade fixa de bits para armazenar um número real, podemos usar boa parte dessa quantidade para armazenar a mantissa e uma parte menor para armazenar o expoente. E com isso conseguimos representar uma quantidade bem grande de valores reais distintos. Entretanto, de modo análogo ao que acontece com os números inteiros, com uma quantidade fixa de bits conseguimos representar uma quantidade limitada de valores reais, ou, em outras palavras, números reais com precisão limitada). E como o computador é uma máquina com capacidade de armazenamento finita, ele é capaz de representar apenas um subconjunto do conjunto (infinito!) de números reais da matemática.

Obs.: O conteúdo desta seção foi extraído do Capítulo 13 - Números Reais - Tipo float, da apostila Introdução a Ciência da Computação em C.

Problemas em Operações Envolvendo Valores do Tipo float

A página sobre propagação de erros traz dicas importantes relacionadas aos cuidados que é preciso ter na implementação de cálculos envolvendo números reais representados em ponto flutuante:

  • Operações de divisão e multiplicação são "seguras".
  • Adições e subtrações são "perigosas", principalmente quando envolvem números de magnitudes muito diferentes (nesses casos, os dígitos menos significativos são perdidos).
    • Essa perda de dígitos pode ser inevitável e sem consequência (quando os dígitos perdidos são insignificantes para o resultado final) ou catastróficos (quando a perda é magnificada e causa um grande erro no resultado).
  • Quanto mais cálculos são realizados, mais impactante no resultado final do cálculo podem ser os erros de arredondamento feitos nos cálculos intermediários.
    • Cuidado com cálculos feitos dentro de laços (como, por exemplo, os somatórios longos)!
  • Pode haver diferentes formas de se realizar um mesmo cálculo. Algumas podem ser estáveis (ou seja, podem reduzir os erros de arredondamento), enquanto outras são instáveis (ou seja, podem magnificar os erros).
    • Ao implementar um cálculo envolvendo número reais, sempre considere diferentes formas de realizar o cálculo e escolha as soluções mais estáveis.

Problema 22.1

Dado um número inteiro $n > 0$, determinar o número harmônico $H_n$ dado por

$$H_n = 1 + \frac{1}{2} + \frac{1}{3} + \frac{1}{4} + ... + \frac{1}{n} $$

Você pode implementar a soma da direita para a esquerda ou da esquerda para direita. Qual dessas formas é estável?

Obs.: Este problema corresponde ao exercício 2 da lista de exercícios sobre reais.

Solução

In [10]:
def harmonico1(n):
    # Solução 1: calcula a soma da esquerda para a direita
    H = 0
    for x in range (1, n+1):
        H += 1 / x
        #print ("Termo %d: 1 / %d = %.10f   H(%d) = %.10f"%(x, x, 1/x, x, H))  # só para vermos os termos calculados
    
    return H

def harmonico2(n):
    # Solução 2: calcula a soma da direita para a esquerda
    H = 0
    for x in range(n, 0, -1):
        H += 1 / x
        #print ("Termo %d: 1 / %d = %.10f   H(%d) = %.10f"%(x, x, 1/x, x, H))  # só para vermos os termos calculados
        
    return H
        
def main():
    n = int(input("Digite n: "))

    H_0aN = harmonico1(n)   # soma calculada da esquerda para a direita
    H_Na0 = harmonico2(n)   # soma calculada da direita para a esquerda

    print("De 0 a N temos: %.30f" %H_0aN)
    print("De N a 0 temos: %.30f" %H_Na0)
    print("Esses valores são iguais? ", H_0aN == H_Na0)

####################
main()
Digite n: 10
De 0 a N temos: 2.928968253968253776520214159973
De N a 0 temos: 2.928968253968253776520214159973
Esses valores são iguais?  True

Obs1.: Aos testar o programa para valores pequenos de n, os valores calculados para Hn "da esquerda para direita" e "da direita para esquerda" são equivalentes. Mas para valores de n maiores, os resultados calculados pelas duas estratégias se diferem. Como exemplo, compare os resultados do programa para n=10 e depois para n=100

Obs2.: A soma calculada dos menores termos para os maiores (função harmonica2) é a solução mais estável, por envolver operações de soma entre números de valores mais próximos um do outro.

Problema 22.2

Dado um número real $x$ e um número real $epsilon > 0$, calcular uma aproximação de $e^x$ através da seguinte série infinita:

$$e^x = 1 + x + \frac{x^2}{2!} + \frac{x^3}{3!} + ... + \frac{x^k}{k!} + . . .$$

Inclua na aproximação todos os termos até o primeiro de valor absoluto (módulo) menor do que $epsilon$.

Solução

In [13]:
def exponencial1(x,eps):
    # solução 1: os termos são calculados como aparecem na fórmula
    
    termo = 1
    soma = 1
    k = 0
    fat = 1
    
    while termo >= eps or -termo >= eps:
        k += 1
        fat *= k
        termo = x**k / fat
        soma += termo    

    return soma


def exponencial2(x,eps):
    # solução 2: mais eficiente pois realiza menos operações 
    # (já que não precisa calcular a exponenciação x**k a cada execução do laço) 
    
    termo = 1
    soma  = 1
    k = 0
    
    while termo >= eps or -termo >= eps:    # equivale a: while abs(termo) >= eps
        k += 1
        termo = x * termo / k
        soma += termo
    
    return soma


    
def main():

    x   = float(input("Digite x: "))
    eps = float(input("Digite epsilon: "))

    # Solução 1: usando o operador de exponenciação
    print("e**(%5.3f) = %7.4f" %(x, exponencial1(x,eps)))

    # Solução 2: sem usar o operador de exponenciação
    print ("e**(%5.3f) = %7.4f" %(x, exponencial2(x,eps)))

################
main()
Digite x: 25.3456
Digite epsilon: 0.00001
e**(25.346) = 101731212450.5873
e**(25.346) = 101731212450.5873

Problema 22.3

Dados números reais $x \leq 0$ e $epsilon > 0$, calcular uma aproximação da raiz quadrada de $x$ através da seguinte sequência: $$\begin{array}{l c l} r_0 & = & x \\ r_{n+1} & = & \left( r_n + \frac{x}{r_n}\right) /2 \end{array}$$

Exemplos:

Para x = 3, r0 = 3, r1 = 2, r2 = 1.75, r3 = 1.732143, r4 = 1.732051

Para x = 4, r0 = 4, r1 = 2.5, r2 = 2.05, r3 = 2.000610, r4 = 2.000000

Para x = 5, r0 = 5, r1 = 3, r2 = 2.33, r3 = 2.238095, r4 = 2.236068

Para x = 0.81, r0 = 0.81, r1 = 0.905, r2 = 0.9000138122, r3 = 0.9000000001

A aproximação será o primeiro valor $r_{n+1}$ tal que $|r_{n+1}-r_n| < epsilon$.

Solução

In [19]:
def raiz_quadrada(x,eps):
    Rant = x
    Ratual  = (Rant + 1.0) / 2.0
    dif    = Ratual - Rant

    while dif >= eps or dif <= -eps:
        Rant  = Ratual
        Ratual  = (Ratual + x / Ratual) / 2.0
        dif    = Ratual - Rant
        # print (x, eps, Ratual, Rant, dif)    # descomente esta linha para imprimir o valor de cada ri
    
    return Ratual


def main():
    x   = float(input("Raiz de x +/- eps ==> digite x >= 0: "))
    eps = float(input("Raiz de x +/- eps ==> digite eps > 0: "))

    print ("A raiz de %f = %f (com precisão %f)" %(x, raiz_quadrada(x,eps), eps))


########################
main()
Raiz de x +/- eps ==> digite x >= 0: 49.6729
Raiz de x +/- eps ==> digite eps > 0: 0.0001
A raiz de 49.672900 = 7.047900 (com precisão 0.000100)

Problema 22.4

Dados $x$ real e $n$ natural, calcular uma aproximação para $cos(x)$ através dos $n$ primeiros termos da seguinte série

$$ cos(x) = 1 − \frac{x^2}{2!} + \frac{x^4}{4!} − \frac{x^6}{6!} + ... + (−1)^k\frac{x^{2k}}{2k!} + ... $$

Compare com os resultados de sua calculadora!

In [28]:
def cosseno(x,n):
    
    soma = t = 1.0
    
    for k in range (1,n+1):
        t = -1 * t * x*x / ((2*k - 1) * 2*k)
        soma += t
    
    return soma

    
def main():
    x = float(input("Digite o valor de x: "))
    n = int(input("Digite o valor de n: "))

    print("O valor aproximado para cos(%f) é: %f" %(x, cosseno(x,n)))

    
##############
main()
Digite o valor de x: 3.14159265359
Digite o valor de n: 1000
O valor aproximado para cos(3.141593) é: -1.000000

Problema Extra (de "Lição de Casa")

Dados $x$ real e $epsilon$ real, $epsilon > 0$, calcular uma aproximação para $sen(x)$ através da seguinte série infinita:

$$ sen(x) = \frac{x}{1!} − \frac{x^3}{3!} + \frac{x^5}{5!} + ... + (−1)^k \frac{x^{2k+1}}{(2k+1)!} +... $$

incluindo todos os termos até que $\frac{|x^{2k+1}|}{(2k+1)!} < epsilon $.

Compare com os resultados de sua calculadora!

Tópicos vistos na Aula 22

  • Representação de números reais em ponto flutuante
  • Problemas de precisão em cálculos envolvendo números representados em ponto flutuante