MENU

JavaScript変数構文サンプルコード

【JavaScript】 ビット単位でデータ管理をする方法

更新日:2020/08/06

 

昔はフラグなどの0か1で済んでしまうデータはビットで管理していました。
メモリの価格が安くなりコンピューターに大容量で搭載するようになった現在は、ほぼ忘れられたテクニックです。

 

しかし最近になって、JavaScriptでビットを取り使う機会があったので、備忘録的にまとめておきます。

 

JavaScriptの2進数やビットに関する情報

 

まずはJavaScriptにおけるビットに関する演算について記載しておきます。

 

2進数リテラル

 

2進数を変数に直接代入するときは、0bを先頭に付加します。

 

下の例は、全て10進数で100を代入しています。

 

数値リテラル

 


const bin = 0b1100100;  // 2進数

const dec = 100;         // 10進数
const hex = 0x64;        // 16進数
const oct = 0o144;       //  8進数

console.log(  bin , dec , hex , oct); // 100 , 100 , 100 , 100

 

2進数リテラルに、0または1以外を使用するとエラーになります。

 

 


const bin = 0b1100103;  // Uncaught SyntaxError: Invalid or unexpected token

 

2進数文字列化

 

数値に対してtoString()メソッドを使用すると、文字列に変換してくれます。
その際引数に2を指定することで、2進数文字列に変換することができます。

 

基数を指定して文字列化

 


console.log(  bin.toString(2) , dec.toString(10) , hex.toString(16) , oct.toString(8));
  // 1100100  100  64  144

 

桁を揃えたいときは、ゼロサプレスしてください。

 

桁ぞろえ(ゼロサプレス)

 


console.log(  ("00000000" + bin.toString(2)).slice( -8 ) );
  // 01100100

 

なお基数を指定しても、マイナス値はマイナスで表示されます。

 

マイナス値の文字列化

 


console.log( ( -10 ).toString(2) ); // -1010
console.log( ( -10 ).toString(16) ); // -a

 

当たり前のように感じますが、ビット演算の結果を表示しようとすると少し混乱してきます。

 

-10は、メモリ上では16進数でfffffff6です。
この値を表示してほしいケースがあります。
その場合は、後述のビット演算の結果をunsignedに変換を見てください。

 

ビット演算の前に32ビット整数に変換される

 

ビット演算は、対象となる値を32ビットの整数に変換してから、計算が行われます。

 

整数変換の際、33ビット以上は切り捨てられます。
また32ビット目が1の場合、マイナス値として扱われます。

 

ビット演算子による32ビット整数化

 


const a = 0xF80000000; // 10進数:66571993088
console.log(  a | 0 ); // -2147483648

 

ビット演算( | )の前に、16進数F80000000は、32ビットに変換されます。
変換後の値は、80000000です。

 

ビット演算( | )はビット論理和で、0を指定すると元の値がそのまま保たれます。
よって結果は、80000000のまま変わりません。

 

80000000は32ビット目が1なので負数です。
10進数なら-2147483648です。

 

 

ビット演算後の比較に注意

 

ビット演算後の値を比較するとき、注意が必要です。

 

ビット演算結果の比較

 


const a = 0x80000000; // 10進数で2147483648

console.log(  (a | 0) === 0x80000000 ); // false
console.log(  (a | 0) ===2147483648 ); // false
console.log(  (a | 0) === a );  // false

console.log(  (a | 0) === -2147483648); // true

 

a | 0 の演算結果は、0x80000000です。
しかし、 (a | 0) === 0x80000000 は偽値となってしまいます。

 

一つ前の項目ビット演算の前に32ビット整数に変換されるを読んでいないと、とても不思議に感じるはずです。

 

偽値となる理由は、a | 0 の演算結果は32ビットなので、値が-2147483648です。
一方、リテラルの0x80000000は64ビットで評価されます。値は2147483648です。

 

イコールとならないため、偽値となったのです。

 

ビット演算の結果をunsignedに変換

 

符号なし右シフト演算子(>>>)を使用すると、32ビット値を符号なしとして扱うことができます。
一つ前の項目ビット演算後の比較に注意の例に対して適用すると、次のような結果となります。

 

ビット演算結果をunsignedに変換

 


const a = 0x80000000;
console.log(  (a | 0) >>> 0 === 0x80000000 );  // true
console.log(  (a | 0) >>> 0 === 2147483648 );  // true
console.log(  (a | 0) >>> 0 === a);  // true

console.log(  (a | 0) >>> 0 === -2147483648);  // false

 

| よりも >>> の方が優先順位が高いので、a | 0 に ( ) が必要です。

 

なおマイナス値をtoString()で出力すると、基数に関係なくマイナスで表示されます。
メモリ上のイメージで出力したい場合は、符号なし右シフト演算子を使用します。

 

メモリ上のイメージで値を出力

 


const a = -10;
console.log( ( a | 0 ).toString(2) ); // -1010
console.log( ( a | 0 ).toString(16) ); // -a

console.log( ( (a | 0)>>>0 ).toString(2) );  // 11111111111111111111111111110110
console.log( ( (a | 0)>>>0 ).toString(16) );  // fffffff6

 

ビット演算子

 

ビット演算子について解説します。

 

【&】ビット論理積 (AND)

 

&演算子は、対応するビットが両方1のとき、1になります。
両方0のとき、0。
異なるときは0です。

 

ビット論理積の例

 


console.log( ("00" + ( 0b010 & 0b010).toString(2)).slice(-3) ); // 010
console.log( ("00" + ( 0b010 & 0b000).toString(2)).slice(-3) ); // 000
console.log( ("00" + ( 0b000 & 0b010).toString(2)).slice(-3) ); // 000
console.log( ("00" + ( 0b000 & 0b000).toString(2)).slice(-3) ); // 000

 

【|】ビット論理和 (OR)

 

| 演算子は、対応するビットが両方0のとき、0になります。
両方1のとき、1。
異なるときは1です。

 

ビット論理和の例

 


console.log( ("00" + ( 0b010 | 0b010).toString(2)).slice(-3) ); // 010
console.log( ("00" + ( 0b010 | 0b000).toString(2)).slice(-3) ); // 010
console.log( ("00" + ( 0b000 | 0b010).toString(2)).slice(-3) ); // 010
console.log( ("00" + ( 0b000 | 0b000).toString(2)).slice(-3) ); // 000

 

【^】ビット排他的論理和 (XOR)

 

^演算子は、対応するビットが同じ値のとき、0になります。
異なるなら1になります。

 

ビット排他的論理和の例

 


console.log( ("00" + ( 0b010 | 0b010).toString(2)).slice(-3) ); // 000
console.log( ("00" + ( 0b010 | 0b000).toString(2)).slice(-3) ); // 010
console.log( ("00" + ( 0b000 | 0b010).toString(2)).slice(-3) ); // 010
console.log( ("00" + ( 0b000 | 0b000).toString(2)).slice(-3) ); // 000

 

【~】ビット否定演算子

 

~演算子は、右辺(右側)の値をビット毎に反転します。
32ビット整数で評価されることに注意してください。

 

ビット排他的論理和の例

 


console.log( ("0".repeat(32) + ( ~ 0b010 >>> 0).toString(2)).slice(-32) ); 
    // 11111111111111111111111111111101

console.log( ("0".repeat(32) + ( ~ 2 >>> 0 ).toString(2)).slice(-32) ); 
    // 11111111111111111111111111111101

 

ビット演算は32ビットで行われるため、ここでは32桁表示しています。

 

【<<】ビット左シフト演算子

 

ビット左シフト演算子は、数値を32ビット整数に変換後ビットを左向きにシフトします。
空いたビットには0がセットされます。

 

ビット左シフト演算子の例

 


const a = 0xf;

console.log(   ( "0".repeat(32) +( a >>>0 ).toString(2)).slice(-32)  );
    // 00000000000000000000000000001111
console.log( ( "0".repeat(32) + ( (a<<1) >>>0 ).toString(2)).slice(-32)  );
    // 00000000000000000000000000011110
console.log( ( "0".repeat(32) + ( (a<<2) >>>0 ).toString(2)).slice(-32)  );
    // 00000000000000000000000000111100

const b = 0x8000000f;

console.log( ( "0".repeat(32) +( b >>>0 ).toString(2)).slice(-32)  );
    // 10000000000000000000000000001111
console.log( ( "0".repeat(32) +( (b<<1) >>>0 ).toString(2)).slice(-32)  );
    // 00000000000000000000000000011110
console.log( ( "0".repeat(32) +( (b<<2) >>>0 ).toString(2)).slice(-32)  );
    // 00000000000000000000000000111100

 

【>>】ビット右シフト演算子

 

ビット右シフト演算子は、数値を32ビット整数に変換後ビットを右向きにシフトします。
空いたビットには、シフト前の最上位ビットと同じ値がセットされます。

 

ビット右シフト演算子の例

 


const a = 0xf;

console.log(   ( "0".repeat(32) +( a >>>0 ).toString(2)).slice(-32)  );
  // 00000000000000000000000000001111
console.log( ( "0".repeat(32) + ( (a>>1) >>>0 ).toString(2)).slice(-32)  );
  // 00000000000000000000000000000111
console.log( ( "0".repeat(32) + ( (a>>2) >>>0 ).toString(2)).slice(-32)  );
  // 00000000000000000000000000000011

const b = 0x8000000f;

console.log( ( "0".repeat(32) +( b >>>0 ).toString(2)).slice(-32)  );
  // 10000000000000000000000000001111
console.log( ( "0".repeat(32) +( (b>>1) >>>0 ).toString(2)).slice(-32)  );
  // 11000000000000000000000000000111
console.log( ( "0".repeat(32) +( (b>>2) >>>0 ).toString(2)).slice(-32)  );
  // 11100000000000000000000000000011

 

【>>>】符号なしビット右シフト演算子

 

符号なしビット右シフト演算子は、数値を32ビット整数に変換後ビットを右向きにシフトします。
空いたビットには、0がセットされます。
変換後は、符号なし32ビット整数として扱われます。

 

符号なしビット右シフト演算子の例

 


const a = 0xf;

console.log( ( "0".repeat(32) + ( a >>> 0 ).toString(2)).slice(-32) );
  // 00000000000000000000000000001111
console.log( ( "0".repeat(32) + ( a >>> 1 ).toString(2)).slice(-32) );
  // 00000000000000000000000000000111
console.log( ( "0".repeat(32) + ( a >>> 2 ).toString(2)).slice(-32) );
  // 00000000000000000000000000000011

const b = 0x8000000f;

console.log( ( "0".repeat(32) + ( b >>> 0 ).toString(2)).slice(-32) );
  // 10000000000000000000000000001111
console.log( ( "0".repeat(32) + ( b >>> 1 ).toString(2)).slice(-32) );
  // 01000000000000000000000000000111
console.log( ( "0".repeat(32) + ( b >>> 2 ).toString(2)).slice(-32) );
  // 00100000000000000000000000000011

 

 

符号なし32ビット整数に対して、ビット演算をおこなうと符号ありになります。

 


const b = 0x8000000f;
console.log( b >>> 0 );  // 2147483663
console.log( b >>> 0 | 0);  // -2147483633

 

ビットデータを取得・セットする方法

 

実際にビットデータを取得やセットをする方法をお伝えします。

 

ビットデータを取得

 

確認したいビットのみに1をセットした値を、用意します。
その値と、確認したいデータとのビット論理積を計算します。

 

結果が0なら、ビットは0です。
0以外なら、ビットは1です。

 

指定位置のビットを確認

 


const a = 0b0101;

 // 右から3ビット目を確認
console.log( (a & 0b0100) ? 1 : 0 );  // 1

 // 右から2ビット目を確認
console.log( (a & 0b0010) ? 1 : 0 );  // 0

 

複数のビットを一つのデータとして扱う場合、対応するビットの論理積を求めた後、符号なしビット右シフト演算子でシフトします。

 

複数のビットから値を取得

 


const a = 0b0101;

 // 右から2,3ビットのデータを取得
console.log( (a & 0b0110) >>> 1 );

 

 

ビットデータをセット

 

ビットに1をセットする場合は、セットしたいビット位置のみ1をセットした値を用意します。
その値と対象データをビット論理和で演算します。

 

0をセットする場合は、セットしたいビット位置に0、その他に1をセットした値をを用意します。
その値と対象データをビット論理積で演算します。

 

指定位置のビットをセット

 


const a = 0b0101;

 // 右から2ビット目に1をセット
console.log( ("0000" + ( a | 0b0010 ).toString(2)).slice(-4) ); // 0111

 // 右から3ビット目に0をセット
console.log( ("0000" + ( a & 0b1011).toString(2)).slice(-4) ); // 0001

 

複数のビットを一つのデータとして扱う場合、対応するビットのみ0をセットしたデータとの論理積を求めます。
この結果、目的のビットがゼロクリアされます。

 

次に、論理和で値をセットします。

 

複数のビットに値をセット

 


const a = 0b0101;

  // 右から2,3ビットに01をセット
console.log( ("0000" + ( a & 0b1001 | 0b0010 ).toString(2)).slice(-4) ); // 0011

ビットデータ管理サンプル

 

ビットデータ管理のサンプルプログラムです。

 

コード

 

ビットデータ管理ライブラリ

 


const BitManagement = function () {

    if( BitManagement.bitDatas === undefined ){
        Object.defineProperty(BitManagement,"bitDatas",{
            value:(()=>{
                    const result=[],result2=[];
                    for( let i = 0 ; i < 32 ; i ++) {
                        const shift = 0x80000000 >>> i;
                        result.push( shift );
                        result2.push( ~shift >>> 0 );
                    }
                    return {
                        bits:result,
                        notBits:result2,
                    };
                })()
        });
        Object.freeze(BitManagement.bitDatas.bits);
        Object.freeze(BitManagement.bitDatas.notBits);
    }
    const bits = BitManagement.bitDatas.bits;
    const notBits = BitManagement.bitDatas.notBits;

    const data = new Uint32Array(1);

    /**
     * ビットデータを取得する
     * @param bitPos ビット位置 上位0~31
     * @param bitLength ビット長
     * @returns {number}
     */
    this.getData = (bitPos,bitLength=1) => {
        if( bitPos < 0 || bitPos+bitLength > 31 ) throw new Error("BitManagement:Position out of range");

        if( bitLength <= 1 ) return ( (data[0] & bits[bitPos])===0 ) ? 0 : 1;
        let b = 0 | 0;
        for( let i = 0 ; i < bitLength ; i ++ ) b |= bits[bitPos+i];
        return ( (data[0] & b) >>> (31-(bitPos+bitLength-1)));
    };

    /**
     * ビットデータをセットする
     * @param bitPos ビット位置 上位0~31
     * @param val セットする値
     * @param bitLength ビット長
     */
    this.setData = (bitPos,val,bitLength=1) => {
        if( bitPos < 0 || bitPos > 31 ) throw new Error("BitManagement:Position out of range");
        if( bitLength <= 1 )
            data[0] =  val!==0 ? data[0]|bits[bitPos] : data[0]&notBits[bitPos];
        else{
            let b = 1 | 0;
            for( let i = 0 ; i < bitLength ; i ++ ) b = (b << 1) | 1;
            const v = val & b;
            const shift = 31-(bitPos+bitLength-1);
            data[0] = ( data[0] & ( ~(b << shift) ) | (v << shift ));
        }
    };
    /**
     * デバッグ用メソッド
     */
    this.debug =()=>{
        console.log( ("0".repeat(32) + data[0].toString(2)).slice(-32) );
    }
};

 

初回実行時に、次のコードでビット演算に使用する固定データを作成しています。

 

固定データの作成

 


if( BitManagement.bitDatas === undefined ){
  // コード省略
}

 

この処理で作成されるBitManagement.bitDatas.bitsは、次のようなデータが格納されます。

 

[0]:0b10000000000000000000000000000000
[1]:0b01000000000000000000000000000000
・・・
[31]:0b00000000000000000000000000000001

 

BitManagement.bitDatas.notBitsは、上の値をビット否定演算したものが格納されます。

 

[0]:0b01111111111111111111111111111111
[1]:0b10111111111111111111111111111111
・・・
[31]:0b11111111111111111111111111111110

 

 

次のコードは、書き換え可能なデータ領域を作成しています。

 

データ領域を作成

 


const data = new Uint32Array(1);

 

JavaScriptの変数は代入をおこなうと、実データを上書きせずに、新しく実データを作成します。
ビット演算をおこない、結果を変数にセットするときも同様です。
この特徴から、わざわざビットで管理しなくても、目的ごとに変数を用意した方が効率がいいです。

 

Uint32Arrayは、ArrayBufferという書き換え可能な領域を確保して、32ビット符号なし整数値への配列としてアクセスを可能とします。

 

使用法

 

コンストラクタを使用することで、32ビット分のデータ領域を確保します。

 


const bitData = new BitManagement();

 

.setData( bitPos , val , bitLength )メソッドは、ビットデータをセットします。

 

bitPos : ビットの位置。 上位ビットが0
val : セットする値。 ビット長より長いビットは切り詰められます
bitLength: 省略可能。セットするビットの長さです。

 


bitData.setData( 5 , 1 );
bitData.debug(); // 00000100000000000000000000000000

bitData.setData( 8 , 13 , 4 );
bitData.debug();  // 00000100110100000000000000000000

 

.getData( bitPos , bitLength )メソッドは、ビットデータを取得します。

 

bitPos : ビットの位置。 上位ビットが0
bitLength: 省略可能。取得するビットの長さです。

 


console.log( test.getData(5) );  // 1
console.log( test.getData(8,4) );  // 13

記事の内容について

 

こんにちはけーちゃんです。
説明するのって難しいですね。


「なんか言ってることおかしくない?」
たぶん、こんなご意見あると思います。

裏付けを取りながら記事を作成していますが、僕の勘違いだったり、そもそも情報源の内容が間違えていたりで、正確でないことが多いと思います。

そんなときは、ご意見もらえたら嬉しいです。

ご意見はこちら。
https://affi-sapo-sv.com/info.php