RMS 的快速计算在 Java 中给出了 NaN - 浮点错误?
Posted
技术标签:
【中文标题】RMS 的快速计算在 Java 中给出了 NaN - 浮点错误?【英文标题】:Fast calculation of RMS gives NaNs in Java - floating point error? 【发布时间】:2013-03-03 02:45:25 【问题描述】:使用浮点数进行数学运算时,我得到了一个令人费解的结果。我的代码永远不会产生负数,这会在我尝试取平方根时导致 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 个样本重新计算总和可以提供很好的运行时间和足够的准确性。谢谢。
【问题讨论】:
尝试使用double
而不是float
。但即便如此,也可能需要对负数进行检查。
你的数据范围是多少,halfWindow的最大值是多少?您的 float
数据具有 24 位有效位。它们的确切平方有 48 位或更少。如果将float
缩放为整数并转换为long
,则有15 位可用,因此如果范围的跨度不是太大,则可以使用long
中的精确算术来保持总和great 和 halfWindow 不是太大。仅当您的数据所有都在您提到的 1e-7 和 1e-8 附近时,这可能是可行的。更大的数据会使范围太大。 double
的“头部和尾部”方法可能会做到。
【参考方案1】:
这是一个浮点错误问题吗?
是的。由于四舍五入,你很可能在减去前一个和后得到负值。
例如:
float sum = 0f;
sum += 1e10;
sum += 1e-10;
sum -= 1e10;
sum -= 1e-10;
System.out.println(sum);
在我的机器上,打印出来
-1.0E-10
即使在数学上,结果也恰好为零。
这是浮点的本质:1e10f + 1e-10f
给出的值与1e10f
完全相同。
就缓解策略而言:
-
您可以使用
double
代替float
来提高精度。
有时,您可以完全重新计算平方和以减少舍入误差的影响。
当总和变为负数时,您可以按照上述 (2) 进行完全重新计算,或者简单地将总和设置为零。后者是安全的,因为您知道您会将总和推向其真实值,并且永远不会偏离它。
【讨论】:
对解决它有什么建议吗?如果我能够让它工作,我真的可以从中获得运行时收益。如果它变成负数,我可以看到只是四舍五入到零 - 但我有点不喜欢。 @AbGator:将总和设置为零并不像听起来那么简单。请参阅我最新编辑中的 (3)。【参考方案2】:尝试在第二个循环中检查您的索引。 i
的最后一个值是 n-halfWindow-1
,n-halfWindow-1+halfWindow-1
是 n-2
。
您可能需要将循环更改为for (int i=halfWindow+1; i<n-halfWindow+1; i++)
。
【讨论】:
虽然这不是行为不端的真正原因......我相信你是对的!【参考方案3】:您遇到了浮点数问题,因为您认为它们就像数学实数一样。它们不是,它们是实数的近似值,映射为离散数字,并添加了一些特殊规则。
如果您打算经常使用它们,请花时间阅读what every programmer should know about floating point numbers。如果不小心,浮点数和实数之间的差异可能会以最糟糕的方式再次出现并咬你。
或者,相信我的话,并且知道每个浮点数都“非常接近”请求的值,其中一些“完全”准确,但大多数“大部分”准确。这意味着您需要考虑测量误差并在计算后牢记这一点,否则可能会在计算值结束时相信您有准确的结果(您没有)。
【讨论】:
以上是关于RMS 的快速计算在 Java 中给出了 NaN - 浮点错误?的主要内容,如果未能解决你的问题,请参考以下文章