O estouro de variável é um possível problema que pode acontecer quando calculamos números de Fibonacci muito grandes. Isto é, o número pode ser grande demais para ser representado utilizando um tipo de dado primitivo.
É razoável pensarmos nisso, uma vez que os números de Fibonacci crescem exponencialmente [1].
No caso específico da linguagem de programação Java, podemos driblar esse problema utilizando a classe BigInteger, que permite representar inteiros com precisão arbitrária [3]. Utilizarei essa classe neste artigo.
Algoritmo para calcular números de Fibonacci
Dentre as dezenas de algoritmos para calcular números de Fibonacci, utilizarei o algoritmo logarítmico apresentado em Calculando números de Fibonacci com potenciação de matrizes, pois é o mais eficiente que eu conheço até a data de publicação desta postagem.
Basicamente, vou trocar o tipo de dado primitivo long por BigInteger e substituirei as operações básicas por métodos da classe BigInteger.
Enfim, não tem segredo. O código é dado a seguir
//GitHub: HenriqueIni
import java.math.BigInteger;
//Calcula números de Fibonacci com precisão arbitrária
//O ideal é utilizar esse algoritmo para números muito grandes
public class FibonacciBigInteger {
//Calcula o n-ésimo número de Fibonacci [O(f(m) log n)]
public static BigInteger fibonacci(int n){
if(n < 0){
throw new IllegalArgumentException("n < 0");
}
if(n == 0){
return BigInteger.ZERO;
}
if(n == 1){
return BigInteger.ONE;
}
//Matriz de base
BigInteger[][] baseMatrix = new BigInteger[][]{
{BigInteger.ZERO,BigInteger.ONE},
{BigInteger.ONE,BigInteger.ONE}};
BigInteger[][] result = powMatrix2x2(baseMatrix, n);
return result[0][1];
}
//Calcula A^n, onde A é uma matrix 2x2 [O(f(m) log n)]
private static BigInteger[][] powMatrix2x2(BigInteger[][] A, int n){
//Potenciação por quadrados
BigInteger[][] p = new BigInteger[][]{
{BigInteger.ONE,BigInteger.ZERO},
{BigInteger.ZERO,BigInteger.ONE}};
while(n > 0){
if((n % 2) != 0)
p = multMatrix2x2(p, A);
A = multMatrix2x2(A, A);
n = n / 2;
}
return p;
}
//Calcula A * B, onde A e B são matrizes 2x2 [O(f(m))]
private static BigInteger[][] multMatrix2x2(BigInteger[][] A, BigInteger[][] B){
BigInteger[][] result = new BigInteger[2][2];
result[0][0] = A[0][0].multiply(B[0][0]).add(A[0][1].multiply(B[1][0]));
result[0][1] = A[0][0].multiply(B[0][1]).add(A[0][1].multiply(B[1][1]));
result[1][0] = A[1][0].multiply(B[0][0]).add(A[1][1].multiply(B[1][0]));
result[1][1] = A[1][0].multiply(B[0][1]).add(A[1][1].multiply(B[1][1]));
return result;
}
//Testes
public static void main(String[]args){
System.out.println("Fibonacci O(f(m) log n)");
for(int i = 0; i < 200; i++){
System.out.printf("F%d = %s\n", i, fibonacci(i).toString());
}
//Fibonacci(200.000)
System.out.printf("F%d = %s\n", 200000, fibonacci(200000).toString());
}
}
Complexidade assintótica
Se considerarmos que as operações básicas (multiplicações e adições) de BigInteger têm complexidade no tempo constante, então a complexidade do algoritmo anterior será $O(\log n)$.
Mas, infelizmente, não podemos fazer isso... Isto é, o custo das operações básicas deve ser considerado, pois o tamanho dos números é arbitrário: quanto maior o número, mais custosa será a operação.
Nós não precisávamos nos preocupar com isso na versão do código utilizando o tipo long porque os números ocupavam 64 bits de memória fixos, logo a complexidade das operações tinha um limitante superior constante.
O correto é dizer que a complexidade é $O(f(m)\times\log n)$, onde $f(m)$ é uma função que representa o custo das operações de adição e multiplicação para números de tamanho $m$.
O que é esse $m$? Pode ser o tamanho do número em bits, em quantidade de dígitos... Não dá para ser mais específico porque precisaríamos de dados mais precisos sobre o algoritmo que a classe BigInteger utiliza nos cálculos.
Outras linguagens de programação
Se você não utiliza a linguagem de programação Java, então você provavelmente vai precisar de alguma estrutura ou classe para substituir a classe BigInteger no algoritmo.
No caso da linguagem C, você pode utilizar a biblioteca GMP.
Se você utiliza a linguagem Python, então você não precisa se preocupar com o problema de estouro de variável ao calcular números de Fibonacci, pois o Python possui suporte nativo para inteiros de precisão arbitrária. O limite é a memória disponível [2].
Bônus: décimo milésimo número de Fibonacci
A seguir, apresento o décimo milésimo número de Fibonacci, que foi calculado utilizando o programa anterior. Esse número possui 2090 dígitos.
Leia também
Referências
- [1] CORMEN, T. H. et al. Algoritmos: teoria e prática. 3 ed. Rio de Janeiro: Elsevier, 2012.
- [2] PEP 237 -- Unifying Long Integers and Integers. Acesso em 15 de abril de 2018.
- [3] Java DOC: Class BigInteger. Acesso em 15 de abril de 2018.

Nenhum comentário:
Postar um comentário