|
| 1 | +# 1.3.3 Funções como Métodos Gerais |
| 2 | + |
| 3 | +import CodePlayground from '@site/src/components/CodePlayground'; |
| 4 | + |
| 5 | +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. |
| 6 | + |
| 7 | +## Encontrando raízes de equações pelo método da metade do intervalo |
| 8 | + |
| 9 | +O *método da metade do intervalo* é uma técnica simples mas poderosa para encontrar raízes de uma equação $f(x)=0$, onde $f$ é uma função contínua. A ideia é que, se nos forem dados pontos $a$ e $b$ tais que $f(a) < 0 < f(b)$, então $f$ deve ter pelo menos um zero entre $a$ e $b$. Para localizar um zero, seja $x$ a média de $a$ e $b$ e calcule $f(x)$. Se $f(x) > 0$, então $f$ deve ter um zero entre $a$ e $x$. Se $f(x) < 0$, então $f$ deve ter um zero entre $x$ e $b$. Continuando dessa maneira, podemos identificar intervalos cada vez menores nos quais $f$ 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 $\Theta(\log(L/T))$, onde $L$ é o comprimento do intervalo original e $T$ é a tolerância de erro (isto é, o tamanho do intervalo que consideraremos "pequeno o suficiente"). |
| 10 | + |
| 11 | +Aqui está uma função que implementa esta estratégia: |
| 12 | + |
| 13 | +<CodePlayground |
| 14 | + code={`function search(f, neg_point, pos_point) { |
| 15 | + const midpoint = average(neg_point, pos_point); |
| 16 | + if (close_enough(neg_point, pos_point)) { |
| 17 | + return midpoint; |
| 18 | + } else { |
| 19 | + const test_value = f(midpoint); |
| 20 | + return positive(test_value) |
| 21 | + ? search(f, neg_point, midpoint) |
| 22 | + : negative(test_value) |
| 23 | + ? search(f, midpoint, pos_point) |
| 24 | + : midpoint; |
| 25 | + } |
| 26 | +}`} |
| 27 | + hiddenCode={`function average(x, y) { |
| 28 | + return (x + y) / 2; |
| 29 | +} |
| 30 | +function positive(x) { return x > 0; } |
| 31 | +function negative(x) { return x < 0; } |
| 32 | +function close_enough(x, y) { |
| 33 | + return abs(x - y) < 0.001; |
| 34 | +} |
| 35 | +function abs(x) { |
| 36 | + return x >= 0 ? x : -x; |
| 37 | +}`} |
| 38 | + height={450} |
| 39 | + showLineNumbers={false} |
| 40 | +/> |
| 41 | + |
| 42 | +Assumimos que inicialmente recebemos a função $f$ 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 $f$ 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. |
| 43 | + |
| 44 | +<a name="footnote-link-1"></a> |
| 45 | +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:[<sup>1</sup>](#footnote-1) |
| 46 | + |
| 47 | +<CodePlayground |
| 48 | + code={`function close_enough(x, y) { |
| 49 | + return abs(x - y) < 0.001; |
| 50 | +}`} |
| 51 | + hiddenCode={`function abs(x) { |
| 52 | + return x >= 0 ? x : -x; |
| 53 | +}`} |
| 54 | + height={150} |
| 55 | + showLineNumbers={false} |
| 56 | +/> |
| 57 | + |
| 58 | +<a name="footnote-link-2"></a> |
| 59 | +A função `search` é complicada para usar diretamente, porque podemos acidentalmente fornecer pontos nos quais os valores de $f$ 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.[<sup>2</sup>](#footnote-2) |
| 60 | + |
| 61 | +<CodePlayground |
| 62 | + code={`function half_interval_method(f, a, b) { |
| 63 | + const a_value = f(a); |
| 64 | + const b_value = f(b); |
| 65 | + return negative(a_value) && positive(b_value) |
| 66 | + ? search(f, a, b) |
| 67 | + : negative(b_value) && positive(a_value) |
| 68 | + ? search(f, b, a) |
| 69 | + : error("values are not of opposite sign"); |
| 70 | +}`} |
| 71 | + hiddenCode={`function average(x, y) { |
| 72 | + return (x + y) / 2; |
| 73 | +} |
| 74 | +function positive(x) { return x > 0; } |
| 75 | +function negative(x) { return x < 0; } |
| 76 | +function close_enough(x, y) { |
| 77 | + return abs(x - y) < 0.001; |
| 78 | +} |
| 79 | +function abs(x) { |
| 80 | + return x >= 0 ? x : -x; |
| 81 | +} |
| 82 | +function search(f, neg_point, pos_point) { |
| 83 | + const midpoint = average(neg_point, pos_point); |
| 84 | + if (close_enough(neg_point, pos_point)) { |
| 85 | + return midpoint; |
| 86 | + } else { |
| 87 | + const test_value = f(midpoint); |
| 88 | + return positive(test_value) |
| 89 | + ? search(f, neg_point, midpoint) |
| 90 | + : negative(test_value) |
| 91 | + ? search(f, midpoint, pos_point) |
| 92 | + : midpoint; |
| 93 | + } |
| 94 | +} |
| 95 | +function error(msg) { |
| 96 | + throw new Error(msg); |
| 97 | +}`} |
| 98 | + height={300} |
| 99 | + showLineNumbers={false} |
| 100 | +/> |
| 101 | + |
| 102 | +O exemplo a seguir usa o método da metade do intervalo para aproximar $\pi$ como a raiz entre 2 e 4 de $\sin x = 0$: |
| 103 | + |
| 104 | +<CodePlayground |
| 105 | + code={`half_interval_method(math_sin, 2, 4);`} |
| 106 | + hiddenCode={`function average(x, y) { |
| 107 | + return (x + y) / 2; |
| 108 | +} |
| 109 | +function positive(x) { return x > 0; } |
| 110 | +function negative(x) { return x < 0; } |
| 111 | +function close_enough(x, y) { |
| 112 | + return abs(x - y) < 0.001; |
| 113 | +} |
| 114 | +function abs(x) { |
| 115 | + return x >= 0 ? x : -x; |
| 116 | +} |
| 117 | +function search(f, neg_point, pos_point) { |
| 118 | + const midpoint = average(neg_point, pos_point); |
| 119 | + if (close_enough(neg_point, pos_point)) { |
| 120 | + return midpoint; |
| 121 | + } else { |
| 122 | + const test_value = f(midpoint); |
| 123 | + return positive(test_value) |
| 124 | + ? search(f, neg_point, midpoint) |
| 125 | + : negative(test_value) |
| 126 | + ? search(f, midpoint, pos_point) |
| 127 | + : midpoint; |
| 128 | + } |
| 129 | +} |
| 130 | +function error(msg) { |
| 131 | + throw new Error(msg); |
| 132 | +} |
| 133 | +function half_interval_method(f, a, b) { |
| 134 | + const a_value = f(a); |
| 135 | + const b_value = f(b); |
| 136 | + return negative(a_value) && positive(b_value) |
| 137 | + ? search(f, a, b) |
| 138 | + : negative(b_value) && positive(a_value) |
| 139 | + ? search(f, b, a) |
| 140 | + : error("values are not of opposite sign"); |
| 141 | +} |
| 142 | +const math_sin = Math.sin;`} |
| 143 | + height={100} |
| 144 | + showLineNumbers={false} |
| 145 | +/> |
| 146 | + |
| 147 | +Aqui está outro exemplo, usando o método da metade do intervalo para procurar uma raiz da equação $x^3 - 2x - 3 = 0$ entre 1 e 2: |
| 148 | + |
| 149 | +<CodePlayground |
| 150 | + code={`half_interval_method(x => x * x * x - 2 * x - 3, 1, 2);`} |
| 151 | + hiddenCode={`function average(x, y) { |
| 152 | + return (x + y) / 2; |
| 153 | +} |
| 154 | +function positive(x) { return x > 0; } |
| 155 | +function negative(x) { return x < 0; } |
| 156 | +function close_enough(x, y) { |
| 157 | + return abs(x - y) < 0.001; |
| 158 | +} |
| 159 | +function abs(x) { |
| 160 | + return x >= 0 ? x : -x; |
| 161 | +} |
| 162 | +function search(f, neg_point, pos_point) { |
| 163 | + const midpoint = average(neg_point, pos_point); |
| 164 | + if (close_enough(neg_point, pos_point)) { |
| 165 | + return midpoint; |
| 166 | + } else { |
| 167 | + const test_value = f(midpoint); |
| 168 | + return positive(test_value) |
| 169 | + ? search(f, neg_point, midpoint) |
| 170 | + : negative(test_value) |
| 171 | + ? search(f, midpoint, pos_point) |
| 172 | + : midpoint; |
| 173 | + } |
| 174 | +} |
| 175 | +function error(msg) { |
| 176 | + throw new Error(msg); |
| 177 | +} |
| 178 | +function half_interval_method(f, a, b) { |
| 179 | + const a_value = f(a); |
| 180 | + const b_value = f(b); |
| 181 | + return negative(a_value) && positive(b_value) |
| 182 | + ? search(f, a, b) |
| 183 | + : negative(b_value) && positive(a_value) |
| 184 | + ? search(f, b, a) |
| 185 | + : error("values are not of opposite sign"); |
| 186 | +}`} |
| 187 | + height={100} |
| 188 | + showLineNumbers={false} |
| 189 | +/> |
| 190 | + |
| 191 | +## Encontrando pontos fixos de funções |
| 192 | + |
| 193 | +Um número $x$ é chamado de *ponto fixo* de uma função $f$ se $x$ satisfaz a equação $f(x)=x$. Para algumas funções $f$ podemos localizar um ponto fixo começando com um palpite inicial e aplicando $f$ repetidamente, |
| 194 | + |
| 195 | +$$ |
| 196 | +f(x), \quad f(f(x)), \quad f(f(f(x))), \quad \ldots |
| 197 | +$$ |
| 198 | + |
| 199 | +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: |
| 200 | + |
| 201 | +<CodePlayground |
| 202 | + code={`const tolerance = 0.00001; |
| 203 | +function fixed_point(f, first_guess) { |
| 204 | + function close_enough(x, y) { |
| 205 | + return abs(x - y) < tolerance; |
| 206 | + } |
| 207 | + function try_with(guess) { |
| 208 | + const next = f(guess); |
| 209 | + return close_enough(guess, next) |
| 210 | + ? next |
| 211 | + : try_with(next); |
| 212 | + } |
| 213 | + return try_with(first_guess); |
| 214 | +}`} |
| 215 | + hiddenCode={`function abs(x) { |
| 216 | + return x >= 0 ? x : -x; |
| 217 | +}`} |
| 218 | + height={450} |
| 219 | + showLineNumbers={false} |
| 220 | +/> |
| 221 | + |
| 222 | +Por exemplo, podemos usar este método para aproximar o ponto fixo do cosseno, começando com 1 como palpite inicial:[<sup>3</sup>](#footnote-3) |
| 223 | + |
| 224 | +<CodePlayground |
| 225 | + code={`fixed_point(math_cos, 1);`} |
| 226 | + hiddenCode={`const math_cos = Math.cos; |
| 227 | +function abs(x) { |
| 228 | + return x >= 0 ? x : -x; |
| 229 | +} |
| 230 | +const tolerance = 0.00001; |
| 231 | +function fixed_point(f, first_guess) { |
| 232 | + function close_enough(x, y) { |
| 233 | + return abs(x - y) < tolerance; |
| 234 | + } |
| 235 | + function try_with(guess) { |
| 236 | + const next = f(guess); |
| 237 | + return close_enough(guess, next) |
| 238 | + ? next |
| 239 | + : try_with(next); |
| 240 | + } |
| 241 | + return try_with(first_guess); |
| 242 | +}`} |
| 243 | + height={100} |
| 244 | + showLineNumbers={false} |
| 245 | +/> |
| 246 | + |
| 247 | +Da mesma forma, podemos encontrar uma solução da equação $y = \sin y + \cos y$: |
| 248 | + |
| 249 | +<CodePlayground |
| 250 | + code={`fixed_point(y => math_sin(y) + math_cos(y), 1);`} |
| 251 | + hiddenCode={`const math_sin = Math.sin; |
| 252 | +const math_cos = Math.cos; |
| 253 | +function abs(x) { |
| 254 | + return x >= 0 ? x : -x; |
| 255 | +} |
| 256 | +const tolerance = 0.00001; |
| 257 | +function fixed_point(f, first_guess) { |
| 258 | + function close_enough(x, y) { |
| 259 | + return abs(x - y) < tolerance; |
| 260 | + } |
| 261 | + function try_with(guess) { |
| 262 | + const next = f(guess); |
| 263 | + return close_enough(guess, next) |
| 264 | + ? next |
| 265 | + : try_with(next); |
| 266 | + } |
| 267 | + return try_with(first_guess); |
| 268 | +}`} |
| 269 | + height={100} |
| 270 | + showLineNumbers={false} |
| 271 | +/> |
| 272 | + |
| 273 | +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 $x$ requer encontrar um $y$ tal que $y^2 = x$. Colocando essa equação na forma equivalente $y = x/y$, reconhecemos que estamos procurando um ponto fixo da função $y \mapsto x/y$, e podemos, portanto, tentar calcular raízes quadradas como |
| 274 | + |
| 275 | +```javascript |
| 276 | +function sqrt(x) { |
| 277 | + return fixed_point(y => x / y, 1); |
| 278 | +} |
| 279 | +``` |
| 280 | + |
| 281 | +Infelizmente, esta busca de ponto fixo não converge. Considere um palpite inicial $y_1$. O próximo palpite é $y_2 = x/y_1$ e o próximo palpite é $y_3 = x/y_2 = x/(x/y_1) = y_1$. Isso resulta em um loop infinito no qual os dois palpites $y_1$ e $y_2$ se repetem indefinidamente, oscilando em torno da resposta. |
| 282 | + |
| 283 | +Uma maneira de controlar tais oscilações é evitar que os palpites mudem tanto. Como a resposta está sempre entre nosso palpite $y$ e $x/y$, podemos fazer um novo palpite que não está tão longe de $y$ quanto $x/y$ fazendo a média de $y$ com $x/y$, para que o próximo palpite após $y$ seja $(1/2)(y + x/y)$ em vez de $x/y$. O processo de fazer tal sequência de palpites é simplesmente o processo de procurar por um ponto fixo de $y \mapsto (1/2)(y + x/y)$: |
| 284 | + |
| 285 | +<CodePlayground |
| 286 | + code={`function sqrt(x) { |
| 287 | + return fixed_point(y => average(y, x / y), 1); |
| 288 | +}`} |
| 289 | + hiddenCode={`function average(x, y) { |
| 290 | + return (x + y) / 2; |
| 291 | +} |
| 292 | +function abs(x) { |
| 293 | + return x >= 0 ? x : -x; |
| 294 | +} |
| 295 | +const tolerance = 0.00001; |
| 296 | +function fixed_point(f, first_guess) { |
| 297 | + function close_enough(x, y) { |
| 298 | + return abs(x - y) < tolerance; |
| 299 | + } |
| 300 | + function try_with(guess) { |
| 301 | + const next = f(guess); |
| 302 | + return close_enough(guess, next) |
| 303 | + ? next |
| 304 | + : try_with(next); |
| 305 | + } |
| 306 | + return try_with(first_guess); |
| 307 | +}`} |
| 308 | + height={150} |
| 309 | + showLineNumbers={false} |
| 310 | +/> |
| 311 | + |
| 312 | +(Observe que $y = (1/2)(y + x/y)$ é uma transformação simples da equação $y = x/y$; para derivar isso, some $y$ a ambos os lados da equação e divida por 2.) |
| 313 | + |
| 314 | +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. |
| 315 | + |
| 316 | +## Exercício 1.35 |
| 317 | + |
| 318 | +<a name="ex-1-35"></a> |
| 319 | +Mostre que a razão áurea $\phi$ (seção 1.2.2) é um ponto fixo da transformação $x \mapsto 1 + 1/x$, e use isso para calcular $\phi$ por meio da função `fixed_point`. |
| 320 | + |
| 321 | +## Exercício 1.36 |
| 322 | + |
| 323 | +<a name="ex-1-36"></a> |
| 324 | +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 $x^x = 1000$ encontrando um ponto fixo de $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$.) |
| 325 | + |
| 326 | +## Exercício 1.37 |
| 327 | + |
| 328 | +<a name="ex-1-37"></a> |
| 329 | +**a.** Uma *fração contínua infinita* é uma expressão da forma |
| 330 | + |
| 331 | +$$ |
| 332 | +f = \frac{N_1}{D_1 + \frac{N_2}{D_2 + \frac{N_3}{D_3 + \cdots}}} |
| 333 | +$$ |
| 334 | + |
| 335 | +Como exemplo, pode-se mostrar que a expansão em fração contínua infinita com os $N_i$ e os $D_i$ todos iguais a 1 produz $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 $k$ termos* — tem a forma |
| 336 | + |
| 337 | +$$ |
| 338 | +\frac{N_1}{D_1 + \frac{N_2}{\ddots + \frac{N_K}{D_K}}} |
| 339 | +$$ |
| 340 | + |
| 341 | +Suponha que `n` e `d` sejam funções de um argumento (o índice do termo $i$) que retornam os $N_i$ e $D_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 $k$ termos. Verifique sua função aproximando $1/\phi$ usando |
| 342 | + |
| 343 | +```javascript |
| 344 | +cont_frac(i => 1, i => 1, k) |
| 345 | +``` |
| 346 | + |
| 347 | +para valores sucessivos de `k`. Quão grande você tem que fazer `k` para obter uma aproximação que é precisa em 4 casas decimais? |
| 348 | + |
| 349 | +**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. |
| 350 | + |
| 351 | +## Exercício 1.38 |
| 352 | + |
| 353 | +<a name="ex-1-38"></a> |
| 354 | +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 $e - 2$, onde $e$ é a base dos logaritmos naturais. Nesta fração, os $N_i$ são todos 1, e os $D_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 $e$, baseado na expansão de Euler. |
| 355 | + |
| 356 | +## Exercício 1.39 |
| 357 | + |
| 358 | +<a name="ex-1-39"></a> |
| 359 | +Uma fração contínua para a função tangente foi publicada em 1770 pelo matemático alemão J.H. Lambert: |
| 360 | + |
| 361 | +$$ |
| 362 | +\tan x = \frac{x}{1 - \frac{x^2}{3 - \frac{x^2}{5 - \cdots}}} |
| 363 | +$$ |
| 364 | + |
| 365 | +onde $x$ 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. |
| 366 | + |
| 367 | +--- |
| 368 | + |
| 369 | +## Notas de Rodapé |
| 370 | + |
| 371 | +<a name="footnote-1"></a> |
| 372 | +**[1](#footnote-link-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. |
| 373 | + |
| 374 | +<a name="footnote-2"></a> |
| 375 | +**[2](#footnote-link-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. |
| 376 | + |
| 377 | +<a name="footnote-3"></a> |
| 378 | +**[3](#footnote-link-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. |
0 commit comments