2013-03-14 113 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,如果你打算經常使用它們。如果不小心,浮點數和實數之間的差異會以最糟糕的方式返回並咬你。或者,只要聽取我的意見,並知道每個浮點數與要求的值「非常接近」,其中一些「準確無誤」,但大部分「大部分」是準確的。這意味着您需要考慮測量誤差並在計算後記住它,或者相信在計算結果(您不知道)的結尾處有確切結果。