Sophie Germain e seus primos: uma aventura matemática!
Vamos falar sobre os números primos de Sophie Germain. Você nunca ouviu falar deles? Então venha comigo nessa aventura divertida e cheia de curiosidades!
Quem foi Sophie Germain?

Primeiro, vamos conhecer a heroína da história: Sophie Germain foi uma matemática francesa brilhante que viveu entre os séculos XVIII e XIX. Em uma época onde mulheres enfrentavam enormes barreiras para estudar matemática, ela brilhou intensamente. Entre suas muitas contribuições, uma das mais curiosas foi a descoberta de números primos muito especiais, que hoje levam seu nome: os primos de Sophie Germain!
Mas afinal, o que são esses primos?
Um número primo é chamado de “primo de Sophie Germain” se ele atende a uma condição simples, porém muito interessante: quando multiplicado por 2 e somado 1, resulta também em outro número primo.
Formalmente:
Um primo
pé de Sophie Germain se2p + 1também é primo.
Parece fácil? Vejamos:
2é primo. Calculando2×2 + 1 = 5, que também é primo. Logo, 2 é um primo de Sophie Germain!3é primo. Calculando2×3 + 1 = 7, primo também. E assim 3 também é de Sophie Germain.5é primo. Calculando2×5 + 1 = 11, que é primo. Voilà! Temos mais um!
Simples, não é mesmo? Mas calma, não é tão simples assim para números maiores… é aqui que começa a aventura!
Por que eles são interessantes?
Esses números são importantes na matemática pura, especialmente em criptografia moderna. Números primos especiais são frequentemente utilizados em algoritmos criptográficos, já que a dificuldade em identificá-los garante mais segurança às transações digitais.
Vamos encontrar primos de Sophie Germain?
Vamos à prática? Nada melhor que um pouco de código para entender melhor!
Código em Python
def eh_primo(n):
if n < 2:
return False
for i in range(2, int(n**0.5)+1):
if n % i == 0:
return False
return True
# Encontrando primos de Sophie Germain até 100
print("Primos de Sophie Germain até 100:")
for num in range(2, 101):
if eh_primo(num) and eh_primo(2*num + 1):
print(num, end=" ")
Código em C
#include <stdio.h>
int eh_primo(int n) {
if (n < 2) return 0;
for (int i = 2; i * i <= n; i++) {
if (n % i == 0) return 0;
}
return 1;
}
int main() {
printf("Primos de Sophie Germain até 100:\n");
for (int num = 2; num <= 100; num++) {
if (eh_primo(num) && eh_primo(2 * num + 1)) {
printf("%d ", num);
}
}
return 0;
}
Aplicações práticas
- Criptografia: Os primos de Sophie Germain são usados em algoritmos de segurança criptográfica, especialmente na geração de chaves seguras.
- Teoria dos Números: Eles ajudam na exploração de propriedades fundamentais dos números primos, auxiliando em provas matemáticas complexas, como o Último Teorema de Fermat.
- Computação: Sua busca automatizada é um excelente exercício de otimização computacional.
Os números primos de Sophie Germain são muito mais do que uma curiosidade matemática: são um lembrete de que ideias simples podem esconder profundezas incríveis. Desde criptografia até os limites da computação moderna, eles seguem sendo ferramentas poderosas e símbolos de beleza intelectual.
Explorá-los é também homenagear uma mulher que, desafiando seu tempo, provou que a mente humana — independentemente do gênero, do século ou da resistência social — é capaz de iluminar os mistérios mais elegantes do universo. Que cada número primo que encontrarmos (ou programarmos!) seja também um tributo à coragem, à inteligência e à ousadia.
A Tragédia de Hipátia de Alexandria: um legado de sabedoria e intolerância
Você, que é cristão como eu e se escandaliza com os terrores fundamentalistas do Estado Islâmico ou do Talibã (principalmente em relação às mulheres), convido você a conhecer a história de Hipátia de Alexandria. Ela foi uma filósofa, matemática e astrônoma que viveu de 370 a 415 d.C., sendo provavelmente a primeira mulher matemática registrada na história.

Hipátia tornou-se chefe da Escola Platônica em Alexandria, um cargo que nunca antes nem depois foi ocupado por uma mulher. Ela lecionava astronomia, matemática e filosofia, e estudou também oratória, retórica, religião, poesia e artes. Entre seus alunos, destacou-se o notável filósofo e bispo Sinésio de Cirene, evidenciando sua influência intelectual na época.
Como cientista, Hipátia desenvolveu instrumentos usados na física e na astronomia, incluindo o hidrômetro, invenção atribuída a ela. Contribuiu com estudos e artigos sobre a Álgebra de Diofanto, escreveu tratados sobre Ptolomeu e, em parceria com seu pai, produziu um artigo sobre Euclides. Obcecada pela demonstração lógica e reconhecida como uma grande solucionadora de problemas, costumava dizer que não se casara porque já era “casada com a verdade”. Era, sem dúvida, uma mulher muito à frente de seu tempo.
Entretanto, em uma tarde de 8 de março de 415, ao regressar do Museu, Hipátia foi atacada em plena rua por uma turba de cristãos enfurecidos. Ela foi arrastada pelas ruas da cidade até uma igreja, onde foi cruelmente torturada até a morte. Após ser morta de forma brutal, seu corpo foi esquartejado e lançado em uma fogueira. Um fim trágico para uma mulher incrível, cujo legado intelectual deveria ter sido celebrado, mas foi silenciado pela intolerância.
A história de Hipátia serve como um lembrete sombrio dos perigos do fanatismo e da intolerância religiosa. Sua vida e morte nos convidam a refletir sobre a importância da liberdade de pensamento e do respeito às diferenças, valores essenciais para o progresso da humanidade.
E sim, eu sei que a história não é “beeem” essa, mas como se diz naquele filme, “quando a lenda é maior que o fato, publique-se a lenda”…
quarta-feira, 9 outubro, 2024 at 1:42 pm Deixe um comentário
A magia dos números irracionais: quando a matemática encanta 🤯
Você já parou para pensar no quão incríveis são os números irracionais? 😲 Sério, tem algo na matemática que eu nunca deixo de admirar, NUNCA mesmo! São esses números que parecem ter vida própria, como a raiz quadrada de 2 (1,4142… 🔢), a raiz quadrada de 3 (1,73205… 🔢), o famoso pi, π (3,141592… 🍰), o número e (do nosso amigo Euler, 2,718… 📈), entre outros infinitos companheiros.
Toda vez que penso que o que está depois da vírgula em todos esses números (que são infinitos, viu? ♾️ Um infinito MAIOR que o infinito dos números inteiros! 😱) NÃO TEM FIM E JAMAIS SE REPETE, eu fico simplesmente extasiado! 🤩 É como se a matemática estivesse nos contando um segredo eterno, uma poesia sem fim. 📜✨
Pensem de novo: ELES NÃO TÊM FIM! E NUNCA, JAMAIS, REPETEM QUALQUER PARTE DELES! 🤯 Isso não é de explodir a cabeça? É algo extraordinário e inacreditável… 💥
E aí, me pego pensando: como é que seres tão ignorantes, brutos e belicistas como nós, os Homo Sapiens 🧍♂️🧍♀️, conseguimos criar (ou será descobrir?) algo tão belo e impactante como isso? Me emociona de verdade! 😢❤️
A matemática tem esse poder de nos lembrar da infinita complexidade e beleza do universo. 🌌 Ela nos mostra que, mesmo em nossa pequenez e imperfeição, somos capazes de alcançar ideias que tocam o infinito. Não é incrível? 🚀
Então, da próxima vez que você ouvir falar do π, do número e, ou de qualquer outro número irracional, lembre-se: há um universo inteiro escondido depois da vírgula, um universo que NUNCA termina e NUNCA se repete. E isso, amigues, é simplesmente fascinante! ✨
A matemática é uma caixinha de surpresas infinitas, não é? 😉
quarta-feira, 9 outubro, 2024 at 1:12 pm Deixe um comentário
Algoritmo da “divisão e média” para obter a raiz quadrada 🎯
O algoritmo de divisão e média é uma técnica clássica, usada desde a antiguidade, para calcular a raiz quadrada de números positivos. Sua simplicidade e eficácia permanecem relevantes até hoje. É baseado em uma abordagem iterativa que ajusta continuamente um valor aproximado, convergindo rapidamente para o valor correto.
História 📜
O método é uma variante do método babilônico (ou método de Herão) que, por sua vez, é uma aplicação do princípio da média geométrica. Ele tem sido usado por matemáticos ao longo dos séculos por sua simplicidade e eficiência.
O algoritmo de divisão e média para calcular a raiz quadrada funciona iterativamente, partindo de uma estimativa inicial e ajustando-a a cada passo. A ideia é simples: dado um número ( n ), escolhe-se um palpite inicial para a raiz quadrada. Em cada iteração, o algoritmo calcula a média entre esse palpite e o quociente entre ( n ) e o palpite atual.
O pseudocódigo do algoritmo da divisão e média para obter raízes quadradas é o seguinte:
Início:
Se numero <= 0
Retorna 0 // Raiz quadrada de número negativo ou zero é zero
Fim se
Definir precisao = 0.000001 // Tolerância para o erro
Definir estimativa = numero / 2 // Primeira estimativa da raiz quadrada
Faça:
novo_valor = (estimativa + numero / estimativa) / 2 // Calcula nova estimativa
erro = |novo_valor - estimativa| / novo_valor // Calcula erro entre estimativas
estimativa = novo_valor // Atualiza a estimativa
Enquanto erro > precisao // Continua até atingir a precisão desejada
Retorna estimativa // Raiz quadrada encontrada
Fim

Por que ele funciona? 🤔
Ele funciona porque, a cada iteração, a média aproxima o valor da raiz quadrada, corrigindo o palpite inicial com uma aproximação melhor baseada no quociente ( n/palpite ).
A gente começa dividindo o número por 2 por simplicidade e eficácia. Esse valor inicial geralmente aproxima razoavelmente bem a raiz quadrada para a maioria dos números positivos, especialmente em casos onde o número não é muito grande ou muito pequeno. Dividir o número por 2 dá um ótimo ponto de partida para este algoritmo! 🎯 porque isso ajuda a garantir que ele vá direto ao ponto, ajustando a estimativa rapidamente. A partir daí o algoritmo ajusta rapidamente a estimativa com cada iteração. Esse método é simples, mas incrivelmente eficaz, garantindo que o algoritmo rapidamente se aproxime da resposta correta🚀.
Base do algoritmo
Esse método é baseado na aproximação contínua da raiz quadrada, refinando o valor até que a diferença entre o palpite ao quadrado e ( n ) seja suficientemente pequena. Ele é um caso especial do método de Newton-Raphson para raízes quadradas, que garante a convergência para o valor correto.
Implementações em C e Python 💻
A simplicidade do algoritmo permite sua fácil implementação em diversas linguagens de programação. Vamos ver exemplos em C e Python:
Implementação em C:
#include <stdio.h>
double raizQuadrada(double numero) {
double estimativa = numero / 2.0;
double precisao = 0.000001; // precisão desejada
while ((estimativa * estimativa - numero) > precisao || (numero - estimativa * estimativa) > precisao) {
estimativa = (estimativa + numero / estimativa) / 2.0;
}
return estimativa;
}
int main() {
double numero = 25.0;
printf("Raiz quadrada de %.2f: %.5f\n", numero, raizQuadrada(numero));
return 0;
}
Implementação em Python:
def raiz_quadrada(numero):
estimativa = numero / 2.0
precisao = 0.000001 # precisão desejada
while abs(estimativa * estimativa - numero) > precisao:
estimativa = (estimativa + numero / estimativa) / 2.0
return estimativa
numero = 25.0
print(f"Raiz quadrada de {numero}: {raiz_quadrada(numero):.5f}")

Na imagem acima que representa o funcionamento do algoritmo, a correspondência com as variáveis do nosso programas são as seguintes:
- a → numero (nos programas): O valor cuja raiz quadrada será calculada.
- x → estimativa (nos programas): A estimativa atual da raiz quadrada.
- y → O novo valor calculado em cada iteração, correspondente à média usada no cálculo da nova estimativa.
- e → A diferença entre a estimativa atual e a nova (controle de erro ou precisão).
- tol → precisao (nos programas): O limite de tolerância para parar o algoritmo.
Exemplo de implementação 🔢
🚀 Vamos acompanhar o algoritmo em ação e ver como ele resolve o problema da raiz quadrada de 25, passo a passo! Está pronto para a matemática voar?
A primeira estimativa sendo .
Passos do Algoritmo:
- A estimativa inicial é
.
- Em cada iteração, calculamos o novo valor usando a fórmula
, e o erro
.
Iteração 1:
- Estimativa inicial:
- Novo valor:
- Erro:
Iteração 2:
- Estimativa anterior:
- Novo valor:
- Erro:
Iteração 3:
- Estimativa anterior:
- Novo valor:
- Erro:
Iteração 4:
- Estimativa anterior:
- Novo valor:
- Erro:
Iteração 5:
- Estimativa anterior:
- Novo valor:
(já suficientemente próximo de 5)
- Erro:
é praticamente
, atingindo o objetivo (ajustado pela precisão) .

Podemos verificar que o algoritmo converge para a raiz quadrada de 25, que é 5, após 4 ou 5 iterações, dependendo da precisão exigida.
Métodos de Obtenção de Raízes em Geral
Além do método da divisão e média, há outros bons métodos de obtenção de raízes quadradas, como o método de Newton-Raphson. Este último também é iterativo, mas sua convergência é mais rápida. O algoritmo da divisão e média é uma forma simplificada do método de Newton, adaptada especificamente para raízes quadradas. Embora o método de divisão e média funcione bem para raízes quadradas, ele não é facilmente generalizável para outras raízes (como raízes cúbicas ou quartas) sem modificações significativas.
Velocidade de Convergência 🚀
Comparado a outros métodos, a divisão e média é um algoritmo de convergência rápida, especialmente quando comparado a técnicas mais rudimentares. No entanto, métodos como o Newton-Raphson são superiores em termos de rapidez, exigindo menos iterações para alcançar o mesmo nível de precisão.
Limitações ⚠️
O algoritmo de divisão e média para calcular a raiz quadrada, apesar de eficiente, tem algumas limitações claras:
- Números negativos ou zero: Não pode calcular a raiz quadrada de números negativos (a não ser que seja adaptado para números complexos) e retorna 0 para entrada igual a 0.
- Convergência lenta para números muito grandes ou muito pequenos: A estimativa inicial afeta o número de iterações necessárias.
- Limitação de precisão: Para números com muitas casas decimais, a precisão do cálculo pode ser afetada, exigindo mais iterações para atingir o erro desejado.
Embora eficaz para muitos casos, esses fatores podem torná-lo menos ideal em situações extremas ou com precisão altamente requisitada.
O algoritmo de divisão e média é uma verdadeira joia 💎 no mundo dos cálculos! Ele combina elegância, simplicidade e eficiência, sendo ideal para calcular raízes quadradas. Fácil de entender, fácil de implementar e poderoso para resolver o problema com precisão. Se você busca simplicidade e rapidez, este é o seu algoritmo!
Sua simplicidade, porém, tem limitações, especialmente ao compará-lo com métodos mais robustos como o de Newton-Raphson, que é preferido para problemas de maior complexidade ou precisão além dele ser ligeiramente mais lento em termos de convergência.
O algoritmo de Newton, por exemplo, requer menos iterações para atingir a mesma precisão, o que o torna mais rápido em cenários que exigem alta performance ou em situações com números muito grandes. Ainda assim, para muitos casos práticos, a diferença de desempenho pode não ser significativa.
Melhorias 🛠️
O algoritmo de divisão e média pode ser melhorado de várias maneiras:
1. Melhorando a estimativa inicial:
A estimativa inicial afeta diretamente a convergência. Em vez de sempre usar (numero / 2), uma estimativa inicial mais próxima da raiz quadrada, calculada com base em heurísticas, pode acelerar a convergência.
2. Uso do Método de Newton-Raphson:
O método de divisão e média é, na verdade, uma versão simplificada do método de Newton-Raphson. Implementar o método de Newton diretamente pode acelerar a convergência, principalmente para números muito grandes ou pequenos.
3. Detecção de Convergência Mais Rápida:
Ajustar os critérios de erro e tolerância (que são hiperparâmetros) pode melhorar em clauns casos a detecção de convergência, evitando cálculos desnecessários em iterações posteriores.
4. Ajustes para Números Pequenos ou Grandes:
Implementar ajustes que tratem de forma especial números extremamente pequenos ou grandes pode prevenir problemas de precisão causados por arredondamentos.
O algoritmo de divisão e média é como aquela ferramenta clássica que nunca sai de moda: simples, eficiente e sempre útil. 💻 Quer um jeito fácil de calcular a raiz quadrada? Este é o caminho! Claro, se você quer mais velocidade, o Newton-Raphson pode ser um upgrade ⚙️, mas esse método tem sua elegância e charme, não acha? Se você gosta de eficiência com simplicidade, o algoritmo de divisão e média é para você! Agora é sua vez, bora implementar! 🚀
quinta-feira, 3 outubro, 2024 at 12:00 pm Deixe um comentário
Representação de Números no computador
A forma como os computadores representam números é fundamental para o entendimento de como programas funcionam e como operações aritméticas são realizadas no nível mais básico. Aqui, vamos explorar a representação de números inteiros e reais nos computadores, destacando conceitos essenciais, possíveis armadilhas e exemplos práticos utilizando a linguagem C. Vou dividir este post (longo) em duas partes, representação de inteiros e de reais.

Representação de Números Inteiros
Os números inteiros são representados nos computadores usando o sistema de numeração binário (sim, o computador, ou os dispositivos computacionais digitais só lidam diretamente com valores discretos como 0 e 1). Cada número é armazenado em uma sequência de bits (binary digits), que são valores binários que podem ser 0 ou 1.
Sistema de Numeração Binário
No sistema binário, os números são representados usando base 2. Cada dígito na posição n representa ( ). Por exemplo, o número decimal 13 é representado em binário como 1101:
Tamanhos de Inteiros
Em C, os tipos inteiros possuem tamanhos que dependem da arquitetura e do compilador, mas seguem algumas especificações do padrão da linguagem. Os tipos comuns são:
char: normalmente 1 byte (8 bits)short: pelo menos 16 bits (4 bytes)int: pelo menos 16 bits (geralmente 32 bits em sistemas modernos)long: pelo menos 32 bitslong long: pelo menos 64 bits
Exemplo em C:
#include <stdio.h>
#include <limits.h>
int main() {
printf("Tamanho de int: %zu bytes\n", sizeof(int));
printf("Valor máximo de int: %d\n", INT_MAX);
printf("Valor mínimo de int: %d\n", INT_MIN);
return 0;
}
Este programa imprime o tamanho em bytes do tipo int, bem como seus valores máximo e mínimo.
Representação de Inteiros com Sinal
Para representar números negativos, utiliza-se a codificação em complemento de dois. Nesse sistema, o bit mais significativo (MSB) indica o sinal do número: 0 para positivo e 1 para negativo.
- Complemento de Dois: Para encontrar o complemento de dois de um número, inverte-se todos os bits e adiciona-se 1 ao resultado. Exemplo: Para representar -5 em um sistema de 8 bits:
- Representação binária de 5:
00000101 - Inverte os bits:
11111010 - Adiciona 1:
11111011(esta é a representação de -5 em complemento de dois)
Overflow e Underflow
Os inteiros possuem limites superiores e inferiores. Quando uma operação aritmética excede esses limites, ocorre um overflow (estouro) ou underflow (“subfluxo” – subrepresentação), o que pode levar a resultados incorretos ou comportamento indefinido.
Exemplo em C:
#include <stdio.h>
#include <limits.h>
int main() {
int max = INT_MAX;
printf("INT_MAX + 1 = %d\n", max + 1);
return 0;
}
Este programa demonstra o comportamento de overflow, onde INT_MAX + 1 resulta em um valor negativo devido ao estouro do limite superior.
Representação de Números Reais
Já representar números reais (ponto flutuante) é mais complexo devido à necessidade de armazenar uma ampla gama de valores, incluindo frações e números muito grandes ou muito pequenos. Existem algumas formas, como o …
Padrão IEEE 754
A maioria dos computadores modernos utiliza o padrão IEEE 754 para representação de números em ponto flutuante. Os tipos básicos são:
float: precisão simples (32 bits)double: precisão dupla (64 bits)long double: precisão estendida (depende da implementação)
Um número em ponto flutuante no padrão IEEE 754 é dividido em três partes:
- Sinal (S): 1 bit
- Expoente (E): define a escala
- Mantissa ou Significand (M): fração que representa o número
A forma geral é:
Onde:
- S é o bit de sinal:
- S=0: número positivo.
- S=1: número negativo.
- M é a mantissa (também chamada de significando ou fração):
- Para números normalizados, M está no intervalo [1,2) após a normalização.
- A mantissa representa a parte fracionária do número.
- E é o valor do expoente armazenado.
- Bias é o viés aplicado ao expoente para permitir a representação de expoentes negativos e positivos:
- Para precisão simples (32 bits), Bias=127.
- Para precisão dupla (64 bits), Bias=1023.
O uso de que é um termo á primeira vista estranho, é uma maneira matemática de incorporar o sinal do número na fórmula, pois quando S=0, o número é positivo e quando S=1, o número é negativo. Essa notação permite que a expressão seja escrita de forma compacta, incluindo tanto números positivos quanto negativos. O Bias é um valor constante que centraliza o intervalo do expoente em torno de zero.
Exemplo:
Vamos considerar um número em precisão simples (32 bits):
- Bit de sinal (S): 1 (número negativo)
- Expoente armazenado (E): 130
- Bias: 127
- Mantissa (M): 1.25 (por exemplo)
Aplicando a fórmula:
Recapitulando…
- O
incorpora o sinal do número.
- M representa a mantissa normalizada.
- E−Bias ajusta o expoente para seu valor real.
Mais um Exemplo de Representação
Considerando um float de 32 bits:
- Sinal (1 bit): determina se o número é positivo ou negativo.
- Expoente (8 bits): armazenado com um Bias de 127.
- Mantissa (23 bits): representa a fração significativa.
Para representar o número -12.375:
- Conversão para binário:
- Parte inteira de 12:
1100 - Parte fracionária de 0.375:
0.011(porque0.375 = 0.25 + 0.125) Então,-12.375em binário é-1100.011 - Normalização:
- Escreve-se em formato (
)
1100.011equivale a- Determinação de S, E e M:
- S: 1 (porque o número é negativo)
- E:
3 + 127 = 130(em binário:10000010) - M: bits após a vírgula na mantissa normalizada:
10001100000000000000000
Visualizando as Representações Binárias
Para entender melhor, vamos visualizar a representação em binário dos números envolvidos. Usando a precisão simples (float), temos:
- 0.1f:
- Sinal:
0 - Expoente:
01111011(123) - Mantissa:
10011001100110011001101
- Sinal:
- 0.2f:
- Sinal:
0 - Expoente:
01111100(124) - Mantissa:
10011001100110011001101
- Sinal:
- 0.3f:
- Sinal:
0 - Expoente:
01111101(125) - Mantissa:
00110011001100110011010
- Sinal:
Note que as mantissas são aproximações binárias dos valores decimais originais.
Precisão e Erros de Arredondamento
Devido ao número limitado de bits para representar a mantissa, nem todos os números reais podem ser representados exatamente. Isso leva a erros de arredondamento.
Exemplo em C:
#include <stdio.h>
int main() {
float a = 0.1f;
if (a == 0.1f) {
printf("a é igual a 0.1\n");
} else {
printf("a não é igual a 0.1\n");
}
return 0;
}
Surpreendentemente, o programa pode imprimir “a não é igual a 0.1” devido às limitações na representação binária de números decimais como 0.1. Há outro exemplo que eu adoro e sempre uso em sala de aula que é:
0.1+0.2
0.3
Sim, eu sei, é incrível mas a operação 0.1 + 0.2 em programas C pode não resultar exatamente em 0.3 devido a limitações na representação dos números de ponto flutuante em binário. Vamos explorar isso com um programa em C e explicar por que isso acontece.
#include <stdio.h>
int main() {
float a = 0.1f;
float b = 0.2f;
float sum = a + b;
printf("a + b = %.17f\n", sum);
printf("Comparando com 0.3:\n");
if (sum == 0.3f) {
printf("a soma é exatamente igual a 0.3\n");
} else {
printf("a soma NÃO é exatamente igual a 0.3\n");
}
return 0;
}
Saída:
a + b = 0.30000001192092896
Comparando com 0.3:
a soma NÃO é exatamente igual a 0.3
Isso ocorre porque
- 0.1 em binário: é uma fração binária infinita e não periódica:
0.000110011001100... - 0.2 em binário: também é uma fração binária infinita:
0.00110011001100...
Devido à limitação do número de bits na mantissa (23 bits para float), essas representações são aproximadas. e isso leva ao erro de Arredondamento
Então, quando o computador armazena esses números, ele guarda apenas uma aproximação deles. Ao realizar a soma, o erro de arredondamento pode se acumular, resultando em um valor que não é exatamente 0.3.
- Valor armazenado de 0.1f: aproximadamente
0.10000000149011612 - Valor armazenado de 0.2f: aproximadamente
0.20000000298023224 - Soma armazenada: aproximadamente
0.30000000447034836
No programa, ao comparar sum == 0.3f, a comparação falha porque:
sumé aproximadamente0.300000004470348360.3fé aproximadamente0.30000001192092896
Esses pequenos erros de arredondamento podem fazer com que a comparação resulte em false. Abaixo eu mostro um hack de COMO resolver esse tipo de comparação
Operações com Ponto Flutuante
No computador, uma máquina discreta que só representa fielmente 0’s e 1’s, operações aritméticas com números em ponto flutuante não são associativas ou distributivas como na aritmética real devido a esses erros de arredondamento.
Exemplo:
#include <stdio.h>
int main() {
float a = 1.0e20f;
float b = -1.0e20f;
float c = 3.14f;
float result1 = (a + b) + c;
float result2 = a + (b + c);
printf("Resultado 1: %f\n", result1);
printf("Resultado 2: %f\n", result2);
return 0;
}
Os resultados result1 e result2 podem ser diferentes devido à forma como a aritmética de ponto flutuante é realizada, mostrando que a propriedade associativa não se mantém.
Comparações de Ponto Flutuante
Devido aos erros de arredondamento, deve-se evitar comparações diretas de igualdade entre números de ponto flutuante. Em vez disso, verifica-se se a diferença absoluta entre eles é menor que um pequeno valor, conhecido como epsilon.
Exemplo:
#include <stdio.h>
#include <math.h>
int main() {
float a = 0.1f;
float b = 0.1f;
float epsilon = 1e-6f;
if (fabs(a - b) < epsilon) {
printf("a é aproximadamente igual a b\n");
} else {
printf("a não é aproximadamente igual a b\n");
}
return 0;
}
Neste exemplo, usamos a função fabs para calcular o valor absoluto da diferença e comparamos com um pequeno valor epsilon para determinar se os números são aproximadamente iguais.
Cuidados com Overflow e Underflow
Em operações que envolvem números muito grandes ou muito pequenos, podem ocorrer overflows ou underflows, levando a valores infinitos ou zero.
Endianness
A ordem dos bytes na memória (endianness) pode afetar a interpretação dos números binários, especialmente ao trabalhar com diferentes sistemas ou ao ler arquivos binários. Há duas ordens:
- Big Endian: o byte mais significativo é armazenado no menor endereço de memória.
- Little Endian: o byte menos significativo é armazenado no menor endereço.
Exemplo de Diferença:
#include <stdio.h>
int main() {
unsigned int x = 0x12345678;
unsigned char *p = (unsigned char*)&x;
printf("Bytes em memória: %02x %02x %02x %02x\n", p[0], p[1], p[2], p[3]);
return 0;
}
A saída varia dependendo da arquitetura:
- Little Endian:
78 56 34 12 - Big Endian:
12 34 56 78
Compreender como os números são representados nos computadores é essencial para escrever programas corretos e eficientes. A representação de inteiros e números reais apresenta desafios distintos, desde limitações de tamanho até erros de arredondamento e peculiaridades da arquitetura do sistema.
A linguagem C, por estar próxima do hardware, é uma ferramenta valiosa para explorar esses conceitos em profundidade. Ela nos permite visualizar e manipular diretamente os bits que compõem os números, proporcionando um entendimento mais claro dos mecanismos subjacentes.
Ao desenvolver programas que envolvem cálculos com números de ponto flutuante, portanto, é crucial:
- Evitar Comparações Diretas: Não comparar números de ponto flutuante usando igualdade direta.
- Utilizar Epsilon: Usar uma tolerância (
epsilon) para determinar se dois números são aproximadamente iguais. - Estar Ciente das Limitações: Reconhecer que operações aritméticas podem introduzir erros de arredondamento acumulados.
Ao dominar esses fundamentos, nós programadores podemos evitar armadilhas comuns, como erros de overflow, underflow e comparações incorretas de ponto flutuante, desenvolvendo softwares mais robustos e confiáveis.
Referências
- Brian W. Kernighan, Dennis M. Ritchie. The C Programming Language. Prentice Hall.
- IEEE Standard for Floating-Point Arithmetic (IEEE 754).
- Documentação da linguagem C e padrões ANSI/ISO.
- Understanding Floating Point Precision
- Endianness Explained
quarta-feira, 2 outubro, 2024 at 3:35 pm Deixe um comentário
A Relevância Duradoura da Linguagem C: Uma Pedra Angular na Computação
A linguagem de programação C ocupa um lugar singular e significativo no universo da computação. Apesar de fazer incríveis 52 anos neste ano de 2024 por ter sido criada em 1972 —uma era que, em termos tecnológicos, pode ser considerada a transição da pré-história para a história—C continua sendo uma ferramenta essencial tanto na indústria quanto na educação. Como professor de programação em cursos de nível superior de Ciência da Computação, Sistemas de Informação, Análise e Desenvolvimento de Sistemas e Sistemas para a Internet, opto por utilizá-la como base para o ensino de “Estruturas de Dados I” e “Técnicas de Desenvolvimento de Algoritmos” (“Estruturas de Dados II” é em Java com exemplos em Python e “Programação de Computadores” é em Scratch e Python). A escolha por C naquelas disciplinas permite que os alunos enfrentem os desafios da programação “real”, sem os recursos facilitadores das linguagens modernas, como syntax sugar ou outras “ajudinhas”.

Durante as aulas, faço questão de destacar as deficiências e problemas da linguagem C, assim como suas idiossincrasias. Essas peculiaridades podem ser “divertidas” para quem já tem experiência com várias linguagens, mas, para os iniciantes, servem como valiosas lições sobre as diversas situações que podem encontrar na computação prática. A ausência de abstrações elevadas (ou a exigência de criar abstrações complexas a partir de poucos elementos) aliado à necessidade de lidar diretamente com detalhes de baixo nível obrigam os estudantes a desenvolver um entendimento mais profundo do funcionamento interno dos programas e do hardware.
C reflete as decisões e necessidades de uma época muito diferente da atual. Na década de 1970, os recursos de hardware eram limitados, e a eficiência era uma prioridade absoluta. O que era considerado moderno e arrojado naquela época pode parecer hoje enfadonho ou até sem sentido. No entanto, essa mesma linguagem foi responsável por grandes avanços e continua a influenciar o desenvolvimento tecnológico. Por exemplo, conceitos como ponteiros, gerenciamento manual de memória e aritmética de baixo nível, presentes em C, são fundamentais para compreender como os computadores operam em um nível mais profundo, “escovando bits” como dizíamos na idade da pedra lascada.

Pessoalmente, tenho um apreço especial por C. Foi minha primeira linguagem de programação “de verdade”, após meus primeiros passos com BASIC—a qual eu considerava (não perguntem) uma linguagem de “brinquedo”. C abriu as portas para um mundo mais complexo e poderoso na programação. Além disso, é a linguagem que sustenta (até hoje) muitos sistemas operacionais importantes, como as diversas variantes do Unix e Linux, bem como o BSD e o Darwin, que serve de base para o macOS. A influência de C está presente em sistemas que utilizamos diariamente, reforçando sua relevância contínua.
A velocidade, o poder, a versatilidade e a ubiquidade de C fazem dela uma pedra angular na computação. Muitas linguagens modernas, como C++, Java e C#, derivam diretamente de C ou são fortemente influenciadas por ela (sendo a sintaxe a maior influência). Mesmo linguagens interpretadas, como Python e JavaScript, são implementadas em C (Python foi escrito em C e sua implementação mais comum, o CPython é escrito, ainda hoje, em C e Javascript foi escrito originalmente em C++). Em áreas onde o desempenho é crítico—como sistemas embarcados, desenvolvimento de sistemas operacionais, drivers e aplicações de alto desempenho—C permanece a escolha preferencial devido ao seu controle sobre o hardware e eficiência.
No contexto educacional, ensinar C permite que os alunos compreendam conceitos fundamentais que linguagens de alto nível abstraem. Gerenciamento de memória, ponteiros e aritmética de ponteiros, estruturas de dados e algoritmos são ensinados de forma mais abrangente com C. Essa base sólida é valiosa, independentemente das linguagens que os alunos venham a utilizar no futuro. Eles aprendem não apenas a programar, mas também a entender como os programas funcionam “por baixo dos panos”.

É importante reconhecer que, apesar das limitações e da curva de aprendizado íngreme, C continua evoluindo. Padrões mais recentes, como C11 e C18 (ISO/IEC 9899:2018), introduzem melhorias e novas funcionalidades, mantendo a linguagem atualizada com as necessidades modernas. Além disso, a comunidade global de desenvolvedores continua ativa, contribuindo para projetos de código aberto e garantindo que C permaneça relevante.
Em suma, embora C possa ser vista como uma linguagem de outra era, sua importância e influência são inegáveis. Ela oferece aos programadores uma compreensão profunda dos fundamentos da computação, algo que linguagens mais modernas tendem a abstrair. Continuar a ensiná-la nas disciplinas fundamentais não é apenas uma homenagem à história da programação, mas também uma forma de preparar os alunos para enfrentar uma variedade de desafios no mundo real. Acredito que C permanecerá relevante por muitos anos, servindo como alicerce sobre o qual grande parte do software moderno é construído.

Sua longevidade não é apenas um testemunho de sua robustez e eficiência, mas também de sua capacidade de adaptação. Enquanto a tecnologia continua a avançar em um ritmo acelerado, a necessidade de entender os fundamentos permanece constante. C, com todas as suas peculiaridades e desafios, continuará a ser uma ferramenta inestimável para educadores e profissionais, mantendo-se como uma pedra basilar no cada vez mais vasto eedifício que é a computação.
P.S. Todos as imagens deste texto foram geradas pelo DALL-E em sua versão free…
Tutorial: compilando e rodando programas em linguagem C no Windows no VSCode
Muitos alunos me veem usando o VSCode para editar, compilar e executar programas em C e sempre perguntam como fazem para configurar o editor para fazer isso automaticamente. Resolvi então fazer esse tutorial rápido para explicar como isto pode ser feito.
Vamos, portanto, configurar o Visual Studio Code (VS Code) para compilar e executar programas em C no Windows 10 ou 11. O processo é um pouco mais demorado que no Linux, pois você precisará instalar o compilador MinGW (ou qualquer outro compilador de C, como o Visual Studio Build Tools) e ajustar algumas configurações específicas para o Windows.
Aqui está o Passo a passo para fazer isso no Windows (a partir do passo 2 há duas alternativas no final do texto, fique ligado):
Passo 1: Instalar o MinGW (Compilador de C)
No Windows, você pode usar o MinGW (Minimalist GNU for Windows) como compilador C. Ele inclui o gcc (GNU Compiler Collection), que vamos utilizar.

- Baixar o MinGW:
- Instalar o MinGW:
- Execute o instalador e escolha a opção de instalar o
gcc-g++, que contém o compilador de C e C++. - Durante a instalação, certifique-se de instalar as opções de desenvolvimento (como compiladores e ferramentas de depuração).
- Execute o instalador e escolha a opção de instalar o



3. Configurar o caminho (PATH):
- Após instalar o MinGW, você precisa adicionar o caminho para os binários do MinGW no PATH do sistema.
- Normalmente, o caminho é algo como:
C:\MinGW\bin - Para adicionar ao
PATH:- Clique com o botão direito no ícone do Meu Computador ou Este PC e escolha Propriedades.
- Vá em Configurações avançadas do sistema > Variáveis de Ambiente.
- Na seção Variáveis do Sistema, selecione a variável
Pathe clique em Editar. - Adicione o caminho para a pasta
bindo MinGW (por exemplo,C:\\MinGW\\bin). !



4. Verificar a instalação do MinGW:
- Abra o Prompt de Comando (
cmd) e digite:gcc --version - Se o
gccestiver corretamente instalado, você verá a versão do compilador.

Passo 2: Instalar a extensão C/C++ no VS Code
- Abra o Visual Studio Code.
- Vá para a aba Extensões (ou pressione
Ctrl+Shift+X). - Pesquise por C/C++ (extensão oficial da Microsoft) e clique em Instalar.
- Instale também a extensão C/C++ Extension Pack


Antes de continuar, saiba que existe uma alternativa a partir deste ponto que está abaixo (no título Alternativa neste mesmo texto — caso não queira criar as tarefas de compilação manualmente)
Passo 3: Criar e configurar o arquivo tasks.json para compilar
Agora, vamos configurar o VS Code para compilar o código C.
- Abra o terminal no VS Code (pressione `Ctrl+“ ou vá para Terminal > New Terminal).
- Vá para Terminal > Configure Default Build Task > Create tasks.json file from template.

3. Escolha Others.
Isso cria um arquivo tasks.json na pasta .vscode do seu projeto. Edite o arquivo para que fique assim:
{
"version": "2.0.0",
"tasks": [
{
"label": "Compilar C",
"type": "shell",
"command": "gcc",
"args": [
"${file}",
"-o",
"${fileDirname}\\\\${fileBasenameNoExtension}.exe"
],
"group": {
"kind": "build",
"isDefault": true
},
"problemMatcher": ["$gcc"],
"detail": "Tarefa de compilação C gerada pelo usuário"
}
]
}
Neste arquivo:
"${file}": refere-se ao arquivo C que está atualmente aberto."${fileDirname}\\\\${fileBasenameNoExtension}.exe": gera o arquivo executável com o mesmo nome do arquivo.c, mas com a extensão.exe, na mesma pasta do código-fonte.
Passo 4: Criar o arquivo launch.json para executar o programa
Agora, configure o VS Code para executar o programa após a compilação.
- Vá para Run > Add Configuration….
- Escolha C++ (GDB/LLDB).
- No arquivo
launch.jsonque aparece, edite para parecer com o seguinte:
{
"version": "0.2.0",
"configurations": [
{
"name": "Executar Programa C",
"type": "cppdbg",
"request": "launch",
"program": "${fileDirname}\\\\${fileBasenameNoExtension}.exe",
"args": [],
"stopAtEntry": false,
"cwd": "${workspaceFolder}",
"environment": [],
"externalConsole": true,
"MIMode": "gdb",
"miDebuggerPath": "C:\\\\MinGW\\\\bin\\\\gdb.exe",
"setupCommands": [
{
"description": "Habilitar impressão automática para gdb",
"text": "-enable-pretty-printing",
"ignoreFailures": true
}
],
"preLaunchTask": "Compilar C",
"internalConsoleOptions": "openOnSessionStart"
}
]
}
Aqui está o que este arquivo faz:
"program": "${fileDirname}\\\\${fileBasenameNoExtension}.exe": indica o executável gerado pela compilação."miDebuggerPath": define o caminho para ogdb.exe, que está dentro da pastabindo MinGW.
Passo 5: Compilar e executar o programa
Agora que o tasks.json e o launch.json estão configurados:
- Compilar: Pressione
Ctrl+Shift+Bpara compilar o código C. Isso deve gerar um arquivo.exeno diretório do código. - Executar: Pressione
F6para rodar o programa (caso tenha instalado a extensão “C/C++ Compile Run”. Caso contrário, clique no botão PLAY na parte superior direita da janela do arquivo e escolhaRun Code). Isso abrirá um terminal externo (console) para exibir a saída do programa.
Passo 6: Testar o Setup
Crie um simples arquivo C como este:
#include <stdio.h>
int main() {
printf("Olá, Mundo!\\n");
return 0;
}
Salve-o como main.c e siga os passos acima para compilar e executar o programa.
Dica: Verificando e selecionando o compilador correto
Se você tem mais de um compilador instalado (como MinGW e Visual Studio), é importante garantir que o compilador correto está sendo usado no terminal do VS Code.
Passo 1: Verificando o compilador padrão
- Abra o terminal no VS Code (`Ctrl+“).
- Verifique o compilador em uso executando o comando:
gcc --version - Isso mostrará a versão do
gcc(compilador do MinGW). Se o comando exibir a versão correta dogcc, significa que o MinGW está sendo chamado corretamente. - Para Visual Studio (MSVC), você pode verificar se o compilador da Microsoft está sendo usado executando:
cl - Esse comando mostrará informações sobre o compilador MSVC (caso esteja configurado corretamente no PATH).
Passo 2: Verificando o PATH e configurando o compilador correto
Se o compilador que você deseja usar não for exibido corretamente, pode ser necessário ajustar a variável PATH para garantir que o compilador certo seja chamado. Para isso:
- Verifique o
PATHatual no terminal do VS Code:echo $PATH - Isso mostrará a lista de diretórios onde o sistema procura executáveis. Verifique se o caminho correto para o compilador que você deseja usar (como
C:\MinGW\binouC:\Program Files\Microsoft Visual Studio\...\bin) está presente. - Alterar o
PATHtemporariamente (para essa sessão do terminal): Se você deseja alterar temporariamente o compilador para essa sessão do terminal, pode usar o seguinte comando:- Para MinGW:
export PATH="/c/MinGW/bin:$PATH" - Para Visual Studio: Use o Developer Command Prompt do Visual Studio, que configura automaticamente o ambiente para o compilador MSVC. Você pode rodar o terminal do VS Code a partir dele ou configurar o ambiente executando:
"C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Auxiliary\Build\vcvarsall.bat" x64
- Para MinGW:
- Verifique novamente o compilador executando
gcc --versionouclpara garantir que o compilador correto está sendo usado.
Passo 3: Configurando o compilador no VS Code
Você pode também configurar explicitamente qual compilador deve ser usado para a compilação em seu projeto no arquivo tasks.json. Dependendo de qual compilador você deseja, ajuste a "command" no tasks.json para apontar para o compilador correto:
- Para MinGW (gcc):
"command": "C:/MinGW/bin/gcc" - Para Visual Studio (MSVC):
"command": "cl"
Alternativa
- Após o passo 2 instalar as extensões
Code RunnereC/C++ Compile Run. Ambas individualmente já fazem os passos 3, 4 e 5 automaticamente. - Uma alternativa à instalação do MingW é instalar o DevC++(link) ou o Codeblocks (link) e no passo 1.3 você colocar o caminho da instalação do Mingw (pasta bin) escolhida por vc na instalação de uma das duas IDE’s
Seguindo estes passos, você pode poderá usar o VS Code para compilar e executar programas em C no Windows 10 ou 11. Se encontrar qualquer problema durante a configuração ou execução fale comigo no Discord.
- Prof. Ed.
quinta-feira, 12 setembro, 2024 at 2:56 pm Deixe um comentário
O Problema dos Três Corpos: Uma Dança Gravitacional Complexa
Muita gente deve ter ficado curiosa ao assistir à série “O problema dos 3 corpos” da Netflix e de ve ter ficado intrigada sobre o problema real que dá nome à serie (e à série de livros) que é de origem astronômica. Fato é que o universo é repleto de fenômenos fascinantes e desafiadores, e um dos mais intrigantes, sem dúvida, é o Problema dos Três Corpos.
O problema: Imagine três corpos celestes, como planetas ou estrelas, orbitando uns aos outros sob a influência de suas próprias forças gravitacionais. O movimento desses corpos é regido pelas leis da física, especialmente pela Lei da Gravitação Universal de Newton. No entanto, mesmo com essas leis bem estabelecidas, o Problema dos Três Corpos desafia nossa capacidade de prever com precisão o comportamento dos componentes do sistema.
O problema dos 3 corpos foi originalmente proposto por Isaac Newton em 1687. Ele foi capaz de encontrar soluções analíticas para o caso especial de três corpos que estão em uma configuração triangular. No entanto, o problema geral de três corpos não foi resolvido analiticamente até o século 20.
Vamos criar uma simulação simplificada do “Problema dos Três Corpos”, para isso vamos considerar três corpos de massas iguais, que interagem apenas através da força gravitacional, seguindo as Leis de Newton da Gravitação Universal. Vou usar o método numérico de Runge-Kutta de quarta ordem para resolver as equações diferenciais que descrevem o movimento dos corpos ao longo do tempo.
As equações diferenciais para cada corpo são baseadas nas Leis de Newton e na Lei da Gravitação Universal. O movimento dos corpos será simulado para um determinado período de tempo, mostrando as trajetórias de A, B e C em um gráfico.
Aqui está a lógica básica:
- Cada corpo A, B e C atrai os outros dois corpos.
- A força gravitacional entre dois corpos iii e jjj é proporcional ao produto das massas e inversamente proporcional ao quadrado da distância entre eles.
- As equações diferenciais são resolvidas iterativamente para simular as posições dos corpos ao longo do tempo.
Vou implementar primeiro em Wolfram Language e gerar o gráfico correspondente da simulação do Problema dos Três Corpos, mostrando as trajetórias dos corpos A, B e C:

O código do Wolfram Language é:
With[{m = 1, g = 1},
(* Posições e velocidades iniciais *)
initialPos = {{-1, 0}, {1, 0}, {0, 0}};
initialVel = {{0, -0.5}, {0, 0.5}, {0.8, 0}};
(* Equações diferenciais para o problema dos 3 corpos *)
eqs = Flatten[
Table[r[i]''[t] ==
Sum[-(g m m (r[i][t] - r[j][t]))/Norm[r[i][t] - r[j][t]]^3,
{j, Complement[{1, 2, 3}, {i}]}], {i, 1, 3}]];
(* Condições iniciais *)
initialConds = Flatten[
Table[{r[i][0] == initialPos[[i]],
r[i]'[0] == initialVel[[i]]}, {i, 1, 3}]];
(* Solução das equações *)
sol = NDSolve[{eqs, initialConds},
Table[r[i], {i, 1, 3}], {t, 0, 20}, MaxSteps -> Infinity];
(* Gráfico das trajetórias *)
ParametricPlot[
Evaluate[Table[r[i][t] /. sol, {i, 1, 3}]], {t, 0, 20},
PlotRange -> All, PlotStyle -> Thick, AxesLabel -> {"x", "y"},
PlotLegends -> {"Corpo A", "Corpo B", "Corpo C"},
PlotLabel -> "Simulação do Problema dos 3 Corpos"]
]
Equações
As equações diferenciais que descrevem o movimento de cada corpo são derivadas das Leis de Newton da Gravitação Universal. Para três corpos ,
e
, com posições
e massas iguais
, as equações diferenciais são:
Para cada corpo (onde
é
,
ou
):
Essas equações foram resolvidas numericamente usando o método de Runge-Kutta de quarta ordem com a função NDSolve no código acima, resultando nas trajetórias dos corpos.
Fiz também uma versão do código Wolfram para Python utilizando a biblioteca scipy para resolver as equações diferenciais e matplotlib para plotar as trajetórias dos corpos:
import numpy as np
from scipy.integrate import solve_ivp
import matplotlib.pyplot as plt
# Constantes
G = 1 # Constante gravitacional (simplificada)
m = 1 # Massas iguais
# Função que define as equações diferenciais
def equations(t, y):
r1 = y[:2]
r2 = y[2:4]
r3 = y[4:6]
v1 = y[6:8]
v2 = y[8:10]
v3 = y[10:12]
# Força gravitacional
def gravitational_force(r_i, r_j):
return -G * m * m * (r_i - r_j) / np.linalg.norm(r_i - r_j)**3
# Derivadas
dr1dt = v1
dr2dt = v2
dr3dt = v3
dv1dt = gravitational_force(r1, r2) + gravitational_force(r1, r3)
dv2dt = gravitational_force(r2, r1) + gravitational_force(r2, r3)
dv3dt = gravitational_force(r3, r1) + gravitational_force(r3, r2)
return np.concatenate([dr1dt, dr2dt, dr3dt, dv1dt, dv2dt, dv3dt])
# Condições iniciais
initial_positions = np.array([[-1, 0], [1, 0], [0, 0]])
initial_velocities = np.array([[0, -0.5], [0, 0.5], [0.8, 0]])
initial_conditions = np.concatenate([initial_positions.flatten(), initial_velocities.flatten()])
# Intervalo de tempo
t_span = (0, 20)
t_eval = np.linspace(*t_span, 1000)
# Resolvendo as equações diferenciais
solution = solve_ivp(equations, t_span, initial_conditions, t_eval=t_eval)
# Extraindo as soluções
r1_sol = solution.y[:2].T
r2_sol = solution.y[2:4].T
r3_sol = solution.y[4:6].T
# Plotando as trajetórias
plt.figure(figsize=(8, 8))
plt.plot(r1_sol[:, 0], r1_sol[:, 1], label="Corpo A")
plt.plot(r2_sol[:, 0], r2_sol[:, 1], label="Corpo B")
plt.plot(r3_sol[:, 0], r3_sol[:, 1], label="Corpo C")
plt.xlabel("x")
plt.ylabel("y")
plt.legend()
plt.title("Simulação do Problema dos 3 Corpos")
plt.grid(True)
plt.show()
O gráfico em Python (executado no Colab) foi o seguinte:

Eu gerei um GIF da animação deste código em Python. Ficou assim:

Percebe-se que evido à natureza não linear do problema e à sensibilidade às condições iniciais, a previsão a longo prazo do movimento dos corpos pode se tornar imprecisa, especialmente para sistemas complexos ou instáveis. Portanto, simulações computacionais detalhadas são frequentemente necessárias para entender o comportamento dos sistemas de três corpos.
Uma das principais razões para a dificuldade do problema é sua grandiosa complexidade matemática. As equações que descrevem o movimento dos corpos são altamente não lineares e não têm solução analítica para o caso geral (n-corpos). Isso significa que não há uma fórmula matemática simples que possa prever o movimento dos corpos ao longo do tempo. Em vez disso, os cientistas precisam recorrer a métodos numéricos e simulações por computador para estudar o problema.
Além da complexidade matemática, o Problema dos Três Corpos exibe comportamento caótico e é altamente sensível às condições iniciais. Isso significa que pequenas variações nas posições ou velocidades iniciais dos corpos podem levar a resultados significativamente diferentes ao longo do tempo. Essa sensibilidade dificulta ainda mais a previsão precisa do movimento dos corpos.
Embora o Problema dos Três Corpos seja desafiador, ele tem amplas aplicações em diversas áreas da ciência, incluindo astronomia, física e engenharia espacial. O estudo desse problema nos ajuda a entender melhor o comportamento dos sistemas gravitacionais complexos no universo e pode fornecer insights importantes para o desenvolvimento de futuras missões espaciais.
O Problema dos Três Corpos continua sendo um dos desafios mais intrigantes e complexos da física. Apesar de sua dificuldade, os cientistas continuam a explorar e estudar esse problema, utilizando métodos avançados de computação e simulação para desvendar os segredos da dança gravitacional dos corpos celestes no universo.
Em uma outra simulação, em python disponível no Github (em 2D) podemos ver o seguinte gráfico do movimento dos 3 corpos:

O que confirma o movimento caótico dos corpos a partir das condições iniciais.
SOBRE A SÉRIE: A série da Netflix é uma adpatação do romance homônimo de Liu Cixin, que ganhou o Prêmio Hugo de Melhor Romance em 2015. O livro conta a história de uma astrofísica chinesa que se envolve em uma investigação secreta relacionada ao problema dos 3 corpos e à possível invasão da Terra por uma civilização alienígena. Na minha opinião, vale a audiência de quem, como eu, curte ficção científica.
Tratando “warning: ignoring return value of ‘scanf’” no compilador gcc do Replit.com (e no Unix-like)
Boa parte das pessoas que começaram a aprender a programar seja em cursos online, presenciais, faculdades e escolas de programação já se deparou com o Replit.com.
O Replit.com é uma plataforma de criação de software que oferece um IDE (Ambiente de Desenvolvimento Integrado), recursos de inteligência artificial e a capacidade de implantar projetos diretamente do navegador. No Replit, você pode gerar, editar e explicar código, colaborar em tempo real e implantar seus projetos diretamente do navegador. É uma ferramenta útil para programadores e educadores, pois permite programar em várias linguagens de programação, como Java, Python, C, C++, entre outras, e é acessível em qualquer dispositivo sem a necessidade de configuração (e o melhor você pode PUBLICAR seu projeto de software – qualquer um!). Eu uso bastante em sala de aula (seja para C, Java, Python ou desenvolvimento Web).
Quem já usou o Replit.com para programar em C (ou aprender a programar ou testar códigos na linguagem C) eventualmente já se deparou com o erro:
warning: ignoring return value of ‘scanf’ declared with attribute ‘warn_unused_result’ [-Wunused-result]
Este erro (na verdade, uma warning) ocorre pela definição de scanf do compilador usar o atributo específico do GCC (warn_unused_result). De algum modo os desenvolvedores da biblioteca C decidiram que o valor de retorno de scanf não deve ser ignorado na maioria dos casos, então eles deram a ele um atributo informando ao compilador para dar um aviso quando você não o usa.
O problema é que na imensa maioria dos casos você não vai lidar (diretamente) com o valor de retorno de scanf. De modo que este é um aviso que você poderá ignorar quase sempre.
Para desativar a warning você pode compilar usando a opção -Wno-unused-result, assim:
gcc nome_do_arquivo.c -o nome_do_arquivo.out -Wno-unused-result
Infelizmente este método te obrigará a setar a flag SEMPRE que for compilar cada arquivo individualmente. Para que você não precise inserir a flag na compilação (e se usa o maravilhoso Make para isso) você pode editar o seu Makefile e inserir essa opção nas flags da compilação, por exemplo, da seguinte forma:
override CFLAGS += -g -Wno-everything -pthread -lm -Wno-unused-result

Deste modo sempre que usar o make nomedoarquivo para compilar o arquivo (ou projeto) o compilador ignorará a warning e você não precisará, digamos, lidar com o valor de retorno de scanf.
7 sites para download de Música de Videogames de graça (free video game music)
A música de videogame é incrivelmente icônica. Para os jogos mais antigos, evoca nostalgia da infância e de uma época mais simples. Jogos mais recentes muitas vezes contam com músicos mundialmente famosos colaborando nas trilhas sonoras. E há uma grande quantidade de conteúdo entre eles que atrai tanto fãs casuais quanto jogadores hardcore.
Mas onde você pode encontrar músicas de videogame? Se você quer reviver trilhas sonoras de jogos sem o estresse de jogar o jogo em si, confira esses principais sites.
Continue Reading domingo, 11 fevereiro, 2024 at 1:09 pm Deixe um comentário
Pseudocódigo. Outra forma de representar algoritmos
Nós já vimos duas das principais formas de representar algoritmos, a descrição narrativa e os fluxogramas. Hoje vamos ver a terceira.
A representação e descrição de algoritmos é uma parte fundamental da programação. Ela permite que os desenvolvedores comuniquem de forma clara e concisa a lógica e os passos necessários para resolver um problema ou realizar uma tarefa. O pseudocódigo é uma forma de representação de algoritmos que combina elementos de linguagem de programação com linguagem natural, tornando-o mais compreensível para humanos, independentemente da linguagem de programação específica.
O pseudocódigo não é uma linguagem de programação real, mas sim uma maneira de descrever a sequência de passos lógicos de um algoritmo. Ele utiliza convenções e estruturas de controle semelhantes às linguagens de programação, como declarações condicionais, loops e instruções de atribuição, permitindo que o desenvolvedor descreva a lógica de um algoritmo de maneira clara e estruturada.
Vamos considerar um exemplo simples para ilustrar o uso do pseudocódigo. Suponha que queremos escrever um algoritmo para calcular a média aritmética de três números. Utilizando pseudocódigo, poderíamos representar o algoritmo da seguinte forma:
1. Ler três números (num1, num2, num3)
2. Calcular a soma dos três números (soma = num1 + num2 + num3)
3. Calcular a média (media = soma / 3)
4. Exibir a média
Nesse exemplo, cada linha descreve uma etapa do algoritmo. Na linha 1, estamos lendo os três números, e na linha 2, calculamos a soma dos três números. Em seguida, na linha 3, calculamos a média dividindo a soma pelo número de elementos (3). Por fim, na linha 4, exibimos o resultado da média.
O pseudocódigo permite que o desenvolvedor se concentre na lógica do algoritmo, independentemente da sintaxe de uma linguagem de programação específica. Isso facilita a compreensão e a comunicação do algoritmo entre os membros da equipe de desenvolvimento.
Além disso, o pseudocódigo é altamente flexível e pode ser adaptado de acordo com as necessidades do projeto. Por exemplo, se desejarmos adicionar uma verificação de erro para garantir que os números inseridos sejam válidos, poderíamos modificar o pseudocódigo da seguinte maneira:
1. Ler três números (num1, num2, num3)
2. Verificar se os números são válidos
2.1. Se algum número for inválido, exibir uma mensagem de erro e encerrar o programa
3. Calcular a soma dos três números (soma = num1 + num2 + num3)
4. Calcular a média (media = soma / 3)
5. Exibir a média
Nesse caso, adicionamos uma etapa extra (2) para verificar se os números são válidos. Se algum número for inválido, exibimos uma mensagem de erro e encerramos o programa. Essa modificação pode ser facilmente realizada no pseudocódigo, sem se preocupar com a sintaxe de uma linguagem de programação específica.
O uso de pseudocódigo também é comum em documentação técnica e algoritmos complexos, onde a compreensão do fluxo lógico é essencial. Ele pode ser facilmente convertido em código real em uma linguagem de programação específica, pois o pseudocódigo é projetado para ser independente de uma linguagem em particular.

Basicamente, o pseudocódigo é uma forma eficaz de representar e descrever algoritmos. Ele combina elementos de linguagem de programação com linguagem natural, permitindo que os desenvolvedores expressem a lógica do algoritmo de forma clara e estruturada. O pseudocódigo facilita a comunicação, a compreensão e a implementação dos algoritmos, independentemente da linguagem de programação utilizada.
Portugol
A representação e descrição de algoritmos utilizando o pseudocódigo conhecido como Portugol é uma prática amplamente utilizada no ensino e aprendizado de programação. O Portugol é uma linguagem de programação simplificada que permite aos iniciantes compreenderem a lógica e a estrutura dos algoritmos de forma mais clara e intuitiva, sem se preocuparem com a sintaxe complexa de linguagens de programação reais.
Ao utilizar o Portugol para representar algoritmos, é possível descrever passos sequenciais, estruturas condicionais e repetições de forma fácil e compreensível. Vamos considerar um exemplo simples de cálculo de média aritmética utilizando o Portugol:
Algoritmo "CalculaMedia"
Var
num1, num2, num3, soma, media: Real
Início
Escreva("Digite o primeiro número: ")
Leia(num1)
Escreva("Digite o segundo número: ")
Leia(num2)
Escreva("Digite o terceiro número: ")
Leia(num3)
soma <- num1 + num2 + num3
media <- soma / 3
Escreva("A média é: ", media)
Fim
Neste exemplo, utilizamos o pseudocódigo Portugol para representar um algoritmo que lê três números, calcula a soma e a média aritmética dos mesmos, e exibe o resultado. As palavras-chave “Algoritmo”, “Var”, “Início” e “Fim” são utilizadas para definir a estrutura básica do algoritmo em Portugol.
Dentro da estrutura do algoritmo, utilizamos comandos como “Escreva” para exibir mensagens na tela, “Leia” para receber valores digitados pelo usuário, e os operadores matemáticos para realizar os cálculos necessários.
O uso do pseudocódigo Portugol é especialmente útil para iniciantes na programação, pois permite que eles se concentrem na lógica e na sequência de passos necessários para resolver um problema, sem se preocuparem com os detalhes específicos de uma linguagem de programação real. Dessa forma, os iniciantes podem aprender a estrutura básica dos algoritmos e ganhar confiança antes de se aventurarem em linguagens de programação mais complexas.
É importante ressaltar que o Portugol é apenas uma representação simplificada e não é executável diretamente. Ele serve como um meio de compreensão e aprendizado dos conceitos fundamentais da programação. Uma vez que o algoritmo tenha sido representado em Portugol, é possível traduzi-lo para uma linguagem de programação real, como Java, Python ou C, para que possa ser compilado e executado.
Como exemplo de pseudocódigos (em formas de programas) temos o Visualg (que usa uma versão do Portugol mais parecida com a usada no texto) e o Portugol Studio.
Até a próxima.
segunda-feira, 13 novembro, 2023 at 4:10 pm Deixe um comentário
Recursividade: a multiplicação recursiva, as definições matemáticas por indução/recursão e os axiomas de Peano
A recursividade, como já vimos anteriormente deve primeiro sempre ser pensada nos casos mais simples (os casos bases ou casos básicos). Vamos ver um exemplo para nos ajudar.
É possível, por exemplo, definir a multiplicação de dois números inteiros m e n, sendo n > 0, em termos da operação de adição. O caso mais simples dessa operação é aquele no qual n = 0. Nesse caso, o resultado da operação é igual a 0, independentemente do valor de m. De acordo com a idéia exposta acima, precisamos agora pensar em como podemos expressar a solução desse problema no caso em que n > 0, supondo que sabemos determinar sua solução para casos mais simples, que se aproximam do caso básico. Neste caso, podemos expressar o resultado de m × n em termos do resultado da operação, mais simples m × (n − 1); ou seja, podemos definir m × n como sendo igual a m + (m × (n − 1)),
para n > 0. Ou seja, a operação de multiplicação pode então ser definida indutivamente pelas seguintes equações:
,
Uma maneira alternativa de pensar na solução desse problema seria pensar na operação de multiplicação m * n como a repetição, n vezes, da operação de adicionar m, isto é:
Raciocínio análogo pode ser usado para expressar a operação de exponenciação, com expoente inteiro não-negativo, em termos da operação de multiplicação (que será visto em outro post).
O programa a seguir apresenta, na linguagem C, uma forma computar o resultado da multiplicação de dois números, dados como argumentos dessas operações de forma recursiva. As função multiplica é definida usando-se chamadas à própria função, razão pela qual ela é chamada de recursiva.
int multiplica(int num1, int num2){
//multiplicação por zero
if (num1 == 0 || num2 == 0) {
return 0;
}
//caso base, onde a recursão para:
else if (num2 == 1) {
return num1;
}
//multiplicando através da soma com recursividade:
else {
return (num1 + multiplica(num1,num2 - 1));
}
}
Considere a função multiplica definida acima. A sua definição espelha diretamente a definição recursiva da operação de multiplicação, em termos da adição, apresentada anteriormente. Ou seja, multiplicar m por n (onde n é um inteiro não-negativo) fornece:
-
0, no caso base (isto é, sen==0); m + multiplica(m, n-1), no caso indutivo/recursivo (isto é,se n!=0).
Vejamos agora, mais detalhadamente, a execução de uma chamada multiplica(3,2). Cada chamada da função multiplica cria novas variáveis, de nome m e n. Existem, portanto, várias variáveis com nomes (m e n), devido às chamadas recursivas. Nesse caso, o uso do nome refere-se à variável local ao corpo da função que está sendo executado. As execuções das chamadas de funções são feitas, dessa forma, em uma estrutura de pilha. Chamamos, genericamente, de estrutura de pilha uma estrutura na qual a inserção (ou alocação) e a retirada (ou liberação) de elementos é feita de maneira que o último elemento inserido é o primeiro a ser retirado.
Em resumo, na tabela abaixo, representamos a estrutura de pilha criada pelas chamadas à função multiplica. Os nomes m e n referem-se, durante a execução, às variáveis mais ao topo dessa estrutura. Uma chamada recursiva que vai começar a ser executada está indicada por um negrito. Nesse caso, o resultado da expressão, ainda não conhecido, é indicado por “…”.
Tabela 4.2: Passos na execução de multiplica(3,2) |
| Comando/Expressão | Resultado (expressão) | Estado (após execução/avaliação) |
multiplica(3,2) … | m↦ 3n↦ 2 | |
n == 0 | falso | m↦ 3n↦ 2 |
return m + multiplica(m,n-1) | … | m↦ 3 m↦ 3n↦ 2 n↦ 1 |
n == 0 | falso | m↦ 3 m↦ 3n↦ 2 n↦ 1 |
return m + multiplica(m,n-1) | … | m↦ 3 m↦ 3 m↦ 3n↦ 2 n↦ 1 n↦ 0 |
n == 0 | verdadeiro | m↦ 3 m↦ 3 m↦ 3n↦ 2 n↦ 1 n↦ 0 |
return 0 | m↦ 3 m↦ 3n↦ 2 n↦ 1 | |
return m + 0 | m↦ 3n↦ 2 | |
return m + 3 | ||
multiplica(3,2) | 6 |
E assim vemos como o procedimento recursivo para multiplicar dois números funciona com um exemplo em linguagem C, facilmente portável para outra linguagem.
Agora, para uma visão matematicamente mais precisa, continue lendo…
Definições por indução
As definições por indução (usando o princípio da indução dos axiomas de Peano) se baseiam na possibilidade de se iterar uma função um número arbitrário ,
n, de vezes.
Mais precisamente, seja uma função cujo domínio e contradomínio são o mesmo conjunto
. A cada
podemos, de modo único, associar uma função
de tal maneira que
. Em particular, se chamarmos
, teremos
. (lembrando que
s(n) é o sucessor de um número natural n, conforme descrito nos axiomas de Peano).
Numa exposição mais, digamos, sistemática da teoria dos números naturais, a existência da n-ésima iterada de uma função
é um teorema, chamado (lindamente) de “Teorema da Definição por Indução”.
É importante ressaltar que não é possível, nesta altura, definir simplesmente como
pois “n vezes” é uma expressão sem sentido no contexto dos Axiomas de Peano, já que um número natural
n é, por enquanto, apenas um elemento do conjunto (um número ordinal), sem condições de servir de resposta à pergunta “quantas vezes”?, até que lhe seja dada a condição de número cardinal.
Admitamos assim que, dada uma função , sabemos associar, de modo único, a cada número natural
, uma função
, chamada a n-ésima iterada de f, de tal modo que
.
Agora, podemos ver um exemplo de definição por indução (recursão) usando o que acabamos de ver, as iteradas da função . Vamos ver o produto de números naturais (semelhantemente a soma e a exponenciação podem ser definidas da mesma forma). Sigamos com a multiplicação… (que vimos acima de forma menos formal e mais voltada para a lógica da programação)… Vejamos.
O produto, a multiplicação, de dois números naturais é definido da seguinte forma:
e
Em outras palavras: multiplicar um número por
não o altera. Multiplicar
por um número maior do que
, ou seja, por um número da forma
, é iterar
a operação de somar
, começando com
.
Assim, por exemplo, ; e
.
Lembrando a definição de , vemos que o produto
está definido indutivamente (ou recursivamente) pelas propriedades abaixo:
,
.
Que é exatamente a mesma definição recursiva que vimos acima!
Fermat, o cálculo integral, quadraturas de curvas e comunicação acadêmica
É lugar-comum que o Cálculo Diferencial e Integral foi “descoberto” simultânea e independentemente por Isaac Newton (1643-1727) e Leibniz (1646-1716), individualmente, cada um a seu modo, diante de problemas semelhantes, mas com interpretações, definições e nomenclaturas totalmente diferentes. Newton, como físico, estava preocupado inicialmente com os problemas de taxas de variação do movimento e Leibiniz buscava algo mais, digamos, “abstrato” e puramente matemático (baseado no problema da tangente em pontos de curvas, ao fim e ao cabo o mesmo problema da taxa de variação). Newton chamava a variação (ou, modernamente, a derivada) de “fluxões” e sua nomenclatura não sobreviveu (a de Leibniz era muito mais robusta).
Poucos sabem, inclusive, que as primeiras edições do “Método das Fluxões” de Newton citava Leibniz e seu método. Após a controvérsia do cálculo, obviamente nas edições posteriores isso foi retirado. O fato é que, por conta da falta de comunicação acadêmica na época (méados do século XVII), muitos cientistas e matemáticos estiveram bem próximos de ter o “insight” do Cálculo diferencial e integral, ou seja, juntar todos os pontos e apresentar algo geral que funcionasse para outros problemas de variação e áreas (que foi o que Newton e Leibiniz fizeram, apresentaram métodos para o problema das taxas de variação (tangentes) e de áreas sob curvas que poderiam ser generalizados para outros problemas semelhantes).

Pierre de Fermat (1601-1665) foi um deles. Este post mostra o quão perto ele estava de apresentar uma gênese do cálculo integral. Precisamos, claro, de contexto. Se você não quer saber do contexto histórico pode pular a parte do contexto.
Vamos lá… O problema de encontrar a área de uma forma plana fechada é conhecido por quadratura. A palavra refere-se à própria natureza da problema: expressar a área em termos de unidades de área, que são quadrados. É um problema que remonta aos gregos antigos e diversos nomes são famosos pelas soluções aproximadas do problema, como Arquimedes (e seu método da exaustão). Mas os gregos não consideravam o infinito como o consideramos hoje (e os paradoxos de Zenão são uma prova disto).
Por volta de 1600, processos infinitos foram introduzidos na matemática e, então, o problema da quadratura tornou-se meramente computacional. O próprio círculo, por esta época, já estava bem definido em relação à sua quadratura (ou área que envolvia a constante irracional
), mas a hipérbole era uma das curvas que resistia a todas as tentativas de quadratura.
A hipérbole é a curva obtida quando um cone é cortado por um plano num ângulo maior do que o ângulo existente entre a base do cone e o seu lado (daí o prefixo “hiper” significando “em excesso de”). O cone (e o corte) sendo algo como a figura abaixo:
Como resultado deste corte, a hipérbole fica com dois “ramos” separados e simétricos. O resultado é a imagem abaixo:
Percebe-se, pela imagem acima que a hipérbole tem um par de linhas retas associadas a ela, suas duas linhas tangentes no infinito. Quando se move ao longo de cada “ramo”, afastando-se do centro, nos aproximamos cada vez mais destas linhas, mas nunca a alcançamos. Essas linhas são as assíntotas da hipérbole. São, grosso modo, a manifestação geométrica do conceito de limite, base do cálculo diferencial e integral.
Os gregos trabalhavam com as curvas de um ponto de vista puramente geométrico, mas a invenção da geometria analítica por Descartes (1596-1650) fez com que o estudo dessas curvas se tornassem cada vez mais parte da álgebra. No lugar da curva em si, considera-se, então, a equação que relaciona as coordenadas x e y de um ponto da curva.

Cada uma das seções cônicas é um caso especial de uma equação quadrática (de segundo grau), cuja forma geral é: .
Para o círculo, por exemplo, temos que e
, assim chegamos, então, à equação
cujo gráfico é um círculo com centro na origem e raio 1 (o círculo unitário). A hipérbole mostrada na figura acima corresponde ao caso
e
; e sua equação é
, ou o equivalente
. Suas assíntotas são, portanto, os eixos x e y. Esse tipo de hiperbole é conhecida como hipérbole regular.
Finalizando o contexto, sabe-se que Arquimedes não conseguiu encontrar a quadratura da hipérbole pelo método da exaustão. Também o métodos dos indivisíveis não alcançou este objetivo, principalmente porque a hipérbole, ao contrário do círculo e da elipse, é uma curva que vai ao infinito, assim é preciso esclarecer o que queremos dizer por quadratura neste caso.
A figura abaixo mostra um ramo da hipérbole . No eixo dos x nós marcamos o ponto fixo
e o ponto arbitrário
. Por área sob a hipérbole queremos nos referir à área entre o gráfico de
, o eixo dos x e as linhas verticais (ordenadas)
e
. É claro que o valor numérico desta área ainda vai depender de nossa escolha de t, sendo, portanto, uma função de t. Vamos chamar essa função de
. O problema da quadratura da hipérbole resume-se a encontrar esta função, isto é, exprimir a área como uma fórmula envolvendo a variável t.
Vários matemáticos tentaram resolver este problema de forma independente (mais uma vez a falta de clareza na “comunicação acadêmica” – que sequer existia, atrapalhava a matemática e a ciência). Os mais destacados foram os já citados, Descartes e Fermat, além de Pascal (1623-1662). Os 3 são o grande triunvirato francês nos anos que antecederam a invenção do Cálculo infinitesimal (outro nome para o cálculo diferencial e integral). Fim do Contexto.
Fermat estava interessado na quadratura de curvas cuja equação geral é , onde n é um inteiro positivo. Essas curvas são às vezes chamadas de parábolas generalizadas (a própria parábola é o caso
). Ele fez a aproximação da área sob cada curva através de uma série de retângulos cujas bases formam uma progressão geométrica decrescente. Isto é muito semelhante ao método da exaustão de Arquimedes, mas ao contrário de seu predecessor, Fermat não evitou recorrer a uma série infinita. A figura abaixo mostra uma porção da curva
entre os pontos
e
no eixo dos x.

Fermat, então, imaginou o intervalo entre e
como sendo dividido em um número infinito de subintervalos pelos pontos …
, onde
. Então, começando em N e trabalhando no sentido inverso, para que esses intervalos formem uma P.G. decrescente, se tem
,
,
, e assim por diante, onde r é menor do que 1. Assim, as alturas (eixo das ordenadas y) da curva nesses pontos são
,
,
, etc. A partir daí é fácil encontrar a área de cada retângulo e então somar as áreas, usando a fórmula do somatório para uma série geométrica infinita. A fórmula resultante é:
(1) ,
onde o r subscrito em A indica que a área ainda depende de nossa escolha de . Fermat então raciocinou que, de modo a melhorar o encaixe entre os retângulos e a curva verdadeira, a largura de cada retângulo devia se tornar bem pequena como na figura abaixo:
Para conseguir isso, a proporção comum r deve se aproximar de 1, e quanto mais próxima, melhor o “encaixe” (e mais fácil a soma). Aliás, quando r → 1 (r tende ao valor 1), a equação 1 torna-se a expressão indeterminada 0/0. Fermat foi capaz de contornar essa dificuldade notando que o denominador da equação 1 acima, , pode ser escrito na forma fatorada, como
.
Quando o fator no numerador e no denominador é cancelado, a equação 1 torna-se:
Quando deixamos r → 1, cada parcela no denominador tende a 1, o que resulta na fórmula
(2)
Todo estudante de cálculo vai reconhecer a equação 2 como a integral . A famosa integral do monômio.
Fermat trabalhou nessa ideia em torno de 1640, cerca de 30 anos antes de Newton e Leibniz estabelecessem esta mesma fórmula como parte de seus respectivos cálculo diferencial e integral. Este trabalho representou MUITO porque conseguia a quadratura não apenas de uma curva, mas de toda uma família de curvas, aquelas fornecidas pela equação para valores inteiros, positivos de n.
Interessante notar que quando , a fórmula dá
o que é exatamente o resultado obtido por Arquimedes pelo método da exaustão para a a párabola.
O mais incrível de tudo é que, ao modificar ligeiramente seu procedimento, Fermat mostrou que a equação 2 permanece válida mesmo quando n é um inteiro negativo , desde que agora calculemos a área de x = a (onde a > 0) até o infinito. Quando n é um inteiro negativo, digamos (onde m é positivo), obtemos a família de curvas
, chamadas de hipérboles generalizadas. Que a fórmula de Fermat funcione mesmo nesse caso é notável, já que as equações
e
, apesar de sua aparente semelhança, representam tipos bem diferentes de curvas: as primeiras são contínuas em toda a parte, enquanto as últimas se tornam infinitas em
e em, portanto, possuem uma “quebra” (uma assíntota vertical) neste ponto.
Fermat ficou muito contente com sua descoberta porque ela permanecia válida mesmo quando a restrição de n ser positivo era removida. Mas, havia um problema. problema. A fórmula de Fermat falhava para uma curva da qual toda a família deriva o seu nome: a hipérbole . Isso ocorre porque para
, o denominador
na equação 2 se torna 0. A frustração de Fermat por não ser capaz de cobrir este caso tão importante deve ter sido grande, mas ele a escondeu atrás de palavras simples. “Eu digo que todas essas hipérboles infinitas, exceto a de Apolônio (a hipérbole
), ou a primeira, podem ser quadradas pelo método da progressão geométrica, de acordo com um procedimento geral e uniforme”.
Quem resolveu este problema foi um dos contemporâneos de Fermat, embora pouco conhecido. Grégoire de Saint-Vicent (1584-1667), um jesuíta belga que trabalhou a maior parte da vida trabalhando em problemas de quadratura.
Seu principal trabalho, Opus geometricum quadraturae circuli et sectionum coni (1647), foi compilado a partir de milhares de textos científicos que Saint-Vincent deixou para trás quando fugiu de Praga ante o avanço dos suecos em 1631. Eles foram resgatados por um colega e devolvidos ao autor dez anos depois. O atraso na publicação torna difícil estabelecer a primazia de Saint-Vincent com certeza absoluta, mas parece que ele foi o primeiro a notar que, quando , os retângulos usados na aproximação da área sob a hipérbole possuem, todos, áreas iguais.
A imagem abaixo mostra que que, conforme a distância de 0 cresce geometricamente, as áreas correspondentes crescem em incrementos iguais — ou seja, aritmeticamente — e isso continua sendo verdade mesmo ao passarmos ao limite quando r → 1 (ou seja, quando fazemos a transição dos retângulos discretos para a hipérbole contínua). Mas isso, por sua vez, implica que a relação entre a área e a distância é logarítmica. Mais precisamente, se denotarmos por A(t) a área sob a hipérbole, a partir de um ponto de referência fixo x > 0 (por conveniência geralmente escolhemos x = 1) até um ponto variável x = t, teremos A(t) = log t. Um dos alunos de Saint-Vincent, Alfonso Anton de Sarasa (1618-1667), escreveu essa relação explicitamente registrando uma das primeiras ocasiões em que se fez o uso de uma função logarítmica (que eram, prioritariamente, uma ferramenta usada para cálculos complexos).

Assim, a quadratura da hipérbole foi finalmente conseguida cerca de dois mil anos depois dos gregos, que primeiro enfrentaram o problema.
Portanto, quando Newton e Leibniz se debruçaram sobre os problemas que os levariam independepentemente à invenção da mais incrível e impressionante ferramenta científica de todos os tempos (o Cálculo diferencial e integral), as princiais ideias por trás do Cálculo já eram razoavelmente bem conhecidas pela comunidade matemática. O método dos indivisíveis, embora repousando em uma base incerta, tinha sido aplicado com sucesso a um conjunto de curvas e sólidos; e o método da exaustão de Arquimedes, em sua forma moderna, revisada, resolvera a quadratura da família de curvas . Embora esses métodos fossem bem-sucedidos, eles ainda não estavam reunidos em um sistema único; cada problema exigia uma abordagem diferente e o sucesso dependia da engenhosidade geométrica, habilidades com a álgebra e uma boa dose de sorte. O que se precisava era de um procedimento geral e sistemático — um conjunto de algoritmos — que permitiriam resolver esses problemas com facilidade e eficiência. Este procedimento foi fornecido por Newton e Leibniz.
O ponto é que estas ferramentas teriam sido desenvolvidas, provavelmente, muito antecipadamente se existisse, à época, como hoje, uma comunidade científica vibrante e atuante, com publicações compartilhadas e o conhecimento sendo construído sobre as bases do que se fez anteriormente.
Problemas de comunicação e, principalmente, locomoção e transporte, claro, afetavam isso, mas a postura de cientistas, como Newton que guardavam para si seus trabalhos (ele só publicou o seu cálculo quando viu que o que Leibniz havia publicado era, ao fim e ao cabo, o mesmo que ele tinha conseguido), atrapalhavam bastante o avanço das contribuições.
O que teria acontecido (sabe-se que a revolução científica, de fato, começou com a invenção do cálculo e a normatização do método científico) se a comunicação acadêmica tivesse levado à invenção do cálculo alguns anos, décadas ou séculos antes do que o que realmente o foi?
…
Nunca saberemos. Mas, gosto de pensar que os avanços científicos teriam sido acelerados enormemente…
Guia de configuração do ambiente de desenvolvimento nativo do Windows para usuários do Linux
Encontrei um repositório excelente com um Guia de configuração do ambiente de desenvolvimento nativo do Windows para usuários do Linux. As dicas são muito interessantes e valem para todos os developers.
Segue o link: https://github.com/rkitover/windows-dev-guide
Enjoy!
quarta-feira, 17 novembro, 2021 at 12:34 pm Deixe um comentário
Renomeando diversos arquivos retirando partes deles no Mac (ou Linux)
Enfrentei um problema ao me deparar com uma cópia de múltiplos (centenas) de arquivos realizada de um hd externo com conteúdo em NTFS para outro em FAT32 que estava em um Mac. Usei um utilitário chamado WinSCP (copiava através de uma máquina com um Windows 10 via SSH).
Os arquivos foram corretamente copiados, mas, ao final, centenas deles (por não terem sido possível de serem renomeados pelo WinSCP) ficaram com o nome original acrescido de .filepart o que inutilizava o reconhecimento dos arquivos por programas que os abriam (ou os reconheciam) diretamente.
Precisei portanto resolver esse problema: renomear várias centenas de arquivos para o nome original SEM o .filepart. Por exemplo:
O arquivo lista_nomes.txt.filepart deveria ser renomeado para somente lista_nomes.txt (que é, ao fim e ao cabo o seu nome original — antes da cópia).
Existem diversas abordagens. Poderia, claro, escrever um script em shell fazendo um laço e, via regex, alterar os nomes individualmente. Quando comecei a codificar vi que levaria um tempo que eu não queria gastar. Então pensei em fazer um script em Python para fazer o mesmo, mas, ainda assim, queria algo ainda mais rápido e, de preferência, SEM escrever um script.
Foi então que lembrei de um comando MARAVILHOSO do Linux em que podemos usar REGEX para alterar nomes: o rename.
O comando rename dá muito mais poder e controle ao usuário na hora de renomear arquivos do que o o mv (normalmente usado para tal — que além de renomear, MOVE arquivos entre diretórios).
Muitas distros já vem com o comando instalado por padrão. Caso a sua não venha (no Mac, que é mais Unix e BSD do que Linux, não vem instalado. Nem no WSL2, diga-se), você pode instalar rapidamente usando o gerenciador de pacotes padrão da distro.
Como eu estava acessando um Mac, usei o brew (caso não conheça esse maravilhoso gerenciador de pacotes para Mac, clique aqui). Depois de instalado a coisa ficou muito mais simples. Supondo que você está no diretório em que deseja alterar os nomes dos arquivos, faça:
rename 's/.filepart//;' *

Simples. Basta isso. Com isso eu renomeei mais de 300 arquivos que estavam com o sufixo .filepart para o seu nome real SEM o sufixo.
Para mais detalhes sobre o rename, digite man rename no terminal ou clique aqui.
Linux é vida!
Se você usa Windows 10, jamais esqueça de explorar o WSL2. Está redondo e bonito! E, principalmente, FUNCIONAL. (daria para ter feito O MESMO rodando o WSL).
quarta-feira, 27 janeiro, 2021 at 5:21 pm Deixe um comentário







Comentários