10進数
我々は日常的に10進数を用いて数値を表現・理解している. 10進数では,各桁の重みが$10^n$であり,各桁に使用できる数字は$0$から$9$の10種類である.ある数は各桁の数字にその重みをかけることで表すことができる.
$1523 = 1\times 10^3 + 5\times 10^2 + 2\times 10^1 + 3\times 10^0$
N進数
10進数の表現方法は,容易に任意の整数$N$を用いたN進数に拡張できる. N進数では,各桁の重みが$N^n$であり,各桁に使用できる数字は$0$から$N-1$のN種類である.ある数は各桁の数字にその重みをかけることで表すことができる.
2進数
2進数では,各桁の重みが$2^n$であり,各桁に使用できる数字は$0$と$1$の2種類である.ある数は各桁の数字にその重みをかけることで表すことができる.
$(56)_{10} = 1\times 2^5 + 1\times 2^4 + 1\times 2^3 + 0\times 2^2 + 0\times 2^1 + 0\times 2^0 = (111000)_2$
コンピュータ内部のデータ
コンピュータ内部では,あらゆるデータは数値であり,またそれらの数値は2進数を用いて表現されている.メモリは有限であり,またひとつのデータを表す数値の範囲も有限である.
C/C++言語などでは,変数の型が存在する.これは,変数に格納されるデータのコンピュータ内部でのビット長に対応する.以下に整数型の型とそのビット長を示す.
| 型 | ビット長 | 値の取りうる範囲 |
|---|---|---|
| char | 8 | -128 〜 127 |
| short | 16 | -32768 〜 32767 |
| int | 32 | -2147483648〜2147483647 |
| long long | 64 | -9223372036854775808〜9223372036854775807 |
負の数
上記の表において,char型は8bitでその範囲は-128〜127となっている.では,負の数は2進数でどのように表現されるのだろうか?
2の補数
コンピュータ内部では,特に断りのない限り,整数値は2の補数表現をとる.
2の補数表現では,最初に,その数値のビット長を決める必要がある.次に,最上位の桁の重みを$-2^n$とする.char型を例にとると,そのビット長は8であるので,例えば-128, 127は以下のように表される.
$(-128)_{10}=1\times (-2^7) + 0\times 2^6+ 0\times 2^5 +0\times 2^4 +0\times 2^3+0\times 2^2 +0\times 2^1+0\times 2^0= (10000000)_2$
$(-127)_10=1\times (-2^7) + 0\times 2^6+ 0\times 2^5 +0\times 2^4 +0\times 2^3+0\times 2^2 +0\times 2^1+1\times 2^0= (10000001)_2$
- 練習問題: 8ビットの2の補数における以下の数の表現を求めよ.
- $100$
- $-1$
- 練習問題: 4ビットの2の補数における以下の数の表現を求めよ.
- $-1$
オーバーフロー(桁あふれ)
以下のコードを実行した結果,変数$b$の値はいくつになっているだろうか?
char a = 127;
char b = a + 1;
前述のとおり,char型の変数には-127〜128の範囲の値しか格納することはできない.本来$127+1=128$であるが,128は範囲外の数値である.以下に,コンピュータ内部で行われる計算過程を示す.
\begin{align} & 01111111 \cr +) & 00000001 \cr \hline \cr = & 00000000 \end{align}
足し算の結果,9ビット目に桁上りのビット”1”が出現するが,これは8ビットに収まらないため,破棄されるのである.このように,桁上りの結果がある変数のビット長に収まらないことをオーバーフロー(桁あふれ)と呼ぶ.オーバーフローを意識せずコーディングすると,意図していないオーバーフローが発生し,計算結果が予期しない値となるため注意が必要である.
ビット演算
ビット演算とは,整数型のデータに定義されるビット単位の様々な処理のことである.「演算」であるので,演算子を用いて表す.以下,C/C++言語における例を通じて種々のビット演算について説明する.以下の例では,シフト対象となる変数名をaとおき,シフト量(移動する桁数)をnとおく.
ビットシフト
ビットシフトは,その名のとおり,ビットをシフト(すべての桁を同じ桁数だけ移動)する演算である.データが符号付きの場合と符号なしの場合で一部の結果が異なる.
論理シフト(符号なしデータのシフト)
符号なしのデータをシフトする演算を論理シフトと呼ぶ.論理シフトには論理右シフトと論理左シフトがある.
- 論理右シフト
論理右シフトでは,右に
n桁だけシフトされる.LSBをはみ出す場合には,その桁は捨てられる.上位の桁には0が追加される.nの値は負であってはならない.
unsigned char a = 5; // a = 0000 0101
int n = 1;
unsigned char b = a >> n; // b = 0000 0010 すなわち 2
- 論理左シフト
論理右シフトでは,左に
n桁だけシフトされる.MSBをはみ出す場合には,その桁は捨てられる.下位の桁には0が追加される.nの値は負であってはならない.
unsigned char a = 5; // a = 0000 0101
int n = 1;
unsigned char b = a << n; // b = 0000 1010 すなわち 10
算術シフト(符号ありのデータのシフト)
算術右シフトでは,右にn桁だけシフトされる.LSBをはみ出す場合には,その桁は捨てられる.上位の桁には1が追加される.
これは,符号ありのデータは2の補数で表されていることから必要な処理であり,符号拡張と呼ばれる.
nの値は負であってはならない.正の値に対する結果は,論理シフトと算術シフトで同一となる.
char a = -5; // a = 1111 1011
int n = 1;
char b = a >> n; // b = 1111 1101 すなわち -3
計算として考えた場合には,以下の演算と同義である.
\begin{align} b &= \left\lfloor \frac{a}{2^n} \right\rfloor \end{align}
算術左シフトは,多くの処理系で未定義であるので注意が必要.
ビット単位の論理演算
AND &
ビットごとの論理積を取る.
unsigned char a = 101; // a = 0110 0101
unsigned char b = 240; // b = 1111 0000
unsigned char c = a & b; // c = 0110 0000
OR |
ビットごとの論理和を取る.
unsigned char a = 101; // a = 0110 0101
unsigned char b = 240; // b = 1111 0000
unsigned char c = a | b; // c = 1111 0101
XOR ^
ビットごとの排他的論理和を取る.2回繰り返すと元に戻る性質があるので,暗号関連の処理に用いられることがある.
unsigned char a = 101; // a = 0110 0101
unsigned char b = 240; // b = 1111 0000
unsigned char c = a ^ b; // c = 1001 0101
unsigned char d = c ^ b; // d = 0110 0101 = a
NOT ~
各ビットを反転する.
unsigned char a = 101; // a = 0110 0101
unsigned char b = ~a; // b = 1001 1010
固定小数点演算
FPGA等のハードウェアでは,浮動小数点演算を用いることができないか,あるいはできるにしても処理速度が遅いなどの問題点が存在する.整数同士の計算では精度が不足する場合(多くのシステムで起こりうる),見かけ上整数を用いるが,ある程度の小数精度を持たせた演算を行うことが可能である.この演算を固定小数点(Fixed-point)演算という.
データ全体のビット数を$w$,符号に用いる1ビットを$s$,整数部を表現するのに用いるビットを$d$,小数部を表現するのに用いるビット数を$f$で表すと,
\begin{align} w &= s + d + f \end{align}
である.
固定小数点演算では,小数点の位置はユーザ(プログラマ)が管理する.以下に例を示す.例中のコメント内の小数点は実際には表示も指定もできないので注意.
- 小数点の位置が
xxxx xxxx.の場合(普通の整数演算として考えた場合)
char a = 5; // a = 0000 0101.
char b = -15; // b = 1111 0001.
char c = a + b; // c = 1111 0110. = -10
char d = a * b; // d = 1011 0101. = -75
- 小数点の位置が
xxxx xxx.xの場合(0000 0001が0.5を表す)
char a = 5; // 実際には 2.5 と考える a = 0000 010.1
char b = -15; // 実際には -7.5 と考える b = 1111 000.1
char c = a + b; // 実際には 2.5 + (-7.5) と考える c = 1111 011.0 = -5
char d = a * b; // 実際には 2.5 * (-7.5) と考える d = 1011 01.01 = -18.75
以上の例からもわかるように,小数点より右側のビットの重みは,$2^{-n}$となると考えることができる.
また,小数部のビット数が$f0$と$f1$である2つの数を乗算した結果の小数部のビット数は$f0 + f1$となることに注意.
固定小数点演算は, 事前にシステム内の計算を調査し,十分な長さの整数データを用意し,移動する小数点を把握しない場合,オーバフローに起因する意図しない結果を招くなど,その使用には複雑さを伴う反面,処理が高速であることから,ソフトウェアにおいても多用される.