3

浮動小数点数を使用して計算すると、困惑する結果が得られます。負の数を生成する負の数を生成してはならないコードがあるため、平方根を取得しようとすると 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));
}

いくつかの背景について: 信号データのローリング 2 乗平均平方根 (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;
}

このコードは、ウィンドウのオーバーラップを利用するのではなく、各ステップで配列からウィンドウ全体を読み取るため、低速です。意図した最適化は、最も古い値を削除して最新の値を追加することにより、そのオーバーラップを使用することでした。

新しいバージョンの配列インデックスをかなり注意深くチェックしました。意図したとおりに機能しているように見えますが、その領域では間違いがある可能性があります。

更新: 私たちのデータでは、型をsumdouble に変更するだけで十分でした。なぜそれが私に起こらなかったのかわかりません。しかし、私は否定的なチェックインを残しました。そして、FWIW、400 サンプルごとに合計を再計算することで優れた実行時間と十分な精度が得られる sol'n を実装することもできました。ありがとう。

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

2 番目のループでインデックスをチェックしてみてください。の最後の値in-halfWindow-1n-halfWindow-1+halfWindow-1です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 に答える