【问题标题】:How to convert a BigInt, to two's complement binary in Javascript?如何将 BigInt 转换为 Javascript 中的二进制补码?
【发布时间】:2022-08-13 21:35:52
【问题描述】:

使用“正常”数字(32 位范围),我使用零填充右移运算符转换为二进制,它适用于正数和负数(导致二进制补码):

const numberToConvert = -100
(numberToConvert  >>> 0).toString(2);
//Result is correct, in two\'s complement: \'11111111111111111111111110011100\'

但是,如何使用负 BigInt 来做到这一点? 如果我做:

(-1000000000000000000n >>> 0).toString(2)

我收到一个错误\"Uncaught TypeError: 不能混合 BigInt 和其他类型,使用显式转换\"

所以然后我尝试使用 0 作为 bigint:

(-1000000000000000000n >>> 0n).toString(2)

我收到以下错误:未捕获的 TypeError:BigInts 没有无符号右移,请改用 >>

这样做会产生非二进制补码二进制文件,并附有 \"-\":

(-1000000000000000000n >> 0n).toString(2)
//Result is:\'-110111100000101101101011001110100111011001000000000000000000\'

如何获得负 bigint 的二进制补码?

  • 你不用说多少位吗?因为,你怎么知道有多少1\'s 放在前面?您可能可以使用 100 或 200。例如,添加足够使其成为 128 位或 256 位

标签: javascript binary bigint


【解决方案1】:

无论如何,位运算符都适用于 32 位整数,以及为什么它不能与 BigInt 一起使用,正如 JavaScript Definitive Guide, 7th Ed, David Flanagan, O'Reilly, p. 中所引用的那样。 78:

零填充右移 (>>>):这是 JavaScript 位运算符中唯一不能与 BigInt 值一起使用的运算符。 BigInt 不会像 32 位整数那样通过设置高位来表示负数,并且该运算符仅对特定的二进制补码表示有意义。

另请注意,它看起来像是给你补码,但实际上,负数被转换为 32 位无符号整数,然后打印为二进制,给你的印象是它是二进制补码:

console.log(-100 >>> 0);     // => 4294967196

二进制补码具有以下性质:

  1. 你有一个数字,比如123,它是 8 位二进制的 01111011,你想要它的负数,即 -123

  2. 二补说:你想要的答案,把它当作一个正数,和原来的数字123相加,你就会得到所有0的8位数字溢出。

  3. 例如,将所有内容都视为正数,123 + theAnswerYouWant01111011 + 10000101,这正是带有溢出的00000000,即100000000(注意前面额外的1)。换句话说,您想要256 - 123,即133,如果您将133 渲染为8 位,这就是您想要的答案。

  4. 因此,您可以使用 28减去原始数字,并将其视为正数并使用您已经拥有的.toString(2) 显示它。

  5. 以下是 64 位的:

    function getBinary(a, nBits) {
      [a, nBits] = [BigInt(a), BigInt(nBits)];
    
      if ((a > 0 && a >= 2n ** (nBits - 1n)) || (a < 0 && -a > 2n ** (nBits - 1n))) {
        throw new RangeError("overflow error");
      }
    
      return a >= 0
        ? a.toString(2).padStart(Number(nBits), "0")
        : (BigInt(2n ** nBits) + a).toString(2);
    }
    
    console.log(getBinary(1000000000000000000n, 64));
    console.log(getBinary(-1000000000000000000n, 64));
    console.log(getBinary(-1, 64));
    console.log(getBinary(-2, 64));
    console.log(getBinary(-3, 64));
    console.log(getBinary(-4, 64n));   // trying the nBits as a BigInt as a test
    console.log(getBinary(2n ** 63n - 1n, 64));
    console.log(getBinary(-(2n ** 63n), 64));
    
    // console.log(getBinary(2n ** 63n, 64));           // throw Error
    // console.log(getBinary(-(2n ** 63n) - 1n, 64));   // throw Error

    请注意,当 a 为负数时,您不必填充它,因为例如,如果它是 8 位,则显示的数字是从 1111111110000000 的任何位置,并且始终为 8 位。

    更多细节:

    1. 您可能已经知道反码只是简单地翻转位(从 0 到 1,以及从 1 到 0)。另一种思考方式是,将两个数字相加,将变为全部1s。
    2. 描述二进制补码的常用方法是翻转位,并将1 添加到其中。你看,如果你从11111111 开始减去01111011(十进制的123),你会得到10000100,它与翻转位完全相同。 (实际上这是从上面得出的:将它们相加得到所有1s,因此使用所有1s 减去其中一个得到另一个。
    3. 那么,如果你从11111111开始减去那个数字,然后加上1,这不就和使用11111111一样,加上1,然后减去那个数字吗?好吧,11111111 加上 1100000000(注意前面额外的 1)——这正是从 2 开始n其中n 是 n 位整数,然后减去该数字。所以你明白为什么这篇文章开头的属性是真的。
    4. 其实二的补码就是为了这样的目的而设计的:如果我们想找出2 - 1,让计算机计算出来,我们只需要把这个“二的补码”当作正数,然后用处理器的“添加电路”:00000010 加上11111111。我们得到00000001,但有进位(溢出)。如果我们通过丢弃它来正确处理溢出,我们会得到答案:1。如果我们使用反码,我们不能使用相同的加法电路来执行00000010 + 11111110 得到1,因为结果是00000000,即0
    5. 考虑 (4) 的另一种方法是,如果你有一辆汽车的里程表,它显示到目前为止000002 英里,你如何从中减去1?好吧,如果您将-1 表示为9999999,那么您只需将999999 添加到2,并得到1000001,但最左边的1 不会显示在里程表上,现在里程表将变为000001。在十进制中,将-1 表示为999999 是10 的补码。在二进制中,将-1 表示为11111111 称为二进制补码。

【讨论】:

    【解决方案2】:

    二进制补码仅对固定位长度有意义。 Numbers 被转换为 32 位整数(这是 javascript 比较混乱时的旧约定)。 BigInt 没有这种转换,因为长度被认为是任意的。因此,为了在BigInt 中使用二进制补码,您需要确定要使用的长度,然后对其进行转换。许多地方都描述了到二进制补码的转换,包括Wikipedia

    在这里,我们使用LSB to MSB method,因为它很容易在 javascript 中实现为字符串处理:

    const toTwosComplement = (n, len) => {
      // `n` must be an integer
      // `len` must be a positive integer greater than bit-length of `n`
    
      n = BigInt(n);
    
      len = Number(len);
      if(!Number.isInteger(len)) throw '`len` must be an integer';
      if(len <= 0) throw '`len` must be greater than zero';
      
      // If non-negative, a straight conversion works
      if(n >= 0){
        n = n.toString(2)
        if(n.length >= len) throw 'out of range';
        return n.padStart(len, '0');
      }
      
      n = (-n).toString(2); // make positive and convert to bit string
      
      if(!(n.length < len || n === '1'.padEnd(len, '0'))) throw 'out of range';
      
      // Start at the LSB and work up. Copy bits up to and including the
      // first 1 bit then invert the remaining
      let invert = false;
      return n.split('').reverse().map(bit => {
        if(invert) return bit === '0' ? '1' : '0';
        if(bit === '0') return bit;
        invert = true;
        return bit;
      }).reverse().join('').padStart(len, '1');
    };
    
    console.log(toTwosComplement( 1000000000000000000n, 64));
    console.log(toTwosComplement(-1000000000000000000n, 64));
    console.log(toTwosComplement(-1, 64));
    console.log(toTwosComplement(2n**63n-1n, 64));
    console.log(toTwosComplement(-(2n**63n), 64));
    div.as-console-wrapper{max-height:none;height:100%;}

    【讨论】:

      猜你喜欢
      • 2013-09-28
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-07-16
      • 2011-04-18
      • 2014-11-15
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多