Se você está procurando um bom limite para o seu erro de arredondamento, não precisa necessariamente de uma biblioteca de precisão arbitrária. Você pode usar a análise de erro em execução.
Não consegui encontrar uma boa referência on-line, mas está tudo descrito na Seção 3.3 do livro de Nick Higham "Exatidão e estabilidade de algoritmos numéricos". A ideia é bastante simples:
- Re-fatore seu código para que você tenha uma única atribuição de uma única operação aritmética em cada linha.
- Para cada variável, por exemplo
x
, crie uma variável x_err
que seja inicializada com zero quando x
for atribuída uma constante.
- Para cada operação, por exemplo
z = x * y
, atualize a variável z_err
usando o modelo padrão da aritmética de ponto flutuante e os z
erros resultantes e em execução x_err
e y_err
.
- O valor de retorno da sua função também deve ter um
_err
valor respectivo anexado. Este é um limite dependente de dados no seu erro total de arredondamento.
A parte complicada é a etapa 3. Para as operações aritméticas mais simples, você pode usar as seguintes regras:
z = x + y
-> z_err = u*abs(z) + x_err + y_err
z = x - y
-> z_err = u*abs(z) + x_err + y_err
z = x * y
-> z_err = u*abs(z) + x_err*abs(y) + y_err*abs(x)
z = x / y
-> z_err = u*abs(z) + (x_err*abs(y) + y_err*abs(x))/y^2
z = sqrt(x)
-> z_err = u*abs(z) + x_err/(2*abs(z))
onde u = eps/2
é o arredondamento da unidade. Sim, as regras +
e -
são as mesmas. As regras para qualquer outra operação op(x)
podem ser facilmente extraídas usando a expansão da série Taylor do resultado aplicado op(x + x_err)
. Ou você pode tentar pesquisar no Google. Ou usando o livro de Nick Higham.
Como exemplo, considere o seguinte código Matlab / Octave que avalia polinômios nos coeficientes a
em um ponto x
usando o esquema Horner:
function s = horner ( a , x )
s = a(end);
for k=length(a)-1:-1:1
s = a(k) + x*s;
end
Para a primeira etapa, dividimos as duas operações em s = a(k) + x*s
:
function s = horner ( a , x )
s = a(end);
for k=length(a)-1:-1:1
z = x*s;
s = a(k) + z;
end
Em seguida, apresentamos as _err
variáveis. Observe que as entradas a
e x
são consideradas exatas, mas também podemos exigir que o usuário transmita valores correspondentes para a_err
e x_err
:
function [ s , s_err ] = horner ( a , x )
s = a(end);
s_err = 0;
for k=length(a)-1:-1:1
z = x*s;
z_err = ...;
s = a(k) + z;
s_err = ...;
end
Por fim, aplicamos as regras descritas acima para obter os termos do erro:
function [ s , s_err ] = horner ( a , x )
u = eps/2;
s = a(end);
s_err = 0;
for k=length(a)-1:-1:1
z = x*s;
z_err = u*abs(z) + s_err*abs(x);
s = a(k) + z;
s_err = u*abs(s) + z_err;
end
Observe que, como não temos a_err
ou x_err
, por exemplo, eles são assumidos como zero, os respectivos termos são simplesmente ignorados nas expressões de erro.
Et voilà! Agora temos um esquema Horner que retorna uma estimativa de erro dependente de dados (nota: esse é um limite superior do erro) ao lado do resultado.
Como observação lateral, como você está usando C ++, considere criar sua própria classe para valores de ponto flutuante, que carrega o _err
termo e sobrecarrega todas as operações aritméticas para atualizar esses valores, conforme descrito acima. Para códigos grandes, essa pode ser a rota mais fácil, embora computacionalmente menos eficiente. Dito isto, você poderá encontrar essa classe online. Uma rápida pesquisa no Google me deu esse link .
PS Observe que tudo isso funciona apenas em máquinas que cumprem estritamente a IEEE-754, ou seja, todas as operações aritméticas são precisas para . Essa análise também fornece um limite mais preciso e realista do que a aritmética do intervalo, pois, por definição, você não pode representar um número em ponto flutuante, ou seja, seu intervalo seria arredondado para o próprio número.± ux ( 1 ± u )