经过几次乘法**和溢出**后,是否有可能得到一个数的原始值?

时间:2011-05-07 11:32:18

标签: c++ c algorithm hash modular

摘要:有没有办法做到这一点?这就是我的意思:假设我有一个 unsigned int 数字。然后我将它乘以几次(并且有溢出,这是预期的)。那么是否可以“恢复”原始值?


详情:

关于Rabin-Karp rolling hash的全部内容。我需要做的是:我有一个长字符串的哈希 - 例如:“abcd”。然后我有一个更短的子串的哈希 - 例如“cd”。如何用O(1)计算“ab”哈希值,使用两个给定的哈希值?

我现在所拥有的算法:

  • 从“abcd”哈希中减去“cd”哈希值(从多项式中删除最后一个元素)
  • 通过p ^ len( "cd" )划分“abcd”哈希,​​其中p是基数(素数)。

所以这是:

a * p ^ 3 + b * p ^ 2 + c * p ^ 1 + d * p ^ 0 - abcd

c * p ^ 1 + d * p ^ 0 - cd

ab 获取:

( 
  ( a * p ^ 3 + b * p ^ 2 + c * p ^ 1 + d * p ^ 0 ) -
  ( c * p ^ 1 + d * p ^ 0 ) 
)
/ ( p ^ 2 )
= a * p ^ 1 + b * p ^ 0

这是有效的,如果我没有溢出(如果p是小数字)。但如果不是 - 它不起作用。

有什么伎俩吗?

P.S。 c++标签是由于数字的溢出,因为它是特定的(并且与python,scheme或sth不同)

6 个答案:

答案 0 :(得分:5)

不知道溢出部分,但有一种方法可以取回原始值。

中国剩余定理帮助很大。我们打电话给h = abcd - cd。 G是值h,没有溢出G = h + k*2^32,假设溢出只是%2^32。因此ab = G / p^2

G = h (mod 2^32)
G = 0 (mod p^2)

如果p ^ 2和2 ^ 32是互质的。 Chinese Remainder Theorem上的此页面为我们提供了

G = h * b * p^2 (mod 2^32 * p^2)

其中b是模数乘法逆p ^ 2模2 ^ 32,b * p^2 = 1 (mod 2^32)。在您计算G后,只需按p^2除以ab

我希望我没有犯任何错误......

答案 1 :(得分:3)

扩展欧几里得算法是一个很好的解决方案,但它太复杂,难以实现。有一个更好的。


还有另一种方法可以做到这一点(感谢我的朋友(:)

ma是互质时,使用欧拉定理的wikipedia - 模乘法逆中有一篇很好的文章:

Euler's theorem for coprime number and modulo

其中φ(m)Euler's totient function

就我而言,m(模数)是散列类型的大小 - 2^322^64等等(在我的情况下为64位)。
嗯,这意味着,我们应该只找到φ(m)的值。但是考虑一下 - m == 2 ^ 64这样,我们就可以保证m 将是所有奇数的 偶数。因此,我们需要做的是获取所有值的数量并将它们除以2.

另外,我们知道m将是未签名的,否则我们会遇到一些问题。比这更让我们有机会这样做:

hash_t x = -1;
x /= 2;
hash_t a_reverse = fast_pow( a, x );

好吧,关于64位数字,x真是一个大数字(19位:9 223 372 036 854 775 807),但是fast_pow非常快,我们可以缓存反向数字,以防我们需要多个查询。

fast_pow是一个众所周知的算法:

hash_t fast_pow( hash_t source, hash_t pow )
{
    if( 0 == pow )
    {
        return 1;
    }

    if( 0 != pow % 2 )
    {
        return source * fast_pow( source, pow - 1 );
    }
    else
    {
        return fast_pow( source * source, pow / 2  );    
    }

}

增加:例如:

    hash_t base = 2305843009213693951;  // 9th mersenne prime
    hash_t x = 1234567890987654321;

    x *= fast_pow( base, 123456789 );   // x * ( base ^ 123456789 )

    hash_t y = -1;
    y /= 2;
    hash_t base_reverse = fast_pow( base, y );

    x *= fast_pow( base_reverse, 123456789 );   // x * ( base_reverse ^ 123456789 )
    assert( x == 1234567890987654321 ) ;

完美且非常快。

答案 2 :(得分:1)

您应该使用无符号整数来获得定义的溢出行为(模2 ^ N)。有符号整数溢出未定义。

此外,不是划分,而是应该乘以p的乘法逆与模相应的值。例如,如果p = 3并且您的哈希值是8位,则乘以171,因为171 * 3 = 513 = 2 * 256 + 1。如果p和模数值是相对素数,则存在乘法逆。

答案 3 :(得分:1)

这里只是一个部分的答案:我认为使用无符号整数是非 。您可以使用one's complement

但请注意,这将为-0和+0提供单独的表示,并且您可能需要手动编码算术运算。

某些处理器指令与整数表示无关,但不是全部。

答案 4 :(得分:1)

你有一个* b = c mod 2 ^ 32(或者根据你的哈希方式修改其他东西)。如果你能找到d这样b * d = 1 mod 2 ^ 32(或mod什么)那么你可以计算a * b * d = a 并检索一个。如果gcd(b,mod 2 ^ 32)= 1,那么你可以使用http://en.wikipedia.org/wiki/Extended_Euclidean_algorithm找到x和y,使得b * x + 2 ^ 32 * y = 1,或者 b * x = 1 - y * 2 ^ 32,或 b * x = 1 mod 2 ^ 32,所以x是你想要乘以的数字。

答案 5 :(得分:0)

所以溢出实际上只是你的编译器对你很好; C / ++标准实际上表明溢出是未定义的行为。因此,一旦你溢出,实际上你无能为力,因为你的程序不再是确定性的。

您可能需要重新考虑算法,或采用模运算/减法来修复算法。