Pular para conteúdo

Lista 01 · Erros e ponto flutuante

Acompanha o capítulo 1. Medir o erro, decidir quando parar e escapar das armadilhas do arredondamento — com exemplos de matemática, física, engenharia e finanças.

Abrir no Colab

Baixar o caderno

Como esta lista funciona

Abra o caderno no Colab, escreva a sua solução no lugar do # TODO e rode a célula de teste logo abaixo. Ela mesma diz o que passou e o que falhou — você não precisa esperar correção de ninguém.

O código de cada exercício também está aqui embaixo, caso você prefira colar num editor local. Os testes comparam números com tolerância.

Exercícios marcados com ✏️ são à mão, no papel, com 4 casas decimais — como na parte em papel das avaliações.

Exercício 01

✏️ À mão. Duas aproximações históricas de \(\pi = 3{,}14159265\ldots\): \(22/7\) (Arquimedes) e \(355/113\) (Zu Chongzhi, século V).

a) Calcule o erro absoluto e o erro relativo percentual \(\varepsilon_t\) de cada uma.

b) Calcule a tolerância de Scarborough \(\varepsilon_s = 0{,}5 \times 10^{2-n}\,\%\) para \(n = 3\) e \(n = 6\) algarismos significativos.

c) Com quantos algarismos significativos corretos cada aproximação acerta \(\pi\)? Use o resultado de (b).

Exercício 02

Escreva quase_iguais(a, b, tol), que devolve True se a diferença entre a e b, em valor absoluto, for menor que tol. É o jeito certo de comparar floats.

0.1 + 0.2 == 0.3                     # -> False (!)
quase_iguais(0.1 + 0.2, 0.3, 1e-9)   # -> True
def quase_iguais(a, b, tol):
    """True se a e b diferem menos que tol."""
    # TODO: abs(...) < tol
    pass


# --- testes (não altere) ---
assert quase_iguais(0.1 + 0.2, 0.3, 1e-9) is True
assert quase_iguais(1.0, 1.1, 1e-9) is False
assert quase_iguais(-2.0, -2.0000001, 1e-6) is True, "funciona com negativos"
assert quase_iguais(5, 6, 1) is False, "a diferença tem de ser MENOR que tol"
print("Exercício 02: todos os testes passaram!")

Exercício 03

Escreva tolerancia(n), que devolve a tolerância de Scarborough \(\varepsilon_s = 0{,}5 \times 10^{2-n}\) (em %), para \(n\) algarismos significativos.

tolerancia(3)    # -> 0.05
tolerancia(1)    # -> 5.0
def tolerancia(n):
    """Tolerância de Scarborough (%) para n algarismos significativos."""
    # TODO: potência em Python é **
    pass


# --- testes (não altere) ---
assert abs(tolerancia(3) - 0.05) < 1e-12
assert abs(tolerancia(1) - 5.0) < 1e-12
assert abs(tolerancia(6) - 0.00005) < 1e-15
print("Exercício 03: todos os testes passaram!")

Exercício 04

Escreva exp_taylor(x, n_termos), que soma os n_termos primeiros termos da série \(e^x = 1 + x + \frac{x^2}{2!} + \frac{x^3}{3!} + \cdots\).

Não use fatorial nem potência: cada termo é o anterior vezes \(x/n\).

exp_taylor(0.5, 1)    # -> 1.0
exp_taylor(0.5, 3)    # -> 1.625
exp_taylor(1, 20)     # -> ≈ 2.718281828 (o número e)

Teste exp_taylor(-20, 100) e compare com np.exp(-20). O que aconteceu? (Os termos alternam de sinal e chegam a \(\approx 4 \times 10^7\): é cancelamento.)

def exp_taylor(x, n_termos):
    """Soma dos n_termos primeiros termos da série de e^x."""
    soma = 0.0
    termo = 1.0
    # TODO: laço; soma o termo e calcula o próximo (termo * x / (n + 1))
    return soma


# --- testes (não altere) ---
import math


assert abs(exp_taylor(0.5, 1) - 1.0) < 1e-12
assert abs(exp_taylor(0.5, 3) - 1.625) < 1e-12
assert abs(exp_taylor(1, 20) - math.e) < 1e-12
assert abs(exp_taylor(2, 5) - 7.0) < 1e-12, "1 + 2 + 2 + 4/3 + 2/3"
print("Exercício 04: todos os testes passaram!")

Exercício 05

O critério de parada. Escreva exp_com_parada(x, n_alg), que soma a série de \(e^x\) termo a termo e para quando o erro relativo aproximado

\[ \varepsilon_a = \left\lvert \frac{\text{atual} - \text{anterior}}{\text{atual}} \right\rvert \times 100\% \]

fica abaixo de tolerancia(n_alg) (já escrita). Devolva a tupla (soma, número de termos usados).

exp_com_parada(0.5, 3)    # -> (1.648697916..., 6)   (a tabela do capítulo)
def tolerancia(n):
    """Tolerância de Scarborough (%). JÁ ESCRITA."""
    return 0.5 * 10**(2 - n)


def exp_com_parada(x, n_alg):
    """(aproximação de e^x, número de termos usados), parando quando o erro
    relativo aproximado fica abaixo da tolerância para n_alg algarismos."""
    eps_s = tolerancia(n_alg)
    soma = 1.0
    termo = 1.0
    n = 0
    # TODO: while True: próximo termo, guarda a soma anterior, soma, calcula
    # eps_a e devolve (soma, n + 1) quando eps_a < eps_s


# --- testes (não altere) ---
resposta = exp_com_parada(0.5, 3)
assert resposta is not None, "faltou o return?"
soma, termos = resposta
assert termos == 6, f"a tabela do capítulo para com 6 termos (veio {termos})"
assert abs(soma - 1.6486979166666667) < 1e-12
soma, termos = exp_com_parada(1, 8)
assert termos == 13
assert abs(soma - 2.718281828) < 1e-6, "8 algarismos significativos de e"
print("Exercício 05: todos os testes passaram!")

Exercício 06

Bhaskara sem cancelamento. Para \(ax^2 + bx + c = 0\) com \(b > 0\), escreva raiz_pequena(a, b, c), que devolve a raiz de menor módulo sem subtrair números quase iguais:

  1. calcule a raiz grande, \(x_1 = \dfrac{-b - \sqrt{b^2 - 4ac}}{2a}\) (aqui os dois termos do numerador têm o mesmo sinal: não há cancelamento);
  2. use \(x_1 x_2 = c/a\) para achar \(x_2\).
raiz_pequena(1, 1e8, 1)     # -> -1e-08   (Bhaskara direto dá -7.45e-09)
raiz_pequena(1, 3, 2)       # -> -1.0
import numpy as np


def raiz_pequena(a, b, c):
    """A raiz de menor módulo de ax² + bx + c = 0 (b > 0), sem cancelamento."""
    # TODO: x_grande com o sinal de menos na frente da raiz; depois c / (a * x_grande)
    pass


# --- testes (não altere) ---
x = raiz_pequena(1, 1e8, 1)
assert x is not None, "faltou o return?"
assert abs(x - (-1e-8)) < 1e-20, f"veio {x}: parece o Bhaskara com cancelamento"
assert abs(raiz_pequena(1, 3, 2) - (-1.0)) < 1e-12
x = raiz_pequena(2, 1e6, 3)
assert abs(2 * x**2 + 1e6 * x + 3) < 1e-9, "a raiz tem de satisfazer a equação"
print("Exercício 06: todos os testes passaram!")

Exercício 07

O relógio que atrasa. Escreva soma_repetida(valor, vezes), que soma valor a um total começado em 0.0, vezes vezes, num laço (sem multiplicar).

Depois de passar nos testes: um relógio que soma 0.1 s a cada décimo de segundo, durante um dia inteiro (\(864\,000\) vezes), marca quanto? Quanto ele erra? E se ele contasse os tiques como inteiro e multiplicasse só no fim?

def soma_repetida(valor, vezes):
    """valor somado a si mesmo `vezes` vezes, num laço."""
    total = 0.0
    # TODO: acumulador
    return total


# --- testes (não altere) ---
s = soma_repetida(0.1, 10)
assert s != 1.0, "0.1 somado dez vezes NÃO dá exatamente 1.0 — foi usado um laço?"
assert abs(s - 1.0) < 1e-12, "mas dá muito perto"
assert soma_repetida(0.5, 4) == 2.0, "0.5 é exato em binário: aqui não há erro"
assert abs(soma_repetida(0.1, 864000) - 86400.00000054126) < 1e-6
print("Exercício 07: todos os testes passaram!")

Exercício 08

Finanças. Uma aplicação rende taxa ao mês (0,005 = 0,5 %), e o sistema guarda o saldo em centavos cortando (e não arredondando) o que passa de dois decimais: np.floor(valor * 100) / 100. Escreva saldo_cortando(capital, taxa, meses).

saldo_cortando(1234.56, 0.005, 120)    # -> 2245.38

Compare com 1234.56 * 1.005**120. Por que cortar erra sempre para o mesmo lado?

import numpy as np


def saldo_cortando(capital, taxa, meses):
    """Saldo depois de `meses` meses, cortando os centavos a cada mês."""
    saldo = capital
    # TODO: laço dos meses; rende e corta os centavos
    return saldo


# --- testes (não altere) ---
assert abs(saldo_cortando(1234.56, 0.005, 120) - 2245.38) < 1e-9
assert abs(saldo_cortando(100, 0.01, 1) - 101.0) < 1e-9
assert abs(saldo_cortando(100.99, 0.013, 12) - 117.86) < 1e-9
assert saldo_cortando(1234.56, 0.005, 120) < 1234.56 * 1.005**120, "cortar perde dinheiro"
print("Exercício 08: todos os testes passaram!")