2014-09-28 3 views
1

Что является более точным способом вычисления среднего числа чисел, ARR[0]/N+ARR[1]/N...+ARR[N-1]/N или (ARR[0]+ARR[1]...+ARR[N-1])/N? (ARR это набор чисел и N является подсчет чисел в этом наборе)Что более точное среднее значение, ARR [0]/N + ARR [1]/N ... + ARR [N-1]/N или (ARR [0] + ARR [1] ... + ARR [N-1])/N в двойном?

Рассмотрим я набор чисел, каждый диапазон от 0.0 до 1.0 (они двойные \ числа с плавающей точкой) и есть тысячи из них или даже миллионов.

Я открыт для новых методов, таких как рекурсивный средний (средние двойные ячейки в массив, а затем снова усредняет его до тех пор, пока не выйдет одноячеечный массив).

+0

(Предположим, что все числа положительны) Сортируйте числа, самые низкие и самые высокие, а затем добавьте от самого низкого до самого высокого. (Если присутствуют отрицательные числа, сортировка по абсолютной величине.) И не нужно делить каждый элемент на N, просто разделите сумму на N. –

+0

См. Также http://stackoverflow.com/q/13417670/ (в частности, суммирование Кахана) – Nemo

ответ

2

Если значения вблизи нуля очень близки к нулю, у вас будет проблема с округлением (может быть округление ошибки вверх или вниз) в суммировании или любым диапазоном чисел, если суммировать большой набор чисел. Один из способов решения этой проблемы - использовать функцию суммирования, которая добавляет только числа с одинаковым показателем (пока вы не вызовете getum(), чтобы получить общую сумму, где она хранит экспоненты как можно ближе). Пример C++ класс для этого (код примечания был скомпилирован с использованием Visual Studio, написанный до того, как uint64_t был доступен).

// SUM contains an array of 2048 IEEE 754 doubles, indexed by exponent, 
// used to minimize rounding/truncation issues when doing 
// a large number of summations 

class SUM{ 
    double asum[2048]; 
public: 
    SUM(){for(int i = 0; i < 2048; i++)asum[i] = 0.;} 
    void clear(){for(int i = 0; i < 2048; i++)asum[i] = 0.;} 
// getsum returns the current sum of the array 
    double getsum(){double d = 0.; for(int i = 0; i < 2048; i++)d += asum[i]; 
        return(d);} 
    void addnum(double); 
}; 

void SUM::addnum(double d)  // add a number into the array 
{ 
size_t i; 

    while(1){ 
//  i = exponent of d 
     i = ((size_t)((*(unsigned long long *)&d)>>52))&0x7ff; 
     if(i == 0x7ff){   // max exponent, could be overflow 
      asum[i] += d; 
      return; 
     } 
     if(asum[i] == 0.){  // if empty slot store d 
      asum[i] = d; 
      return; 
     } 
     d += asum[i];   // else add slot to d, clear slot 
     asum[i] = 0.;   // and continue until empty slot 
    } 
} 

Пример программы, использующей класс сумму:

#include <iostream> 
#include <iomanip> 
using namespace std; 

static SUM sum; 

int main() 
{ 
double dsum = 0.; 
double d = 1./5.; 
unsigned long i; 

    for(i = 0; i < 0xffffffffUL; i++){ 
     sum.addnum(d); 
     dsum += d; 
    } 
    cout << "dsum    = " << setprecision(16) << dsum << endl; 
    cout << "sum.getsum()  = " << setprecision(16) << sum.getsum() << endl; 
    cout << "0xffffffff * 1/5 = " << setprecision(16) << d * (double)0xffffffffUL << endl; 

    return(0); 
} 
+0

Непонятно, что это, по-видимому, лучший способ добавить кучу чисел. вы знаете, если это правда? если да, можете ли вы предложить простой набросок доказательства? – thang

+0

@thang: это не лучший способ.Но это лучший разумный способ, о котором я могу думать. Самый низкий способ суммирования чисел для исключения усечения малых величин - это сортировать числа и суммировать их от наименьшего к самому большому. –

+0

@ RafaelBaptista - суммирование набора отсортированных номеров не решает проблему. У вас все еще будет потенциальная проблема суммирования большого набора чисел с одинаковым показателем. В конце этой последовательности вы добавляете относительно небольшое число к относительно большой рабочей сумме. – rcgldr

0

(ARR[0]+ARR[1]...+ARR[N-1])/N быстрее и точнее, поскольку вы опускаете бесполезные подразделения с N, которые замедляют процесс и добавляют ошибку в вычислениях.

+0

Убедитесь, что вы используете хороший алгоритм суммирования, например [this] (http://en.wikipedia.org/wiki/Kahan_summation_algorithm) –

+0

Почему деление на каждое значение вызывает ошибку при расчете? Я подумал, что когда вы добавляете два значения, например '10000' и' 0.1', раунды «0,1» с действительно большой ошибкой, потому что они очень малы. Мне нужен надежный источник для этого. – KugBuBu

+0

Как подсказка, сортируйте по величине, прежде чем добавлять их, и начинайте с нижнего конца. (Не имеет значения, могут ли цифры быть добавлены точно без ошибок) – Deduplicator

0

Если у вас есть куча чисел с плавающей точкой, наиболее точный способ, чтобы получить среднее значение, как это:

template<class T> T mean(T* arr, size_t N) { 
    std::sort(+arr, arr+N, [](T a, T b){return std::abs(a) < std::abs(b);}); 
    T r = 0; 
    for(size_t n = 0; n < N; n++) 
     r += arr[n]; 
    return r/N; 
} 

Важные моменты:

  • n сначала сохраняются наименьшие значения, чтобы сохранить значимые цифры.
  • Только одно подразделение, чтобы уменьшить погрешность округления.

Тем не менее промежуточная сумма может стать слишком большой.

Смежные вопросы