2.9 ビット演算子、シフト演算子

(1/1)
デジタルデータ風の背景素材(緑)
JavaScriptには、ビット演算子として、ビット論理和とビット論理積の加え、シフト演算子が備わっている。フラグ値を1つの変数に収めたり、RGBのように複数のデータを1つにまとめた値を分解・結合するときにビット演算子が活躍する。
また、IoT(モノのインターネット)でデバイスの状態や計測値の入力を行う際、ビット演算を行うことが多い。
(2024年2月10日)「コラム:機械語、コンパイラ、インタプリタ」追記。TeX画像をMathJaxに置き換え。

目次

サンプル・プログラム

ビット論理和とビット論理積

Microsoft Word
Microsoft Wordでは、1文字ずつ、イタリック体、ボールド文字、下線を指定することができる。これをプログラムに実装することを考える。
ここでは話を簡単にするため、下表のパラメータのみを指定できるものとする。
文字装飾
文字装飾取り得る値
ボールドON / OFF
イタリックON / OFF
下線ON / OFF
文字装飾の1つ1つの項目に変数を割り当ててもいいのだが、文字数の3倍もの変数が必要になってしまう(後述する配列を使うことで実現は可能だが)。

上表をもう一度見てみよう。
文字装飾の情報量は、それほど多くない。ボールド、イタリック、取り消し線は2値だから、各々の情報量は1ビット。3つでも、わずか3ビットである。これは1つの変数に押し込めることを考えてみよう。
文字装飾
 下線
第3ビット
0b100
イタリック
第2ビット
0b010
ボールド
第1ビット
0b001
ON111
OFF000
ビット演算子
変数 attribute の値が2進数の 001 なら「第1ビットが立っている=第1ビットがON=ボールド体」と解釈する。同様に、010 ならイタリック体、100 なら下線となる。
組み合わせも可能で、011 ならボールドかつイタリック体、111 ならボールドかつイタリック体かつ下線というように解釈する。
変数 attribute にビット値を設定したり取り出すのに使うのがビット演算子だ。

  20: /**
  21:  * 計算と画面表示
  22: */
  23: function execute() {
  24:     //定数:文字装飾データのビット位置
  25:     const bold      = 0b001;
  26:     const italic    = 0b010;
  27:     const underline = 0b100;
  28: 
  29:     //文字装飾データを格納する変数
  30:     let attribute   = 0b000;
  31: 
  32:     //form要素を取得
  33:     let element = document.getElementById('myform');
  34: 
  35:     //ラジオボタングループ name='bold' の値を取得
  36:     if (element.bold.value == 'on') {
  37:         attribute |bold;
  38:     } else {
  39:         attribute &~bold;
  40:     }
  41: 
  42:     //ラジオボタングループ name='italic' の値を取得
  43:     if (element.italic.value == 'on') {
  44:         attribute |italic;
  45:     } else {
  46:         attribute &~italic;
  47:     }
  48: 
  49:     //ラジオボタングループ name='underline' の値を取得
  50:     if (element.underline.value == 'on') {
  51:         attribute |underline;
  52:     } else {
  53:         attribute &~underline;
  54:     }
  55: 
  56:     //属性値を表示
  57:     document.getElementById('attr').innerHTML = '属性値= 0b' + ('000' + attribute.toString(2)).slice(-3);
  58: 
  59:     //style属性を付与する:ボールド
  60:     if (Number(attribute & bold!0) {
  61:         document.getElementById('character').style.fontWeight = 'bold';
  62:     } else {
  63:         document.getElementById('character').style.fontWeight = 'normal';
  64:     }
  65: 
  66:     //style属性を付与する:イタリック
  67:     if (Number(attribute & italic!0) {
  68:         document.getElementById('character').style.fontStyle = 'italic';
  69:     } else {
  70:         document.getElementById('character').style.fontStyle = 'normal';
  71:     }
  72: 
  73:     //style属性を付与する:下線
  74:     if (Number(attribute & underline!0) {
  75:         document.getElementById('character').style.textDecoration = 'underline';
  76:     } else {
  77:         document.getElementById('character').style.textDecoration = 'none';
  78:     }
  79: }

まず、ボールドのON/OFFフラグ位置を定数 bold に代入する。同様に、イタリックは italic に、下線は underline に代入する。文字装飾データ(3ビット値)を代入する変数は attribute とする。

次に、ラジオボタンの値を読み込んで、変数 attribute に値を設定する。
ボールドのラジオボタンがONなら、ビット論理和 | を使って変数 attribute にフラグを立てる。ここではビット論理和代入演算子 |= を使っている。
逆に、フラグを降ろすには、ビット論理積 & と否定演算子 ~ を組み合わせて使う。
真理値表
attributeboldフラグを立てる
attribute | bold
~boldフラグを降ろす
attribute & ~bold
01100
11100
真理値表に整理すると、ビット演算子を使ってフラグを立てたり降ろしたりできることが分かる。ビット演算子は、他のビット位置(italic, underline)には影響を及ぼさない。

シフト演算子

JavaScriptには、ビット演算子として、左シフト右シフトがある。8ビット・データに対してシフト演算を行うイメージを下図に示す。
右シフト演算
右シフト演算
左シフト
左シフト演算
それでは、スタイルシートで指定するRGBカラーコード(#ではじまる3バイトの値)の補色を計算するプログラムでシフト演算を使ってみよう。
補色とは、色相環の180度反対側に位置する2色のことで、補色の組み合わせは互いの色を引き立て合う相乗効果がある。また、補色を混ぜると白色になる。

補足は次の計算式によって求めることができる。R0,G0,B0は元のカラーコード、R1,G1,B1は補色カラーコードを意味する。max(r,g,b)はr,g,bの最大値を、min(r,g,b)はr,g,bの最小値を求める関数である。
\[ \begin{eqnarray}
MAX &=& max(R_0,G_0,B_0) \\
MIN &=& min(R_0,G_0,B_0) \\
C &=& MAX + MIN \\
R_1 &=& C - R_0 \\
G_1 &=& C - G_0 \\
B_1 &=& C - B_0
\end{eqnarray} \]
補色計算
"bit2.html" を実行すると左図のようになる。

  25:     let origin = document.getElementById('origin').value;   //元のカラーコード
  26:     origin = parseInt(origin, 16);                              //10進整数化
  27:     let ret = '';
  28: 
  29:     //バリデーション
  30:     let errmsg = '';
  31:     if (isNaN(origin|| origin < 0 || origin > 0xFFFFFF) {
  32:         errmsg = '正しいカラーコードを入力してください.';
  33:     }
  34: 
  35:     if (errmsg == '') {
  36:         //元のカラーコード
  37:         let red = origin & 0xFF0000;        //赤
  38:         red = red >> 16;                    //右シフトで8bit化
  39:         let green = origin & 0x00FF00;      //赤
  40:         green = green >> 8;                 //右シフトで8bit化
  41:         let blue = origin & 0x0000FF;       //青
  42:         //補色のカラーコード
  43:         let max = Math.max(red, green, blue);
  44:         let min = Math.min(red, green, blue);
  45:         let cc = min + max;
  46:         let red1   = cc - red;
  47:         let green1 = cc - green;
  48:         let blue1  = cc - blue;
  49:         let complement = (red1 << 16| (green1 << 8| blue1;  //左シフト
  50:         ret = '補色 #' + ('000000' + complement.toString(16)).slice(-6);

元のカラーコードは、RGBの順序に1バイト(8ビット)の値が入っている。そこで、まず最上位の8ビットをビット論理積を使ってマスクし、それを16ビットだけ右シフトさせることでRedの値にする。Green、Blueについても同様に操作する。Blueは最下位8ビットなので、右シフトさせる必要はない。
RGBの3つの値に分解できたところで、前述の計算式で補色のRGBを求める。
今度は分解したときとは逆に、左シフトさせていくことでRGBのカラーコードを組み立ててゆく。

ビット演算子一覧

ビット演算子
演算子意味
a & bビット論理積(AND)
a | bビット論理和(OR)
a ^ bビット排他的論理和(XOR)
~aビット否定(NOT)
a << b左シフト
a >> b右シフト(符合維持)
a >>> b右シフト(ゼロ埋め)

コラム:IoTとビット演算、シフト演算

ワンボードマイコンのイラスト
IoT(モノのインターネット)が流行っている。
さまざまなセンサーからの信号入力処理を行うが、この時に役立つのがビット演算とシフト演算だ。センサーの状態はビット値としてインタフェースに入ってくる。また、測定値は必ずしもバイト単位ではない。
12ビットの音声データや、42bit(14bit×3)の画像データが入ってくることもある。これをNumber型(整数型)として演算処理する前に、ビット演算やシフト演算でデータ形式を整えることがある。
リアルタイム処理をするのに便利なCやC++言語には、もちろん、ビット演算とシフト演算が備わっている。
初期の8ビットCPUには乗算・除算命令がなかった。その代わり、比較的簡単な回路で実装できるシフト演算命令が備わっていた。
1ビットだけ左シフトするということは、整数データとしてみると2倍したことと同じ意味になる。2ビットなら4倍、3ビットなら8倍と、2のべき乗を乗したことになる。
そこで、たとえば \( 12 \times 35 \) という乗算なら \( 12 \times (32 + 2 + 1) = 12 \times 32 + 12 \times 2 + 12 = (12 <\!\!< 5) + (12<\!\!< 1) + 12 \) のようにシフト演算と加算に分解して計算する。この方が、 \( 12 + 12 \) を34回繰り返すより速いからだ。
除算は、逆に右シフトを使って求めた。
今どきの IoT デバイスに搭載されているCPUには乗除算命令が備わっていると思うが、もし乗除算が備わっておらず、高速フーリエ変換が求められたら、迷わずシフト演算で実装してみてほしい。

コラム:機械語、コンパイラ、インタプリタ

最初のFORTRAN解説書
最初のFORTRAN解説書
初期のコンピュータ・プログラムは、電子回路を直接操作する命令を使って書かれていた。これを、ハードウェアが直接解釈できることから、機械語(マシン語)と呼ぶ。
1954年(昭和29年)に登場したメインフレーム IBM 704 は、真空管と磁気コアメモリを使った浮動小数点数演算ハードウェアを搭載した初の量産型コンピュータで、複雑な計算ができる世界唯一のコンピュータとみなされた。そこで、専門の機械語プログラマではなく、科学者でもプログラムを組むことができるよう、IBMのジョン・バッカスは科学技術計算に適したプログラミング言語 FORTRAN を考案した。
FORTRAN は、一定の論理(文法)に基づき、ハードウェアとは関係なく自由にプログラムを書くことができる。これを高級言語と呼ぶ。そして、ハードウェア毎に FORTRANコンパイラが用意され、FORTRAN で書いたプログラムを機械語に変換し、コンピュータを駆動する。

IBM 704用の FORTRANコンパイラは、機械語で書いたプログラムと遜色ない計算スピードをみせたことから(それが開発の目的だった)、コンパイラで書いたプログラムは処理速度が速いという結果論が後世に伝わってしまう。だが、これは間違いで、本来は、高級言語で記述されたプログラムを機械語に変換する仕組みをコンパイラと呼ぶ(のちに、ALGOLやJavaのように仮想マシンの機械語の変換するコンパイラが登場する)。

FORTRAN に少し遅れ、1958年(昭和33年)に「コラム:ノイマン型コンピュータと万能チューリングマシン」で紹介した万能チューリングマシンをソフトウェアだけで実現する LISP というプログラミング言語が登場する。LISPは自分自身をLISPで記述することができるインタープリタである。
プログラミングが広く普及する過程で、、同じプログラムでも、BASICインタプリタで実行すると、BASICコンパイラで変換したプログラムより遅いことから、インタプリタはコンパイラに比べて遅いという伝わり方をしてしまう。これも間違いで、本来は、万能チューリングマシンをソフトウェア的に実現する1つの方法がインタプリタなのである。

残念ながら、JavaScriptLISP のように自分自身を記述することはできないが、インタプリタでありevalメソッドを備えているという点で、万能チューリングマシンを指向している。万能チューリングマシンを指向するということは、プログラムの移植性(可用性)が高いという結果をもたらす。
(この項おわり)
header