XORがハッシュを結合するデフォルトの方法であるのはなぜですか?


145

2つのハッシュがH(A)ありH(B)、それらを結合したいとします。私は、2つのハッシュを組み合わせるための良い方法はXORそれらにあることを読んだ、例えばXOR( H(A), H(B) )

私が見つけた最良の説明は、これらのハッシュ関数ガイドラインについてここで簡単に触れられています

ほぼランダムな分布で2つの数値をXORすると、ほぼランダムな分布*を持つ別の数値になりますが、これは2つの値に依存します。
...
*組み合わせる2つの数値の各ビットで、2つのビットが等しい場合は0が出力され、それ以外の場合は1が出力されます。つまり、組み合わせの50%では1が出力されます。したがって、2つの入力ビットがそれぞれ約50〜50の確率で0または1になる可能性がある場合、出力ビットも同様です。

XORが(ORやANDなどではなく)ハッシュ関数を組み合わせるためのデフォルトの演算である必要がある理由の直観や数学について説明できますか?


20
私はあなたがやったばかりだと思います;)
マッサ

22
XORは、「組み合わせ」で何をしたいかによって、ハッシュを「組み合わせる」ための「良い」方法である場合とそうでない場合があることに注意してください。XORは可換です。XOR(H(A)、H(B))はXOR(H(B)、H(A))と等しくなります。つまり、XORは順序を取得しないため、順序付けされた値のシーケンスの一種のハッシュを作成する適切な方法ではありません。
Thomas Pornin、

6
順序の問題(上記のコメント)に加えて、値が等しい場合の問題があります。XOR(H(1)、H(1))= 0(関数Hの場合)、XOR(H(2)、H(2))= 0など。任意のNの場合:XOR(H(N)、H(N))= 0。等しい値は実際のアプリで非常に頻繁に発生します。つまり、XORの結果が0になりすぎて、適切なハッシュと見なされなくなる可能性があります。
Andrei Galatyn

値の順序付けシーケンスに何を使用しますか?タイムスタンプまたはインデックスのハッシュを作成するとします。(MSBはLSBよりも重要ではありません)。このスレッドが1歳の場合は申し訳ありません。
Alexis

回答:


120

一様にランダムな(1ビット)入力を想定すると、AND関数の出力確率分布は75%0と25%になり1ます。逆に、ORは25%0と75%1です。

XOR関数は50%0と50%1であるため、均一な確率分布を組み合わせるのに適しています。

これは、真理値表を書き出すことで確認できます。

 a | b | a AND b
---+---+--------
 0 | 0 |    0
 0 | 1 |    0
 1 | 0 |    0
 1 | 1 |    1

 a | b | a OR b
---+---+--------
 0 | 0 |    0
 0 | 1 |    1
 1 | 0 |    1
 1 | 1 |    1

 a | b | a XOR b
---+---+--------
 0 | 0 |    0
 0 | 1 |    1
 1 | 0 |    1
 1 | 1 |    0

演習:どのように多くの2つの1ビット入力の論理機能ab、この均一な出力分布を持っていますか?質問で述べた目的にXORが最も適しているのはなぜですか?


24
運動に応答:16回の可能な異なるAのXXX Bの操作から(0, a & b, a > b, a, a < b, b, a % b, a | b, !a & !b, a == b, !b, a >= b, !a, a <= b, !a | !b, 1)、次のaおよびbを仮定して、0と1の50%-50%分布を有するが、0と1の50%-50%の分布を有する:a, b, !a, !b, a % b, a == bすなわち、反対of XOR(EQUIV)も使用できた可能性があります...
Massa

7
グレッグ、これは素晴らしい答えです。私はあなたの元の答えを見て、私自身の真理値表を書いた後、電球が私のために続きました。ディストリビューションを維持するための6つの適切な操作があるかについて@Massaの回答を検討しました。そしてa, b, !a, !b、それぞれの入力と同じ分布になりますが、他の入力のエントロピーは失われます。つまり、aとbの両方からエントロピーを取得したいので、XORはハッシュを組み合わせる目的に最適です。
Nate Murray、

1
ここでは、各関数が1回だけ呼び出されるハッシュを安全に組み合わせることが、各ハッシュ値のビット数の合計よりも少ないビットを出力しないと不可能であると説明しています。これは、この答えが正しくないことを示唆しています。
タマシュSzelei

3
@Massa XORに使用されたり、等しくないのを見たことがありません。
2014

7
Yakkが指摘が同じ値のゼロを生成するように、XORは危険であることができます。これは(a,a)(b,b)両方ともゼロを生成することを意味します。これにより、多くの(ほとんどの場合)ハッシュベースのデータ構造での衝突の可能性が大幅に増加します。
Drew Noakes、2016年

170

xorハッシュ時に使用する危険なデフォルト関数です。andand よりも優れていますが、それはorあまり意味がありません。

xorは対称なので、要素の順序が失われます。したがって、"bad"ハッシュ結合はと同じになり"dab"ます。

xor ペアワイズの同一の値をゼロにマッピングします。「共通」の値をゼロにマッピングすることは避けてください。

その(a,a)ため、0にマッピングされ、0に(b,b)もマッピングされます。このようなペアは、ほとんどの場合、ランダム性が意味するよりも一般的であるため、ゼロよりはるかに多くの衝突が発生します。

これらの2つの問題により、xor最終的には表面上はまともなように見えるハッシュコンバイナになりますが、さらに検査した後ではありません。

最近のハードウェアでは、通常、追加と同じくらいの速さで追加されますxor(おそらく、これをオフにするためにより多くの電力を使用します)。加算の真理値表はxor、問題のビットに似ていますが、両方の値が1の場合、次のビットにビットを送信します。つまり、消去する情報が少なくなります。

したがって、ifの場合hash(a) + hash(b)よりも、結果は0ではなく、より優れています。hash(a) xor hash(b)a==bhash(a)<<1

これは対称的なままです。したがって、同じ結果"bad""dab"得るのは依然として問題です。適度なコストでこの対称性を破ることができます。

hash(a)<<1 + hash(a) + hash(b)

別名hash(a)*3 + hash(b)。(hash(a)シフトソリューションを使用する場合は、一度計算して保存することをお勧めします)。の代わりに奇数定数を使用する3と、「- kbit」の符号なし整数がそれ自体に全単射でマッピングされます。符号なし整数のマップは2^ksomeの数学モジュロkであり、奇数定数はに対して比較的素数2^kです。

より洗練されたバージョンについては、を調べることができますboost::hash_combine

size_t hash_combine( size_t lhs, size_t rhs ) {
  lhs ^= rhs + 0x9e3779b9 + (lhs << 6) + (lhs >> 2);
  return lhs;
}

ここでは、いくつかのシフトされたバージョンのseed定数と定数(基本的にランダムな0sと1sです。特に、32ビットの固定小数点部分としての黄金比の逆数です)といくつかの加算とxorを足し合わせます。この休憩は対称、着信ハッシュされた値、すなわち、0にすべてのコンポーネントのハッシュを想像(貧弱であれば、いくつかの「ノイズ」を紹介- 、上記のハンドルを、それは井戸のスミアが発生1して0。それぞれが結合した後、俺のナイーブは3*hash(a)+hash(b)単に出力0でのその場合)。

(C / C ++に精通していない場合、a size_tは、メモリ内のオブジェクトのサイズを説明するのに十分な大きさの符号なし整数値です。64ビットシステムでは、通常64ビットの符号なし整数です。32ビットシステムでは、32ビットの符号なし整数。)


いい答え、ヤック。このアルゴリズムは32ビットシステムと64ビットシステムの両方で同じように機能しますか?ありがとう。
デイブ

1
@daveはさらにビットを追加します0x9e3779b9
Yakk-Adam Nevraumont、2015年

10
完了しました...完全な64ビット定数(long doubleとunsigned long longで計算)は次のとおりです:0x9e3779b97f4a7c16。興味深いことに、それはまだ均一です。黄金比の代わりにPIを使用して同じ計算を再実行すると、0x517cc1b727220a95が生成されます。これは偶数ではなく奇数であるため、おそらく他の定数よりも「素数が多い」でしょう。私が使用した:std :: cout << std :: hex <<(unsigned long long)((1.0L / 3.14159265358979323846264338327950288419716939937510L)*(powl(2.0L、64.0L)))<< std :: endl; cout.precision(numeric_limits <long double> :: max_digits10); Yakkに再度感謝します。
Dave、

2
@Daveこれらのケースの逆黄金比ルールは、実行している計算以上の最初の奇数です。したがって、1を追加するだけです。N*の比率、最大サイズ(ここでは2 ^ 64)のシーケンスは、次の値を最大の「ギャップ」の真ん中にその比率で正確に配置するため、重要な数値です。番号。詳細については、「フィボナッチハッシュ」をウェブで検索してください。
スコットキャリー2017年

1
@Daveの正しい数値は0.9E3779B97F4A7C15F39になります... リンクを参照してください。偶数への丸め規則(これは会計士にとっては良いことです)に悩まされている可能性があります。または、単純に、リテラルsqrt(5)定数で開始した場合、1を減算すると、上位ビットを削除します。ビットは失われたに違いありません。
18年

29

その便利なビット混合特性にもかかわらず、XORはその可換性のためにハッシュを組み合わせるための良い方法ではありません。{1、2、…、10}の順列を10タプルのハッシュテーブルに格納するとどうなるかを考えてみましょう。

より良い選択はですm * H(A) + H(B)。ここで、mは大きな奇数です。

クレジット:上記のコンバイナーは、Bob Jenkinsからのヒントでした。


2
時には可換性は良いことですが、XORお粗末な選択をするとしても、次に一致するアイテムのすべてのペアがゼロにハッシュされますので。算術合計が優れています。一致するアイテムのペアのハッシュは、32ではなく31ビットの有用なデータのみを保持しますが、ゼロを保持するよりもはるかに優れています。別のオプションは、算術合計をaとして計算してからlong、上部を下部に変更することです。
スーパーキャット2013年

1
m = 3実際には多くのシステムで良い選択であり、非常に高速です。任意の奇数のためにという注意m整数乗算剰余である2^322^64、あなたは任意のビットを失っていないので、したがって、可逆です。
StefanKarpinski 2014

MaxIntを超えるとどうなりますか?
2014年

2
奇数ではなく素数を選択する必要があります
TermoTux

2
@Infinumは、ハッシュを組み合わせるときに必要ありません。
Marcelo Cantos 2017年

17

Xorはハッシュを組み合わせる「デフォルト」の方法である可能性がありますが、Greg Hewgillの答えは、なぜそれが落とし穴を持っているかを示しています。2つの同一のハッシュ値のxorはゼロです。実際には、同じハッシュが予想よりも一般的です。これらの(まれではない)コーナーケースでは、結果の結合ハッシュは常に同じ(ゼロ)であることがわかります。ハッシュの衝突は、予想よりもはるかに頻繁に発生します。

不自然な例として、管理しているさまざまなWebサイトのユーザーのハッシュ化されたパスワードを組み合わせる場合があります。残念ながら、多数のユーザーがパスワードを再利用しているため、結果として得られるハッシュの驚くべき割合はゼロです。


不自然な例が発生しないことを願っています。パスワードはソルト処理する必要があります。
user60561 2015

8

このページを見つけた他の人にはっきりと指摘したいことがあります。ANDおよびORは、BlueRajaのような出力を制限します-Danny Pflughoeが指摘しようとしていますが、より適切に定義できます。

まず、これを説明するために使用する2つの単純な関数、Min()とMax()を定義します。

Min(A、B)は、AとBの間でより小さい値を返します。たとえば、Min(1、5)は1を返します。

Max(A、B)は、AとBの間でより大きい値を返します。たとえば、Max(1、5)は5を返します。

あなたが与えられた場合: C = A AND B

次にC <= Min(A, B)、AまたはBの0ビットと1にするためにANDできるものは何もないため、これを知っていることがわかります。したがって、すべてのゼロビットはゼロビットのままであり、すべての1ビットはゼロビットになる可能性があります(したがって、より小さい値になります)。

と: C = A OR B

逆が真です。C >= Max(A, B)これにより、AND関数の結果がわかります。既に1になっているビットをORで0にすることはできないため、1のままですが、すべての0ビットは1になる可能性があり、したがってより大きな数になります。

これは、入力の状態が出力に制限を適用することを意味します。ANDで90を指定すると、他の値に関係なく、出力が90以下になることがわかります。

XORの場合、入力に基づく暗黙の制限はありません。255のバイトとXORを実行すると逆の結果が得られるが、そこから可能なバイトを出力できるという特殊なケースがあります。すべてのビットは、他のオペランドの同じビットに応じて状態を変更する可能性があります。


6
一つは、言うことができるORビット単位の最大、かつANDあるビット単位分
–PaŭloEbermann、2011

非常によく述べたパウロ・エバーマン。ここでまたCrypto.SEに会えてうれしいです!
Corey Ogburn、2011

暗号化のタグが付けられたすべてを含むフィルターを作成し、古い質問への変更も行いました。このように私はここであなたの答えを見つけました。
–PaŭloEbermann、2011

3

XOR入力にバイアスをかけたランダム入力の場合、出力はランダムになります。ANDまたはについても同じことが言えませんOR。例:

00101001 XOR 00000000 = 00101001
00101001および00000000 = 00000000
00101001 OR 11111111 = 11111111

@Greg Hewgillが述べているように、両方の入力がランダムであってANDも、またはORを使用すると出力にバイアスがかかります。

私たちがXORより複雑なものに対して使用する理由は、まあ、必要はないからです。XOR完全に機能し、それは非常に 愚かです。


1

左の2列をカバーし、出力だけを使用して入力が何であるかを調べてみてください。

 a | b | a AND b
---+---+--------
 0 | 0 |    0
 0 | 1 |    0
 1 | 0 |    0
 1 | 1 |    1

1ビットを見たとき、両方の入力が1であることがわかりました。

XORでも同じようにします

 a | b | a XOR b
---+---+--------
 0 | 0 |    0
 0 | 1 |    1
 1 | 0 |    1
 1 | 1 |    0

XORは、その入力について何も提供しません。


0

さまざまなバージョンのソースコードhashCode()におけるjava.util.Arraysは固体、一般的な使用のハッシュアルゴリズムのための優れた参考あります。それらは簡単に理解され、他のプログラミング言語に翻訳されます。

大まかに言えば、ほとんどのマルチ属性hashCode()実装は次のパターンに従います。

public static int hashCode(Object a[]) {
    if (a == null)
        return 0;

    int result = 1;

    for (Object element : a)
        result = 31 * result + (element == null ? 0 : element.hashCode());

    return result;
}

他のStackOverflowのQ&Aを検索して31、の背後にある魔法の詳細と、Javaコードがそれを頻繁に使用する理由を調べることができます。不完全ですが、一般的なパフォーマンス特性は非常に良好です。


2
Javaのデフォルトの「31で乗算して追加/累積」ハッシュは、衝突(たとえばstringstring + "AA"IIRC との衝突)でロードされ、そのアルゴリズムを仕様に組み込まないことをずっと前から望んでいました。つまり、より多くのビットを設定してより大きな奇数を使用し、シフトまたはローテーションを追加すると、その問題が修正されます。MurmurHash3の「ミックス」はこれを行います。
Scott Carey

0

XORは、ORANDなどの一部の入力を無視しません。

たとえば、AND(X、Y)を取り、入力Xをfalseでフィードする場合、入力Yは問題ではありません。

XOR(X、Y)を取る場合、両方の入力が常に重要になります。Yが問題ではない場合、Xの値はありません。XまたはYが変更された場合、出力はそれを反映します。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.