2012-03-08 1 views
3

私はフロートのマップ上で線形結合を計算するいくつかのコードを持っていて、コピーコンストラクタを使用するという面白い副作用がありました。Java HashMapコピーコンストラクタが浮動小数点精度に影響するのはなぜですか?

私は2つのマップ内の値の線形結合を計算し、2つのコピーそれらのマップの内の値を使用して計算線形結合と比較した場合、計算は実際に10 ^の周辺に(わずかに異なってい-7 )結果は浮動小数点精度と思われるために発生します。

どうしてですか?

はここにいくつかのサンプルコードです:

import java.util.*; 

public class WTF { 
    public static void main(String[] args) { 
     Random rand = new Random(); 

     for (int c = 0; c < 1000; c++) { 
      Map<String, Float> weights = new HashMap<String, Float>(); 
      Map<String, Float> values = new HashMap<String, Float>(); 

      for (int j = 0; j < 10; j++) { 
       weights.put("sig" + j, Float.valueOf(rand.nextFloat())); 
       values.put("sig" + j, Float.valueOf(rand.nextFloat())); 
      } 

      Map<String, Float> weightsCopy = new HashMap<String, Float>(weights); 
      Map<String, Float> valuesCopy = new HashMap<String, Float>(values); 

      float score1 = getScore(weights, values); 
      float score2 = getScore(weightsCopy, valuesCopy); 

      if (score1 != score2) { 
       System.out.println(score1-score2); 
      } 
     } 
    } 

    public static float getScore(Map<String, Float> weights, Map<String, Float> values) { 
     float score = 0.0f; 
     for (String name : weights.keySet()) { 
      Float weight = weights.get(name); 
      Float value = values.get(name); 
      score += weight.floatValue() * value.floatValue(); 
     } 
     return score; 
    } 
} 

UPDATE:

同じ問題がまたputAll操作に適用されます。それを使用してHashMapを「コピー」すると、同じ浮動小数点精度の問題が発生します。

+2

このコードは、具体的には 'for'ループを含む、コンパイルされません。コンパイルしたサンプルを投稿できますか? (質問の答えについての理論はありますが、最初に検証したいと思います) –

+0

あなたのコードは現在コンパイルされていません。 'main'の' for'ループはどちらも壊れています。 (あなたは*どんなマップにも何も追加していません...)それらを修正できますか? –

+0

マップに入力するコードは表示されません。 –

答えて

5

マップの順序が変わっており、操作が異なる順序で実行されています。簡単な計算のために変更する出力の例(裏返しDとEに注意してください):

class WTF { 
    public static void main(String[] args) { 
     final float a = 0.42890447f * 0.37233013f; 
     final float b = 0.2648958f * 0.05867535f; 
     final float c = 0.8928169f * 0.7546882f; 
     final float d = 0.0039135218f * 0.59395087f; 
     final float e = 0.9114683f * 0.33522367f; 

     System.out.println(a + b + c + d + e); 
     System.out.println(a + b + c + e + d); 
    } 
} 
5

ハッシュテーブルを再構築しているため(おそらくサイズが違うため)、繰り返しの順序が元のマップからコピーに変更されています。

丸めの差が山車に*+かなり可換/結合ではないという事実から来ている、とあなたはa * (b * c)または(a * c) * bまたは(a * b) * cを行うかどうかに応じて異なる丸め誤差を取得します。エントリとキーの順序が原本とコピーの間で変更されているので、結果には小さな丸めの違いがあります。

HashMapの代わりにLinkedHashMapを使用して、保存された繰り返し順序を確実にする場合は、毎回まったく同じ結果が得られるはずです。 (私は私のマシンでこれを確認しました)

0

あなたはフロートビットを見れば、あなたは1つのバイトの指数と1 mantisseビット(左に8)を参照してくださいよスワップされるので、1ビットエラーです。 (2,384186e-07 34800000

  float ds = score1-score2; 
      int bits = Float.floatToIntBits(ds); 
      System.out.printf("%e %x%n", score1-score2, bits); 
0

あなたは浮動小数点数を追加するためには、結果に影響を与えることができます。 HashMapは順序を保証しないため、HashMapをコピーすると順序が変わる可能性があります。つまり、値の合計は異なります。

public static void main(String... args) throws IOException { 
    List<Float> floats = new ArrayList<>(); 
    Random rand = new Random(); 
    float sum0 = 0; 
    for (int i = 0; i < 1000; i++) { 
     float f = rand.nextFloat() - rand.nextFloat(); 
     floats.add(f); 
     sum0 += f; 
    } 
    floats.add(-sum0); 

    SortedSet<Float> sums = new TreeSet<>(); 
    for (int i = 0; i < 200000; i++) { 
     Collections.shuffle(floats, rand); 
     float sum = 0; 
     for (Float f : floats) 
      sum += f; 
     if (sums.add(sum)) 
      System.out.println(sum); 
    } 
    System.out.println("Unique sums count " + sums.size() 
      + " from " + sums.first() + " to " + sums.last()); 
} 

プリント

1.8239021E-5 
2.0623207E-5 
-2.1278858E-5 
1.847744E-5 
2.18153E-5 
    .... 
-2.4557114E-5 
-3.415346E-5 
1.9788742E-5 
-2.270937E-5 
Unique sums count 795 from -3.4868717E-5 to 3.1232834E-5 
関連する問題