经过几次乘法**溢出**后,是不是可以得到一个数字的原始值?

Posted

技术标签:

【中文标题】经过几次乘法**溢出**后,是不是可以得到一个数字的原始值?【英文标题】:Is it possible to get the original value of a number, after several multiplications **with overflow**?经过几次乘法**溢出**后,是否可以得到一个数字的原始值? 【发布时间】:2011-08-20 17:10:53 【问题描述】:

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


详细说明:

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

我现在拥有的算法:

从“abcd”哈希中减去“cd”哈希(从多项式中删除最后一个元素) 将“abcd”哈希除以p ^ len( "cd" ),其中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 是小数字),这将有效。但如果不是 - 它不起作用。

有什么诀窍吗?

附: c++标签是因为数字溢出,因为它是特定的(并且不同于python,scheme或sth)

【问题讨论】:

对于p = 2 这是不可能的。对于所有其他素数p,它可能... @Sven Marnach - 怎么样?我不能减去最后一个字母,然后除以基数 (p),然后再次减去最后一个字母并再次除以 p 等,因为我不知道字符串,但只知道它们的哈希.. @Sven Marnach - 另外,可能是什么?要“还原”数字还是计算哈希?如果是第二个,我想我需要接受 cnicutar 的回答并提出关于散列的新问题? 【参考方案1】:

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

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

【讨论】:

无符号值不会溢出。您的答案仅适用于有符号算术。 不是标准定义的,而是架构定义的。在 x86 上,add 指令具有称为溢出的确定性行为。 @R:无符号值可能溢出。 uint8_t x = 255; x += 1; x 的值将是 0 -- 溢出。 @Travis:不,这是包装,不是溢出。对于无符号类型,这是定义明确的行为,标准准确描述了这是如何完成的,即使用算术模块,对应的幂为 2。 是的,当我发布我的答案时,尚未对未签名进行澄清。此外,架构定义没有任何意义。它在一般情况下不起作用,因为并非所有架构的行为都相同!【参考方案2】:

溢出部分不知道,但是有办法取回原来的值。

中国剩余定理有很大帮助。让我们致电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

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

【讨论】:

【参考方案3】:

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

此外,您应该乘以 p 的乘法逆元,而不是除以适当的值。例如,如果 p=3 并且您的哈希值为 8 位,则乘以 171,因为 171*3=513=2*256+1。如果 p 和模值互质,则存在乘法逆元。

【讨论】:

它是无符号整数,我刚刚编辑了问题。嗯,这听起来很合理,但据我所知,找到这样的逆元素绝非易事,而且会减慢速度。这个想法+1。我会考虑的。【参考方案4】:

这里只是一个部分的侧面答案:我相信使用无符号整数不是绝对必要的。您可以使用one's complement。

但请注意,这将为 -0 和 +0 提供单独的表示,并且您可能必须在此过程中手动编码算术运算。

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

【讨论】:

【参考方案5】:

你有 a * b = c mod 2^32 (或 mod 其他东西,取决于你如何做你的哈希)。如果你能找到 d 使得 b * d = 1 mod 2^32 (或 mod 其他),那么你可以计算 a * b * d = a 并检索 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 是您要乘以的数字。

【讨论】:

【参考方案6】:

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


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

wikipedia 中有一篇不错的文章 - 模乘逆ma 互质的情况下使用欧拉定理:

其中φ(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 ) ;

工作完美且速度非常快。

【讨论】:

以上是关于经过几次乘法**溢出**后,是不是可以得到一个数字的原始值?的主要内容,如果未能解决你的问题,请参考以下文章

1.24 Java周末总结 乘法数据的溢出 判断字符串是否为纯数字

如何解决可能的乘法溢出以获得正确的模数运算?

如何使用递归函数(C#,D&C)避免非常大的数字的溢出乘法

ubuntu开机后无法进入桌面

请问JavaScript中如何处理数字和字符串相乘的情况?

为什么我会在一段时间后继续得到这个Node SyntaxError?