经过几次乘法**溢出**后,是不是可以得到一个数字的原始值?
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 中有一篇不错的文章 - 模乘逆 在m
和a
互质的情况下使用欧拉定理:
其中φ(m)
是Euler's totient function。
在我的例子中,m
(模数)是哈希类型的大小 - 2^32
、2^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 ) ;
工作完美且速度非常快。
【讨论】:
以上是关于经过几次乘法**溢出**后,是不是可以得到一个数字的原始值?的主要内容,如果未能解决你的问题,请参考以下文章