64位整数上的模7使用shift和add(32位工作,但64位不工作)

时间:2017-06-08 00:10:59

标签: c# math optimization bit-manipulation modulo

我有一个工作实现来计算32位无符号int的模7但我遇到了64位实现的问题。 32位实现来自this blog post(有一些bug修复)。我能够得到64位版本的模数3,5,15和6但不是7.数学有点过头了。

供参考,here is a gist with the full code

这是工作32位:

static public uint Mersenne7(uint a)
{
    a = (a >> 24) + (a & 0xFFFFFF); // sum base 2**24 digits
    a = (a >> 12) + (a & 0xFFF);    // sum base 2**12 digits
    a = (a >> 6) + (a & 0x3F);      // sum base 2**6 digits
    a = (a >> 3) + (a & 0x7);       // sum base 2**2 digits
    a = (a >> 2) + (a & 0x7);       // sum base 2**2 digits
    if (a > 5) a = a - 6;
    return a;
}

我做了一个看似明显的扩展,它适用于模5,5和15,但是对于mod 7,结果都是没有明显模式的地方(除了结果都在7以下):

static public ulong Mersenne7(ulong a)
{
    a = (a >> 48) + (a & 0xFFFFFFFFFFFF); // sum base 2**48 digits
    a = (a >> 24) + (a & 0xFFFFFF); // sum base 2**24 digits
    a = (a >> 12) + (a & 0xFFF);    // sum base 2**12 digits
    a = (a >> 6) + (a & 0x3F);      // sum base 2**6 digits
    a = (a >> 3) + (a & 0x7);       // sum base 2**2 digits
    a = (a >> 2) + (a & 0x7);       // sum base 2**2 digits
    if (a > 5) a = a - 6;
    return a;
}

64位的相同技术显然不适用于mod 7.我一直在尝试一些变化,但我没有得到任何明显更好的东西,我不知道如何系统地完成它。

test results for modulo 7

我已经进行了基准测试并证明,使用shift和add for Mersenne数计算模数比我环境中的内置模数运算符快,并且这是在热路径中的紧密循环中运行(索引到静态大小循环缓冲区) )。这些较低值的除数也比较大的缓冲区大小更常见。

live environment benchmark

1 个答案:

答案 0 :(得分:2)

这背后的数学实际上非常简单。

(注意数学部分I' m使用a^b表示" a表示电源b"不是 " a xor b"。这些数学部分不应该是C#代码)

关键技巧是你将a分成两部分,以便

a = b * 2^3 + c

其中b = a / 2^3 = a >> 3c = a mod 2^3 = a & 0x7

然后

a mod 7 = ((b mod 7) * (2^3 mod 7) + c ) mod 7

但是2^3 mod 7 = 1所以

a mod 7 = ( b mod 7 + c ) mod 7 = (b + c) mod 7

我们使用

多次应用此技巧
1 = 2^3 mod 7 = 2^6 mod 7 = 2^12 mod 7 = 2^24 mod 7 = 2^48 mod 7

考虑到这一点,它看起来像你的工作" Mersene7不起作用。

我想这个:

static public uint Mersenne7(uint a)
{
    a = (a >> 24) + (a & 0xFFFFFF); // sum base 2**24 digits
    a = (a >> 12) + (a & 0xFFF);    // sum base 2**12 digits
    a = (a >> 6) + (a & 0x3F);      // sum base 2**6 digits
    a = (a >> 3) + (a & 0x7);       // sum base 2**2 digits
    a = (a >> 2) + (a & 0x7);       // sum base 2**2 digits
    if (a > 5) a = a - 6;
    return a;
}

应该是

static public uint Mersenne7(uint a)
{
    a = (a >> 24) + (a & 0xFFFFFF); // sum base 2**24 digits
    a = (a >> 12) + (a & 0xFFF);    // sum base 2**12 digits
    a = (a >> 6) + (a & 0x3F);      // sum base 2**6 digits
    a = (a >> 3) + (a & 0x7);       // sum base 2**3 digits
    a = (a >> 3) + (a & 0x7);       // sum base 2**3 digits
    if (a >= 7) a = a - 7;
    return a;
}

注意最终比较中值的变化,以及最终总和线的删除。

通过这些更改,单位和ulong版本应该会产生正确的结果。 (但是,Haven未经过测试)

我已经复制了第二次收缩 - 我不确定它是否真的需要。 (它是为了处理溢出 - 但可能不会发生你需要尝试一些值来检查)

在ulong案例中,您需要a=(a>>48) + a & 0xFFFFFFFFFFFFL行,就像您已经实施的那样。