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.
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.
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.
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
fica abaixo de tolerancia(n_alg) (já escrita). Devolva a tupla
(soma, número de termos usados).
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:
- 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);
- use \(x_1 x_2 = c/a\) para achar \(x_2\).
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).
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!")