blob: f8867b9bf1a212a44ccaad90b92de9d214b63f49 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2010, 2016 Google, Inc and others.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Sergey Prigogin (Google) - initial API and implementation
*
* Based on lookup3.c, by Bob Jenkins {@link "http://burtleburtle.net/bob/c/lookup3.c"}
*
* Here is the original comment by Bob Jenkins:
* -------------------------------------------------------------------------------
* lookup3.c, by Bob Jenkins, May 2006, Public Domain.
*
* These are functions for producing 32-bit hashes for hash table lookup.
* hashword(), hashlittle(), hashlittle2(), hashbig(), mix(), and final()
* are externally useful functions. Routines to test the hash are included
* if SELF_TEST is defined. You can use this free for any purpose. It's in
* the public domain. It has no warranty.
*
* You probably want to use hashlittle(). hashlittle() and hashbig()
* hash byte arrays. hashlittle() is is faster than hashbig() on
* little-endian machines. Intel and AMD are little-endian machines.
* On second thought, you probably want hashlittle2(), which is identical to
* hashlittle() except it returns two 32-bit hashes for the price of one.
* You could implement hashbig2() if you wanted but I haven't bothered here.
*
* If you want to find a hash of, say, exactly 7 integers, do
* a = i1; b = i2; c = i3;
* mix(a, b, c);
* a += i4; b += i5; c += i6;
* mix(a, b, c);
* a += i7;
* finalMix(a, b, c);
* then use c as the hash value. If you have a variable length array of
* 4-byte integers to hash, use hashword(). If you have a byte array (like
* a character string), use hashlittle(). If you have several byte arrays, or
* a mix of things, see the comments above hashlittle().
*
* Why is this so big? I read 12 bytes at a time into 3 4-byte integers,
* then mix those integers. This is fast (you can do a lot more thorough
* mixing with 12*3 instructions on 3 integers than you can with 3 instructions
* on 1 byte), but shoehorning those bytes into integers efficiently is messy.
*******************************************************************************/
package org.aspectj.org.eclipse.jdt.internal.core.nd;
/**
* Computes a 64-bit hash value of a character stream that can be supplied one chunk at a time.
* Usage:
* <pre>
* StreamHasher hasher = new StreamHasher();
* for (long offset = 0; offset < streamLength; offset += chunkLength) {
* hasher.addChunk(offset, chunkOfCharacters);
* }
* int64 hashValue = hasher.computeHash();
* </pre>
*
* Based on lookup3.c by Bob Jenkins from {@link "http://burtleburtle.net/bob/c/lookup3.c"}
*/
public final class StreamHasher {
private static final long SEED = 3141592653589793238L; // PI
private static final long EMPTY_STRING_HASH = new StreamHasher().computeHashInternal();
long hashedOffset; // Current position in the stream of characters.
int state; // Current position in the stream of characters modulo 6, or -1 after computeHash is called.
int a;
int b;
int c;
char previousCharacter;
public StreamHasher() {
// Set up the internal state.
this.hashedOffset = 0;
this.state = 0;
this.a = this.b = this.c = (int) SEED;
this.c += SEED >>> 32;
}
/**
* Adds a chunk of data to the hasher.
* @param chunk Contents of the chunk.
*/
public void addChunk(char[] chunk) {
for (int pos = 0; pos < chunk.length; pos++, this.hashedOffset++) {
char cc = chunk[pos];
switch (this.state++) {
case -1:
throw new IllegalStateException("addChunk is called after computeHash."); //$NON-NLS-1$
case 0:
case 2:
case 4:
this.previousCharacter = cc;
break;
case 1:
this.a += this.previousCharacter | (cc << 16);
break;
case 3:
this.b += this.previousCharacter | (cc << 16);
break;
case 5:
this.c += this.previousCharacter | (cc << 16);
mix();
this.state = 0;
break;
}
}
}
/**
* Computes and returns the hash value. Must be called once after the last chunk.
* @return The hash value of the character stream.
*/
public long computeHash() {
if (this.state < 0) {
throw new IllegalStateException("computeHash method is called more than once."); //$NON-NLS-1$
}
return computeHashInternal() ^ EMPTY_STRING_HASH;
}
private long computeHashInternal() {
switch (this.state) {
case 1:
this.a += this.previousCharacter;
break;
case 3:
this.b += this.previousCharacter;
break;
case 5:
this.c += this.previousCharacter;
break;
}
this.state = -1; // Protect against subsequent calls.
finalMix();
return (this.c & 0xFFFFFFFFL) | ((long) this.b << 32);
}
/**
* Computes a 64-bit hash value of a String. The resulting hash value
* is zero if the string is empty.
*
* @param str The string to hash.
* @return The hash value.
*/
public static long hash(String str) {
StreamHasher hasher = new StreamHasher();
hasher.addChunk(str.toCharArray());
return hasher.computeHash();
}
/**
* Mixes three 32-bit values reversibly.
*
* This is reversible, so any information in a, b, c before mix() is
* still in a, b, c after mix().
*
* If four pairs of a, b, c inputs are run through mix(), or through
* mix() in reverse, there are at least 32 bits of the output that
* are sometimes the same for one pair and different for another pair.
* This was tested for:
* * pairs that differed by one bit, by two bits, in any combination
* of top bits of a, b, c, or in any combination of bottom bits of
* a, b, c.
* * "differ" is defined as +, -, ^, or ~^. For + and -, I transformed
* the output delta to a Gray code (a ^ (a >> 1)) so a string of 1's
* (as is commonly produced by subtraction) look like a single 1-bit
* difference.
* * the base values were pseudo-random, all zero but one bit set, or
* all zero plus a counter that starts at zero.
*
* Some k values for my "a -= c; a ^= Integer.rotateLeft(c, k); c += b;"
* arrangement that satisfy this are
* 4 6 8 16 19 4
* 9 15 3 18 27 15
* 14 9 3 7 17 3
* Well, "9 15 3 18 27 15" didn't quite get 32 bits diffing
* for "differ" defined as + with a one-bit base and a two-bit delta.
* I used http://burtleburtle.net/bob/hash/avalanche.html to choose
* the operations, constants, and arrangements of the variables.
*
* This does not achieve avalanche. There are input bits of a, b, c
* that fail to affect some output bits of a, b, c, especially of a.
* The most thoroughly mixed value is c, but it doesn't really even
* achieve avalanche in c.
*
* This allows some parallelism. Read-after-writes are good at doubling
* the number of bits affected, so the goal of mixing pulls in the opposite
* direction as the goal of parallelism. I did what I could. Rotates
* seem to cost as much as shifts on every machine I could lay my hands
* on, and rotates are much kinder to the top and bottom bits, so I used
* rotates.
*/
private void mix() {
this.a -= this.c; this.a ^= Integer.rotateLeft(this.c, 4); this.c += this.b;
this.b -= this.a; this.b ^= Integer.rotateLeft(this.a, 6); this.a += this.c;
this.c -= this.b; this.c ^= Integer.rotateLeft(this.b, 8); this.b += this.a;
this.a -= this.c; this.a ^= Integer.rotateLeft(this.c, 16); this.c += this.b;
this.b -= this.a; this.b ^= Integer.rotateLeft(this.a, 19); this.a += this.c;
this.c -= this.b; this.c ^= Integer.rotateLeft(this.b, 4); this.b += this.a;
}
/**
* Final mixing of 3 32-bit values a, b, c into c
*
* Pairs of a, b, c values differing in only a few bits will usually
* produce values of c that look totally different. This was tested for
* * pairs that differed by one bit, by two bits, in any combination
* of top bits of a, b, c, or in any combination of bottom bits of
* a, b, c.
* * "differ" is defined as +, -, ^, or ~^. For + and -, I transformed
* the output delta to a Gray code (a ^ (a >> 1)) so a string of 1's (as
* is commonly produced by subtraction) look like a single 1-bit
* difference.
* * the base values were pseudo-random, all zero but one bit set, or
* all zero plus a counter that starts at zero.
*
* These constants passed:
* 14 11 25 16 4 14 24
* 12 14 25 16 4 14 24
* and these came close:
* 4 8 15 26 3 22 24
* 10 8 15 26 3 22 24
* 11 8 15 26 3 22 24
*/
private void finalMix() {
this.c ^= this.b; this.c -= Integer.rotateLeft(this.b, 14);
this.a ^= this.c; this.a -= Integer.rotateLeft(this.c, 11);
this.b ^= this.a; this.b -= Integer.rotateLeft(this.a, 25);
this.c ^= this.b; this.c -= Integer.rotateLeft(this.b, 16);
this.a ^= this.c; this.a -= Integer.rotateLeft(this.c, 4);
this.b ^= this.a; this.b -= Integer.rotateLeft(this.a, 14);
this.c ^= this.b; this.c -= Integer.rotateLeft(this.b, 24);
}
}