0%
Pular para o conteúdo principal
0%

1.3.3 Funções como Métodos Gerais

Introduzimos funções compostas na seção 1.1.4 como um mecanismo para abstrair padrões de operações numéricas de modo a torná-los independentes dos números particulares envolvidos. Com funções de ordem superior, como a função integral da seção 1.3.1, começamos a ver um tipo mais poderoso de abstração: funções usadas para expressar métodos gerais de computação, independentemente das funções particulares envolvidas. Nesta seção discutimos dois exemplos mais elaborados — métodos gerais para encontrar zeros e pontos fixos de funções — e mostramos como esses métodos podem ser expressos diretamente como funções.

Encontrando raízes de equações pelo método da metade do intervalo

O método da metade do intervalo é uma técnica simples mas poderosa para encontrar raízes de uma equação f(x)=0f(x)=0, onde ff é uma função contínua. A ideia é que, se nos forem dados pontos aa e bb tais que f(a)<0<f(b)f(a) < 0 < f(b), então ff deve ter pelo menos um zero entre aa e bb. Para localizar um zero, seja xx a média de aa e bb e calcule f(x)f(x). Se f(x)>0f(x) > 0, então ff deve ter um zero entre aa e xx. Se f(x)<0f(x) < 0, então ff deve ter um zero entre xx e bb. Continuando dessa maneira, podemos identificar intervalos cada vez menores nos quais ff deve ter um zero. Quando chegamos a um ponto onde o intervalo é pequeno o suficiente, o processo para. Como o intervalo de incerteza é reduzido pela metade em cada passo do processo, o número máximo de passos necessários cresce como Θ(log(L/T))\Theta(\log(L/T)), onde LL é o comprimento do intervalo original e TT é a tolerância de erro (isto é, o tamanho do intervalo que consideraremos "pequeno o suficiente").

Aqui está uma função que implementa esta estratégia:

Carregando playground de código...

Assumimos que inicialmente recebemos a função ff juntamente com pontos nos quais seus valores são negativos e positivos. Primeiro calculamos o ponto médio dos dois pontos dados. Em seguida, verificamos se o intervalo dado é pequeno o suficiente e, se for, simplesmente retornamos o ponto médio como nossa resposta. Caso contrário, calculamos como um valor de teste o valor de ff no ponto médio. Se o valor de teste for positivo, continuamos o processo com um novo intervalo indo do ponto negativo original até o ponto médio. Se o valor de teste for negativo, continuamos com o intervalo do ponto médio ao ponto positivo. Finalmente, há a possibilidade de que o valor de teste seja 0, caso em que o ponto médio é em si a raiz que estamos procurando.

Para testar se os pontos finais estão "próximos o suficiente", podemos usar uma função similar à usada na seção 1.1.7 para calcular raízes quadradas:1

Carregando playground de código...

A função search é complicada para usar diretamente, porque podemos acidentalmente fornecer pontos nos quais os valores de ff não têm o sinal necessário, caso em que obteríamos uma resposta errada. Em vez disso, usaremos search por meio da seguinte função, que verifica quais dos pontos finais tem um valor de função negativo e qual tem um valor positivo, e chama a função search de acordo. Se a função tiver o mesmo sinal nos dois pontos dados, o método da metade do intervalo não pode ser usado, caso em que a função sinaliza um erro.2

Carregando playground de código...

O exemplo a seguir usa o método da metade do intervalo para aproximar π\pi como a raiz entre 2 e 4 de sinx=0\sin x = 0:

Carregando playground de código...

Aqui está outro exemplo, usando o método da metade do intervalo para procurar uma raiz da equação x32x3=0x^3 - 2x - 3 = 0 entre 1 e 2:

Carregando playground de código...

Encontrando pontos fixos de funções

Um número xx é chamado de ponto fixo de uma função ff se xx satisfaz a equação f(x)=xf(x)=x. Para algumas funções ff podemos localizar um ponto fixo começando com um palpite inicial e aplicando ff repetidamente,

f(x),f(f(x)),f(f(f(x))),f(x), \quad f(f(x)), \quad f(f(f(x))), \quad \ldots

até que o valor não mude muito. Usando esta ideia, podemos desenvolver uma função fixed_point que recebe como entradas uma função e um palpite inicial e produz uma aproximação de um ponto fixo da função. Aplicamos a função repetidamente até encontrarmos dois valores sucessivos cuja diferença é menor que alguma tolerância prescrita:

Carregando playground de código...

Por exemplo, podemos usar este método para aproximar o ponto fixo do cosseno, começando com 1 como palpite inicial:3

Carregando playground de código...

Da mesma forma, podemos encontrar uma solução da equação y=siny+cosyy = \sin y + \cos y:

Carregando playground de código...

O processo de busca de ponto fixo nos lembra o processo que usamos para encontrar raízes quadradas. Ambos são baseados na ideia de melhorar repetidamente um palpite até que o resultado satisfaça algum critério. De fato, podemos formular facilmente a computação de raiz quadrada como uma busca de ponto fixo. Calcular a raiz quadrada de algum número xx requer encontrar um yy tal que y2=xy^2 = x. Colocando essa equação na forma equivalente y=x/yy = x/y, reconhecemos que estamos procurando um ponto fixo da função yx/yy \mapsto x/y, e podemos, portanto, tentar calcular raízes quadradas como

function sqrt(x) {
return fixed_point(y => x / y, 1);
}

Infelizmente, esta busca de ponto fixo não converge. Considere um palpite inicial y1y_1. O próximo palpite é y2=x/y1y_2 = x/y_1 e o próximo palpite é y3=x/y2=x/(x/y1)=y1y_3 = x/y_2 = x/(x/y_1) = y_1. Isso resulta em um loop infinito no qual os dois palpites y1y_1 e y2y_2 se repetem indefinidamente, oscilando em torno da resposta.

Uma maneira de controlar tais oscilações é evitar que os palpites mudem tanto. Como a resposta está sempre entre nosso palpite yy e x/yx/y, podemos fazer um novo palpite que não está tão longe de yy quanto x/yx/y fazendo a média de yy com x/yx/y, para que o próximo palpite após yy seja (1/2)(y+x/y)(1/2)(y + x/y) em vez de x/yx/y. O processo de fazer tal sequência de palpites é simplesmente o processo de procurar por um ponto fixo de y(1/2)(y+x/y)y \mapsto (1/2)(y + x/y):

Carregando playground de código...

(Observe que y=(1/2)(y+x/y)y = (1/2)(y + x/y) é uma transformação simples da equação y=x/yy = x/y; para derivar isso, some yy a ambos os lados da equação e divida por 2.)

Com essa modificação, a função de raiz quadrada funciona. Na verdade, se desenrolarmos as declarações, podemos ver que a sequência de aproximações ao ponto fixo de busca de ponto fixo é precisamente a mesma sequência gerada pela nossa função de raiz quadrada original da seção 1.1.7. Este método de fazer a média de aproximações sucessivas a uma solução, uma técnica que chamamos de amortecimento médio, geralmente ajuda a convergência de buscas de ponto fixo.

Exercício 1.35

Mostre que a razão áurea ϕ\phi (seção 1.2.2) é um ponto fixo da transformação x1+1/xx \mapsto 1 + 1/x, e use isso para calcular ϕ\phi por meio da função fixed_point.

Exercício 1.36

Modifique fixed_point para que ela imprima a sequência de aproximações que gera, usando a função primitiva display mostrada no exercício 1.22. Então encontre uma solução de xx=1000x^x = 1000 encontrando um ponto fixo de xlog(1000)/log(x)x \mapsto \log(1000)/\log(x). (Use a função primitiva math_log do JavaScript, que calcula logaritmos naturais.) Compare o número de passos necessários com e sem amortecimento médio. (Observe que você não pode iniciar fixed_point com um palpite de 1, pois isso causaria divisão por log(1)=0\log(1) = 0.)

Exercício 1.37

a. Uma fração contínua infinita é uma expressão da forma

f=N1D1+N2D2+N3D3+f = \frac{N_1}{D_1 + \frac{N_2}{D_2 + \frac{N_3}{D_3 + \cdots}}}

Como exemplo, pode-se mostrar que a expansão em fração contínua infinita com os NiN_i e os DiD_i todos iguais a 1 produz 1/ϕ1/\phi, onde ϕ\phi é a razão áurea (descrita na seção 1.2.2). Uma forma de aproximar uma fração contínua infinita é truncá-la após um dado número de termos. Tal truncamento — uma chamada fração contínua de kk termos — tem a forma

N1D1+N2+NKDK\frac{N_1}{D_1 + \frac{N_2}{\ddots + \frac{N_K}{D_K}}}

Suponha que n e d sejam funções de um argumento (o índice do termo ii) que retornam os NiN_i e DiD_i dos termos da fração contínua. Declare uma função cont_frac tal que avaliar cont_frac(n, d, k) calcule o valor de uma fração contínua de kk termos. Verifique sua função aproximando 1/ϕ1/\phi usando

cont_frac(i => 1, i => 1, k)

para valores sucessivos de k. Quão grande você tem que fazer k para obter uma aproximação que é precisa em 4 casas decimais?

b. Se sua função cont_frac gera um processo recursivo, escreva uma que gere um processo iterativo. Se ela gera um processo iterativo, escreva uma que gere um processo recursivo.

Exercício 1.38

Em 1737, o matemático suíço Leonhard Euler publicou um artigo De Fractionibus Continuis, que incluía uma expansão em fração contínua para e2e - 2, onde ee é a base dos logaritmos naturais. Nesta fração, os NiN_i são todos 1, e os DiD_i são sucessivamente 1, 2, 1, 1, 4, 1, 1, 6, 1, 1, 8, ... Escreva um programa que usa sua função cont_frac do exercício 1.37 para aproximar ee, baseado na expansão de Euler.

Exercício 1.39

Uma fração contínua para a função tangente foi publicada em 1770 pelo matemático alemão J.H. Lambert:

tanx=x1x23x25\tan x = \frac{x}{1 - \frac{x^2}{3 - \frac{x^2}{5 - \cdots}}}

onde xx está em radianos. Declare uma função tan_cf(x, k) que calcula uma aproximação da função tangente baseada na fórmula de Lambert. A constante k especifica o número de termos a calcular, como no exercício 1.37.


Notas de Rodapé

1 Usamos 0.001 como um número "pequeno" representativo para indicar uma tolerância para o erro aceitável em um cálculo. A tolerância apropriada para um cálculo real depende do problema a ser resolvido e das limitações do computador e do algoritmo. Isso é muitas vezes uma consideração muito sutil, exigindo ajuda de um analista numérico ou algum outro tipo de mago.

2 Isso pode ser feito usando error, que recebe como argumento uma string que é exibida como mensagem de erro juntamente com a informação de que o programa foi interrompido.

3 Tente isso durante um intervalo de ócio. Veja se você consegue usar isso para implementar a calculadora de ``logaritmo'' inventada pelo brincalhão mencionado na nota de rodapé da seção 1.3.3.

📝 Encontrou algo errado nesta página?

Sua ajuda é muito importante para melhorar a qualidade da tradução!

🐛 Encontrou um erro?

Se você encontrou:

  • Erro de tradução (palavra incorreta, termo técnico errado)
  • Erro de ortografia ou gramática
  • Link quebrado
  • Código de exemplo que não funciona
  • Problema de formatação

Reporte um bug →

❓ Tem uma dúvida?

Se você tem:

  • Dúvida sobre o conteúdo desta seção
  • Pergunta sobre um conceito do SICP
  • Dificuldade em entender algum exemplo
  • Questão sobre a tradução de algum termo

Inicie uma discussão →

💡 Tem uma sugestão de melhoria?

Se você quer sugerir:

  • Melhoria na explicação
  • Exemplo adicional
  • Recurso visual (diagrama, ilustração)
  • Qualquer outra ideia

Sugira uma melhoria →

🌍 Quer discutir a tradução?

Se você quer debater:

  • Escolha de tradução de algum termo
  • Consistência de terminologia
  • Nuances do português

Discussão de tradução →

Obrigado por ajudar a melhorar o SICP.js PT-BR! ✨