Desenvolva um código em Fortran que seja capaz de realizar
a soma do número 0,00001 por 100.000 vezes, empregando precisão simples e também precisão dupla.

Passo 1

O que o enunciado pede e quais dados temos:
  • Pede para desenvolver um programa em Fortran que realize a soma de \,0{,}00001 por 100\,000 vezes.
  • Deve-se fazer isso usando precisão simples e também precisão dupla.
  • Resultado matemático exato esperado: somar \,0{,}00001 100\,000 vezes deveria resultar em \,1{,}0.
  • Precisamos apresentar um código que efetue as duas somas (single e double), imprima os resultados e permita comparar o erro em cada caso.

Passo 2

Decisão do método de implementação (por que usar esse método):
  • Método escolhido: um loop simples que acumula um valor de incremento em um acumulador. Esse é o método direto que se pedirá na prova: repetir N vezes a operação de soma.
  • Por que não outro método mais complexo? O enunciado pede explicitamente a operação repetida; além disso, isso evidencia os efeitos de precisão (erro de arredondamento) ao longo de muitas somas, que é o objetivo didático.
  • Observação: existe um método para reduzir erro de soma chamado soma de Kahan (ou algoritmo de compensação). Vou mencionar e explicar, mas o código principal será o loop direto (e ao final mostro como incluir Kahan opcionalmente).

Passo 3

Conceitos numéricos importantes (explicação didática, sem pular etapas)
  • Representação em ponto flutuante: computadores representam números reais com um número finito de bits para mantissa e expoente. Isso gera arredondamento.
  • Precisão simples e dupla:
    • Precisão simples guarda aproximadamente 6–9 dígitos decimais significativos (mantissa ~24 bits).
    • Precisão dupla guarda aproximadamente 15–17 dígitos decimais significativos (mantissa ~53 bits).
  • Efeito prático: quando se soma repetidamente pequenos incrementos, os arredondamentos em cada operação podem acumular um erro.
  • Machine epsilon: para um tipo real x em Fortran, a função intrínseca \epsilon(x) retorna a menor diferença relativa entre 1 e o próximo número representável. Isso dá uma ideia da resolução do formato.
  • Objetivo prático: implementar o loop em dois tipos (single e double), imprimir soma, diferença em relação a 1{,}0 e os valores de \epsilon para comparar.

Passo 4

Como representar precisão simples e dupla em Fortran (detalhes e recomendações)
  • Usar a função intrínseca selected_real_kind para escolher kinds portáveis:
    • selected_real_kind(p=6) normalmente dá um real de precisão simples suficiente.
    • selected_real_kind(p=15) normalmente dá um real de precisão dupla.
  • Definir parâmetros:
    • integer, parameter :: sp = selected_real_kind(p=6)
    • integer, parameter :: dp = selected_real_kind(p=15)
  • Declarar variáveis com real(kind=sp) e real(kind=dp).
  • Literais com sufixo de kind: ex.: 1.0e-5_sp ou 1.0e-5_dp (após declarar sp e dp).

Passo 5

Estrutura do algoritmo e cuidados de implementação (passo a passo do que o código fará)
  1. Declarar constantes: N = 100\,000, sp, dp.
  2. Declarar acumuladores:
    • s_sp inicializado com \,0{,}0 na precisão simples.
    • s_dp inicializado com \,0{,}0 na precisão dupla.
  3. Declarar incrementos:
    • inc_sp = 1.0e-5_sp
    • inc_dp = 1.0e-5_dp
  4. Loop do i=1,N:
    • s_sp = s_sp + inc_sp
    • s_dp = s_dp + inc_dp
  5. Após o loop, imprimir:
    • Valor de s_sp e s_dp.
    • Diferença s_sp - 1.0_sp e s_dp - 1.0_dp.
    • epsilon(1.0_sp) e epsilon(1.0_dp) para ver a precisão de cada tipo.
  6. Observação sobre portabilidade: selected_real_kind torna o código menos dependente de real*4 ou real*8.

Passo 6

Explicação linha a linha (ensinando a ler e entender o código sem pular passos)
  • program soma_preciso: inicia o programa.
  • implicit none: força declaração explícita de variáveis (boa prática).
  • Declarações dos parameter:
    • sp e dp obtêm os kinds adequados.
    • N é o número de repetições: 100\,000.
  • Variáveis:
    • i contador inteiro.
    • inc_sp, s_sp são real(kind=sp).
    • inc_dp, s_dp são real(kind=dp).
  • Inicializações:
    • inc_sp = 1.0e-5_sp significa o literal \,0{,}00001 na precisão simples.
    • s_sp = 0.0_sp inicializa o acumulador com zero no mesmo kind.
  • Loop do efetua exatamente N adições. Cada adição é uma operação em ponto flutuante com arredondamento.
  • Impressões: usamos print * para saída legível; mostramos também o erro relativo/absoluto subtraindo 1{,}0.
  • Finaliza com end program.

Passo 7

Comentário sobre melhoria opcional (soma de Kahan)
  • Se for necessário reduzir o erro de acumulação sem usar dupla precisão, a soma de Kahan é uma técnica simples:
    • Mantém um valor de compensação c.
    • A cada soma, corrige y = inc - c, t = s + y, c = (t - s) - y, s = t.
  • Essa técnica reduz sensivelmente erro de cancelamento em somas de muitos termos.
  • Vou deixar um comentário no código indicando como ativar Kahan, caso queira comparar resultados.

Passo 8

Como compilar e executar (passo a passo)
  • Salve o código em arquivo, por exemplo soma_precisao.f90.
  • Compile com um compilador Fortran moderno, por exemplo gfortran:
    • gfortran -O2 -o soma_precisao soma_precisao.f90
  • Execute:
    • ./soma_precisao
  • A saída mostrará os dois somatórios, os erros e as epsilons. Compare s_sp e s_dp com 1.0.

Resposta

program soma_precisao
implicit none
integer, parameter :: sp = selected_real_kind(p=6)
integer, parameter :: dp = selected_real_kind(p=15)
integer, parameter :: N = 100000
integer :: i
real(kind=sp) :: inc_sp, s_sp
real(kind=dp) :: inc_dp, s_dp
! Inicializações
inc_sp = 1.0e-5_sp
inc_dp = 1.0e-5_dp
s_sp = 0.0_sp
s_dp = 0.0_dp
! Loop de acumulação simples
do i = 1, N
s_sp = s_sp + inc_sp
s_dp = s_dp + inc_dp
end do
! Impressão dos resultados e erros
print *, 'Resultados da soma de 0.00001 repetida ‘, N, ’ vezes:’
print *, ’ Single precision (real(kind=sp)) : ', s_sp
print *, ’ Double precision (real(kind=dp)) : ', s_dp
print *, ‘Erros absolutos (valor - 1.0):’
print *, ’ Erro single : ', s_sp - 1.0_sp
print *, ’ Erro double : ', s_dp - 1.0_dp
print *, ‘Machine epsilon:’
print *, ’ epsilon(1.0_sp) = ', epsilon(1.0_sp)
print *, ’ epsilon(1.0_dp) = ', epsilon(1.0_dp)
! Observação: para reduzir erro em single sem usar double, pode-se usar
! a soma de Kahan (não implementada por padrão). Exemplo de Kahan:
! real(kind=sp) :: c, y, t
! c = 0.0_sp; s_sp = 0.0_sp
! do i=1,N
! y = inc_sp - c
! t = s_sp + y
! c = (t - s_sp) - y
! s_sp = t
! end do
end program soma_precisao

Exercícios relacionados