在JavaScript中使用64位结果的32位带符号乘法

时间:2012-11-28 03:36:03

标签: javascript algorithm integer multiplication

我正在用JavaScript开发一个虚拟机,需要将两个带符号的32位数字与一个64位有符号结果相乘,存储为两个32位带符号数字(高32位和低32位)。

我设法对无符号数字做同样的事情,将两个数字分成16位对并乘以:a*b = (ah * 2^16 + al) * (bh * 2^16 + bl)

function mul_32_unsigned( a, b )
{
  var ah = a >>> 16;
  var bh = b >>> 16;
  var al = a & 0xFFFF;
  var bl = b & 0xFFFF;

  var mid = ah * bl + al * bh;
  var albl = al * bl;

  var imm = mid + ( albl >>> 16 );

  var carry = ( imm > 0xffffffff ) ? 0x10000 : 0;

  var lo = ( ( mid << 16 ) + albl ) >>> 0;
  var hi = ( ah * bh + ( imm >>> 16 ) + carry ) >>> 0;

  return [ lo, hi ];
}

但是,我真的不明白如何为签名号码做同样的事情。我唯一能想到的是否定任何否定ab以使两者都为正,执行无符号乘法,然后在需要时否定结果,但这感觉就像一个无能的次优解。关于如何做得更好的任何想法?将ab拆分成两个带符号的16位数字似乎都是合乎逻辑的,但是我觉得如何在没有任何错误的情况下执行其余操作时会感到迷茫。

P.S。如果您认为我的未签名实现也不是最理想的,请随时指出。

2 个答案:

答案 0 :(得分:4)

将带符号的32位整数分割为两个16位整数的正确方法是作为带符号的16位上半部分和无符号的16位下半部分 - 您需要对负数进行调整,从上半部分中减去一个,并在下半部分加上2 ^ 16(以使其成为正数)。

例如,数字-100000应该变为-2的上半部分和31072的下半部分。您可以通过重构看到-2 * 2 ^ 16 + 31072 == -131072 + 31072 == -100000。

在此之后,您可以正常进行交叉乘法算法;结果的上半部分将是一个带符号的32位整数(因为它是其中一些产品被签名的乘积之和),而下半部分将是无符号的32位整数。解释它涉及反过来做同样的“技巧”。

顺便说一下,如果你在本机上进行乘法运算的机器上查看原始整数的单个单词,这就相当自然地解释了你会看到的内容。

答案 1 :(得分:0)

我发现自己遇到了同样的问题,并没有找到完整的答案。这不是一件轻而易举的事。所以我在这里提出了一个解决方案:

if (!Math.umul32_64) {
    Math.umul32_64 = function (a, b, result) {
        if (result === undefined) result = [0, 0];

        a >>>= 0;
        b >>>= 0;

        if (a < 32767 && b < 65536) {
            result[0] = a * b;
            result[1] = (result[0] < 0) ? -1 : 0;
            return result;
        }

        var a00 = a & 0xFFFF, a16 = a >>> 16;
        var b00 = b & 0xFFFF, b16 = b >>> 16;

        var c00 = a00 * b00;
        var c16 = (c00 >>> 16) + (a16 * b00);
        var c32 = c16 >>> 16;
        c16 = (c16 & 0xFFFF) + (a00 * b16);
        c32 += c16 >>> 16;
        var c48 = c32 >>> 16;
        c32 = (c32 & 0xFFFF) + (a16 * b16);
        c48 += c32 >>> 16;

        result[0] = ((c16 & 0xFFFF) << 16) | (c00 & 0xFFFF);
        result[1] = ((c48 & 0xFFFF) << 16) | (c32 & 0xFFFF);
        return result;
    };
}

if (!Math.imul32_64) {
    Math.imul32_64 = function (a, b, result) {
        if (result === undefined) result = [0, 0];

        if (a == 0) return result[0] = result[1] = 0, result;
        if (b == 0) return result[0] = result[1] = 0, result;

        a |= 0, b |= 0;

        if ((a >= -32768 && a <= 32767) && (b >= -32768 && b <= 32767)) {
            result[0] = a * b;
            result[1] = (result[0] < 0) ? -1 : 0;
            return result;
        }

        var doNegate = (a < 0) ^ (b < 0);

        Math.umul32_64(Math.abs(a), Math.abs(b), result);

        if (doNegate) {
            result[0] = ~result[0];
            result[1] = ~result[1];
            result[0] = (result[0] + 1) | 0;
            if (result[0] == 0) result[1] = (result[1] + 1) | 0;
        }

        return result;
    };
}