AI生成コンテンツ / AI-generated content
目次
サンプル・プログラム
ビット論理和とビット論理積
| 文字装飾 | 取り得る値 |
|---|---|
| ボールド | ON / OFF |
| イタリック | ON / OFF |
| 下線 | ON / OFF |
上表をもう一度見てみよう。
文字装飾の情報量は、それほど多くない。ボールド、イタリック、下線は2値だから、各々の情報量は1ビット。3つでも、わずか3ビットである。この3ビットを1つの変数にまとめてみよう。0bで始まる数値は、JavaScriptの2進数表記である。ここでは右端を「第1ビット」と呼ぶが、ビット番号を0から数える資料もある。
| 下線 第3ビット 0b100 | イタリック 第2ビット 0b010 | ボールド 第1ビット 0b001 | |
|---|---|---|---|
| ON | 1 | 1 | 1 |
| OFF | 0 | 0 | 0 |
変数 attribute にビット値を設定したり取り出すのに使うのがビット演算子だ。
bit1.html
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').textContent = '属性値= 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 |
フラグの確認には (attribute & bold) !== 0、反転には attribute ^= bold を使う。比較演算子との優先順位による誤りを防ぐため、確認式のビット演算を括弧で囲もう。
このサンプルは操作のたびに0から値を組み立て直すので、OFF側の処理は省略しても結果は同じである。ここでは、既存の値からフラグを降ろす書き方も示している。
Numberのビット演算は32ビット
&、
ビット論理積 &・ビット論理和 | と、条件を組み合わせる論理演算子 &&・|| は別物である。
32ビットを超える整数のビット演算には BigInt を使える。ただし、NumberとBigIntを混ぜて演算することはできず、BigIntには >>> がない。たとえば 1n << 40n は1099511627776nとなる。
シフト演算子
| 式 | 結果(2進数) | 結果(10進数) |
|---|---|---|
| 0b01011100 | 1011100 | 92 |
| 92 >> 2 | 10111 | 23 |
| 23 >> 1 | 1011 | 11 |
| 92 << 2 | 101110000 | 368 |
| 368 << 1 | 1011100000 | 736 |
>> は左端の符号ビットを引き継ぐので、-8 >> 1 は -4 になる。>>> は左側を0で埋めるので、-8 >>> 1 は2147483644になる。正の整数を右に1ビットずらすと、2で割った商の小数部分を切り捨てた値になる。負数の >> は負の無限大方向への丸めとなり、-3 >> 1 は -2 である。
Numberのシフト数には下位5ビットだけを使う。たとえば 1 << 32 は 1 << 0 と同じ1になる。これはBigIntのシフトには当てはまらない。
このプログラムでは、色相環で反対側にある色を求める。具体的には、色を色相・彩度・明度で表す HSL の色相を180度回し、彩度と明度を保った色を「補色」と呼ぶ。これはRGBの各成分を255から引く「反転色」とは異なる。また、絵の具を混ぜる場合も含め、補色を混ぜれば必ず白になるという意味ではない。
この定義の補色は次の計算式で求められる。R0,G0,B0は元のカラーコード、R1,G1,B1は補色カラーコードを意味する。max(r,g,b)はr,g,bの最大値を、min(r,g,b)はr,g,bの最小値を求める関数である。 $$ \begin{alignedat}{2} \text{MAX} &= \max(R_0, G_0, B_0) \\ \text{MIN} &= \min(R_0, G_0, B_0) \\ C &= \text{MAX} + \text{MIN} \\ R_1 &= C - R_0 \\ G_1 &= C - G_0 \\ B_1 &= C - B_0 \end{alignedat} $$
bit2.html
25: let input = document.getElementById('origin').value.trim(); //元のカラーコード
26: let origin; //入力検証に成功してから数値へ変換する
27: let ret = '';
28:
29: //バリデーション
30: let errmsg = '';
31: if (!/^[0-9a-fA-F]{6}$/.test(input)) {
32: errmsg = '6桁の16進数(0~9、a~f)を入力してください。#は不要です。';
33: }
34:
35: if (errmsg == '') {
36: origin = parseInt(input, 16);
37: //元のカラーコード
38: let red = origin & 0xFF0000; //赤
39: red = red >> 16; //右シフトで8bit化
40: let green = origin & 0x00FF00; //緑
41: green = green >> 8; //右シフトで8bit化
42: let blue = origin & 0x0000FF; //青
43: //補色のカラーコード
44: let max = Math.max(red, green, blue);
45: let min = Math.min(red, green, blue);
46: let cc = min + max;
47: let red1 = cc - red;
48: let green1 = cc - green;
49: let blue1 = cc - blue;
50: let complement = (red1 << 16) | (green1 << 8) | blue1; //左シフト
51: ret = '補色 #' + ('000000' + complement.toString(16)).slice(-6);
RGBの3つの値に分解できたところで、前述の計算式で補色のRGBを求める。
今度は分解したときとは逆に、左シフトさせていくことでRGBのカラーコードを組み立ててゆく。
赤 #ff0000 の補色は #00ffff である。一方、白・黒・灰色は彩度が0で色相を持たず、この式では元と同じ色になる。この計算だけで、読みやすい文字色や十分なコントラストが保証されるわけではない。
ビット演算子一覧
| 演算子 | 意味 |
|---|---|
| a & b | ビット論理積(AND):両方のビットが1なら1 |
| a | b | ビット論理和(OR):少なくとも一方が1なら1 |
| a ^ b | ビット排他的論理和(XOR):ビットが異なれば1 |
| ~a | ビット否定(NOT):各ビットの0と1を反転 |
| a << b | 左シフト |
| a >> b | 右シフト(符号維持) |
| a >>> b | 右シフト(ゼロ埋め) |
コラム:IoTとビット演算、シフト演算
42ビットのように32ビットを超える値を、Numberのビット演算で一度に処理すると情報が失われる。バイトごとに処理するか、BigIntなどを使う必要がある。CやC++にもビット演算はあるが、型の幅や符号に関する規則はJavaScriptと同じではない。
たとえば $12 \times 35 = 12 \times (32 + 2 + 1)$ なので、JavaScriptでは (12 << 5) + (12 << 1) + 12 と書けば420になる。ただし、シフトで有効なビットが失われない範囲で使う必要がある。
右シフトは2のべき乗で割る処理に使えるが、一般の割り算には比較や減算なども必要である。通常のJavaScriptでは、読みやすい * や / を使おう。シフトに書き換えれば必ず高速になるわけではない。
コラム:機械語、コンパイラ、インタプリタ
インタプリタは、プログラムや中間表現を読み取り、その意味に従って処理を実行する仕組みである。必ずソースコードを1行ずつ実行するわけではない。コンパイラとインタプリタは、速い・遅いという違いで定義されるものでもない。
LISPは1958年(昭和33年)に実装が始まったプログラミング言語であり、言語そのものをインタプリタと呼ぶのは正確ではない。LISPの処理系にはインタプリタもコンパイラもあり、自分自身の言語で評価器を記述できることでも知られる。JavaScriptでJavaScriptの処理系を書くことも可能であり、JS-Interpreterがその実例である。
現代のJavaScript処理系は、解釈実行とコンパイルを組み合わせる。たとえばV8は Ignition というインタプリタを持ち、実行中に機械語へ変換する JITコンパイル も利用する。JavaScriptを単に「インタプリタだから遅い」と決めつけることはできない。
eval は文字列をJavaScriptのコードとして評価する関数である。その有無が、計算できる問題の範囲やプログラムの移植性を決めるわけではない。移植性は、言語仕様や利用するAPIが移植先でも使えるかに左右される。万能チューリングマシンは計算の可能性を考える理論上のモデルであり、インタプリタや移植性の定義ではない。
参考サイト
- ECMAScript:ビット演算子:Ecma International
- 左シフト演算子:MDN
- 符号なし右シフト演算子:MDN
- parseInt():MDN
- CSS Color Module Level 4:HSL:W3C
- Fortran:IBM
- The implementation of LISP:John McCarthy
- Ignition:V8
- TurboFan:V8
- JS-Interpreter:Neil Fraser

また、IoT(モノのインターネット)でデバイスの状態や計測値の入力を行う際、ビット演算を行うことが多い。