ラベル データ型 の投稿を表示しています。 すべての投稿を表示
ラベル データ型 の投稿を表示しています。 すべての投稿を表示

2012/12/16

文系のための「日付型」

我々は、無意識に様々な種類の情報を使い分けている。
例えば、「数値」と「数字」の区別は、何も考えずに出来ている。
人の認識能力は、非常に柔軟に、当たり前のことをこなしてしまうのである。

しかしながら、コンピュータは、人の認識能力にははるかに及ばず、
多種多様な情報の種類を柔軟に分離する能力を持っていない。
人に違和感を感じさせないためには、一つ一つ、定義しなければならない。

そういった定義の中に「データ型」がある。
「データ型」は、非常に多くの種類が存在していて、
このブログで全てを紹介することは不可能に近い。それくらい多い。

本ブログでは、すでに、「文字列型」と「数値型」について整理した。
この二つは、最も基本的なデータ型ではあるが、これだけではない。
ということで、今回は「日付型」と呼ばれる「データ型」に注目したい。

まず、一つの疑問として、なぜ、「日付型」が必要なのか?
一見すると、その型を定義することに重要な意味を見いだせないかもしれない。
しかしながら、この型を定義しなければ少々厄介なことが起きるのである。

「12月31日の『1』日後の日付を教えてください。」という質問にどのように答えるか?
おそらく、普通の人は、即座に「1月1日」と答えるであろう。常識的に。
では、この質問の内容を、データ型の考え方を含めて整理してみるとどうなるか?

「1日後」の日付ということは、これは日付の「足し算」をしていることになる。
したがって、この問い合わせに適したデータ型は文字列型ではなく、数値型であろう。
月と日は、「整数値」で表すことができるので、仮に、そのように定義したとする。

すると、困ったことになっていまう。「31+1=32」になってしまうので、
「12月31日」の次の日が「12月32日」になってしまい、「1月1日」にはならない。
年末の忙しい時期、「32日」があったら嬉しいかもしれないが…システムとしては困る。

また、日付の表し方の問題もある。「○○月××日」という表し方、
「○○-××」、「○○/××」あるいは「××/○○」といった表現方法もある。
これらの問題を解決しないと、コンピュータ上で予定表が作れない。

国や地域、文化圏によって異なる表記さえも考慮しなくてはならない。
要するに、「日付型」というのは、これらの問題を解決するための「データ型」である。
つまり、日付型の主要な役目というのは、次の二つに分けることができる。
  • 日付や時間の演算を可能にする。
  • 要求に応じて適切な日付表記を提供する。
さてさて、かなり前置きが長くなったが、まずは、PostgreSQL 9.1 のデータ型を確認。

型名サイズ説明最遠の過去最遠の未来精度
timestamp8 バイト
日付と時刻両方
(時間帯なし)
4713 BC294276 AD1μ秒、14桁
timestamp8バイト
日付と時刻両方、
時間帯付き
4713 BC294276 AD1μ秒、14桁
date4バイト日付(時刻なし)4713 BC5874897 AD1日
time 8バイト時刻(日付なし)00:00:0024:00:001μ秒、14桁
time12バイト
その日の時刻のみ、
時間帯付き
00:00:00+145924:00:00-14591μ秒、14桁
interval12バイト時間間隔-178000000年178000000年1μ秒、14桁

大きく分けて4種類、全部で6種類の「日付型」が定義されていることが解る。
このデータ型に関しても、表現できる日付の区間には「上限」と「下限」があるので、
この範囲を超えた日付は、これらの型では扱えない。これは、他のデータ型と同じ。

また、「精度」に「14桁」と書いてある型は「浮動小数点型」が使われている。
date型は「整数値型」のようである。いずれも、どこかを基準に「0」とおいていて、
そこからの日数で日付を計算しているらしい。

実は、この「基準となる日」が重要。
PostgreSQL の場合、その基準日は「2000年1月1日 00:00」となっていて、
基本的に、時間に関しては、小数点以下の部分で表すようになっている。

とりあえず、timestamp型を見てみる。これは、日付と時間の両方を表すことができ、
さらに、時間帯(タイムゾーン)の指定できる。
date型とtime型は、timestamp型で表記できる部分を、日付と時間に分けたようなもの。

時間帯というのは、世界協定時間(UTC)からの時差。
例えば、「2012-12-16 12:58:23 -9:00」のように表すことができる。
通常は、timestamp型、date型、time型の3つのいずれかを使うことになろう。

コンピュータ上で日付を扱う際には、気を付けるべきことがいくつかある。
日付を記述する際の、「年」と「月」と「日」の書き方である。
この表記方法は、国や地域によって異なり、間違えると年と日が入れ替わる。

例えば、YMD方式と言われる方法では、Year/Month/Dayの順番で表記し、
一方、MDY方式と言われる方法では、Month/Day/Year の順番で表記する。
例えば、「12/12/10」の場合、どちらかを区別することは可能であろうか?

  • YMD方式:2012年12月10日
  • MDY方式:2010年12月12日

となり、全く異なる日付になってしまう。この問題を回避するのは簡単である。
要するに、年を省略せずに「4桁」で記述する癖をつけておくこと。
かつては、データの容量を節約するために「2桁」表記をしていたが、今は関係無い。

最も安全な記述方法は、年と月と日を「-(ハイフン)」で区切った方法。
例えば、「2012-12-16」と記述する。間違い無く、特定の日付を指定できる。
実は、ISO 8601という国際標準で定められた方式。

少し、話が逸れるが、コンピュータ上で日付を扱う際に「和暦」を用いている人がいる。
確かに、ソフトウェアによっては、これを上手く操作している場合もあるが、
計算プロセスが増えてしまうので良い方法とは言えない。西暦表記に直した方が良い。

これらの問題が、なぜ重要かは、データ型のことが分かっていれば解るハズ。

また、いわゆる文系分野では、歴史を扱う場合もあるが、
それぞれの日付型の「上限」と「下限」についても考慮した方が良い。
 timestamp型、date型、time型の3つの型では、「縄文時代」は扱えない。

そもそも、現在のデータベースシステムは、それほどまでに古い時間を扱う想定が無い。
日付型で古い時代を扱うためには、interval型を拡張すれば良いのかもしれないが、
正直なところ、私は、interval型を上手く使いこなせていない。色々と検証が必要。

さて、今回は、「日付型」について整理した。
最も重要なことは、ある基準となる日があって、そこからの日数で計算すること。
したがって、その実体は数値型であり、timestamp型などでは浮動小数点型を使っている。

ただし、注意しないといけない。

「文字列型」と「数値型」と同様に、システムやソフトウェアによって定義が異なる。
面倒なことに、基準日の設定そのものが異なるので、
自分が使っているシステムやソフトウェアの仕様を確認する必要がある。

特に、Excelは色々と厄介で、知っていないと困ることがある。
基本的には、1900年1月1日が基準であるが、1904年1月2日を基準とする方法もある。
1904年1月2日を基準というのは、何とも半端な気もするが閏年が関係しているらしい。

ちなみに、この方法で表された時間のことを「シリアル時間」と呼ぶ。

Windows版のExcelの場合には、1900年が基本であるが「負の値」を扱えない。
一方、Mac版Excelの場合には、1904年が基本であり「負の値」も扱える。
もちろん、どちらの版も、相互に変換できるが、4年と1日ほどずれる。
(この話、執筆時にUbuntuを使っていたので未検証。)

この問題が理解できていれば、Excelのあの不思議な現象の原因も容易に理解できる。
つまり、ID番号のつもりで「12-12-16」などと入力すると「2012年12月16日」になり、
慌てて、文字列型に変換すると「42716」となる問題…これは1900年1月1日からの日数である。

日付では無いのであれば、日付型に判断されないようにする工夫が必要で、
最も簡単な方法は、先頭に何らかの文字を置いて日付型に見えないようにするか、
あるいは、「"」や「'」で囲んで文字列型にするという手段がある。

ところで、当然のことながら、Python にも日付型が定義されているが、
その基準日は、1970年1月1日 00:00 である。PostgreSQL ともExcelとも異なる…。
ということで、ここからは、Python で実験してみる。

試しに、Python コンソールを開いて、以下の二行を実行してみる。

import time
time.gmtime(0)

ここでは、一行目で「時間」を扱うライブラリ「time」を読み込んで、
二行目では、そのライブラリが持っている「gmtime()」という機能を使っている。
「0」とすることで、基準となる「0」の日時を取得できる。結果は以下の通り。

>>> time.gmtime(0)
time.struct_time(tm_year=1970, tm_mon=1, tm_mday=1, tm_hour=0, tm_min=0, tm_sec=0, tm_wday=3, tm_yday=1, tm_isdst=0)

この結果から、1970年1月1日 00:00 が基準であることが解る。
また、「tm_wday=3」となっているので、月曜日を「0」として、
この日が「木曜日」であることが解る。「tm_yday」は、365日換算した時の日数。

最後に、tm_isdstと言うのは、年を2桁で表示した時の問題を回避するためのものらしい。
いわゆる「2000年問題(Y2K問題)」の対策のためのもの。
ついでに、以下のコマンドも実行してみる。

time.time()

これは、現在時刻を浮動小数点型で教えてくれる。実行結果は以下の通り。

>>> time.time()
1355635704.049219

これが、基準日からの現在の日付と時刻を表している。
さらに、これを、我々が見慣れた表記にするためには、
最初の「time.gmtime」を使って以下のようにする。

time.gmtime(time.time())

そして、この実行結果は以下の通り。

>>> time.gmtime(time.time())
time.struct_time(tm_year=2012, tm_mon=12, tm_mday=16, tm_hour=5, tm_min=30, tm_sec=7, tm_wday=6, tm_yday=351, tm_isdst=0)

このように、日付型は内部的には数値型で計算され、
必要に応じて、適切な表示がなされるのである。

「日付型」は、我々が、コンピュータ上で頻繁に扱っているにも関わらず、
あまり意識することが無いデータ型であり、意外に、奥が深い。
扱いを間違えると、全く異なる日時を示すことがあるので注意が必要である。

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」に戻っている。
少々、不思議な話かもしれないが、このような特徴がある。

2012/12/11

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

コンピュータ上において、いわゆる「文字」は対応表の位置のコードで表した。
また、この対応表には、「数」を文字として表した「数字」も含まれる。
一方、「数」を計算のための「数値」として扱う場合には2進数に変換するのであった。

重要な問題は、「数値」と「数字」が異なる体系で表され、
したがって、コンピュータがこの両者を区別するためには、
その体系の定義である「データ型」というのを決める必要があった。

すでに、「文字列型」について説明をしたので、今回は「数値型」について考えてみる。
実は、「数値型」には様々な種類があって、環境によってその定義は異なる。
そこで、「文字列型」と同様に、まずはPostgreSQL 9.1 のデータ型から見てみる。
  • 実数型
    • 整数型
      • 狭範囲(smallint):2バイト(-32768〜+32767)
      • 並範囲(integer):4バイト(-2147483648〜+2147483647)
      • 広範囲(bigint):8バイト(-9223372036854775808〜9223372036854775807)
    • 小数点型
      • 固定小数点型
        • decimal:小数点前までは131072桁、小数点以降は16383桁
        • numeric:小数点前までは131072桁、小数点以降は16383桁
      • 浮動小数点型
        • 単精度(real):4バイト(6桁精度)
        • 倍精度(double):8バイト(15桁精度)
PostgreSQLの既定の「数値型」は、いわゆる「実数」しか定義されていないが、
数値計算ライブラリの「R」などなどでは、「虚数」を定義している場合もある。
...が、話が拗れるので、ここでは割愛。「実数型」のみを扱う。今は不要。

さて、これら複数の数値型の適切なものを選ぶには、実際に扱うデータを観察し、
それぞれの数値型と見比べ、適切な「桁数」と「精度」を持つものを選ぶことが重要。
また、データ量が増えてくると、処理効率も考慮する必要があるかもしれない。

まず、「整数型」から見てみる。「整数値」には、範囲に応じて3つの型が存在する。
どれを使うべきかは、実際のデータの範囲と見比べて考えるべきであるが、
私がこれまでに扱ってきたデータで「integer」の範囲を超えたことはない。

「整数型」で気を付けるべき点は、数字の有効桁数を考えるだけ。
これは、文字列型を扱ったときと同じ注意事項である。難しくない。

次に、「小数点型」について整理してみる。やはり、複数の型が用意されている。
実は、整数型と比較して「小数点」が含まれる数値は、少々、厄介。
コンピュータは、「1」と「0」しか扱えない。したがって、「.(点)」は表せない。

アイデアは二つある。一つ目は、「整数部」と「小数部」を分ける方法であり、
二つ目は、「指数」によって表現し、「仮数部」と「指数部」に分ける方法である。
それぞれの方法を、「固定小数点」と「浮動小数点」呼ぶ。

まずは、「固定小数点型」は、「整数部」と「小数部」に分けるだけなので簡単。
この方法、数値は「正しい」が、扱える範囲がかなり狭いという問題がある。
通常は、資金管理システムなど、厳密な数字を扱う場合に用いる。

なお、PostgreSQL では、「decimal」と「numeric」の二種類が用意されているが、
両方とも等価であるので、いずれを用いても構わないことになっている。
PostgreSQL では、「固定小数点型」の処理速度は、「浮動小数点型」と比較して低速。

一方、「浮動小数点型」は、数値を「指数」によって表現する方法であり、
「仮数部」と「指数部」に分けて「1」と「0」の2進数に直す。
この方法、「指数」というのを理解していないと、何のことか解らない。

「指数」というのは、簡単に言うと、「ある数が何回掛けられているか?」ということ。
つまり、「1000」という数値は、「10」が「3」回掛けられた数値であり、
指数表現を用いると、「」 のように表現することができる。

この時、「10」の部分のことを「低」と呼び、「3」の部分のことを「指数」と呼ぶ。
高校の数学で習った気もする。もちろん、「低」の部分を「2」にすることも可能。
実は、コンピュータのように2進数の世界ではこちらの方が便利。

例えば、以下のように表現することができる。
よく考えてみれば、それほど難しいものでもあるまい。


「1.101011」の部分を「仮数」と呼び、「16」の部分が「指数」である。
ここで、コンピュータ上では「低」は「2」と決まっているので、
元の数値を復元するためには、「仮数部」と「指数部」さえ解れば良い。

この方法、「固定小数点型」よりもビット数を節約できるので、
「固定小数点」による表現方法に比べて、表現できる数値の幅が広い。
コンピュータの性能が低かった時代に生み出された画期的な方法。

現在でも、「浮動小数点型」は数値の一般的な表現方法である。
指数部によって、小数点の位置が変わることことから、
「固定」に対して「浮動」なのである。

さて、話が逸れたが、この表現方法、実は大きな問題がある。
「固定小数点型」と異なり、近似解として元の数を表しているため、
実を言うと、元の数値を正確に表現できているとは限らない。

そこで、出てくるのが「精度」という言葉。これが重要。
「6桁精度」の場合、6桁目までは信用しても良いが、それ以降は「誤差」。
例えば、「0.1234567」という数の場合、最後の「7」は正しいかは不明。

この精度の差が「real型」と「double型」の大きな違い。
「double」という言葉の通り、  「real型」の倍の精度を持っている。
この知識は極めて重要である。出てきた数値を鵜呑みにすることは極めて危険!

例えば、GPSで取得した緯度経度の座標値を表現する場合に、
「6桁精度」の「浮動小数点型」を用いてしまうとかなり厄介なことになる。
やはり、この場合には、「15桁精度」の「double型」を使った方が安全。

さて、今回は、数値型について整理をしてみた。注意すべき点は3つ。

一つ目は、文字列型の時にも書いたように表現できる桁には限界があること。
二つ目は、「固定小数点型」と「浮動小数点型」の二種類が存在すること。
三つ目は、「浮動小数点型」の場合には「桁精度」があるということ。

この3つのポイントが理解できていれば良い。
次回は、Python における「数値型」について整理し、
簡単なプログラムを書いてみることにする。

2012/12/06

文系のための「文字列型」(1)

コンピュータで扱う情報は「1」と「0」。そのようなデータをデジタルデータと呼ぶ。
デジタルデータには、様々な「型」があって、その「型」に応じた処理が行われる。
ここまでの話を整理すると、ざっと、こんな感じである。

さて、今回は、様々な「データ型」の中で、利用頻度が高い「文字列型」について。
おそらく、一般的なユーザが最も頻繁に用いている「型」かもしれない。
もちろん、現在、書いているブログの文章も「文字列型」で構成されている。

まずは、「文字列型」とは何かについて考えてみる。

すでに、述べたように、コンピュータ上で扱う「文字」は、
たとえ数字であったとしても、何らかの「対応表」を参照していて、
その対応表の場所を「2進数」で示しているのであった。

こうして対応表を通して「2進数」で記述することを「符号化」と呼び、
「符号化」された1文字を「文字型」、これを「配列」にしたものを「文字列型」と呼ぶ。
プログラミング言語の中には、「文字型」しか持たないものもある。

さて、今回は「文字列」の限界、すなわち、「文字数限界」について考えてみる。
実は、コンピュータで扱える「文字数」には限界がある。
もちろん、エクセルの一つのセルに入力できる文字数にも限界がある。

このことは、データベースを自分で構築する際の最も重要な知識の一つであり、
また、エクセルなどで比較的長い文字を扱う人は理解しておいた方が良い。
特に、文献資料等の文字資料を扱う人は注意すべきで問題である。

では、早速、PostgreSQL9.1における「文字列型」を見てみる。
  1. character あるいは char
  2. character(n) あるいは char(n)
  3. character varying(n) あるいは varchar(n)
  4. text
実は、PostgreSQL 9.1 では、以上の4つの「文字列型」が存在している。

一つ目は、いわゆる「文字型」つまり、1文字だけ格納可能な型。
二つ目は「固定長文字列型」、三つ目は、「可変長文字列型」であり、
そして、四つ目は「テキスト型」と呼ばれている。

見方が解らない人が居るかもしれない。ということで、まずは、表示方法について。
括弧が付いている場合は、「()」の所までが「型名」で、
「()」の中が「引数(パラメータ)」であり、この型の「限界値」である。

「限界」というのは、この文字列の配列の数を表していて、
「n」には、配列の長さをあらわす「1つの整数値」が入る。
つまり、「n」は、「文字数の限界」を決めるための「変数」である。

また、「可変長」と「固定長」というのは、この「文字数の限界」に関係していて、
「可変長」の場合は、この「型」に入っている文字列の長さが文字の数に依存し、
「固定長」の場合は、この「型」に入っている文字列の長さはその限界の数である。

要するに、「可変長型文字列」と「固定長文字列型」は文字数を決めないといけない。
一方、「character」と「text」には、「()」が付いていないので、
この型については、引数が不要であることを示している。

例えば、「人名」を「10文字(20bite)」の「文字列型」で指定し、
この型に対応する値として「新島 襄」という値を入力するとする。
この時、それぞれの型ではどのようになるか?

・文字型の場合  :"新"
・可変長の場合  :"新島□襄"
・固定長の場合  :"新島□襄□□□□□□"
・テキスト型の場合:"新島□襄"

「文字型」の場合、「1文字」しか受け付けないので、「新」以降は切り捨てられる。
「可変長」と「テキスト型」の場合は、同じで、入力した通りに値が入る。
そして、「固定長」の場合には、10文字になるまで「空白(□)」で埋める。

もしも、10文字以上の名前が存在したらどうなるのか?
これも考えてみる。もっと長い名前と言えば...「寿限無」?
といことで、寿限無を入力してみる。

・文字型の場合  :"じ"
・可変長の場合  :"じゅげむ□じゅげむ□"
・固定長の場合  :"じゅげむ□じゅげむ□"
・テキスト型の場合:"じゅげむ□じゅげむ□ごこうのすりきれ
          かいじゃりすいぎょの□すいぎょうまつ
          うんらいまつ□ふうらいまつくうねると
          ころに□すむところ□やぶらこうじの□ぶ
          らこうじ□ぱいぽぱいぽぱいぽのしゅー
          りんがん□しゅーりんがんのぐーりんだ
          い□ぐーりんだいのぽんぽこぴーのぽん
          ぽこなーのちょうきゅうめいのちょうす
          け"

このようになる。「テキスト型」のみは全ての文字を格納できるが、
「文字型」は1文字のみ、「可変長」と「固定長」はともに10文字の限界があるので、
超過した部分は切り捨てられる。

通常は意識したことは無いであろうが、
実は、このような「文字数の制限」というのは、
データベースに限らず、様々な所で出てくる問題である。

なぜ、このように複雑な定義が必要なのか?

最近のコンピュータは、性能が高いので意識することは「少ない」が、
コンピュータのメモリやCPUの負担を少なくするための工夫が現れているのである。
ちょっと大きなデータを扱うと、最新のコンピュータでも処理が追いつかないのである。

ちなみに、少し前までは、引数「n」に指定できる限界値は非常に小さく、
「128文字(bite)」あるいは、もう少し大きくて、「256文字(bite)」であった。
かつては、限られた資源を上手く使い回すのが腕の見せ所だった。

なお、PostgreSQL 9.1 では、最大で1GBまでサポートされている。
単純に計算すると、2バイト文字で非圧縮の場合4096文字程度まで格納できる計算である。
これよりも長い「文字列」を扱う時には、ちゃんと、仕様書を確認した方が良い。

実際には、文字列を圧縮するようなので、もう少し格納できるかもしれない。
「文字数限界」については、深く知ろうとすると色々と厄介なのでここでは割愛。
いずれにせよ、長大な「文字列」を扱う際には注意が必要である。

次回は、Python というプログラミング言語を通して、
もう少し、文字列型の性質や操作について考えてみる。

文系のための「データ型」

これまでの話から、コンピュータは、2進数と呼ばれる「1」と「0」の値によって、
データを扱っていることは理解できているハズ。
ONとOFFのスイッチの組合せで動いているとも言える。

また、重要なこととして、同じ2進数による表現であっても、
対応表を参照する方法と、10進数を2進数変換した数値で扱う方法の、
二種類方法が存在していることも理解できているはず。

例えば、ASCII文字コードに代表される「文字」は、対応表による方法であり、
色の場合は、8bitの場合には、256段階の色の強さを2進数の数値で表した方法であった。
もちろん、通常の計算は、2進数の計算で対応できるのであった。

そうそう。同じ「数」であっても、「数字」と「数値」は別のもの。
見た目が同じでも、コンピュータは、別のものとして扱うのだった。
では、こういったことを知っていないと、どのようなことで困るか?

エクセルなどの表計算ソフトを使っていて、
次ような問題に出会ったことは無いだろうか?

ID番号と思っていたものが勝手に日付になったり、
足し算しないといけないにも関わらず、なぜか、計算できていなかったり...。
あるいは、小数点以下の数値が勝手に四捨五入されていたり、ということもある。

知っていれば、こういった問題に頭を抱えることが激減する。自力で解決できる。

コンピュータのデータの扱い型を知ることは、ある居程度は重要なことである。
「文系」とか「理系」とかではなく、無用な仕事を増やさぬためにも。
実際に、こうした問題で相談を受けることは少なくない。いや、多い。

もちろん、これらの質問に対して、不快に感じたことは無いが、
現実問題として、文系であってもコンピュータで困ることはある。
「文系だからそこまで知らなくてもね〜」と言ったのは誰だったか...。

「文系は使えれば良い派」には断固として反対。

さて、そんな愚痴っぽい批判のようなことはさておき、
コンピュータの仕組みをある程度知っておいて損は無い。
応用も利くし、複雑な処理が、簡単な処理で済むことが極めて多い。

理解しておくべきことは、人が目で見て理解していることと、
コンピュータが理解できることは全く異なるということ。それゆえに、
人が当たり前にできるようなことであっても、コンピュータが出来ないことも多い。

日付の計算は、その代表的な例と言える。「12月31日」の次の日は?
普通の人は、「1月1日」と答えるであろう。常識である。
しかしながら、うっかりすると、コンピュータは「12月32日」と答える。

コンピュータは、それほど賢いものでは無い。

では、人が当たり前のように行なっているような、
非常に複雑な手続きを扱うために、どのような仕組みを持っているのか?
というのが今回の話のテーマ。

キーワードは「データ型」。

一般的に、「データ型」と呼ばれる存在は、
この問題を解決するために生み出された「苦肉の策」的仕組みである。
この仕組みは、理想的とは言えないものの、現在の唯一の手段である。

簡単に言うと、人がコンピュータに何らかの命令を与える時に、
扱うべきデータが、文字なのか数値なのかを教えてやる、という方法。
文字と数値だけではな無い。日付、時間、図形といった「型」を教えてやる。

コンピュータに様々な「型」の種類と、その「型」特有の処理方法を与えておけば、
コンピュータはその通りにデータを処理してくれる。
人が、コンピュータに合せてやるということが必要ではあるが...。

さて、この話をスムーズに理解できた人にとっては「それで?」と思うかもしれない。
「一つずつそのデータ型とやらの説明してくれれば良いではないか?」とも思うかも。
実を言うと、それほど単純ではない。例えば、こうした「型」は環境によって定義が異なる。

Microsoft Excel という馴染み深い表計算ソフトが扱う「データ型」と、
PostgreSQL などのデータベース管理システムが扱う「データ型」、
C言語よ呼ばれるプログラミングが扱う「データ型」は、それぞれ、定義が異なる。

さらに、同じMicrosoft Excel であっても、「データ型」に関して、
なぜか、Windows版とMac版で微妙に異なる部分があるなど、
一つでは説明しきれないことも色々とある。

同じデータベース管理システムであっても同様。PostgreSQL、Oracle、
DB2、SQL Server、SQLite、Access といった商品毎によって、
「データ型」の定義は、多かれ少なかれ、違いがある。

もちろん、プログラミング言語も。C言語、PL/I、Delphi、COBOL、Python、Ruby、
などなど、これら意外にも、数えきれないほどプログラミング言語は存在するが、
プログラミング言語によって、「データ型」の定義が異なる。

さて、この問題は、「データ型」についての知識を「教える側」としては、
非常に悩み深い問題でなのである。すなわち、何をベースにするべきか...。
どれを選んでも、多少の矛盾が出てきてしまう。

ということで、悩みに悩んだ末、PostgeSQLとPythonの「データ型」をベースに、
以下の話を進めていくことにする。この二つを対照させながら、
データ型について整理することにする。

ただし、PostgreSQL のデータ型は非常に多いので、全ては説明せず、
一般論として関係しそうな基本的なデータ型に重点を置いて整理することにする。

なお、以下の表は、PostgreSQL 9.1 のデータ型の一覧である。
赤字の所が、現在、説明を予定しているデータ型。
名称別名説明
bigintint88バイト符号付き整数
bigserialserial8自動増分8バイト整数
bit [ (n) ]固定長ビット列
bit varying [ (n) ]varbit可変長ビット列
booleanbool論理値(真/偽)
box平面上の矩形
byteaバイナリデータ("バイトの配列(byte array)")
character varying [ (n) ]varchar [ (n) ]可変長文字列
character [ (n) ]char [ (n) ]固定長文字列
cidrIPv4もしくはIPv6ネットワークアドレス
circle平面上の円
date暦の日付(年月日)
double precisionfloat8倍精度浮動小数点(8バイト)
inetIPv4もしくはIPv6ホストアドレス
integerint, int44バイト符号付き整数
interval [ fields ] [ (p) ]時間間隔
line平面上の無限直線
lseg平面上の線分
macaddrMAC(メディアアクセスコントロール)アドレス
money貨幣金額
numeric [ (p, s) ]decimal [ (p, s) ]精度の選択可能な高精度数値
path平面上の幾何学的経路
point平面上の幾何学的点
polygon平面上の閉じた幾何学的経路
realfloat4単精度浮動小数点(4バイト)
smallintint22バイト符号付き整数
serialserial4自動増分4バイト整数
text可変長文字列
time [ (p) ] [ without time zone ]時刻(時間帯なし)
time [ (p) ] with time zonetimetz時間帯付き時刻
timestamp [ (p) ] [ without time zone ]日付と時刻(時間帯なし)
timestamp [ (p) ] with time zonetimestamptz時間帯付き日付と時刻
tsqueryテキスト検索問い合わせ
tsvectorテキスト検索文書
txid_snapshotユーザレベルのトランザクションIDスナップショット
uuid汎用一意識別子
xmlXMLデータ