我从黑客喜悦档案中获得了下面的Js代码(查看源代码)
代码接收一个值(例如 7)并输出一个要相乘的幻数。然后你通过位移来得到结果。我不记得汇编或任何数学,所以我确信我错了,但我找不到我错的原因根据我的理解,您可以通过编写
ceil(1/divide * 1<<32)
(或 64 位值的
<<64
,但您需要更大的
int
)来获得幻数。如果将一个整数与
imul
相乘,您将在一个寄存器中得到结果,而在另一个寄存器中得到余数。结果寄存器神奇地是用我的公式中的这个神奇数字除法的正确结果我写了一些 C++ 代码来表达我的意思。不过我只用下面的值进行了测试。看来是对的
#include <cstdio>
#include <cassert>
int main(int argc, char *argv[])
{
auto test_divisor = 7;
auto test_value = 43;
auto a = test_value*test_divisor;
auto b = a-1; //One less test
auto magic = (1ULL<<32)/test_divisor;
if (((1ULL<<32)%test_divisor) != 0) {
magic++; //Round up
}
auto answer1 = (a*magic) >> 32;
auto answer2 = (b*magic) >> 32;
assert(answer1 == test_value);
assert(answer2 == test_value-1);
printf("%lld %lld\n", answer1, answer2);
}
来自 hackers pleasure 的 JS 代码有一个循环等等,我想知道为什么?我错过了什么吗?我可以使用哪些值来获得 JS 代码可以正确获得的错误结果?我数学不太好所以我不明白任何评论
var two31 = 0x80000000
var two32 = 0x100000000
function magic_signed(d) { with(Math) {
if (d >= two31) d = d - two32// Treat large positive as short for negative.
var ad = abs(d)
var t = two31 + (d >>> 31)
var anc = t - 1 - t%ad // Absolute value of nc.
var p = 31 // Init p.
var q1 = floor(two31/anc) // Init q1 = 2**p/|nc|.
var r1 = two31 - q1*anc // Init r1 = rem(2**p, |nc|).
var q2 = floor(two31/ad) // Init q2 = 2**p/|d|.
var r2 = two31 - q2*ad // Init r2 = rem(2**p, |d|).
do {
p = p + 1;
q1 = 2*q1; // Update q1 = 2**p/|nc|.
r1 = 2*r1; // Update r1 = rem(2**p, |nc|.
if (r1 >= anc) { // (Must be an unsigned
q1 = q1 + 1; // comparison here).
r1 = r1 - anc;}
q2 = 2*q2; // Update q2 = 2**p/|d|.
r2 = 2*r2; // Update r2 = rem(2**p, |d|.
if (r2 >= ad) { // (Must be an unsigned
q2 = q2 + 1; // comparison here).
r2 = r2 - ad;}
var delta = ad - r2;
} while (q1 < delta || (q1 == delta && r1 == 0))
var mag = q2 + 1
if (d < 0) mag = two32 - mag // Magic number and
shift = p - 32 // shift amount to return.
return mag
}}
byId('subBtn').onclick = function (e) {
e.preventDefault();
inputVal = byId('value').value + 0;
byId('result').innerText = "" + magic_signed(inputVal);
}
function byId(x) {
return document.getElementById(x);
}
<h1>Test</h1>
<form name="Magic">Value:
<input id="value">
<button id='subBtn'>Calculate</button><br>
<label id="result"></label>
</form>
auto magic = (1ULL<<32)/test_divisor;
我们在 magic
中得到一个整数值,因为
(1ULL<<32)
和
test_divisor
都是整数。该算法需要在某些条件下递增
magic
,这是下一个条件语句。现在,乘法也给出整数:
auto answer1 = (a*magic) >> 32;
auto answer2 = (b*magic) >> 32;
C 代码完成!JS代码中:
var
;没有数据类型!没有整数除法,没有整数乘法!
int
或
unsigned long long
不同。因此,该算法使用循环来迭代相加并比较“除法和乘法”是否发生在最接近的整数内。
两个版本都尝试实现相同的算法。两个“应该”给出相同的答案,但 JS 版本是“有缺陷的”且非标准。 虽然 JS 版本有很多问题,但我只强调 3 个:
p = p + 1;
q1 = 2*q1; // Update q1 = 2**p/|nc|.
它基本上是递增计数器并将数字乘以 2,这是 C++ 中的左移。 C++ 版本不需要这些繁琐的内容。
while
条件在
||
的RHS上有2个相等比较:
while (q1 < delta || (q1 == delta && r1 == 0))
但是这两个在浮点计算中都是假的[[例如检查Math.sqrt(2)*Math.sqrt(0.5) == 1
:即使这必须是真的,它几乎总是假的]]因此
while
条件基本上是
||
的LHS,因为 RHS 永远是假的。
mag
,但用户应该获取(并使用)由全局变量访问给出的偶数变量
shift
。不一致且不好!
auto
,而是使用已知位数的
int64_t
。
ceil(1/divide * 1<<32)
可能会出现结果相差一的情况,具体取决于除法。所以你不需要循环,但有时你需要一个校正因子。其次,JS 代码似乎允许除
32
之外的其他转变:
shift = p - 32 // shift amount to return
。但它永远不会返回。所以不确定那里发生了什么。为什么不在 C++ 中实现 JS 代码,然后对所有 int32_t 运行循环,看看它们是否给出相同的结果?这应该不会花太长时间。
当您找到不同的
d
时,您可以使用两个幻数测试所有
a / d
的
int32_t a
并比较
a / d
、
a * m_ceil
和
a * m_js
。