【问题标题】:Modulo 7 on 64 bit integer using shift and add (32 bit works but 64 doesn't)使用移位和加法对 64 位整数取模 7(32 位有效,但 64 无效)
【发布时间】:2017-06-08 11:09:44
【问题描述】:

我有一个有效的实现来计算 32 位无符号整数的模 7,但我在 64 位实现上遇到了问题。 32 位实现来自this blog post(修复了一些错误)。我能够获得适用于模 3、5、15 和 6 的 64 位版本,但不适用于 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;
}

我做了看起来很明显的扩展,它适用于模 3、5 和 15,但对于模 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。我一直在尝试一些变体,但没有得到明显更好的效果,而且我不确定如何系统地完成它。

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

【问题讨论】:

    标签: c# math optimization bit-manipulation modulo


    【解决方案1】:

    这背后的数学原理其实很简单。

    (注意数学部分我使用a^b 表示“a 到 b 的幂”不是 “异或 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;
    }
    

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

    通过这些更改,我认为 unit 和 ulong 版本都应该产生正确的结果。 (虽然没有测试)

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

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

    【讨论】:

    • 你说得对,我设法错过了运行 mod 7 32 位的测试用例。您的更正似乎也有一些错误:14%7=7、7、21 和 28。 4294967286%7=2(应该是 1)和几个更大的数,当它们应该是 1 时 =2
    • 啊,a>7 应该是 a>=7,这将修复低数字。它应该是 a-7 而不是 a-6。希望这也能解决大量问题。
    • 完美!该博客的作者在其中有很多错误。感谢您的解释,我能够为 2^n-1 形式的其他除数推导模数
    • @BenVoigt - 公平点 - 添加了一个免责声明,即数学部分是数学,而不是代码......
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-03-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-06-22
    • 2019-03-25
    相关资源
    最近更新 更多