Executando verificação de segurança...
9

Matemática é fundamental para programar bem, mas muitos não percebem porque geralmente usa-se ela de maneira indireta e/ou sem perceber.

Computação tem uma ligação profunda com matemática, tanto que muitos cursos de Ciência da Computação nasceram em institutos de Matemática (só pra citar dois exemplos, foi assim na USP e na Unicamp). É claro que a Ciência da Computação acabou se tornando uma área de conhecimento própria, mas ainda flerta muito com a Matemática.

Muitos dos conceitos que a gente usa no dia a dia vieram diretamente da matemática. Funções, por exemplo, seguem basicamente a mesma ideia: recebe dados, faz algo com eles (seguindo determinadas regras) e retorna um resultado. SQL veio diretamente da Álgebra Relacional. Programação funcional é basicamente uma implementação do Lambda Calculus ("Daí que vem o lambda que muitas linguagens tem?" - De certa forma, sim) - e por aí vai, muita coisa que a gente usa nada mais é que um conceito matemático adaptado para ser usado em programação.

A própria lógica que usamos para criar algoritmos, também é um ramo da matemática. Álgebra booleana também. Quantas vezes vc já viu código assim:

int x = fazAlgo();
if (x >= 10) {
    // faz alguma coisa
} else if (x < 10) {
    // faz outra coisa
}

Ora, ele só entra no if se x for maior ou igual a 10. Se não for, a única opção que sobra é ele ser menor que 10, então pra que testar isso de novo? Bastava um else { faz outra coisa }. Esse código tem uma instrução inútil, completamente desnecessária. Agora imagine um sistema gigante com centenas de coisas parecidas espalhadas... Um mínimo de conhecimento em lógica já eliminaria tudo isso.


Várias técnicas matemáticas são usadas para provar que um algoritmo está correto, ou para determinar a sua complexidade. Muita coisa pronta que usamos no dia-a-dia são adaptações de coisas que já haviam sido resolvidas matematicamente.

Você pode não usar isso diretamente pra fazer o seu "sisteminha CRUD" ou sua API web, mas indiretamente garanto que está. Algum algoritmo usado em alguma função interna daquela dependência da dependência do seu projeto com certeza usa alguma coisa que veio diretamente da matemática, ou foi criada por teóricos da computação (e muitos deles também eram matemáticos). Um simples sort tem uma história enorme até chegar onde está (interessante notar que os primeiros algoritmos foram criados antes do computador). Ou seja, sem matemática, ficaríamos penando até para mostrar os itens do nosso site em ordem (seja alfabética, de preço, data, o que for).


Falando de forma mais ampla, o pensamento matemático amplia a sua capacidade de criar abstrações, que é algo importantíssimo, pois no fundo é isso que fazemos o tempo todo.

A matemática, se fosse pra resumir grosseiramente, vive de abstrações: cria-se um conjunto de regras sobre determinado conjunto de "coisas" (por exemplo, operações que podem ser feitas em um conjunto de números: somar, subtrair, etc), e a partir daí pode-se compor outras coisas (uso as operações para resolver problemas reais, como contabilizar quantidades/valores e produzir relatórios, etc).

Em programação é similar: criamos estruturas de dados/classes (abstrações que representam alguma entidade do mundo real) e regras sobre as mesmas (ex: cada compra tem N produtos, só pode adicionar produto se tiver no estoque, etc), e a partir daí materializamos isso no código. Sem as abstrações corretas, o código tende a ficar confuso.

E estudar matemática faz você melhorar a capacidade de criar abstrações. O problema é que esse ganho é indireto, por isso é tão difícil de perceber, e por isso que tanta gente acha que não precisa saber nada de matemática. Vc pode até conseguir, mas vai penar muito mais...

Outro problema, que vai mais além da questão, é o nosso sistema de ensino, que basicamente faz com que a esmagadora maioria das pessoas odeie matemática, ache que é inútil, sem nenhuma função social (sim, já ouvi essa barbaridade), etc. Some-se a isso os cursos e youtubers picaretas (que gostam de dizer que não precisa saber nada de matemática pra programar), e temos uma legião de programadores que realmente acham que matemática não vai ajudar em nada.

Felizmente temos posts como esse, que ressaltam a importância da matemática na nossa área.


Por fim, uma "pequena" observação (é só um detalhe, que meu pedantismo não deixa passar).

Embora o fatorial seja um exemplo "clássico" para ensinar recursão, a implementação recursiva é a pior solução. Ela só vale a pena em linguagens que otimizam a recursão em cauda, caso contrário, vc fica lotando a pilha à toa, correndo o risco de estourá-la - o que não ocorre com um loop simples (geralmente a solução mais adequada).

Esse é um ponto em que matemática e computação divergem. Na matemática, uma definição recursiva facilita em muitos casos, mas em computação, uma função recursiva nem sempre é a melhor opção (em termos de desempenho, de empilhamento de dados, memória, etc). Muitas vezes um loop é a melhor opção.

Só um detalhe, pela fórmula que você colocou:

seno

Não deveria ser assim?

function seno(x) {
    function fat(n) {
        if (n === 1)
            return 1;
        else {
            return n * fat(n - 1);
        }
    }
    return x - ((x ** 3) / fat(3)) + ((x ** 5) / fat(5)) - ((x ** 7) / fat(7));
}

Enfim, uma versão não recursiva seria:

// posso escolher até qual termo vou (coloquei 7 para ficar igual à versão recursiva)
function senoSemRecursao(x, limite = 7) {
    var result = x, sign = -1, denominador = 1;
    for (var factor = 3; factor <= limite; factor += 2) {
        denominador *= factor * (factor - 1);
        result += sign * (x ** factor) / denominador;
        sign *= -1;
    }
    return result;
}

Repare também que com um loop fica mais simples estabelecer até qual termo eu vou - na versão recursiva vai até o 7, mas e se eu quisesse ir até o 999? Basta passar como parâmetro: senoSemRecursao(angulo, 999) (claro que também dá para mudar a versão recursiva para fazer um loop, mas ainda sim não elimina o principal problema, que são as chamadas recursivas).

Outra otimização é que você não precisa calcular o fatorial toda hora. Pois o 5! é o mesmo que 5 * 4 * 3! (e olha só, no passo anterior eu já calculei 3!, não preciso calcular tudo de novo desde o 1). O mesmo vale para 7!: eu já calculei 5! no passo anterior, então basta pegar este resultado e multiplicar por 6 e 7. É isso que a versão não-recursiva está fazendo.

Em um teste simples, a versão recursiva foi cerca de 40% mais lenta. Claro que, se for rodar poucas vezes, a diferença será imperceptível, mas enfim...

Sei que o foco não é o algorimo em si, e sim a importância da matemática na nossa área, mas é que eu sou chato mesmo :-)

3

A forma de resolver é fruto da minha cadeira de Algorítimos na faculdade, onde utilizamos Fortran para fazer essa solução, aí sim a recursão pode ser muito melhor do que em linguagens como Javascript e afins. Na maior parte das vezes utilizar iteração vai ser mais rápido mesmo, mas eu acho mais elegante utilizar recursão, puro gosto mesmo.

Normalmente essas otimizações que você falou são feitas a nível de compilação, o próprio compilador faz isso para nós, pois um código muito otimizado tende a ser menos legível, especialmente quando estamos falando de matemática computacional. Lembrando que dificilmente bibliotecas com ferramentais matemáticos vão ser escritas em linguagens como Python ou Javascript, o que ocorre normalmente é escrever essas funções em C, C++ e outras linguagens de mais baixo nível e então disponibilizar para uma ser utilizada por uma linguagem de alto nível. Isso ocorre por vários fatores, principalmente o fator de que rodar um binário diretamente é muito mais rápido do que rodar um interpretador que compila em tempo real por exemplo, além de que o processo de compilação consegue otimizar muito o código.

Um exemplo para isso é a biblioteca Numpy do Python que é feita grande parte em C e é isso que faz com que seja rápida e otimizada. Por exemplo, construir uma Array com 1 milhão de elementos aleatórios e somar todos eles depois é imensamente mais rápido utilizando as funcionalidades do numpy do que com as próprias funcionalidades nativas do Python.

Mesmo iterando ainda assim não teríamos um resultado muito otimizado, saltos dentro da memória causam sempre um certo gargalo na aplicação. Isso serve tanto para recursão quanto para iteração, a cada chamada de função estamos fazendo mais e mais saltos dentro da memória o que causa lentidão. Em linguagens compiladas isso não é um problema, pois o próprio compilador se encarrega por otimizar, por exemplo substituindo a chamada da função pela própria função em cada local onde é chamada, removendo código morto, simplificando constantes, desdobrando laços de repetição e etc.

A minha solução na verdade não está completa, falta ajustar a precisão, de forma que consigamos garantir e definir quanto de erro teremos em relação ao valor real do seno. Eu poderia ter colocado apenas os valores exatos de cada fatorial, mas isso seria um problema para próximas implementações. A sua implementação é boa também, são várias formas de resolver o mesmo problema e você achou uma implementação até mais otimizada, porém um pouco menos legível. E programação é isso, saber negociar entre legibilidade e legibilidade. Parabéns pela implementação!