サンプル・プログラム
ビット論理和とビット論理積
文字装飾 | 取り得る値 |
---|---|
ボールド | ON / OFF |
イタリック | ON / OFF |
下線 | ON / OFF |
上表をもう一度見てみよう。
文字装飾の情報量は、それほど多くない。ボールド、イタリック、取り消し線は2値だから、各々の情報量は1ビット。3つでも、わずか3ビットである。これは1つの変数に押し込めることを考えてみよう。
下線 第3ビット 0b100 | イタリック 第2ビット 0b010 | ボールド 第1ビット 0b001 | |
---|---|---|---|
ON | 1 | 1 | 1 |
OFF | 0 | 0 | 0 |
変数 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: }
次に、ラジオボタンの値を読み込んで、変数 attribute に値を設定する。
ボールドのラジオボタンがONなら、ビット論理和 | を使って変数 attribute にフラグを立てる。ここではビット論理和代入演算子 |= を使っている。
逆に、フラグを降ろすには、ビット論理積 & と否定演算子 ~ を組み合わせて使う。
attribute | bold | フラグを立てる attribute | bold | ~bold | フラグを降ろす attribute & ~bold |
---|---|---|---|---|
0 | 1 | 1 | 0 | 0 |
1 | 1 | 1 | 0 | 0 |
補色とは、色相環の180度反対側に位置する2色のことで、補色の組み合わせは互いの色を引き立て合う相乗効果がある。また、補色を混ぜると白色になる。
補色は次の計算式によって求めることができる。R0,G0,B0は元のカラーコード、R1,G1,B1は補色カラーコードを意味する。max(r,g,b)はr,g,bの最大値を、min(r,g,b)はr,g,bの最小値を求める関数である。
\[ \begin
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} \]
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の3つの値に分解できたところで、前述の計算式で補色のRGBを求める。
今度は分解したときとは逆に、左シフトさせていくことでRGBのカラーコードを組み立ててゆく。
ビット演算子一覧
演算子 | 意味 |
---|---|
a & b | ビット論理積(AND) |
a | b | ビット論理和(OR) |
a ^ b | ビット排他的論理和(XOR) |
~a | ビット否定(NOT) |
a << b | 左シフト |
a >> b | 右シフト(符合維持) |
a >>> b | 右シフト(ゼロ埋め) |
コラム:IoTとビット演算、シフト演算
さまざまなセンサーからの信号入力処理を行うが、この時に役立つのがビット演算とシフト演算だ。センサーの状態はビット値としてインタフェースに入ってくる。また、測定値は必ずしもバイト単位ではない。
リアルタイム処理をするのに便利なCやC++言語には、もちろん、ビット演算とシフト演算が備わっている。
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には乗除算命令が備わっていると思うが、もし乗除算が備わっておらず、高速フーリエ変換が求められたら、迷わずシフト演算で実装してみてほしい。
コラム:機械語、コンパイラ、インタプリタ
IBM 704用の FORTRANコンパイラは、機械語で書いたプログラムと遜色ない計算スピードをみせたことから(それが開発の目的だった)、コンパイラで書いたプログラムは処理速度が速いという結果論が後世に伝わってしまう。だが、これは間違いで、本来は、高級言語で記述されたプログラムを機械語に変換する仕組みをコンパイラと呼ぶ(のちに、ALGOLやJavaのように仮想マシンの機械語の変換するコンパイラが登場する)。
FORTRAN に少し遅れ、1958年(昭和33年)に「コラム:ノイマン型コンピュータと万能チューリングマシン」で紹介した万能チューリングマシンをソフトウェアだけで実現する LISP というプログラミング言語が登場する。LISPは自分自身をLISPで記述することができるインタープリタである。
プログラミングが広く普及する過程で、、同じプログラムでも、BASICインタプリタで実行すると、BASICコンパイラで変換したプログラムより遅いことから、インタプリタはコンパイラに比べて遅いという伝わり方をしてしまう。これも間違いで、本来は、万能チューリングマシンをソフトウェア的に実現する1つの方法がインタプリタなのである。
残念ながら、JavaScript は LISP のように自分自身を記述することはできないが、インタプリタでありevalメソッドを備えているという点で、万能チューリングマシンを指向している。万能チューリングマシンを指向するということは、プログラムの移植性(可用性)が高いという結果をもたらす。
また、IoT(モノのインターネット)でデバイスの状態や計測値の入力を行う際、ビット演算を行うことが多い。