10

非常に低いいくつかの確率値 (たとえば、1E-80) の積を保存する必要があります。プリミティブ Java double を使用すると、アンダーフローのためにゼロになります。値がゼロになることは望ましくありません。後で、double が処理できる範囲内の値をもたらす大きな数値 (たとえば、1E100) が存在するためです。

そこで、基数部分と指数部分を保存する別のクラス (MyDouble) を自分で作成しました。掛け算などの計算を行うときは、基数を掛けて指数を足します。

プリミティブ double 型でプログラムは高速です。ただし、独自のクラス (MyDouble) を使用すると、プログラムが非常に遅くなります。これは、単純な操作を作成するたびに新しいオブジェクトを作成する必要があり、オブジェクトが不要になったときにガベージ コレクターが多くの作業を行う必要があるためだと思います。

私の質問は、私がこの問題を解決できると思うより良い方法はありますか? そうでない場合、自分のクラス (MyDouble) でプログラムを高速化する方法はありますか?

[注: ログを取ってから指数を取っても問題は解決しません]

MyDouble クラス:

public class MyDouble {
    public MyDouble(double base, int power){
    this.base = base;
    this.power = power;
    }

    public static MyDouble multiply(double... values) {
    MyDouble returnMyDouble = new MyDouble(0);
    double prodBase = 1;
    int prodPower = 0;
    for( double val : values) {
            MyDouble ad = new MyDouble(val);
            prodBase *= ad.base;
            prodPower += ad.power;
        }   
        String newBaseString = "" + prodBase;
        String[] splitted = newBaseString.split("E");   
        double newBase = 0; int newPower = 0;
        if(splitted.length == 2) {
            newBase = Double.parseDouble(splitted[0]);
            newPower = Integer.parseInt(splitted[1]);
        } else {
            newBase = Double.parseDouble(splitted[0]);
            newPower = 0;
        }
        returnMyDouble.base = newBase;
        returnMyDouble.power = newPower + prodPower;        
        return returnMyDouble;
    }
}
4

6 に答える 6

5

これを解決する方法は、ログ空間で作業することです--- 問題を矮小化します。うまくいかないと言う場合、具体的な理由を教えていただけますか? 確率アンダーフローは、確率モデルではよくある問題であり、それが他の方法で解決されたことを私は知らなかったと思います。

log(a*b) は単に log(a) + log(b) であることを思い出してください。同様に、log(a/b) は log(a) - log(b) です。アンダーフローの問題を引き起こしている乗算と除算の確率を扱っていると思います。ログ スペースの欠点は、log(a+b) を計算するために特別なルーチンを使用する必要があることです。

したがって、簡単な答えは、対数空間で作業し、最後に再累乗して人間が読める数値を取得することです。

于 2012-10-16T10:21:30.247 に答える
2

使用できます

BigDecimal bd = BigDecimal.ONE.scaleByPowerOfTen(-309)
        .multiply(BigDecimal.ONE.scaleByPowerOfTen(-300))
        .multiply(BigDecimal.ONE.scaleByPowerOfTen(300));
System.out.println(bd);

プリント

1E-309

または、log10スケールを使用する場合

double d = -309 + -300 + 300;
System.out.println("1E"+d);

プリント

1E-309.0
于 2012-10-10T05:42:05.177 に答える
2

乗算を行うたびに文字列を解析しようとしています。事前計算ステップとして、すべての値を実数部や指数部などの構造に計算してから、乗算、加算、細分、累乗などのアルゴリズムを作成してみませんか。

また、大きい/小さい数のフラグを追加することもできます。1 つの計算で 1e100 と 1e-100 の両方を使用しないと思います (したがって、いくつかの計算を単純化できます)。また、異なるペア (大、大)、(小、小)、(大、小) の計算時間を改善できます。

于 2012-10-10T04:40:22.860 に答える
1

これは double よりもかなり遅くなると確信していますが、おそらく大きな要因は String 操作でしょう。それをやめて、代わりに算術で電力を計算できますか? 再帰的または反復的な演算でさえ、文字列に変換して数値のビットを取得するよりも高速な場合があります。

于 2012-10-10T04:29:30.643 に答える
1

パフォーマンスの高いアプリケーションでは、基本情報をプリミティブに格納する方法を見つけたいと考えています。この場合、固定部分がベースになるように、おそらく long 変数またはその他の変数のバイトを分割できます。

次に、double のように long または Long を乗算するカスタム メソッドを作成できます。base と exp を表すビットを取得し、それに応じて切り捨てます。

探している操作を効率的に実行するバイトコードが必要なため、ある意味では、ここで車輪を再発明しています。

編集:

2 つの変数に固執したい場合は、コードを変更して単純に配列を取ることができます。これは、オブジェクトよりもはるかに軽量です。さらに、文字列解析関数の呼び出しを削除する必要があります。それらは非常に遅いです。

于 2012-10-10T04:29:50.627 に答える
1

速度が遅いのは、分割および文字列連結で作成される中間文字列オブジェクトが原因である可能性があります。

これを試して:

/**
 * value = base * 10 ^ power.
 */

public class MyDouble {

    // Threshold values to determine whether given double is too small or not. 
private static final double SMALL_EPSILON = 1e-8;
private static final double SMALL_EPSILON_MULTIPLIER = 1e8;
private static final int    SMALL_EPSILON_POWER = 8;

private double myBase;
private int    myPower;

public MyDouble(double base, int power){
    myBase  = base;
    myPower = power;
}

public MyDouble(double base) 
{
    myBase  = base;
    myPower = 0;
    adjustPower();
}

/**
 * If base value is too small, increase the base by multiplying with some number and 
 * decrease the power accordingly. 
 * <p> E.g 0.000 000 000 001 * 10^1  => 0.0001 * 10^8  
 */
private void adjustPower()
{
    // Increase the base & decrease the power 
    // if given double value is less than threshold.
    if (myBase < SMALL_EPSILON) {
        myBase = myBase * SMALL_EPSILON_MULTIPLIER;
        myPower -= SMALL_EPSILON_POWER;
    }
}

/**
 * This method multiplies given double and updates this object.
 */
public void multiply(MyDouble d)
{
    myBase  *= d.myBase;
    myPower += d.myPower;
    adjustPower();
}

/**
 * This method multiplies given primitive double value with this object and update the 
 * base and power.
 */
public void multiply(double d)
{
    multiply(new MyDouble(d));
}

@Override
public String toString()
{
    return "Base:" + myBase + ", Power=" + myPower;
}

/**
 * This method multiplies given double values and returns MyDouble object.
 * It make sure that too small double values do not zero out the multiplication result. 
 */
public static MyDouble multiply(double...values) 
{
    MyDouble result = new MyDouble(1);
    for (int i=0; i<values.length; i++) {
        result.multiply(values[i]);
    }
    return result;
}

public static void main(String[] args) {
    MyDouble r = MyDouble.multiply(1e-80, 1e100);
    System.out.println(r);
}

}

これでもまだ遅い場合は、MyDouble オブジェクトを作成する代わりに、multiply() メソッドを変更して、プリミティブ double を直接操作することができます。

于 2012-10-10T05:04:36.957 に答える