Количество битов для представления числа

Я пытаюсь написать функцию, чтобы вернуть количество битов положительное целое число меньше, чем предел Javascript (2^53)-1. Однако im поражен проблемами точности и хочет избежать больших целочисленных библиотек.

Способ 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 ответов


вы можете сделать:

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

на toString() метод Number принимает radix в качестве необязательного аргумента.

вот некоторые тесты. Работает в Chrome, Safari, Opera и Firefox. Нет доступа к IE, извините.


побитовые операции будут надежно работать только в Javascript для "целых чисел" до 32-бит. цитата из Полная Ссылка На Номер JavaScript:

побитовые операции немного взломать в JavaScript. Поскольку все числа в Javascript-это плавающая точка, и побитовые операторы работают только на целые числа, Javascript делает немного за кулисами магия, чтобы сделать это. появляются побитовые операции применяется к 32bit знаковое целое число.

в частности, Javascript принимает количество и принимает целую часть числа. Он затем преобразует целое число самых количество бит, которое представляет число, до 31 бит (1 бит для знака). Так 0 создаст двухразрядное число (1 для знак и 1 бит для 0), аналогично 1 создало бы два бита. 2 создать 3-битное число, 4 создаст 4 бит номер и т. д...

важно понимать что ты не гарантированное число 32bit, для экземпляр, работающий не на нуле, должен, теоретически, преобразовать 0 в 4,294,967,295, вместо этого он вернет -1 для двух причины, Во-первых, это все номера подписаны в Javascript, поэтому "не" всегда меняет знак, и второй Javascript не смог сделать больше чем один бит от числа ноль и не ноль становится единицей. Поэтому ~0=-1.

так побитовые знаки в Javascript вверх до 32 биты.

как отмечает Anurag, вы должны просто использовать встроенный num.toString(2) в этой ситуации вместо этого, который выводит строку минимальной длины ASCII '1's и '0's, которые вы можете просто взять длину.


стандарт 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>

On документация MDN по математике.clz32 полифилл обеспечен:

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);

создайте таблицу поиска с соответствующими границами, в которых меняются биты. Вы можете сделать это только для больших значений и по-прежнему делать меньшие через логарифм. Кажется, что это вообще связано с плавающей запятой, поскольку я могу воспроизвести его в PowerShell здесь.


опоздал на вечеринку, но я хочу похвалить trincot 32-разрядных ответ С более быстрым, простым и лучше поддерживаемым полным 53-битным методом.

следующие 2 примера будут просто читать/анализировать и возвращать значение экспоненты поплавка.

для современных браузеров, поддерживающих ES6 ArrayBuffer и DataView (не заботится о платформе 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


пример для немного более старых браузеров, которые поддерживают Float64Array и Uint16Array (но не DataView, поэтому endianness зависит от платформы, и этот фрагмент предполагает "стандартный" little-endian):

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 это было воспринято как аргумент.
Они работают без ошибок в полном диапазоне [-253, 253], и за этим, охватывая полный диапазон положительных показателей с плавающей запятой за исключением где округление уже произошло на входе Number (например, 255-1) is хранится как 255 (который, очевидно, тут равна 56 бит).

объясняя формат с плавающей запятой IEEE 754 действительно выходит за рамки этого ответа, но для тех, кто имеет базовое понимание, я включил свернутый фрагмент, ниже которого содержится расчет в табличной форме, из которого можно увидеть/объяснить логику: эффективно мы просто берем первое слово float (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) оставляет разработчикам свободу выбора способа реализации языка. Таким образом, в мире wild x-browser приходится иметь дело не только с округлением различий, но и с различными алгоритмами, такими как, например Math.log и Math.log2 etc.
Как вы уже заметили (ваш метод 1), общий пример, где log2 (polyfill) может не работать 248 (=49, что один к многим, когда на полу), но это не единственный пример.
Некоторые версии chrome, например, даже испортить значительно меньшие числа, такие как:Math.log2(8) = 2.9999999999999996 (от одного до меньшего при настиле).
Подробнее об этом читайте в этом stackoverflow Q/A:математика.точность log2 изменилась в Chrome.
Это означает, что мы не можем знать, когда пол или ceil наш логарифмический результат (или легко предсказать, когда мы уже один выкл до округления).

таким образом, вы можете подсчитать, как часто вы можете разделить входное число на 2, прежде чем оно будет меньше 1 в цикле (так же, как ваш подсчитанный 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;
}

форматирование призвано помочь понять алгоритм. Это будет minify довольно приятно жесткий!
Это имеет положительный целочисленный диапазон [0, 253] без ошибок (и до 264 с тем же предсказуемым округлением - "ошибка").

в качестве альтернативы вы можете попробовать некоторые другие (повторяется, для inputvalues больше, чем 32 бит) bithacks.

самый простой и короткий (но потенциально медленнее, по сравнению с вычислением фрагмент выше), чтобы stringify число и подсчитать результирующую длину строки, как вответ по сути: return n && n.toString(2).length; (предполагая, что браузеры могут дать результат до (по крайней мере) 53 бит).