Qual algoritmo é mais preciso para calcular a soma de uma matriz classificada de números?

22

Dada é uma sequência finita crescente de números positivos . Qual dos dois algoritmos a seguir é melhor para calcular a soma dos números?z1,z2,.....zn

s=0; 
for \ i=1:n 
    s=s + z_{i} ; 
end

Ou:

s=0; 
for \ i=1:n 
s=s + z_{n-i+1} ; 
end

Na minha opinião, seria melhor começar a adicionar os números do maior para o menor, porque o erro é cada vez menor. Também sabemos que, quando adicionamos um número muito grande a um número muito pequeno, o resultado aproximado pode ser o número grande.

Isso está correto? O que mais pode ser dito?

CryptoBeginner
fonte

Respostas:

18

A adição de números arbitrários de ponto flutuante geralmente gera algum erro de arredondamento, e o erro de arredondamento será proporcional ao tamanho do resultado. Se você calcular uma soma única e começar adicionando os números maiores primeiro, o resultado médio será maior. Então você começaria a adicionar com os menores números.

Mas você obtém um melhor resultado (e ele corre mais rápido) se você produzir quatro somas, por exemplo: Comece com sum1, sum2, sum3, sum4 e adicione quatro elementos de matriz, por sua vez, para sum1, sum2, sum3, sum4. Como cada resultado é, em média, apenas 1/4 da soma original, seu erro é quatro vezes menor.

Melhor ainda: adicione os números em pares. Em seguida, adicione os resultados em pares. Adicione esses resultados em pares novamente e assim por diante até que você tenha dois números para adicionar.

Muito simples: use maior precisão. Use long double para calcular uma soma de duplas. Use double para calcular uma soma de carros alegóricos.

Quase perfeito: procure o algoritmo de Kahan, descrito anteriormente. Melhor ainda usado adicionando começando com o menor número.

gnasher729
fonte
26

São números inteiros ou números de ponto flutuante? Supondo que seja ponto flutuante, eu usaria a primeira opção. É melhor adicionar números menores entre si e depois adicionar números maiores mais tarde. Com a segunda opção, você acaba adicionando um número pequeno a um número grande à medida que aumenta, o que pode levar a problemas. Aqui está um bom recurso sobre aritmética de ponto flutuante: O que todo cientista da computação deve saber sobre aritmética de ponto flutuante

Kurt
fonte
24

A resposta de animal_magic está correta em que você deve adicionar os números do menor para o maior, no entanto, quero dar um exemplo para mostrar o porquê.

Suponha que estamos trabalhando em um formato de ponto flutuante que nos fornece três dígitos impressionantes de precisão. Agora queremos adicionar dez números:

[1000, 1, 1, 1, 1, 1, 1, 1, 1, 1]

É claro que a resposta exata é 1009, mas não podemos obtê-la em nosso formato de 3 dígitos. Arredondando para 3 dígitos, a resposta mais precisa que obtemos é 1010. Se adicionarmos o menor ao maior, em cada loop, obtemos:

Loop Index        s
1                 1
2                 2
3                 3
4                 4
5                 5
6                 6
7                 7
8                 8
9                 9
10                1009 -> 1010

Portanto, obtemos a resposta mais precisa possível para o nosso formato. Agora vamos supor que adicionamos do maior para o menor.

Loop Index        s
1                 1000
2                 1001 -> 1000
3                 1001 -> 1000
4                 1001 -> 1000
5                 1001 -> 1000
6                 1001 -> 1000
7                 1001 -> 1000
8                 1001 -> 1000
9                 1001 -> 1000
10                1001 -> 1000

Como os números de ponto flutuante são arredondados após cada operação, todas as adições são arredondadas, aumentando nosso erro de 1 para 9 do exato. Agora imagine se o seu conjunto de números a adicionar tivesse 1000 e, em seguida, cem 1 ou um milhão. Observe que, para ser realmente preciso, você deseja somar os dois números menores e, em seguida, recorrer o resultado ao seu conjunto de números.

Godric Seer
fonte
15

Para o caso geral, eu usaria a soma compensada (ou a soma Kahan). A menos que os números já estejam classificados, classificá-los será muito mais caro do que adicioná-los . O somatório compensado também é mais preciso que o somatório classificado ou o somatório ingênuo (consulte o link anterior).

Quanto às referências, o que todo programador deve saber sobre aritmética de ponto flutuante abrange os pontos básicos em detalhes suficientes para que alguém possa lê-lo em 20 (+/- 10) minutos e entender o básico. "O que todo cientista da computação deve saber sobre aritmética de ponto flutuante" de Goldberg é a referência clássica, mas a maioria das pessoas que conheço que recomendam que o artigo não o tenha lido em detalhes por conta própria, porque tem cerca de 50 páginas (mais do que isso, em alguns impressos) e escritos em prosa densa, por isso tenho dificuldade em recomendar isso como referência de primeira linha para as pessoas. É bom dar uma segunda olhada no assunto. Uma referência enciclopédica é a Precisão e Estabilidade de Algoritmos Numéricos de Higham, que abrange esse material, bem como o acúmulo de erros numéricos em muitos outros algoritmos; também tem 680 páginas, então também não examinaria essa referência primeiro.

Geoff Oxberry
fonte
2
Para completar, no livro de Higham, você encontrará a resposta para a pergunta original na página 82 : a ordem crescente é a melhor. Há também uma seção (4.6) discutindo a escolha do método.
Federico Poloni 23/02
7

As respostas anteriores já discutem o assunto em geral e dão bons conselhos, mas há uma peculiaridade adicional que eu gostaria de mencionar. Na maioria das arquiteturas modernas, o forloop que você descreveu seria executado de qualquer maneira na precisão estendida de 80 bits , o que garante precisão adicional, pois todas as variáveis ​​temporárias serão colocadas nos registros. Então você já tem alguma forma de proteção contra erros numéricos. No entanto, em loops mais complicados, os valores intermediários serão armazenados na memória entre as operações e, portanto, truncados para 64 bits. eu acho que

s=0; 
for \ i=1:n 
    printf("Hello World");
    s=s + z_{i} ; 
end

basta obter menor precisão no seu somatório (!!). Portanto, tenha muito cuidado se quiser imprimir o código-debug enquanto verifica a precisão.

Para os interessados, este artigo descreve um problema em uma rotina numérica amplamente usada (fatoração QR que revela a classificação de Lapack) cuja depuração e análise foram muito complicadas justamente por causa desse problema.

Federico Poloni
fonte
1
As máquinas mais modernas são de 64 bits e usam unidades SSE ou AVX, mesmo para operações escalares. Essas unidades não suportam aritmética de 80 bits e usam a mesma precisão interna dos argumentos da operação. O uso da FPU x87 é geralmente desencorajado agora e a maioria dos compiladores de 64 bits precisa de opções especiais para ser forçada a usá-lo.
Hristo Iliev
1
@HristoIliev Obrigado pelo comentário, eu não sabia disso!
Federico Poloni
4

Das 2 opções, adicionar de menor a maior produzirá menos erro numérico do que adicionar de maior a menor.

No entanto, há mais de 20 anos, na minha aula de "Métodos Numéricos", o instrutor declarou isso e me ocorreu que isso ainda estava introduzindo mais erros do que o necessário, devido à diferença relativa de valor entre o acumulador e os valores que estavam sendo adicionados.

Logicamente, uma solução preferível é adicionar os 2 menores números da lista e, em seguida, reinserir o valor somado na lista classificada.

Para demonstrá-lo, elaborei um algoritmo que poderia fazer isso de forma eficiente (no espaço e no tempo) usando o espaço liberado quando os elementos foram removidos da matriz primária para criar uma matriz secundária dos valores somados que eram inerentemente ordenados desde as adições eram das somas de valores que estavam sempre aumentando. Em cada iteração, as "dicas" de ambas as matrizes são verificadas para encontrar os 2 menores valores.

user3054301
fonte
2

Como você não restringiu o tipo de dados a ser usado, para obter um resultado perfeitamente preciso, basta usar números arbitrários de comprimento ... nesse caso, o pedido não será importante. Será muito mais lento, mas obter a perfeição leva tempo.

SingleStepper
fonte
0

Use a adição da árvore binária, ou seja, escolha a média da distribuição (número mais próximo) como a raiz da árvore binária e crie uma árvore binária classificada adicionando valores menores à esquerda do gráfico e valores maiores à direita e assim por diante . Adicione todos os nós filhos de um único pai recursivamente em uma abordagem de baixo para cima. Isso será eficiente, pois o erro médio aumenta com o número de somas e, em uma abordagem de árvore binária, o número de somas está na ordem do log n na base 2. Portanto, o erro médio seria menor.

Ullas Kashyap
fonte
É o mesmo que adicionar pares adjacentes na matriz original (uma vez que é classificada). Não há razão para colocar todos os valores na árvore.
Godric Seer
0

O que Hristo Iliev disse acima sobre os compiladores de 64 bits que preferem as instruções SSE e AVX sobre o FPU (AKA NDP) é absolutamente verdadeiro, pelo menos para o Microsoft Visual Studio 2013. No entanto, para as operações de ponto flutuante de precisão dupla que eu estava usando, encontrei na verdade, é mais rápido e, em teoria, mais preciso, usar a FPU. Se for importante para você, sugiro testar várias soluções primeiro, antes de escolher uma abordagem final.

Ao trabalhar em Java, frequentemente utilizo o tipo de dados BigDecimal de precisão arbitrária. É muito fácil e geralmente não se nota a diminuição da velocidade. O cálculo das funções transcendentais com séries infinitas e sqrt usando o método de Newton pode levar um milissegundo ou mais, mas é factível e bastante preciso.

CElliott
fonte
0

Eu só deixei isso aqui /programming//a/58006104/860099 (quando você for lá, clique em 'show snippet de código' e execute-o por botão

É um exemplo de JavaScript que mostra claramente que a soma da maior dá um erro maior

arr=[9,.6,.1,.1,.1,.1];

sum     =             arr.reduce((a,c)=>a+c,0);  // =  9.999999999999998
sortSum = [...arr].sort().reduce((a,c)=>a+c,0);  // = 10

console.log('sum:     ',sum);
console.log('sortSum:',sortSum);
Kamil Kiełczewski
fonte
As respostas somente para links são desencorajadas neste site. Você pode explicar o que é fornecido no link?
nicoguaro
@nicoguaro Eu atualizo a resposta - todas as respostas são muito boas, mas aqui está um exemplo concreto
Kamil Kiełczewski 19/09/09