表示数字的位数

时间:2010-06-20 23:14:28

标签: javascript bit-manipulation bits

我正在尝试编写一个函数来返回一个正整数的位数,小于(2 ^ 53)-1的Javascript限制。但是我遇到了精度问题,并希望避免使用大整数库。

方法1:

function bitSize(num)
{
return Math.floor( Math.log(num) / Math.log(2) ) + 1;
}

Pass: bitSize( Math.pow(2, 16) -1 ) = 16
Pass: bitSize( Math.pow(2, 16) ) = 17
Fail (Should be 48): bitSize( Math.pow(2, 48) -1 ) = 49 
Pass: bitSize( Math.pow(2, 48) ) = 49

方法2:

function bitSize(num)
{
var count = 0;
while(num > 0)
{
    num = num >> 1;
    count++;
}
return count;
}

Pass: bitSize( Math.pow(2, 16) -1 ) = 16
Pass: bitSize( Math.pow(2, 16) ) = 17
Fail (Should be 48): bitSize( Math.pow(2, 48) -1 ) = 1
Fail (Should be 49): bitSize( Math.pow(2, 48) ) = 1

我认为这两种方法都不会出现精确问题。

任何人都可以提出一种替代方法,该方法适用于0 - >之间的数字。 2 ^ 53-1

感谢。

5 个答案:

答案 0 :(得分:10)

按位操作只能在Javascript中可靠地运行,最多可达32位“整数”。引用The Complete JavaScript Number Reference

  

按位操作有点像黑客攻击   在Javascript中。因为所有号码都在   Javascript是浮点数,而且   按位运算符只能工作   整数,Javascript做了一点   在幕后魔术制作它   出现按位运算   应用于32位有符号整数。

     

具体来说,Javascript需要   你正在努力的数字   数字的整数部分。它   然后将整数转换为最大值   数字代表的位数,   最多31位(符号为1位)。所以   0将创建一个两位数(1为   符号,1位为0),同样为1   会产生两位。 2会创造   一个3位数字,4将创建一个4位   号码等......

     

认识到你是很重要的   不能保证32位数字   实例运行不是零应该,   在理论上,将0转换为4,294,967,295,   相反,它将返回-1为两   原因,首先是所有   数字是用Javascript签名的   “不”总是反转标志,并且   第二个Javascript无法生成更多   比数字零和一点多   不是零变成一个。因此〜0 = -1。

     

所以Javascript中的按位符号都在上升   到32位。

正如Anurag所说,在这种情况下你应该简单地使用内置的num.toString(2),它输出一个最小长度的ASCII '1''0' s字符串,你可以只需要长度。

答案 1 :(得分:9)

你可以这样做:

function bitSize(num) {
    return num.toString(2).length;
}

Number的{​​{3}}方法将基数作为可选参数。

以下是一些toString()。适用于Chrome,Safari,Opera和Firefox。不能访问IE,抱歉。

答案 2 :(得分:2)

ES6标准带来Math.clz32(),因此对于32位范围内的数字,您可以写:

num_bits = 32 - Math.clz32(0b1000000);   

在此代码段中测试:

var input = document.querySelector('input');
var bits = document.querySelector('#bits');

input.oninput = function() {
    var num = parseInt(input.value);
    bits.textContent = 32 - Math.clz32(num);
};
Number (Decimal): <input type="text"><br>
Number of bits: <span id="bits"></span>

MDN's documentation of Math.clz32上提供了一个polyfill:

Math.imul = Math.imul || function(a, b) {
  var ah = (a >>> 16) & 0xffff;
  var al = a & 0xffff;
  var bh = (b >>> 16) & 0xffff;
  var bl = b & 0xffff;
  // the shift by 0 fixes the sign on the high part
  // the final |0 converts the unsigned value into a signed value
  return ((al * bl) + (((ah * bl + al * bh) << 16) >>> 0)|0);
};

Math.clz32 = Math.clz32 || (function () {
  'use strict';

  var table = [
    32, 31,  0, 16,  0, 30,  3,  0, 15,  0,  0,  0, 29, 10,  2,  0,
     0,  0, 12, 14, 21,  0, 19,  0,  0, 28,  0, 25,  0,  9,  1,  0,
    17,  0,  4,   ,  0,  0, 11,  0, 13, 22, 20,  0, 26,  0,  0, 18,
     5,  0,  0, 23,  0, 27,  0,  6,  0, 24,  7,  0,  8,  0,  0,  0]

  // Adapted from an algorithm in Hacker's Delight, page 103.
  return function (x) {
    // Note that the variables may not necessarily be the same.

    // 1. Let n = ToUint32(x).
    var v = Number(x) >>> 0

    // 2. Let p be the number of leading zero bits in the 32-bit binary representation of n.
    v |= v >>> 1
    v |= v >>> 2
    v |= v >>> 4
    v |= v >>> 8
    v |= v >>> 16
    v = table[Math.imul(v, 0x06EB14F9) >>> 26]

    // Return p.
    return v
  }
})();

document.body.textContent = 32 - Math.clz32(0b1000000);

答案 3 :(得分:1)

使用位改变的相应边界构建查找表。您可以仅为较大的值执行此操作,并通过对数仍然执行较小的值。它似乎通常与浮点相关,因为我也可以在PowerShell中重现它。

答案 4 :(得分:1)

晚会,但我想赞成trincot的32位answer更快,更简单,更好支持全53位方法。

以下两个示例将只读取/解析并返回float的exponent-value。

对于支持ES6 ArrayBufferDataView的现代浏览器(不关心平台的endianness,但遗留兼容性较低):

reqBits4Int = (function(d){ 'use strict';
  return function(n){
    return n && (                         // return 0 if n===0 (instead of 1)
      d.setFloat64(0, n),                 // OR set float to buffer and
      d.getUint16(0) - 16352 >> 4 & 2047  // return float's parsed exponent
    );                                    // Offset 1022<<4=16352; 0b1111111=2047
  };                                      // DataView methods default to big-endian
})( new DataView(new ArrayBuffer(8)) );   // Pass a Buffer's DataView to IFFE


支持Float64ArrayUint16Array但没有DataView的略微较旧的浏览器示例,因此字节顺序取决于平台,此代码段假定为“标准”小端:

reqBits4Int = (function(){ 'use strict';
  var f = new Float64Array(1),            // one 8-byte element (64bits)
      w = new Uint16Array(f.buffer, 6);   // one 2-byte element (start at byte 6)
  return function(n){ 
    return ( f[0] = n                     // update float array buffer element
                                          // and return 0 if n===0 (instead of 1)
           ) && w[0] - 16352 >> 4 & 2047; // or return float's parsed exponent
  };  //NOTE : assumes little-endian platform
})(); //end IIFE


上述两个版本都返回一个正整数Number,表示保存作为参数传递的整数Number所需的最大位。
它们在[-2 53 ,2 53 ] 的整个范围内无误差地工作,并且超出此范围,覆盖整个浮动范围的正值浮点指数除了,其中输入Number上已经发生舍入(例如2 55 -1) 存储为2 < sup> 55 (显然, 等于56位)。

解释IEEE 754浮点格式实际上超出了本答案的范围,但是对于那些基本了解的人我已经在下面包含了折叠片段,其中包含表格形式的计算,从中可以看到/解释逻辑:实际上我们只是抓住浮动的第一个字(16个MSB包含符号和全指数),减去4位移位偏移和zeroing_offset(保存2个操作),移位和屏蔽结果作为输出。 0在函数中得到了处理。

<xmp> PREVIEW of data to be generated: 

Float value :  S_exponent__MMMM :  # -(1022<<4)#### :  #   >> 4     :    & 2047    : Result integer
         -9 :  1100000000100010 :  1000000001000010 :  100000000100 :          100 :     4
         -8 :  1100000000100000 :  1000000001000000 :  100000000100 :          100 :     4
         -7 :  1100000000011100 :  1000000000111100 :  100000000011 :           11 :     3
         -6 :  1100000000011000 :  1000000000111000 :  100000000011 :           11 :     3
         -5 :  1100000000010100 :  1000000000110100 :  100000000011 :           11 :     3
         -4 :  1100000000010000 :  1000000000110000 :  100000000011 :           11 :     3
         -3 :  1100000000001000 :  1000000000101000 :  100000000010 :           10 :     2
         -2 :  1100000000000000 :  1000000000100000 :  100000000010 :           10 :     2
         -1 :  1011111111110000 :  1000000000010000 :  100000000001 :            1 :     1
          0 :                 0 :   -11111111100000 :   -1111111110 :  10000000010 :  1026
          1 :    11111111110000 :             10000 :             1 :            1 :     1
          2 :   100000000000000 :            100000 :            10 :           10 :     2
          3 :   100000000001000 :            101000 :            10 :           10 :     2
          4 :   100000000010000 :            110000 :            11 :           11 :     3
          5 :   100000000010100 :            110100 :            11 :           11 :     3
          6 :   100000000011000 :            111000 :            11 :           11 :     3
          7 :   100000000011100 :            111100 :            11 :           11 :     3
          8 :   100000000100000 :           1000000 :           100 :          100 :     4
          9 :   100000000100010 :           1000010 :           100 :          100 :     4

after 18 the generated list will only show 3 values before and after the exponent change
</xmp>

<script> //requires dataview, if not available see post how to rewrite or just examine example above
firstFloatWord = (function(d){ 
  return function(n){
    return d.setFloat64(0, n), d.getUint16(0);
  };
})( new DataView(new ArrayBuffer(8)) );

function pad(v, p){
  return ('                    '+v).slice(-p);
}

for( var r= '',   i=-18, c=0, t
   ; i < 18014398509481984
   ; i= i>17 && c>=5
      ? (r+='\n', c=0, (i-2)*2-3)
      : (++c, i+1) 
   ){
  r+= pad(i, 19) + ' : '   
    + pad((t=firstFloatWord(i)).toString(2), 17) + ' : '
    + pad((t-=16352).toString(2), 17) + ' : '
    + pad((t>>=4).toString(2), 13) + ' : '
    + pad((t&=2047).toString(2), 12) + ' : '
    + pad(t, 5) + '\n';
}

document.body.innerHTML='<xmp>        Float value :  S_exponent__MMMM :  # -(1022<<4)#### : '
                       + ' #   >> 4     :    & 2047    : Result integer\n' + r + '</xmp>';
</script>

后备选项:

ECMAScript(javascript)让实施者可以自由选择如何实现该语言。因此,在野外的x浏览器世界中,不仅需要处理舍入差异,还需要处理不同的算法,例如Math.logMath.log2等。 正如您已经注意到的那样(您的方法1),log2(polyfill)可能不起作用的常见示例是2 48 (= 49,当它被覆盖时是一对多),但那是不是唯一的例子 例如,某些版本的chrome甚至会将显着更小的数字搞砸,例如:Math.log2(8) = 2.9999999999999996(当被覆盖时减少一个)。
在此stackoverflow中了解更多相关内容Q / A:Math.log2 precision has changed in Chrome 这意味着我们无法知道何时落地或细化我们的对数结果(或者在舍入之前我们已经离开时很容易预测)。

因此,您可以计算在循环中小于1之前将输入数除以2的频率(非常类似于您计算的32位移位方法2):

function reqBits4Int(n){ for(var c=0; n>=1; ++c, n/=2); return c }

但这是相当蛮力(也可能让你陷入四舍五入的问题)。你可以通过一些划分和征服来改善这一点,当你在它时,展开循环:

function reqBits4Int(n){ 'use strict';
  var r= 4294967295 < n  ? ( n= n/4294967296 >>>   0,      32 ) : 0 ;
              65535 < n && (               n >>>= 16,  r+= 16 );
                255 < n && (               n  >>=  8,  r+=  8 );
                 15 < n && (               n  >>=  4,  r+=  4 );
  //              3 < n && (               n  >>=  2,  r+=  2 ); 
  //              1 < n && (               n  >>=  1,  r+=  1 ); 
  // return r + n;

  // OR using a lookup number instead of the lines comented out above
  // position: 15 14 13 12 11 10  9  8  7  6  5  4  3  2  1  0  = 16 values
  //   binary: 11 11 11 11 11 11 11 11 10 10 10 10 01 01 00 00  = 4294945360 dec

  return (n && (4294945360 >>> n * 2 & 3) + 1) + r;
}

格式化旨在帮助理解算法。它会变得非常坚韧!
这有一个正整数范围[0,2 53 ] 没有错误(最多2 64 具有相同的可预测舍入 - '错误“)。

或者,您可以尝试其他一些(重复,对于大于32位的输入值)bithacks

与上面的计算片段相比,最简单和最短(但可能更慢)是将数字字符串化并计算得到的字符串长度,如Anurag的answer,基本上是:return n && n.toString(2).length;(假设浏览器)可以得到最多(至少)53位的结果。