2017-02-17 14 views
1

float 배열의 제곱의 합을 계산하려고합니다.float의 제곱합의 반올림 오류를 줄입니다.

반올림 오류를 줄이는 방법은 무엇입니까?

실제 프로그램의 내부 루프에서 약 5,000,000 개의 수레를 합치려고합니다.

Test.cpp에 :

#include <iostream> 
#include <stdint.h> 
template <typename Sum, typename Element> 
Sum sum(const size_t st, const size_t en) { 
    Sum s = 0; 
    for (size_t i = st; i < en; ++ i) { 
     s += Element(i)*Element(i); 
    } 
    return s; 
} 
int main() { 
    size_t size = 100000; 
    std::cout << "double, float: " 
       << sum<double, float>(0,size) << "\n"; 
    std::cout << "int, int: " 
       << sum<int, int>(0,size) << "\n"; 
} 

출력 : 플로트의 포맷은 예컨대 IEEE로서 알려진 경우

double, float: 3.33328e+14 
int, int: 216474736 
+2

은 집에 돌아온 후 https://en.wikipedia.org/wiki/Pairwise_summation 및 https://en.wikipedia.org/wiki/Kahan_summation_algorithm에 대해 자세히 읽습니다. –

+0

정렬하고 가장 작게 시작하십시오. – EJP

+0

'size_t size = 100000;'은 이제'int'를 위해 너무 많습니다. – LogicStuff

답변

2

다음 플로트의 지수에 의해 인덱스 어레이가 사용될 수있다 부분 합계를 저장 한 다음 합계를 계산하여 합계를 산출합니다. 배열 업데이트 중에 동일한 지수가있는 부동 소수점 만 함께 더해지고 해당 위치의 배열에 저장됩니다. 최종 합계는 가장 작은 값에서 가장 큰 값으로 바뀝니다. C++의 경우 배열과 함수는 클래스의 멤버가 될 수 있습니다. 배열 함수에 매개 변수로 전달됩니다 수레

예 :

당신이 Element에 대한 유형 int를 사용
/* clear array */ 
void clearsum(float asum[256]) 
{ 
size_t i; 
    for(i = 0; i < 256; i++) 
     asum[i] = 0.f; 
} 

/* add a number into array */ 
void addtosum(float f, float asum[256]) 
{ 
size_t i; 
    while(1){ 
     /* i = exponent of f */ 
     i = ((size_t)((*(unsigned int *)&f)>>23))&0xff; 
     if(i == 0xff){   /* max exponent, could be overflow */ 
      asum[i] += f; 
      return; 
     } 
     if(asum[i] == 0.f){  /* if empty slot store f */ 
      asum[i] = f; 
      return; 
     } 
     f += asum[i];   /* else add slot to f, clear slot */ 
     asum[i] = 0.f;   /* and continue until empty slot */ 
    } 
} 

/* return sum from array */ 
float returnsum(float asum[256]) 
{ 
float sum = 0.f; 
size_t i; 
    for(i = 0; i < 256; i++) 
     sum += asum[i]; 
    return sum; 
} 
+0

이러한 방법의 오류 추정을위한 수학적 파생어가 있습니까? –

+0

@AlexP. - 모든 중간 덧셈은 같은 지수를 가진 두 개의 부동 소수점을 포함합니다. 두 부동 소수점이 동일한 부호를 갖는다면 합계의 지수는 두 부동 소수점의 지수보다 1 크며 24 비트 가수의 24 번째 비트 (msb == 1이고 저장되지 않음)에서 반올림이 발생하므로 가수는 24 비트 만 있기 때문에 결과 가수는 +/- (1^2 (-25))가 될 수 있습니다. 전반적인 오류는 반올림의 효과에 따라 달라집니다. – rcgldr

2

,하는 수, std::sqrt(std::numeric_limits<int>::max()) 후 각 i의 광장에서 오버 플로우가 시스템에 46341이 (가) 있어야합니다. std::numeric_limits<int>::max()에 도달하면 합계에 오버플로가 있습니다.

이 숫자를 늘리려면 int 대신 long 또는 long long을 사용할 수 있습니다.

첫 번째 floatdouble 또는 long double에 저장하거나 캐스팅 한 다음 곱한 다음 부동 소수점 제곱 연산의 오류도 줄이는 것이 좋습니다. 계산 집합의 마지막 단계를 반올림하면 내부 계산에서 표현 오류를 전파 (및 증가)하지 않으므로 초기 단계에서 반올림하는 것보다 항상 좋은 결과를 얻을 수 있습니다.

당신이 정말로 정밀도를 원하고, 몇 가지 복잡한 기술을 사용하여 바퀴를 재발견하고 싶지 않은 경우, 당신은 GNU Multi-Precision Library 또는 Boost Multiprecision 같은 멀티 정밀 도서관 사용할 수 있습니다 : 그들은 더 정확 https://en.wikipedia.org/wiki/List_of_arbitrary-precision_arithmetic_software

을 그 long double 유형 시스템의

2

연속 값의 제곱을 더하는 경우, n*(n+1)*(2n+1)/6 수식을 사용하여 1에서 n까지의 모든 값의 제곱 합계를 계산하십시오.

결과를 나타낼 수있는 유형을 사용하는 경우 반올림의 효과가 대부분 제거됩니다. 예를 들어; 결과를 저장하는 int을 사용 100000 동일 size 위해 정의되지 않은 동작 (서명 일체형의 오버플로) 제공,

template<typename Sum> Sum sumsq(size_t n) 
{ 
    // calculates sum of squares from 1 to x 
    // assumes size_t can be promoted to a Sum 

    Sum temp(n);  // force promotion to type Sum 
    return temp * (temp + 1)* (2*temp + 1)/6; 
} 

template<typename Sum> Sum alternate_sum(size_t st, size_t en) 
{ 
     Sum result = sumsq(en - 1); 
     if (st > 0) result -= sumsq(st-1); 
     return result; 
} 

int main() 
{ 
    size_t size = 100000; 
    std::cout << "double, float: " 
       << alternate_sum<double>(0,size) << "\n"; 
    std::cout << "int, int: " 
      << alternate_sum<long long>(0,size) << "\n"; 
} 

참고.

alternate_sum()-1의는 루프 형태 for (size_t i = st; i < en; ++ i)

당신은 고정 된 기능으로 size_t 유형의 사용을 제거 할 수의 복지 반영,하지만 난 운동으로 있음을 떠날거야.

BTW :이 코드가 내부 루프에 있다고 말하면이 수식이 사용하고있는 루프보다 훨씬 빠르다는 점에 유의해야합니다.

+0

실제로 내가 0.0과 1.0 사이의 수레를 합계하고있다 –

+0

당신의 질문은 그렇게 말하지 않았고 또한 설명하지 않았다. 사람들은 관심사가 아니며 일반적으로 실제로 묻는 질문 만 할 수 있습니다. 덕분에 – Peter

2

float에는 24 개의 유효 비트가 있고, 이중에는 53 개의 비트가 있습니다. 따라서 가드 비트 수는 29 비트이고 이는 5000000보다 약 100 배입니다.

반올림 오류는 최대 값보다 100 배 작은 값에 대해서만 발생합니다.

또한 인텔 아키텍처에서 부동 소수점 레지스터는 실제로 확장 정밀도 숫자를 80 비트로 유지하는데 그 중 63 비트가 중요합니다.

그러면 최대 값의 100000 배보다 작은 수만 잘라내 야합니다.

정말 걱정 되십니까?

+0

. 다시 돌아 가기 전에 나머지 프로그램을 디버깅 할 것입니다. 하지만 그 부분 합계와 다음 값을 비교할 때 상황이 발생할 수 있다고 생각합니다. 나는 메모리를 절약하기 위해 플로트를 사용한다. –