为什么 JavaScript 除法中有一个循环作为乘法代码?

问题描述 投票:0回答:2

我从黑客喜悦档案中获得了下面的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>

c++ math multiplication integer-division
2个回答
1
投票
在C代码中:

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

;没有数据类型!
没有整数除法,没有整数乘法!
按位运算并不容易,也不适合在该算法中使用。 数字数据是通过 number 和 BigInt 实现的,这与 C
int
unsigned long long
 不同。

因此,该算法使用循环来迭代相加并比较“除法和乘法”是否发生在最接近的整数内。

两个版本都尝试实现相同的算法。两个“应该”给出相同的答案,但 JS 版本是“有缺陷的”且非标准。 虽然 JS 版本有很多问题,但我只强调 3 个:

  1. 在循环中,在尝试获得 2 的最佳幂时,我们有以下两个语句:

    p = p + 1; q1 = 2*q1; // Update q1 = 2**p/|nc|.
    它基本上是递增计数器并将数字乘以 2,这是 C++ 中的左移。 C++ 版本不需要这些繁琐的内容。

  2. while

    条件在
    ||
    的RHS上有2个相等比较:

    while (q1 < delta || (q1 == delta && r1 == 0))
    但是这两个在浮点计算中都是假的[[例如检查

    Math.sqrt(2)*Math.sqrt(0.5) == 1

    :即使这必须是真的,它几乎总是假的]]因此
    while
    条件基本上是
    ||
    的LHS,因为 RHS 永远是假的。

  3. JS 版本仅返回一个变量

    mag

    ,但用户应该获取(并使用)由全局变量访问给出的偶数变量
    shift
    。不一致且不好!

比较一下,我们发现C版本更标准,但重点是不使用

auto

,而是使用已知位数的
int64_t


0
投票
首先,我认为

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

最新问题
© www.soinside.com 2019 - 2025. All rights reserved.