Search code examples
javascriptjavabitwise-operatorsxor

JavaScript's xor result different than the one of Java


SOLUTION

I had a bug in my internal conversion logic.

ORIGINAL QUESTION

I need to implement an algorithm both in Java and JavaScript, whereas the Java implementation and calculation result is the reference. However, when invoking the XOR operator on a "negative" value (I know that Java and JavaScript make use of 2's complement) causes Java's result to be positive, whereas the JavaScript result is negative, as shown in the output below:

Java output:
    hash A: 16777619
    hash B: 637696617
    hash A: 637696613
    hash B: 988196095
    hash A: 988196062
    hash B: -1759370886
    hash A: 1759370917    <-- here the JavaScript implementation behaves different
    hash B: -1169850945

JavaScript output:

    hash A: 16777619
    hash B: 637696617
    hash A: 637696613
    hash B: 988196095
    hash A: 988196062
    hash B: -1759370886
    hash A: -1759370843   <-- this result should be equal to the Java result
    hash B: -1883572545        

Below you can see the Java source code:

private static final int FNV_PRIME = 0x1000193;
private static final int FNV_COMPRESS = 0xFFFF;
...

public long getHash(int inputNumber)
{
    int hash = FNVCalculator.FNV_PRIME;
    ByteBuffer intToByteArrayConverter = ByteBuffer.allocate(4);
    intToByteArrayConverter.putInt(inputNumber);
    byte[] inputValues = intToByteArrayConverter.array();

    // inputValues.length is always equal to 4
    for (byte processCounter = (byte) 0; processCounter < inputValues.length; processCounter++)
    {
        hash ^= inputValues[processCounter];
        System.out.println("hash A: " + hash);
        hash *= FNV_PRIME;
        System.out.println("hash B: " + hash);
    }
    return (hash & FNVCalculator.FNV_COMPRESS);
}

The following snippet shows the JavaScript code:

var Constants =
{
    FNV_PRIME: parseInt("1000193", 16),
    FNV_COMPRESS: parseInt("FFFF", 16),
    BYTE_ARRAY_LENGTH: 4,
    ...
};
Object.freeze(Constants);

var hash = Constants.FNV_PRIME;

for (var counter = 0; counter < Constants.BYTE_ARRAY_LENGTH; counter++)
{
    hash ^= inputNumberArray[counter];
    console.log("hash A: " + hash);
    // mutltiply the hash with the 32 bit FNV prime number: 2^24 + 2^8 + 0x93
    // source: https://github.com/wiedi/node-fnv/blob/master/fnv.js
    hash += ((hash << 24) + (hash << 8) + (hash << 7) + (hash << 4) + (hash << 1));
    hash |= 0;
    console.log("hash B: " + hash);
}

return (hash & Constants.FNV_COMPRESS);

The array with the numbers is equal in the Java as well as in the JavaScript version, as show below (all numbers are decimal numbers):

Java version:
    inputValues[0]: 0
    inputValues[1]: 12
    inputValues[2]: 33
    inputValues[3]: -33
JavaScript version:
    inputNumberArray[0]: 0
    inputNumberArray[1]: 12
    inputNumberArray[2]: 33
    inputNumberArray[3]: -33

I have already tried replacing the byte array with an integer array, but it has not helped. I'm using WebKit's JavaScriptCore engine.


Solution

  • Seeing the values, I suspect that Java is sign extending the 223 when you convert it to a series of bytes and Javascript isn't. 223 = 0xDF = 0xFFFFFFDF when sign extended....

    Things to be careful about when porting between Java and JavaScript.

    Bitwise shift operators only operate on 32 bit values in Javascript. JavaScript internally represents all numbers as 64 bit floats and does not, distinguish between floats and ints as Java does. Even more, JavaScript does not have int or float sizes e.g. there is no byte, int or long types.

    There is always a risk becoming unstuck because of the above statements and the difference in the way the languages represents numbers.