2011-01-27 4 views
12

Мне нужно вычислить среднеквадратичную ошибку 16-разрядной операции для произвольного количества точек данных (более 100 миллионов). Я решил пойти со средним значением, поэтому мне не пришлось бы беспокоиться о переполнении из добавления большого числа квадратов ошибок. У 100 миллионов образцов у меня были проблемы с точностью с плавающей запятой (неточные результаты), поэтому я перешел к удвоению.Поддержание точности с плавающей точкой с текущим средним

Вот мой код

int iDifference = getIdeal() - getValue(); 

m_iCycles++; 


// calculate the running MSE as 

// http://en.wikipedia.org/wiki/Moving_average 

// MSE(i + 1) = MSE(i) + (E^2 - MSE(i))/(i + 1) 

m_dMSE = m_dMSE + ((pow((double)iDifference,2) - m_dMSE)/(double)m_iCycles); 

Есть ли лучший способ осуществить это, чтобы сохранить точность? Я считал нормализацию MSE одним и просто держал сумму с окончательным делением на завершение, чтобы вычислить среднее значение.

+1

Как полностью примечание стороны, в зависимости от того, вы поднимаете 'Pow (двойной, Int)' перегрузки, 'iDifference * iDifference' может быть на несколько порядков быстрее, чем' pow' вызова. –

+0

Согласовано. Я должен был это понять. Спасибо, Марк! –

ответ

4

Номера с плавающей запятой не переполняются в подобной ситуации, они только теряют точность. Таким образом, здесь нет преимуществ от среднего значения по текущей сумме. Следствием является то же, растет ли текущая сумма или знаменатель.

Чтобы сохранить точность в общем количестве, продолжайте промежуточные итоги вместо одного итога. Просто продолжайте добавлять к промежуточному итогу, пока добавление еще одного приведет к переполнению. Затем переходите к следующему промежуточному итогу. Поскольку они все одинаковы по порядку величины (в базе 2), оптимальная точность может быть достигнута путем преобразования в плавающую точку и использования парного накопления в одну конечную сумму.

// first = errors, second = counter 
typedef pair< vector<uint32_t>, uint32_t > running_subtotals; 

void accumulate_error(uint32_t error, running_subtotals &acc) { 
    (numeric_limits<uint32_t>::max() - error < acc.first.back()? 
     * acc.first.insert(acc.first.end(), 0) : acc.first.back()) 
     += error; // add error to current subtotal, or new one if needed 
    ++ acc.second; // increment counter 
} 

double get_average_error(running_subtotals const &total) { 
    vector<double> acc(total.first.begin(), total.first.end()); 
    while (acc.size() != 1) { 
     if (acc.size() % 2) acc.push_back(0); 
     for (size_t index = 0; index < acc.size()/2; ++ index) { 
      acc[ index ] = acc[ index * 2 ] + acc[ index * 2 + 1 ]; 
     } 
     acc.resize(acc.size()/2); 
    } 
    return acc.front()/total.second; 
} 
+0

Решение отлично работает. –

+0

«Числа с плавающей точкой не переполняются в такой ситуации, они только теряют точность». Нельзя ли потеря точности привести к переполнению? Менее точный поплавок может быть меньше * или * больше, чем реальное значение, не так ли? –

+0

@JosephGarvin Когда наиболее значимый бит сложения меньше наименее значимого бита всего, округление гарантируется. В противном случае, вы правы, точка от кругового к ближайшему должна предотвратить такое смещение.(Полное устранение смещения требует линейного распределения чисел, хотя и многие наборы данных имеют экспоненциальное или иное неравномерное распределение.) Хорошо разработанная программа никогда не должна приближаться к переполнению (до бесконечности); Я думаю, что было бы ошибкой иметь «INFINITY» в пределах погрешности. – Potatoswatter

12

Вы можете посмотреть на Kahan Summation Algorithm - это не точно то, что вам нужно здесь, но это решает очень похожие проблемы, и вы можете быть в состоянии адаптировать его к вашим потребностям.

+0

Очень круто, +1. Тем не менее, это перестанет быть эффективным, общее количество запусков достаточно велико, что добавление к нему любой ошибки добавит 0. В этот момент накопитель ошибок начинает быстро расти, пока он не потеряет точность. Затем он вернулся на площадь 1. – Potatoswatter

+0

+1, определенно здорово. Я склонен согласиться с Potatoswatter, что я все равно потеряю точность, так как число точек данных растет. –

2

Если другие решения не работают вы можете Исследуете Bignum library

«GMP является свободной библиотекой для произвольной точности арифметики, работающим на целых числах, рациональных числах и числа с плавающей точкой. Там нет никакого практического предела к точности, за исключением тех, что подразумевается доступной памятью в машине GMP. GMP имеет богатый набор функций, а функции имеют регулярный интерфейс ».

+0

Мне тоже нравится. В конечном итоге я решил не добавлять лишние накладные расходы для одной переменной. –

-1

То, что у вас есть, является экспоненциальной скользящей средней. Это более тяжело переносит ошибки в данных, чем раньше. Вам нужно линейное среднее. Средние ваши данные в блоках, скажем, 1 миллион, затем возьмите среднее из этих блоков. Вы даже можете сделать это на нескольких уровнях. Это будет равномерно взвешивать все ошибки.