2012/12/13

文系のための「数値型」(2)

さて、PostgreSQL 9.1 の「数値型」を整理したできたところで、
今度は、Python の「数値型」について、色々と考えてみることにする。
「文字列型」の話(2)でも書いたようにPython のデータ型は、PostgreSQLよりも大雑把。

今回の話、少々、話が複雑で、しかも、結果が微妙に異なるかもしれない。
エラーが出なければ、おそらく、問題は無い。以下の話は、基礎知識を得るための参考。
本日のプログラムは、パソコンの機種やOSにも依存するので、一致するとは限らない

実は、Python の「数値型」は4つしか無い。これらは「組み込み型」と呼ばれる型の一種。
PostgreSQL と対比させてみると、相互に変換できるものは3つしかない。
また、Python には、「複素数型(Complex)」があるのも興味深い。

PostgreSQL9.1
Python2.7
狭範囲(smallint)
{範囲:-32768〜+32767}
該当なし
並範囲(integer)
{範囲:-32768〜+32767}
整数型(int)
{範囲:-2147483648〜+2147483647
広範囲(bigint)
{範囲:-2147483648〜+2147483647}
長整数型(long)
{無制限}
固定小数点(decimal/numeric)
小数点までの範囲:131072桁
{小数点以降の範囲:16383桁}
該当なし
単精度(real)
{精度:4バイト(6桁精度)}
該当なし
倍精度(double)
{精度:8バイト(15桁精度)}
浮動小数点型(long)
{精度:8バイト(15桁精度)}
該当なし
複素数型(complex)

複素数型」は、少し複雑な数値計算を必要とする場合に必要となる。
Python は、科学計算などにも用いられることがある。特徴がよく現れている。
この型については、今回は説明しない。数値計算が必要な時にでもまとめよう。

さて、実を言うと、Pythonの「数値型」というのは、
C言語と呼ばれる別のプログラミング言語の「数値型」を用いている。
それを、Pythonが借用しているのである。つまり、上記4つの数値型はC言語のもの。

呼び方も全く違う。統一してくれれば良いのだが、何かの事情があるのだろう。
残念ながら、私は、なぜ呼び方が異なっているかの理由は知らないが、
どちらも、何となくは、言いたいことが解るので、まぁ、良いことにする。

いつも通りに話が逸れた。

順番に見てみると、PostgreSQL で「integer」だったものが「int」になっている。
integer」をフルネームで呼ぶか、略称で呼ぶかという違い。これは納得。
bigint」が「long」になっている。「大きい」と考えるか、「長い」と考えるか。

名前の違いで見ると、「浮動小数点型」の部分。PostgreSQLの方では、二つあった。
Python の方は一つだけ。実は、「Float」というのは、浮動小数点の総称
PostgreSQL では、単精度倍精度の二種類があるので分けている。

さて、次に実際の「有効桁数」と「精度」について見てみる。
Python数値型は、C言語数値型を借用していることはすでに述べた。
では、C言語のどの型を借用しているのか?
  • int     ← C言語long型
  • long  ← C言語には無い?
  • float  ← C言語におけるdouble型
なんと、C言語のデータ型の大きい方だけを使っているようである。
Pythonという言語は、比較的最近に開発された言語であるので、
近年のコンピュータの能力に合せているのかもしれない。

なるほど、ここまで、整理できたところで、
実際にPython を立ち上げて、数値型について理解を深めてみる。
とりあえず、「文字列型」の話の手順でPythonのコンソールを立ち上げる

最初にするべきことは、「>>>」の後ろに、以下をコピペ。

import sys

これは、「sys」という名前の「部品群」の読み込み作業。
Python では、様々な外部の部品を読み込んで使うことができる。
一般的には、「ライブラリ」あるいは「パッケージ」と呼ぶ。

さて、この「sys」という名前の「部品群」を読み込むと、
OSが提供する、つまり、システムが提供する様々な機能が使えるようになる。
だから「System」の略で「sys」。コンピュータの世界では略称をよく使う

さて、この「sys」の部品群が提供してくれている「機能」を使うためには、
sys.機能名」というように、読み込んだ部品群の名前の後ろに「.」をつけ、
さらに、その後ろに使いたい機能の名前を書く。

使いたい機能はどうやって知ることができるか?
実を言うと、APIリファレンスと呼ばれるマニュアルを読む必要がある。
これには、ちょっとしたコツがあって、読み方が解ればプログラミングは簡単。

しかし、今回の話は、プログラミングの入門講座ではない。
基本的な説明は終わったので、先に進むとしよう。
とにかく、「sys」を読み込むことができれば、次の一行をコピペ

sys.maxint

この実行結果は以下の通り。

>>> import sys
>>> sys.maxint
2147483647

この結果で出てきたのが、Pythonにおける「int型」の最大値。
この値は、C言語long型であり、PostgreSQLの「bigint型」に等しい。
実を言うと、最小値を求める関数は無い。これに「-1」を掛けた値が最小値となる。

では、この最大値を超えるとどうなるかを実験してみる。
次の行をコピーして実行してみる。
現在表示されているのが最大値なので、これに「1」を足してみる。

sys.maxint+1

この実行結果は以下の通り。

>>> sys.maxint+1
2147483648L

あれっ!?ちゃんと計算できている。限界を超えているのに?
いや、よく見ると数値の最後に「L」が付いている。
実は、Python の場合、限界を超えると自動的にlong型に変換される。

こういった機能を持つプログラミング言語は珍しく、
もちろん、PostgreSQL においてもエラーが発生する。
したがって、Pythonのint型の限界を超えた値をPostgreSQL には入れれない

では、いよいよ、厄介な「浮動小数点」について見てみる。

浮動小数点」において注意すべきことは何か?答えは「有効精度」の問題。
小数点以下で保証される桁数がいくつか?ということ。
これを間違えると、計算するときに誤った結果を得ることになる。

とりあえず、誤差というのが何か?という問題から考えてみる。
何も考えずに、以下の二行のコマンドをコピペ。

float("10.000000000000001")


少し強引な例ではあるが...。
このコマンドの実行結果は以下の通り。

>>> float("10.000000000000001")
10.000000000000002

文字列型の話(2)では、「"」で囲むと文字列になると解説した。
つまり、一度、文字列として「数字」を作り、それをfloat型に再変換している。
そうそう。「キャスト」と呼ばれる処理のことであった。結果は?

浮動小数点は、あくまで、近似的に値を表しているため、
このように元の数値が元の通りに再生できるとは限らない
場合によっては、有効精度以内であっても生じることがある

次は、「浮動小数点型」の最大と最小の値を確認してみる。
float型の情報を得るためには、もう少し難しいなコマンドが必要になる。
整数型に比べて、見ないと行けない値が多い。

sys.float_info

同様に、この実行結果は以下の通り。
見易いように以下の出力は少し整形してある。
通常は、ひと続きで出力される。

>>> sys.float_info
sys.float_info(max=1.7976931348623157e+308, 
                      max_exp=1024, 
                      max_10_exp=308, 
                      min=2.2250738585072014e-308, 
                      min_exp=-1021, 
                      min_10_exp=-307, 
                      dig=15, 
                     mant_dig=53, 
                     epsilon=2.220446049250313e-16, 
                     radix=2, 
                     rounds=1)

何やら、一杯出てきた。このうち、「max」と「min」は10進数での「指数」の限界値
1.7976931348623157」の「10」の「308乗」が最大値であり、
2.2250738585072014」の「10」の「308乗」が最小値である。

ところで、「max」と「min」の部分の表示方法、覚えておいた方が良い
Excel などでも、非常に大きいあるいは小さい数値を扱うとこの表示になる。
これは、「0」の数が多いと数えるのが面倒だから。つまり、「エラー」では無い

さてさて、またまた、話が逸れた。

つまり、上記のことが解れば、「max_10_exp」と「min_10_exp」は、大体、予想がつく。
要するに、10進数における「指数部」の限界値であり、その上限と下限である。
この値よりも「-1乗だけ小さい値の範囲までが扱うことができる

ということは、なるほど、「max_exp」と「min_exp」は、2進数での「指数」の限界値
2進数における「指数」の取り得る範囲は、最大で「1024」であり、
負の方向には、最小で「-1021」までの値を取ることができる。

いわゆる、「精度」というのは、「dig」の部分であり「15桁精度」であることを示している。
radix」というのは、指数における「」のことで、したがって、「2」。これも良い。
mant_dig」は、仮数部に保持できる2進数(ビット)での桁数

あと二つ残っているか。「epsilon」は、float型で表現できる「1」の次の値
なぜ必要か理解できない人もいるかもしれないが、厳密な科学計算では精度確認に必要
そして最後の「round」は、何やら、「丸め込み」の方法らしい。良く解らない。

さて、「浮動小数点」の精度の問題は、よく理解しておいた方良い。
ここで簡単な実験をしてみる。どのような実験か?
0.1」を「10」回足すという作業をPython で書いてみる。

ついでなので、計算式の立て方についても触れておくことにする。
まずは、以下のように考えることができる。小学生でも作れる計算式。

0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 = 1.0

このまま計算しても良いのだけれど、もう少し、この式を一般化してみる。
0.1」の部分を「x」という記号に置き換えると、以下のように表せる。
次は、中学生レベルでの式の立て方。



ここで右下に付いている数字や記号のことを「添字」と呼ぶ。
一番前から数えて、不特定番目を「i」とし「n」番目までの数を計算するという意味。
また、「sum」や「x」というのは、状況によって値が変化するので「変数」と呼ぶ。

さて、最後に高校生レベルの計算式の作り方。
多くの自称「文系」の人達が吐き気を催すかもしれない「Σ」を使う
Σ」は足し算の記号。実は、エクセルの足し算の記号も「Σ」になっているはず。



Σ」の記号の下には「i=1」と書いてある。つまり、不特定番目は「1」から開始
そして、「Σ」の上の部分で、全部で「n」個の数が存在していることが示されている。
Σ」の横には、「」と書かれているので、「i」を一つづつ繰り上げながら「n」回」足す」

さて、さらに、このような一般式が立てれたところで、
今度は、当初の計算の目的である「0.1」と「10」を入れてみる。
以下のように書くことができる。少々、特殊な書き方であるが。



ここまでの話はこれから書くプログラムを理解する上で重要。
この数式を実際に動かすようにプログラムすることを「実装」と呼ぶ。

では、まずは「変数」の初期値を与えてやる。初期値を必要とするのは、「sum」
sum」には最終的に結果が格納されるのだが、計算途中の中間結果もここに入るので、
最初に何も入っていない状態にしないといけない。これを「初期化」と呼ぶ。

sum = 0
n = 10

まずは、上記をコピーしてコンソールに貼り付ける。
そして、次に「Σ」の部分の計算。「iを1〜10の範囲で計算する」ということは、
英語にすると「for i in range of 10」なので、これを「n」を用いてPython 風に書きなおす

for i in range(n):

最後の「」を忘れないように。このような「for」で始まる部分を「ループ文」と呼ぶ。
ループ文」では、処理を「入れ子」に書くことができる。プログラミングの基本機能
Pythonにおける「入れ子」の状態は、「インデント」と呼ばれる「空白」で表す。

さて、この部分までコピーして実行すると、次の行には、「...」という行が出てくる。
今度は、「Σ」の後ろの部分を書く。ここで重要なことが一つ。
Python では、コマンドの前の「空白の数」が重要。今回は、空白を「4」つ入れる。

     x = 0.1
    sum = sum + x

スペース「空白」の部分も含めて「厳密」に一行ずつコピーする。
上手く行かなった人もいるかもしれない。そのような人は、
以下に、まとめたコマンドを準備したので、以下をコピペして実行。

sum = 0
n = 10
for i in range(n):
    x = 0.1
    sum = sum + x

ここまでを貼りつけて、コンソールの先頭が「...」の状態であれば、
もう一度、エンターキーを押して「>>>」となるようにする。
この状態では、まだ、結果は表示されないので、最後に、次のように入力して実行する。

sum

そして、この結果は以下の通りになる。

>>> sum
0.9999999999999999

あれ〜!何か、計算間違いでもしたのだろうか「1」にならない...。

実は、この結果、Python のエラーバグではない!
これは正しい計算結果。浮動小数点の性質が原因
0.1」は、厳密には「0.1」では無いので、微細な誤差が累積するのである。

なお、上記の結果を「文字列型」に直には...

str(sum)

であり、その実行結果は以下の通り。

>>> str(sum)
'1.0'

となる。ちゃんと、「1.0」に戻っている。
少々、不思議な話かもしれないが、このような特徴がある。

0 件のコメント:

コメントを投稿