快速计算RMS给出了Java中的NaNs - 浮点错误?

AbG*_*tor 2 java algorithm math floating-point nan

用浮点数做数学时,我得到了一个令人困惑的结果.我的代码永远不会产生负数,产生负数,当我尝试取平方根时会导致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));
}
Run Code Online (Sandbox Code Playgroud)

对于某些背景:我正在尝试优化计算信号数据的滚动均方根(RMS)函数的函数.优化非常重要; 它是我们加工过程中的热点.基本方程很简单 - http://en.wikipedia.org/wiki/Root_mean_square - 在窗口上对数据的平方求和,将和除以窗口的大小,然后取平方.

原始代码:

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;
}
Run Code Online (Sandbox Code Playgroud)

这段代码很慢,因为它在每一步都从数组中读取整个窗口,而不是利用窗口中的重叠.预期的优化是使用该重叠,通过删除最旧的值并添加最新值.

我仔细检查了新版本中的数组索引.它似乎按预期工作,但我在那个领域肯定是错的!

更新: 使用我们的数据,足以将类型更改sum为double.不知道为什么我没有发生这种情况.但我留下了负面检查.而且,我还能够实现一个sol'n,其中每400个样本重新计算总和,提供了极大的运行时间和足够的准确性.谢谢.

NPE*_*NPE 5

这是一个浮点错误问题吗?

是的.由于四舍五入,在减去先前的加数后,您可能会得到负值.

例如:

    float sum = 0f;
    sum += 1e10;
    sum += 1e-10;
    sum -= 1e10;
    sum -= 1e-10;
    System.out.println(sum);
Run Code Online (Sandbox Code Playgroud)

在我的机器上,这打印

-1.0E-10
Run Code Online (Sandbox Code Playgroud)

即使在数学上,结果也是零.

这是浮点的本质:1e10f + 1e-10f给出与之完全相同的值1e10f.

就缓解策略而言:

  1. 您可以使用double而不是float增强精度.
  2. 有时,您可以完全重新计算平方和,以减少舍入误差的影响.
  3. 当总和为负数时,您可以像上面(2)中那样进行完全重新计算,或者只是将总和设置为零.后者是安全的,因为你知道你将把这笔钱推向真正的价值,并且永远不会远离它.