정렬 된 숫자 배열의 합을 계산하는 데 더 정확한 알고리즘은 무엇입니까?


22

양수 유한 시퀀스가 ​​증가 합니다. 다음 두 알고리즘 중 숫자의 합을 계산하는 데 더 적합한 알고리즘은 무엇입니까?z1,z2,.....zn

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

또는:

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

제 생각에는 오류가 점점 작아지기 때문에 가장 큰 숫자부터 가장 작은 숫자로 숫자를 추가하는 것이 좋습니다. 또한 매우 작은 숫자에 매우 큰 숫자를 추가하면 대략적인 결과가 큰 숫자 일 수 있습니다.

이 올바른지? 다른 무엇을 말할 수 있습니까?

답변:


18

임의의 부동 소수점 숫자를 추가하면 일반적으로 반올림 오류가 발생하며 반올림 오류는 결과 크기에 비례합니다. 단일 합계를 계산하고 가장 큰 숫자를 먼저 추가하여 시작하면 평균 결과가 더 커집니다. 따라서 가장 작은 숫자를 추가하기 시작합니다.

그러나 sum1, sum2, sum3, sum4로 시작하고 sum1, sum2, sum3, sum4에 4 개의 배열 요소를 추가하면 4 개의 합계를 생성하면 더 나은 결과를 얻을 수 있습니다. 각 결과는 원래 합계의 평균 1/4 수준이므로 오차가 4 배 작습니다.

더 나은 방법 : 숫자를 쌍으로 추가하십시오. 그런 다음 결과를 쌍으로 추가하십시오. 추가 할 두 숫자가 남을 때까지 결과를 쌍으로 다시 추가하십시오.

매우 간단 : 더 높은 정밀도를 사용하십시오. long double을 사용하여 double의 합을 계산하십시오. float의 합을 계산하려면 double을 사용하십시오.

완벽에 가까움 : 앞에서 설명한 Kahan의 알고리즘을 찾아보십시오. 가장 작은 숫자로 시작하여 가장 잘 사용됩니다.


26

이 정수 또는 부동 소수점 숫자입니까? 부동 소수점이라고 가정하면 첫 번째 옵션을 사용합니다. 더 작은 숫자를 서로 더한 다음 나중에 더 큰 숫자를 추가하는 것이 좋습니다. 두 번째 옵션을 사용하면 같은 큰 수를 작은 수를 추가하게 될 겁니다 내가 문제가 발생할 수 증가. 부동 소수점 산술에 대한 유용한 자료는 다음과 같습니다. 모든 컴퓨터 과학자가 부동 소수점 산술에 대해 알아야 할 사항


24

animal_magic의 대답은 가장 작은 숫자에서 가장 큰 숫자를 더해야하지만, 그 이유를 보여주는 예제를 제시하고 싶습니다.

우리가 3 자리의 정확도를 제공하는 부동 소수점 형식으로 작업한다고 가정합니다. 이제 10 개의 숫자를 추가하려고합니다.

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

물론 정답은 1009이지만 3 자리 형식으로는 얻을 수 없습니다. 3 자리로 반올림하면 가장 정확한 답은 1010입니다. 가장 작은 것을 더 크게하면 각 루프에서 다음을 얻습니다.

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

따라서 우리는 형식에 대해 가장 정확한 답변을 얻습니다. 이제 우리는 가장 큰 것에서 가장 작은 것으로 추가한다고 가정하자.

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

부동 소수점 숫자는 각 연산 후에 반올림되므로 모든 추가가 반올림되어 오차가 정확히 1에서 9로 증가합니다. 이제 더할 숫자 집합에 1000이 있고 100이 1 또는 백만인지 상상해보십시오. 실제로 정확하려면 가장 작은 두 숫자를 합한 다음 결과를 숫자 집합에 의지해야합니다.


15

일반적인 경우에는 보상 합산 (또는 Kahan 합산)을 사용합니다. 숫자가 이미 정렬되어 있지 않으면 정렬하는 것이 추가하는 것보다 훨씬 비쌉니다 . 보상 된 합산은 정렬 된 합산 또는 순진 합산보다 더 정확합니다 (이전 링크 참조).

참고로, 모든 프로그래머가 부동 소수점 산술에 대해 알아야 할 사항은 20 분 안에이를 읽고 기본 사항을 이해할 수있는 기본 사항을 충분히 자세하게 다룹니다. "골드버그 (Goldberg)의"모든 부동 소수점 산술에 대해 모든 컴퓨터 과학자들이 알아야 할 것 "은 고전적인 참고 자료이지만, 내가 아는 사람들은 대부분 50 페이지가 넘기 때문에 논문을 자세히 읽지 않았다는 것을 알고 있습니다. 인쇄), 그리고 조밀 한 산문으로 작성되었으므로 사람들을 위해 일선 참조로 추천하는 데 어려움이 있습니다. 주제를 다시 살펴 보는 것이 좋습니다. 백과 사전 참조는 수치 알고리즘 의 Higham의 정확성과 안정성입니다이 자료는 많은 다른 알고리즘에서 수치 오류의 축적뿐만 아니라이 자료를 다루고 있습니다. 또한 680 페이지 이므로이 참조를 먼저 보지 않을 것입니다.


2
완성도를 높이기 위해 Higham의 책에서 82 페이지 의 원래 질문에 대한 답변을 찾을 수 있습니다. 순서가 증가하는 것이 가장 좋습니다. 방법의 선택을 논의하는 섹션 (4.6)도 있습니다.
Federico Poloni 2019

7

이전 답변은 이미 문제를 크게 논의하고 건전한 조언을 제공하지만 언급하고 싶은 추가 단점이 있습니다. 대부분의 최신 아키텍처에서는 for설명 된 루프가 80 비트 확장 정밀도 로 수행 되므로 모든 임시 변수가 레지스터에 배치되므로 추가 정확도가 보장됩니다. 따라서 이미 숫자 오류로부터 보호 할 수있는 형태가 있습니다. 그러나 더 복잡한 루프에서는 중간 값이 작업 사이의 메모리에 저장되므로 64 비트로 잘립니다. 내가 추측 컨대

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

요약에서 정밀도를 낮추기에 충분합니다 (!!). 따라서 정확성을 확인하면서 코드를 printf-debug하려는 경우 매우주의하십시오.

관심을 끌기 위해이 백서 는이 문제로 인해 디버깅 및 분석이 매우 까다로운 널리 사용되는 수치 루틴 (Lapack의 순위 표시 QR 인수 분해)의 문제점을 설명합니다.


1
대부분의 최신 컴퓨터는 64 비트이며 스칼라 작업에도 SSE 또는 AVX 장치를 사용합니다. 이러한 단위는 80 비트 산술을 지원하지 않으며 연산 인수와 동일한 내부 정밀도를 사용합니다. x87 FPU의 사용은 일반적으로 권장되지 않으며 대부분의 64 비트 컴파일러는이를 사용하기 위해 특별한 옵션이 필요합니다.
Hristo Iliev

1
@HristoIliev 댓글 주셔서 감사합니다, 나는 이것을 몰랐다!
Federico Poloni

4

두 가지 옵션 중 더 작은 값에서 큰 값을 추가하면 수치 오류가 줄어들고 더 큰 숫자에서 작은 값이 추가됩니다.

그러나 20 년 전 나의 "Numerical Methods"클래스에서 강사는 이것을 언급했으며, 누적 기와 추가되는 값 사이의 값의 상대적인 차이로 인해 이것이 여전히 필요한 것보다 더 많은 오류를 발생시키는 것으로 나타났습니다.

논리적으로 바람직한 해결책은 목록에서 가장 작은 2 개의 숫자를 추가 한 다음 합산 된 값을 정렬 된 목록에 다시 삽입하는 것입니다.

이를 입증하기 위해 요소를 기본 배열에서 제거 할 때 여유 공간을 사용하여 공간 및 시간에 효율적으로 수행 할 수있는 알고리즘을 연구하여 추가 이후 본질적으로 정렬 된 합산 값의 보조 배열을 작성했습니다. 항상 증가하고있는 가치의 합이었습니다. 각 반복에서 두 배열의 "팁"을 검사하여 가장 작은 2 개의 값을 찾습니다.


2

사용할 데이터 유형을 제한하지 않았으므로 완벽하게 정확한 결과를 얻으려면 임의의 길이를 사용하십시오.이 경우 순서는 중요하지 않습니다. 속도는 훨씬 느리지 만 완벽을 얻는 데 시간이 걸립니다.


0

이진 트리 덧셈을 사용하십시오. 즉, 이진 트리의 루트로 분포의 평균 (가장 가까운 숫자)을 선택하고 그래프 왼쪽에 적은 값을, 오른쪽에 큰 값을 더하여 정렬 된 이진 트리를 만듭니다. . 상향식 접근 방식에서 단일 상위의 모든 하위 노드를 재귀 적으로 추가합니다. 이것은 합산 횟수에 따라 평균 오차가 증가하고 이진 트리 접근 방식에서 합산 ​​횟수는 기수 2의 로그 n 순서로되어 있으므로 효율적입니다. 따라서 평균 오차는 더 적습니다.


이것은 원래 배열에 인접한 쌍을 추가하는 것과 같습니다 (정렬되었으므로). 모든 값을 트리에 넣을 이유가 없습니다.
Godric Seer

0

Hristo Iliev가 FPU (AKA NDP)보다 SSE 및 AVX 명령어를 선호하는 64 비트 컴파일러에 대해 위에서 말한 것은 적어도 Microsoft Visual Studio 2013에서는 절대적으로 사실입니다. 그러나 내가 사용한 배정도 부동 소수점 연산의 경우 실제로 FPU를 사용하는 것이 이론적으로 더 정확할뿐만 아니라 더 빠릅니다. 중요한 경우 최종 접근 방식을 선택하기 전에 다양한 솔루션을 먼저 테스트하는 것이 좋습니다.

Java로 작업 할 때 임의 정밀도 BigDecimal 데이터 유형을 자주 사용합니다. 너무 쉬우 며 일반적으로 속도 감소를 느끼지 못합니다. Newton의 방법을 사용하여 무한 시리즈 및 sqrt를 사용하여 초월 함수를 계산하는 데 밀리 초 이상이 걸릴 수 있지만 실행 가능하고 매우 정확합니다.


0

나는 이것을 여기에 남겨두고 /programming//a/58006104/860099 (거기에 갈 때 '코드 스 니펫 표시'를 클릭하고 버튼으로 실행하십시오.

가장 큰 것부터 시작하는 합계가 더 큰 오류를 준다는 것을 분명히 보여주는 JavaScript 예제입니다

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);

이 사이트에서는 링크 전용 답변을 사용하지 않는 것이 좋습니다. 링크에서 제공되는 내용을 설명 할 수 있습니까?
nicoguaro

@nicoguaro 나는 답변을 업데이트-모든 답변은 매우 좋지만, 여기에 구체적인 예입니다
Kamil Kiełczewski
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.