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 , onde é uma função contínua. A ideia é que, se nos forem dados pontos e tais que , então deve ter pelo menos um zero entre e . Para localizar um zero, seja a média de e e calcule . Se , então deve ter um zero entre e . Se , então deve ter um zero entre e . Continuando dessa maneira, podemos identificar intervalos cada vez menores nos quais 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 , onde é o comprimento do intervalo original e é 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:
Assumimos que inicialmente recebemos a função 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 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
A função search é complicada para usar diretamente, porque podemos acidentalmente fornecer pontos nos quais os valores de 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
O exemplo a seguir usa o método da metade do intervalo para aproximar como a raiz entre 2 e 4 de :
Aqui está outro exemplo, usando o método da metade do intervalo para procurar uma raiz da equação entre 1 e 2:
Encontrando pontos fixos de funções
Um número é chamado de ponto fixo de uma função se satisfaz a equação . Para algumas funções podemos localizar um ponto fixo começando com um palpite inicial e aplicando repetidamente,
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:
Por exemplo, podemos usar este método para aproximar o ponto fixo do cosseno, começando com 1 como palpite inicial:3
Da mesma forma, podemos encontrar uma solução da equação :
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 requer encontrar um tal que . Colocando essa equação na forma equivalente , reconhecemos que estamos procurando um ponto fixo da função , 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 . O próximo palpite é e o próximo palpite é . Isso resulta em um loop infinito no qual os dois palpites e 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 e , podemos fazer um novo palpite que não está tão longe de quanto fazendo a média de com , para que o próximo palpite após seja em vez de . O processo de fazer tal sequência de palpites é simplesmente o processo de procurar por um ponto fixo de :
(Observe que é uma transformação simples da equação ; para derivar isso, some 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 (seção 1.2.2) é um ponto fixo da transformação , e use isso para calcular por meio da função fixed_point.
💡 Mostrar solução — tente primeiro!💡 Esconder solução
A demonstração. satisfaz (é a raiz positiva dessa equação, por definição). Dividindo os dois lados por : — ou seja, é exatamente um ponto fixo de .
O resultado, , é a razão áurea.
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 encontrando um ponto fixo de . (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 .)
💡 Mostrar solução — tente primeiro!💡 Esconder solução
Versão com impressão e a busca por :
Sem amortecimento a sequência oscila e leva cerca de 34 passos. Com amortecimento médio — troque a última linha por
fixed_point(x => (x + math_log(1000) / math_log(x)) / 2, 2);
— converge em cerca de 9 passos para . O amortecimento não muda o ponto fixo (a média de com coincide com exatamente quando ), só doma a oscilação.
Exercício 1.37
a. Uma fração contínua infinita é uma expressão da forma
Como exemplo, pode-se mostrar que a expansão em fração contínua infinita com os e os todos iguais a 1 produz , onde é 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 termos — tem a forma
Suponha que n e d sejam funções de um argumento (o índice do termo ) que retornam os e 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 termos. Verifique sua função aproximando 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.
💡 Mostrar solução — tente primeiro!💡 Esconder solução
a. Recursivo — contando do termo para dentro:
Com , a precisão de 4 casas decimais chega com k = 11 ou 12.
b. Iterativo — construindo de trás para frente, do termo até o 1:
A fração contínua se "enrola" naturalmente de dentro para fora, então a versão iterativa começa pelo último termo — um caso em que pensar na ordem de construção é o coração do exercício.
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 , onde é a base dos logaritmos naturais. Nesta fração, os são todos 1, e os 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 , baseado na expansão de Euler.
💡 Mostrar solução — tente primeiro!💡 Esconder solução
Ideia. Só precisamos descrever a sequência de denominadores : nas posições (isto é, ) o valor é ; nas demais é 1.
Com 20 termos o resultado já é com todas as casas mostradas corretas.
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:
onde 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.
💡 Mostrar solução — tente primeiro!💡 Esconder solução
Ideia. Na fórmula de Lambert, e os demais (o sinal de menos da fórmula vira sinal do numerador); os denominadores são os ímpares
Com , tan_cf(1, 10) coincide com math_tan(1) em todas as casas exibidas — a fração de Lambert converge rápido.
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
❓ 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
💡 Tem uma sugestão de melhoria?
Se você quer sugerir:
- Melhoria na explicação
- Exemplo adicional
- Recurso visual (diagrama, ilustração)
- Qualquer outra ideia
🌍 Quer discutir a tradução?
Se você quer debater:
- Escolha de tradução de algum termo
- Consistência de terminologia
- Nuances do português
Obrigado por ajudar a melhorar o SICP.js PT-BR! ✨