Я получаю озадачивающий результат, выполняя математику с поплавками. У меня есть код, который никогда не должен давать отрицательное число, генерирующее отрицательное число, что вызывает NaN, когда я пытаюсь извлечь квадратный корень.
Этот код очень хорошо работает в тестах. Однако при работе с реальными числами (т. е. потенциально очень маленькими, с семью и восемью отрицательными показателями) числами в конечном итоге сумма становится отрицательной, что приводит к NaN. Теоретически шаг вычитания удаляет только число, которое уже было добавлено к sum
; это проблема с ошибкой с плавающей запятой? Есть ли способ это исправить?
Код:
public static float[] getRmsFast(float[] data, int halfWindow) {
int n = data.length;
float[] result = new float[n];
float sum = 0.000000000f;
for (int i=0; i<2*halfWindow; i++) {
float d = data[i];
sum += d * d;
}
result[halfWindow] = calcRms(halfWindow, sum);
for (int i=halfWindow+1; i<n-halfWindow; i++) {
float oldValue = data[i-halfWindow-1];
float newValue = data[i+halfWindow-1];
sum -= (oldValue*oldValue);
sum += (newValue*newValue);
float rms = calcRms(halfWindow, sum);
result[i] = rms;
}
return result;
}
private static float calcRms(int halfWindow, float sum) {
return (float) Math.sqrt(sum / (2*halfWindow));
}
Для некоторого фона: я пытаюсь оптимизировать функцию, которая вычисляет функцию скользящего среднеквадратичного значения (RMS) для данных сигнала. Оптимизация очень важна; это горячая точка в нашей обработке. Основное уравнение простое: http://en.wikipedia.org/wiki/Root_mean_square - Sum квадраты данных над окном, разделить сумму на размер окна, затем взять квадрат.
Исходный код:
public static float[] getRms(float[] data, int halfWindow) {
int n = data.length;
float[] result = new float[n];
for (int i=halfWindow; i < n - halfWindow; i++) {
float sum = 0;
for (int j = -halfWindow; j < halfWindow; j++) {
sum += (data[i + j] * data[i + j]);
}
result[i] = calcRms(halfWindow, sum);
}
return result;
}
Этот код медленный, потому что он считывает все окно из массива на каждом шаге вместо того, чтобы использовать перекрытие окон. Предполагаемая оптимизация заключалась в том, чтобы использовать это перекрытие, удаляя самое старое значение и добавляя самое новое.
Я довольно тщательно проверил индексы массива в новой версии. Кажется, он работает так, как задумано, но я, конечно, могу ошибаться в этой области!
Обновление: с нашими данными было достаточно изменить тип sum
на двойной. Не знаю, почему мне это не пришло в голову. Но я оставил отрицательную проверку. И FWIW, я также смог реализовать решение, в котором пересчет суммы каждые 400 выборок давал большое время выполнения и достаточную точность. Спасибо.
double
вместоfloat
. Но проверка на негатив и тогда понадобится наверное. - person Joop Eggen   schedule 14.03.2013float
имеют 24-битные значащие символы. Их точные квадраты имеют 48 бит или меньше. Если вы масштабируетеfloat
до целого числа и конвертируете вlong
, у вас остается 15 свободных битов, поэтому может быть возможно сохранить сумму с точной арифметикой в long
, если диапазон диапазона не слишком велик, а половина окна не слишком велика. большой. Скорее всего, это возможно только в том случае, если все ваши данные близки к 1e-7 и 1e-8, которые вы упомянули. Большие данные сделают диапазон слишком большим. Может подойти подход «голова и хвост» сdouble
. - person Eric Postpischil   schedule 14.03.2013