2013-03-14 114 views
2

我得到了一个令人困惑的结果与浮游物做数学。我有代码,不应该产生负数,产生负数,当我尝试取平方根时会导致NaN。RMS的快速计算给出Java中的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个样本重新计算总和就可以提供很好的运行时间和足够的准确性。谢谢。

+0

试着用'double'而不是'float'。但是可能需要检查负值。 – 2013-03-14 16:27:13

+0

你的数据的范围是什么,halfWindow的最大值是多少?你的float数据有24位有效数字。他们的确切正方形有48位或更少。如果你将'float'缩放为整数并将其转换为'long',那么你有15位空余空间,所以如果该范围的跨度不是太长,那么可以用'long'中的精确算术来保持和。伟大和halfWindow不是太大。这只有在** all **你的数据接近你提到的1e-7和1e-8时才可行。更大的数据会使范围过大。 “双头”的“头部和尾部”方法可能会有效。 – 2013-03-14 17:26:09

回答

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)中的方法进行完整的重新计算,也可以将总和设置为零。后者是安全的,因为你知道你会将总和推向真正的价值,并且永远不会离开它。
+0

解决它的任何建议?我真的可以使用运行时获得的收益,如果我能够得到它的工作。我可以看到四舍五入到零,如果它消极 - 但我不喜欢那一点。 – AbGator 2013-03-14 16:27:36

+0

@AbGator:查看我的最新编辑建议。 – NPE 2013-03-14 16:28:16

+0

@AbGator:将总和设置为零比听起来要少。见(3)在我最近的编辑。 – NPE 2013-03-14 16:37:58

0

尝试在第二个循环中检查您的指数。 i的最后一个值将是n-halfWindow-1n-halfWindow-1+halfWindow-1n-2

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

+0

虽然这不是不正之风的真正原因......我相信你是对的! – AbGator 2013-03-14 16:44:28

0

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

花时间阅读what every programmer should know about floating point numbers,如果你打算经常使用它们。如果不小心,浮点数和实数之间的差异会以最糟糕的方式返回并咬你。或者,只要听取我的意见,并知道每个浮点数与要求的值“非常接近”,其中一些“准确无误”,但大部分“大部分”是准确的。这意味着您需要考虑测量误差并在计算后记住它,或者相信在计算结果(您不知道)的结尾处有确切结果。