简体   繁体   English

将 FNV-1a 算法从 C# 移植到 Lua,乘法结果不匹配

[英]Porting FNV-1a algorithm from C# to Lua, multiplication result don't match

I'm trying to port the Accidental Noise library from C# to Lua.我正在尝试将意外噪声库从 C# 移植到 Lua。 I encounter an issue when trying to port the FNV-1A algorithm.我在尝试移植 FNV-1A 算法时遇到问题。 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:首先,我想展示算法的 C# 代码:

// 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.我注意到sizeof(Int32)结果总是乘以Int32[]数组中的元素数。 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.在这种情况下(在我的机器上)结果是 12,这导致 FNV32Buffer 函数中的缓冲区大小为 12 个字节的数组。

Inside the for loop we see the following:在 for 循环中,我们看到以下内容:

  1. A bitwise XOR operation is performed on hvalhval执行按位异或运算
  2. hval is multiplied by a prime number hval乘以一个素数

The result of the multiply operation doesn't match with the result of my Lua implementation.乘法运算的结果与我的 Lua 实现的结果不匹配。

My Lua implementation is as such:我的 Lua 实现是这样的:

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 .我没有在我的实现中提供缓冲区长度,因为 Lua 的 Bitwise 运算符总是在 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:比较 C# 和 Lua 字节数组时,我得到的结果大致相似:

byte #字节# C# C# Lua路亚
1 1 00000000 00000000
2 2 00000000 00000000
3 3 00000000 00000000
4 4 00000000 00000000
5 5 00000000 00000000
6 6 00000000 00000000
7 7 00000000 00000000
8 8 00000000 00000000
9 9 00101100 00000000
10 10 00000001 00000000
11 11 00000000 00000001
12 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.对于 C# 和 Lua 字节数组,我遍历每个字节并对每个字节执行 FNV-1A 算法。

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:当使用值{0, 0, 300} (x, y, seed) 作为 C# 和 Lua 函数的输入时,在 FNV 散列循环的第一次迭代完成后,我得到以下结果:

C#: 00000101_00001100_01011101_00011111 (84696351) C#: 00000101_00001100_01011101_00011111 (84696351)

Lua: 01111110_10111100_11101000_10111000 (2126309560) 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.我相信原因可能是 Lua 默认使用有符号数,而 C# 实现适用于无符号整数。 Or perhaps the results are different due to differences in endianness?或者可能由于字节顺序的差异而导致结果不同?

I did read that Lua uses unsigned integers when working with hex literals.我确实读到 Lua 在处理十六进制文字时使用无符号整数。 Since FNV_32_PRIME is a hex literal, I guess it should work the same as the C# implementation, yet the end result differs.由于FNV_32_PRIME是十六进制文字,我想它应该与 C# 实现相同,但最终结果不同。

How can I make sure the Lua implementation matches the results of the C# implementation?如何确保 Lua 实现与 C# 实现的结果匹配?

LuaJIT supports CPU native datatypes. LuaJIT 支持 CPU 原生数据类型。
64-bit values (suffixed with LL ) are used to avoid precision loss of multiplication result. 64 位值(以LL为后缀)用于避免乘法结果的精度损失。

-- 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

Arithmetic on 32 bit unsigned numbers does not necessarily produce a 32 bit number. 32 位无符号数的算术不一定产生 32 位数。

Not tested, but I think the result of the multiplication with the prime number should be normalized using bit.toBit() as stated in the reference you provide.未测试,但我认为应使用 bit.toBit() 对与素数相乘的结果进行归一化,如您提供的参考资料中所述。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM