intとfloatにご用心(ついでにド・モルガンの法則)

intとfloatにご用心(ついでにド・モルガンの法則)

こんにちは、Nitです。
今回は基本に立ち返り、プログラムで使用するデータ型の内、特に数値型についてお話したいと思います。

int型

言わずと知れた整数型で、Javaでは32ビットです。
仲間にshort型(16ビット)、long型(64ビット)がいます。

ここで厄介なのは、頭1ビットに鎮座する符号ビットの存在です。
これは他のビットと同じく、足し算引き算で変動してしまうので注意が必要です。

例として、最大値である
01111111111…11111= 2,147,483,647 に1を足すと、
10000000000…00000=-2,147,483,648 になります。
(↑先頭が符号ビット)

ぱっと見て符号ビット以外0なので、10進数としても0に見えるのですが、
マイナス(符号ビットが1)の場合の計算方法は次のようになります。
1. 符号ビット以外のビットを反転 → 111…(31バイト分)…111 = 2,147,483,647
2. 1を足す → 2,147,483,648
3. 頭にマイナスをつける → -2,147,483,648

float型

小数も扱いたい時にはこれ、浮動小数点数型の出番です。
Javaではfloat型は32ビットで、特に単精度浮動小数点数と呼ばれます。
また、仲間のdouble型は64ビットの倍精度浮動小数点数です。

float型の構造を図で表すと次のようになります。

計算機で叩くとわかりますが、ものすごく大きな数まで表すことができます。

しかし10進数の小数を扱うとなった場合、2進数では表せないものが少なからずあるので困りものです。
例えば10進数の0.1を2進数で表すと、0.0001100110011…という無限小数になります。

ちなみに小数の10進数から2進数への変換は、次のように行います。

        0. (先頭に0と小数点を書いておきましょう)
0.1*2=0.2 … 0
0.2*2=0.4 … 0
0.4*2=0.8 … 0
0.8*2=1.6 … 1 (次の桁を計算する時に、頭の1は取ります)
0.6*2=1.2 … 1 (同上)
0.2*2=0.4 … 0 (この辺で「あ、さっきも出てきた数だ…」と冷や汗をかきます)
0.4*2=0.8 … 0
0.8*2=1.6 … 1
0.6*2=1.2 … 1
:       ↑ここを上から読んで、答えは「0.000110011…」

この無限小数の内、仮数部に入りきらない桁は切り捨てられるため、誤差が出てしまいます。
そして、繰り返し計算していくと誤差はどんどん蓄積して大きくなります。

また、先程「ものすごく大きな数まで表すことができます」と言いましたが、
整数の場合も同様に切り捨てられるため、
結局のところ2進数換算で有効桁数23+1桁までしか正確に表せません。

お金の計算等には使いたくないですね。

(おまけ)ド・モルガンの法則による条件式の書き換え

ビットに触れたついでに、論理演算の便利な法則についてご紹介です。
ビットは0か1を取るのに対し、論理演算は偽(false)か真(true)を取ります。
ANDやORで計算するところは同じです。

ここでご紹介したい「ド・モルガンの法則」とは、簡単に言ってしまいますと、

ある条件AとBがあった時、
NOT( A OR B ) = NOT(A) AND NOT(B)
となる。

という法則です。

この法則には
1. NOTの数を減らせる。
2. ORをANDに、ANDをORに変換できる。
といった効果があります。

例えば、Javaで「AまたはBまたはCが入力されていたら、処理を行う。」
というIf文があったとします。
(入力されているかどうかのチェックには、IsEmpty()関数を使っています。)

If(!IsEmpty(A) || !IsEmpty(B) || !IsEmpty(C)) {
処理
}

この場合、IsEmpty()関数の処理を3回、||(OR)の処理を2回、!(NOT)の処理を3回しています。
これは、次のように書くことができます。

If(!(IsEmpty(A) && IsEmpty(B) && IsEmpty(C))) {
処理
}

IsEmpty()関数の処理を3回、&&(AND)の処理を2回、!(NOT)の処理を1回、と
NOTの回数が2回減らせました。

・・・NOT2回では、あまりありがたみを感じないかもしれませんね。

しかし、これがSQLのWHERE句であった場合、

SELECT * FROM tableX
WHERE NOT(NAME <> 'a'
OR NAME <> 'b'
OR NAME <> 'c')



SELECT * FROM tableX
WHERE NAME = 'a'
AND NAME = 'b'
AND NAME = 'c'

と書き換える事が出来ます。
SQLの場合、NOT、OR演算はインデックスが効かなくなり、処理速度が遅くなります。
そのため「NOTを減らしたい、ORをANDに変換したい」といった場合が出てくるのです。
(上の様にすっきり書き換えられる場合は稀ですが、
条件式やサブクエリが複雑な場合に、一部でもインデックスが効くように書き換えると、
その分処理速度が向上します。)

ちなみに変換方法の簡単な覚え方は次のようになります。
1. 出てくる条件全てにNOTを付ける
2. ORをANDに、ANDをORに変える
3. 全体にNOTをつける
(例)「NOT(A OR B)」
→1. 「NOT(NOT(A) OR NOT(B))」
→2. 「NOT(NOT(A) AND NOT(B))」
→3. 「NOT(A) AND NOT(B)」

情報技術系の勉強をした時に出てくる法則なので、ご存知の方も多い事でしょう。
実務でも使える機会が度々ありますので、覚えて置いて損はないかと思います。 

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です