Vamos começar o capítulo vendo uma forma mais simples de se rodar testes. Nos testes que vimos até agora sempre havia o teste de uma condição booleana associado a uma mensagem de erro quando não funcionasse. Mas, observando que a mensagem de erro geralmente está ligada à condição, por vezes a condição pode ser auto-explicativa.
Logo, uma forma elegante de expressar as condições pode ser útil na escrita dos testes. Para isso, vamos usar o módulo de testes. Em linguagens modernas, várias das situações repetitivas que enfrentamos podem ser evitadas usando alguma técnica mais moderna.
usingTest@testset"Modelo de testes"begin@test2==1+1@testtrue@test !falseend
Test Summary: | Pass Total Time
Modelo de testes | 3 3 0.1s
No trecho acima primeiro indicamos que queremos fazer testes. Em seguida usamos o test que espera uma condição ou valor booleano. Finalmente todos os testes são reunidos em um testset.
Claro que o teste dá infomações relevantes quando falha:
usingTest@test2+2!=4
Test Failed at REPL[2]:1
Expression: 2 + 2 != 4
Evaluated: 4 != 4
Agora sim, vamos pensar em problemas algoritmicos novos. Que tal fazer a soma dos dígitos de um número inteiro. Ou seja, pensar em um número dígito à dígito. Vamos aos testes primeiro:
usingTest@testset"Teste da Soma de Dígitos"begin@testsomaDig(0) ==0@testsomaDig(1) ==1@testsomaDig(100) ==1@testsomaDig(123) ==6@testsomaDig(321) ==6@testsomaDig(99) ==18end
Vamos agora tentar pensar em como “descascar” um número, dado o número 123, uma forma seria pegar o resto por 10 (ou seja 3) e depois dividir por 10 (ou seja 12), e assim por diante. Ou seja.
functionsomaDig(n)if n <=0return0elsereturn n %10+somaDig(n ÷10)endendprintln(somaDig(1234))
10
Vamos agora a um outro problema clássico, a verificação se um número é ou não é primo. Na prática para fazer isso, temos a definição, um número \(n\) é primo apenas se for divisível apenas por 1 e por ele mesmo. Ou seja, nenhum número entre 2 e \(n - 1\) pode ser divisor de um número primo.
A forma de se fazer isso é relativamente simples. Vamos pensar em uma função que tenta dividir um número recursivamente, se conseguir devolve falso, se não conseguir devolve verdadeiro.
Vamos aos código:
functiondivide(n, i)if n % i ==0returnfalseelseif i == n -1returntrueelsereturndivide(n, i +1)endend
divide (generic function with 1 method)
Que pode ser chamada por:
functionéPrimo(n)returndivide(n, 2)end
éPrimo (generic function with 1 method)
Mais um exemplo, o método de Newton para o cálculo de raiz quadrada. Para achar a raiz de \(x\), a partir de um chute inicial (por exemplos \(y= x /2\)), chegamos a um novo chute que é a média de \(y\) e \(x/y\).
Mas, sim, vamos começar com os testes. Como estamos usando números do tipo double é bom sempre ter uma tolerância, por isso vamos usar uma comparação aproximada. Também poderiamos ter usado a função isapprox da linguagem Julia.
usingTestfunctionquaseIgual(a, b)ifabs(a - b) <=1e-10returntrueelsereturnfalseendend@testset"Teste da raiz pelo método de Newton"begin@testquaseIgual(3.0, raiz(3.0*3.0))@testquaseIgual(33.7, raiz(33.7*33.7))@testquaseIgual(223.7, raiz(223.7*223.7))@testquaseIgual(0.7, raiz(0.7*0.7))@testquaseIgual(1.0, raiz(1.0*1.0))end
Note que como estamos comparando números em ponto flutuante, não usamos a comparação exata.
A solução final é:
functionnewton(c, n) q = n / cifquaseIgual(q, c)return qelsereturnnewton( (c + q) /2.0, n)endendfunctionraiz(n) a =newton(n /2.0, n)println("a raiz de ", n, " é ", a)return aend
raiz (generic function with 1 method)
7.1 Funções caóticas
Vamos brincar um pouco agora com funções caóticas :), isso é, funções, que conforme o comportamento de uma constante \(k\), apresentam resultados que podem convergir ou não. Isso é, a cada passo, quero saber o valor do próximo ponto aplicando a função novamente, isso é: \[x_1 = f(x_0), x_2 = f(x_1), \ldots, x_n = f(x_{n - 1})\]
As funções caóticas desempenham um papel significativo em diversas áreas da matemática e da física, com aplicações que vão desde a modelagem de crescimento populacional até a previsão de padrões climáticos. Elas também são fundamentais na análise de circuitos elétricos não lineares, onde pequenas variações nas condições iniciais podem levar a resultados drasticamente diferentes.
Para o nosso teste, a função \(f\) é extremamente simples: \(x_{i + 1}=x_i * (1 - x_i) * k\).
Implemente a função e imprima os 30 primeiros resultados. Comece com um valor de \(x\) entre 0 e 1, como 0.2. Use constantes \(k = 2.1, 2.5, 2.8\) e \(3.1\) o que ocorre com \(k = 3.7\)?
Entregue o código e um pequeno relatório sobre o que acontece.