如何从八 (8) 个 4 位整数创建 32 位整数?

How to create a 32-bit integer from eight (8) 4-bit integers?

假设我有一个最大 32 位整数 -

const a =
  ((2 ** 32) - 1)
  
const b =
  parseInt("11111111111111111111111111111111", 2) // 32 bits, each is a one!
  
console.log(a === b) // true

console.log(a.toString(2))
// 11111111111111111111111111111111  (32 ones)

console.log(b.toString(2))
// 11111111111111111111111111111111  (32 ones)

到目前为止一切顺利。但现在假设我想使用八 (8) 个 4 位数字生成一个 32 位数字。这个想法很简单:将每个 4 位序列移位 (<<) 到位并将它们相加 (+) -

const make = ([ bit, ...more ], e = 0) =>
  bit === undefined
    ? 0
    : (bit << e) + make (more, e + 4)

const print = n =>
  console.log(n.toString(2))

// 4 bits
print(make([ 15 ])) // 1111

// 8 bits
print(make([ 15, 15 ])) // 11111111

// 12 bits
print(make([ 15, 15, 15 ])) // 111111111111

// 16 bits
print(make([ 15, 15, 15, 15 ])) // 1111111111111111

// 20 bits
print(make([ 15, 15, 15, 15, 15 ])) // 11111111111111111111

// 24 bits
print(make([ 15, 15, 15, 15, 15, 15 ])) // 111111111111111111111111

// 28 bits
print(make([ 15, 15, 15, 15, 15, 15, 15 ])) // 1111111111111111111111111111

// almost there ... now 32 bits
print(make([ 15, 15, 15, 15, 15, 15, 15, 15 ])) // -1 :(

我得到 -1 预期 结果是全 32 位,或 11111111111111111111111111111111.

更糟糕的是,如果我从预期的结果开始并向后工作,我会得到预期的结果 -

const c =
 `11111111111111111111111111111111`

const d = 
  parseInt(c, 2)
  
console.log(d) // 4294967295

console.log(d.toString(2) === c) // true

我尝试调试我的 make 函数以确保没有明显的问题 -

const make = ([ bit, ...more ], e = 0) =>
  bit === undefined
    ? `0`
    : `(${bit} << ${e}) + ` + make (more, e + 4)

console.log(make([ 15, 15, 15, 15, 15, 15, 15, 15 ])) 
// (15 << 0) + (15 << 4) + (15 << 8) + (15 << 12) + (15 << 16) + (15 << 20) + (15 << 24) + (15 << 28) + 0

这个公式看起来很不错。我认为这可能与 + 有关并切换到按位或 (|) 应该在这里有效地做同样的事情 -

const a =
  parseInt("1111",2)
  
const b =
  (a << 0) | (a << 4)
  
console.log(b.toString(2)) // 11111111

const c =
  b | (a << 8)
  
console.log(c.toString(2)) // 111111111111

但是,当我尝试组合所有八 (8) 个数字时,我的 make 函数出现了同样的错误 -

const make = ([ bit, ...more ], e = 0) =>
  bit === undefined
    ? 0
    : (bit << e) | make (more, e + 4)

const print = n =>
  console.log(n.toString(2))


print(make([ 15, 15, 15, 15, 15, 15, 15 ])) // 1111111111111111111111111111 (28 bits)

print(make([ 15, 15, 15, 15, 15, 15, 15, 15 ])) // -1 :(

什么给了?

目标是使用 JavaScript 将八 (8) 个 4 位整数转换为单个 32 位整数 - 这只是我的尝试。我很好奇我的功能哪里出了问题,但我愿意接受其他解决方案。

我想避免将每个 4 位整数转换为二进制字符串,将二进制字符串混合在一起,然后将二进制字符串解析为单个 int。首选数值解决方案。

按位运算符将产生一个 signed 32 位数,这意味着如果位置 31 的位(从右边的最低有效位开始计算,即位 0 ) 为 1,则数为负数。

为避免这种情况发生,请使用除 <<| 之外的其他运算符,它们都会生成带符号的 32 位数字。例如:

(bit * 2**e) + make (more, e + 4)

强制 无符号 32 位

位移运算符旨在将结果强制到带符号的 32 位范围内,至少在 mdn(撰写本文时)上声称如此:

The operands of all bitwise operators are converted to signed 32-bit integers

这其实不完全正确。 >>> 运算符是一个例外。 EcmaScript 2015, section 12.5.8.1 声明操作数在移入 0 位之前映射到 unsigned 32 位。因此,即使您将 位移动,您也会看到这种效果。

您只需将它应用于最终值一次,例如在您的 print 函数中:

console.log((n>>>0).toString(2))

BigInt 解决方案

如果您需要超过 32 位,并且您的 JavaScript 引擎支持 BigInt like some 已经支持,那么将 BigInts 用于位运算符中涉及的操作数——这些将 not 使用 32 位有符号数包装(注意 n 后缀):

const make = ([ bit, ...more ], e = 0n) =>
  bit === undefined
    ? 0n
    : (bit << e) + make (more, e + 4n)

const print = n =>
  console.log(n.toString(2))

// Test
for (let i=1; i<20; i++) {
    print(make(Array(i).fill(15n))) // longer and longer array...
}

注意:如果出现上述错误 运行,请使用 Chrome...

再试一次