I'm trying to port the Accidental Noise library from C# to Lua. I encounter an issue when trying to port the FNV-1A algorithm. The result of the multiplication with the prime doesn't match when using same input values.
First I'd like to show the C# code of the algorithm:
// The "new" FNV-1A hashing
private const UInt32 FNV_32_PRIME = 0x01000193;
private const UInt32 FNV_32_INIT = 2166136261;
public static UInt32 FNV32Buffer(Int32[] uintBuffer, UInt32 len)
{
//NOTE: Completely untested.
var buffer = new byte[len];
Buffer.BlockCopy(uintBuffer, 0, buffer, 0, buffer.Length);
var hval = FNV_32_INIT;
for (var i = 0; i < len; i++)
{
hval ^= buffer[i];
hval *= FNV_32_PRIME;
}
return hval;
}
This function is called as such (simplified) elsewhere in the codebase:
public static UInt32 HashCoordinates(Int32 x, Int32 y, Int32 seed)
{
Int32[] d = { x, y, seed };
return FNV32Buffer(d, sizeof(Int32) * 3);
}
I noticed the sizeof(Int32)
result is always multiplied by the number of elements in the Int32[]
array. In this case (on my machine) the result is 12, which causes the buffer size in the FNV32Buffer function to be an array of 12 bytes.
Inside the for loop we see the following:
hval
hval
is multiplied by a prime numberThe result of the multiply operation doesn't match with the result of my Lua implementation.
My Lua implementation is as such:
local FNV_32_PRIME = 0x01000193
local FNV_32_INIT = 0x811C9DC5
local function FNV32Buffer(buffer)
local bytes = {}
for _, v in ipairs(buffer) do
local b = toBits(v, 32)
for i = 1, 32, 8 do
bytes[#bytes + 1] = string.sub(b, i, i + 7)
end
end
local hash = FNV_32_INIT
for i, v in ipairs(bytes) do
hash = bit.bxor(hash, v)
hash = hash * FNV_32_PRIME
end
return hash
end
I don't supply the buffer length in my implementation as Lua's Bitwise operators always work on 32-bit signed integers.
In my implementation I create a bytes array and for each number in the buffer table I extract the bytes. When comparing the C# and Lua byte arrays I get mostly similar results:
byte # | C# | Lua |
---|---|---|
1 | 00000000 |
00000000 |
2 | 00000000 |
00000000 |
3 | 00000000 |
00000000 |
4 | 00000000 |
00000000 |
5 | 00000000 |
00000000 |
6 | 00000000 |
00000000 |
7 | 00000000 |
00000000 |
8 | 00000000 |
00000000 |
9 | 00101100 |
00000000 |
10 | 00000001 |
00000000 |
11 | 00000000 |
00000001 |
12 | 00000000 |
00101100 |
It seems due to endianness the byte ordering is different, but this I can change. I don't believe this has anything to do with my issue right now.
For the C# and Lua byte arrays, I loop through each byte and perform the FNV-1A algorithm on each byte.
When using the values {0, 0, 300}
(x, y, seed) as input for the C# and Lua functions I get the following results after the first iteration of the FNV hashing loop is finished:
C#: 00000101_00001100_01011101_00011111
(84696351)
Lua: 01111110_10111100_11101000_10111000
(2126309560)
As can be seen the result after just the first hashing loop are very different. From debugging I can see the numbers diverge when multiplying with the prime. I believe the cause could be that Lua uses signed numbers by default, whereas the C# implementation works on unsigned integers. Or perhaps the results are different due to differences in endianness?
I did read that Lua uses unsigned integers when working with hex literals. Since FNV_32_PRIME
is a hex literal, I guess it should work the same as the C# implementation, yet the end result differs.
How can I make sure the Lua implementation matches the results of the C# implementation?
LuaJIT supports CPU native datatypes.
64-bit values (suffixed with LL
) are used to avoid precision loss of multiplication result.
-- LuaJIT 2.1 required
local ffi = require'ffi'
-- The "new" FNV-1A hashing
local function FNV32Buffer(data, size_in_bytes)
data = ffi.cast("uint8_t*", data)
local hval = 0x811C9DC5LL
for j = 0, size_in_bytes - 1 do
hval = bit.bxor(hval, data[j]) * 0x01000193LL
end
return tonumber(bit.band(2^32-1, hval))
end
local function HashCoordinates(x, y, seed)
local d = ffi.new("int32_t[?]", 3, x, y, seed)
return FNV32Buffer(d, ffi.sizeof(d))
end
print(HashCoordinates(0, 0, 300)) --> 3732851086