在Java语言中,快速计算RMS使nan具有浮点误差



我得到一个令人困惑的结果与浮点数做数学。我的代码永远不会产生一个负数产生一个负数,当我试图取平方根时,这会导致nan。

这段代码在测试中似乎运行得很好。然而,当对现实世界(即可能非常小,7和8负指数)的数字进行操作时,总和最终变为负值,从而导致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 -将窗口上的数据的平方相加,将总和除以窗口的大小,然后取平方。

原始代码:

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类型更改为double类型就足够了。不知道我怎么就没想到。但我留下了否定支票。顺便说一句,我也能够实现一个算法,每400个样本重新计算一次和,这样可以获得很好的运行时间和足够的准确性。谢谢。

这是浮点错误问题吗?

是的。由于四舍五入,您很可能在减去前一个求和后得到负值。

例如:

    float sum = 0f;
    sum += 1e10;
    sum += 1e-10;
    sum -= 1e10;
    sum -= 1e-10;
    System.out.println(sum);

在我的机器上,这打印

-1.0E-10

即使在数学上,结果是零。

这是浮点数的本质:1e10f + 1e-10f1e10f给出完全相同的值。

就缓解策略而言:

  1. 您可以使用double代替float来提高精度。
  2. 有时,你可以完全重新计算平方和,以减少舍入误差的影响。
  3. 当总和为负时,您可以像上面(2)那样进行完全重新计算,或者简单地将总和设置为零。后者是安全的,因为你知道你将把总和推向它的真实值,而不会偏离它。

尝试在第二个循环中检查索引。i的最后一个值是n-halfWindow-1, n-halfWindow-1+halfWindow-1的最后一个值是n-2

您可能需要将循环更改为for (int i=halfWindow+1; i<n-halfWindow+1; i++)

您正在遇到浮点数的问题,因为您认为它们就像数学上的实数。它们不是,它们是实数的近似值,映射成离散数,并在混合中添加了一些特殊规则。

如果你打算经常使用浮点数,花点时间阅读一下每个程序员都应该知道的关于浮点数的知识。如果不小心浮点数和实数之间的差异,可能会反过来以最糟糕的方式伤害你。

或者,只要相信我的话,知道每个浮点数都"非常接近"所请求的值,有些是"完全"准确的,但大多数是"大部分"准确的。这意味着您需要考虑测量误差,并在计算之后记住它,否则您可能会认为在值的计算结束时您有一个确切的结果(实际上您没有)。

最新更新