3

我用浮点数做数学得到了一个令人困惑的结果。我的代码永远不会产生负数,这会在我尝试取平方根时导致 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 - 将窗口上数据的平方相加,将总和除以窗口大小,然后取平方。

原代码:

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,我还能够实现一个 sol'n,其中每 400 个样本重新计算总和可以提供很好的运行时间和足够的准确性。谢谢。

4

3 回答 3

5

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

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

例如:

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

在我的机器上,这打印

-1.0E-10

即使在数学上,结果也完全为零。

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

就缓解策略而言:

  1. 您可以使用double而不是float提高精度。
  2. 有时,您可以完全重新计算平方和以减少舍入误差的影响。
  3. 当总和变为负数时,您可以按照上面的 (2) 进行完全重新计算,或者简单地将总和设置为零。后者是安全的,因为您知道您会将总和推向其真实价值,并且永远不会偏离它。
于 2013-03-14T16:21:48.007 回答
0

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

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

于 2013-03-14T16:26:26.633 回答
0

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

如果您打算经常使用浮点数,请花时间阅读每个程序员应该了解的浮点数知识。如果不小心,浮点数和实数之间的差异可能会以最糟糕的方式回来并咬你。

或者,相信我的话,并且知道每个浮点数都“非常接近”请求的值,其中一些“完全”准确,但大多数“大部分”准确。这意味着您需要考虑测量误差并在计算后牢记这一点,否则可能会在计算值结束时相信您有准确的结果(您没有)。

于 2013-03-14T16:26:47.910 回答