Rubyでのプログラミングではあまり計算機の中の都合を意識しなくてよいようになっている。しかし、凝ったプログラムを作ろうとしたり、Ruby以外で少し複雑な計算などを行なわせようとするとき、あるいはとくにコンピュータグラフィックスなどの基礎知識を習得したい場合には、計算機の内部構造、とくに2進数の性質を知らないとスムーズに理解できないことがある。
コンピュータ内部では、数字も文字も画像も音楽も全て 1
と 0
の2進数の状態で保存している。人間にとって分かりやすい10進数でなく2進数が使われるのは、計算機が何かを記憶するための格納場所であるメモリが、スイッチのように ON
か OFF
の2つの状態をもつトランジスタという半導体部品で構成されているからである。また、1
と 0
の2種類だけの値であれば、スイッチの ON
/OFF
だけでなく、磁極の向きや、光の点滅で表現することも可能となる。例えば、ハードディスクはS極とN極の磁化パターンにより1
と0
を記録しているし、CDやDVDはディスク表面に溝を彫り、そこにレーザー光を当てた際の反射光の拡散の有無により1
と0
を区別している。
上記の ON
/OFF
値を何個も組み合わせてると一つのデータ格納領域になる。たとえば、ON
と OFF
の箱を8個用意して、次のようなデータを入れる。
OFF | ON | OFF | OFF | OFF | OFF | OFF | ON |
これは模式的な絵であるが、計算機のメモリの中では、電気が ON
になっていたり、磁石がN
極を向いている状態を想像するのが実状に近い。この ON
と OFF
の状態を2進数にしよう。
0 | 1 | 0 | 0 | 0 | 0 | 0 | 1 |
2進数の
01000001
128倍 | 64倍 | 32倍 | 16倍 | 8倍 | 4倍 | 2倍 | 1倍 |
0 | 1 | 0 | 0 | 0 | 0 | 0 | 1 |
これは、64×1 + 1×1 = 65 となる。
ちなみに、情報科学では2進数の1桁のことをビット(bit)と言い、8ビットを並べた情報量の単位をバイト(byte)と呼ぶ。
2進数は2種類の数字のみで表現するため桁数が大きくなりやすくて、人間にとっては読みづらい。かといって、10進数に直すのは計算が面倒である。そのため、2進数4桁をまとめて16進数にして数値を表記することが多い。
2進数4桁をまとめると、2**4=16個の数を表すことができる。
2進数 | 16進数 | 10進数 |
---|---|---|
0000 | 0 | 0 |
0001 | 1 | 1 |
0010 | 2 | 2 |
0011 | 3 | 3 |
0100 | 4 | 4 |
0101 | 5 | 5 |
0110 | 6 | 6 |
0111 | 7 | 7 |
1000 | 8 | 8 |
1001 | 9 | 9 |
1010 | a | 10 |
1011 | b | 11 |
1100 | c | 12 |
1101 | d | 13 |
1110 | e | 14 |
1111 | f | 15 |
ON
と OFF
の箱で出てきた、
OFF | ON | OFF | OFF | OFF | OFF | OFF | ON |
つまり
0 | 1 | 0 | 0 | 0 | 0 | 0 | 1 |
は、2進数で 01000001
だが、これを16進数で表すと4桁毎に変換すればよいので
0100
→ 4
0001
→ 1
となる。2進数 01000001
は、16進数で41
である。ちなみに41は文字コードでいえば、アルファベットの大文字の A
に該当する。つまり、Aという文字を計算機の内部に記憶させたいときは以下のようにデータが格納されることになる。
0 | 1 | 0 | 0 | 0 | 0 | 0 | 1 |
16進数を10進数に変換するには、各桁に16の倍数を掛けたものを足す。たとえば、16進数 56ce
を10進数に直してみよう。
4096倍 (16**3) | 256倍 (16**2) | 16倍 | 1倍 |
5 | 6 | c | e |
c
の桁は10進数で 12
を意味することと、e
の桁は10進数で 14
を意味することに注意して計算すると、以下のようになる。
4096*5 + 256*6 + 16*12 + 14 = 22222
10進数を16進数に変換するには、2つの方法がある。
元の10進数を割れなくなるまで16で割り、割ったときの余りを逆に拾って行けばよい。たとえば、10進数 64206
を16進数に直してみよう。
16) 64206 (余り) 16) 4012 …… 14 → e 16) 250 …… 12 → c 16) 15 …… 10 → a 16) 0 …… 15 → f ↑下から上に読む
割り算の余りを下から順に読んだ、face
が16進の値となる。
元の10進数を2進数に変換する(2で割り商を下に、剰余を右に書くのを、商が0になるまで繰り返し、剰余を下からの順に拾う)。
2) 64206 (余り) 2) 32103 …… 0 2) 16051 …… 1 2) 8025 …… 1 2) 4012 …… 1 2) 2006 …… 0 2) 1003 …… 0 2) 501 …… 1 2) 250 …… 1 2) 125 …… 0 2) 62 …… 1 2) 31 …… 0 2) 15 …… 1 2) 7 …… 1 2) 3 …… 1 2) 1 …… 1 2) 0 …… 1 ↑ 下から上に読む
余りを下から上に読むと、
1111101011001110
となるが、これを1の位(右側)から4桁毎に区切る。
← 右から4桁毎に区切る 1111,1010,1100,1110
区切った2進数を16進数に直していく。
1111,1010,1100,1110 ↑ ↑ ↑ ↑ f a c e (16進数)
Rubyでは、プログラム中に書く数値の先頭に付ける記号(接頭辞)でその値が何進数であるかを指定する。
接頭辞 | 意味 |
---|---|
なし | 10進数 |
0b | 2進数 |
0 | 8進数 |
0x | 16進数 |
たとえば、0b1010
は、2進数の 1010
(10進数でいえば 10
)を表すことになり、0x10f0
は、16進数の 10F0
(10進数でいえば 4336
)を表すことになる。
プログラム中に、0b1010
と書こうが 10
と書こうが、0xa
と書こうが、一度読み込まれれば計算機の内部ではどれも10進数でいうところの 10
として扱われ、どれも全く同じである。また、以下のプログラムで確認できるように、特に何も指定せず数値を出力するともとは2進数や16進数であったとしてもデフォルトでは10進数として表示される。
dec_bin_hex.rb
#!/usr/koeki/bin/ruby
# -*- coding: utf-8 -*-
puts 64206
puts 0b1111101011001110
puts 0xface
結果:
64206
64206
64206
数値を出力するときに何進法にするかは printf
の%
つきフォーマットで選択することができる。以下は、printf
の%フォーマットで数値を何進法で出すかの意味を示す。
%フォーマット | 何進法で出力されるか |
---|---|
%b | 2進法 |
%o | 8進法 |
%d | 10進法 |
%x | 16進法 |
これを利用すると、16進数を10進数に直したり、その逆をしたりすることをRubyプログラムに任せることができる。
練習問題convert_nums.rb
#!/usr/koeki/bin/ruby
# -*- coding: utf-8 -*-
printf("16進の%xは10進で%dです\n", 0x555, 0x555)
printf("10進の%dは2進で%bです\n", 1365, 1365)
printf("10進の%dは16進で%xです\n", 0b10101010101, 0b10101010101)
結果:
16進の555は10進で1365です
10進の1365は2進で10101010101です
10進の1365は16進で555です
また、gets
によってキーボードやファイルから10進数以外の数値を読み込みする場合は、読み込みされた文字列を数値に変換するto_i
メソッドの引数として、何進数であるかを指定する。
例:
練習問題% irb
irb(main):001:0> gets.to_i(2) # 2進数の数値を入力
1101
=> 13
irb(main):001:0> gets.to_i(16) # 16進数の数値を入力
f
=> 15
解説:=>
の後ろに表示される結果は入力された数値を10進数に変換した結果である。
コンピュータ内では全てのデータが 1
と 0
の組み合わせとして保管されていると説明したが、文字についても同じである。計算機で文字を扱う場合、内部では文字に付けた番号を元に管理している。計算機で利用できる文字には全て番号がついている。この番号のことを文字コードという。
abcdefg... ABCD..., 0123..., !@#$ ()
などの、日本語入力OFFのまま入力できる文字や記号には全てASCIIコード(American Standard Code for Information Interchange)が割り当てられている。
ASCIIコード表を以下に示す。
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | A | B | C | D | E | F | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | NUL | SOH | STX | ETX | EOT | ENQ | ACK | BEL | BS | HT | LF | VT | FF | CR | SO | SI |
1 | DLE | DC1 | DC2 | DC3 | DC4 | NAK | SYN | ETB | CAN | EM | SUB | ESC | FS | GS | RS | US |
2 | ! | " | # | $ | % | & | ' | ( | ) | * | + | , | - | . | / | |
3 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | : | ; | < | = | > | ? |
4 | @ | A | B | C | D | E | F | G | H | I | J | K | L | M | N | O |
5 | P | Q | R | S | T | U | V | W | X | Y | Z | [ | \ | ] | ^ | _ |
6 | ` | a | b | c | d | e | f | g | h | i | j | k | l | m | n | o |
7 | p | q | r | s | t | u | v | w | x | y | z | { | | | } | ~ | DEL |
この表は16進数での文字コード表記となっている。最左列の縦に並ぶ0-7が16進数の10の位で、最上行の横に並ぶ0-Fが16進数の1の位である。たとえば、アルファベットの大文字 A
は、「4の行」の「1の列」にあるので、16進数の「41」が文字コードとなる。以下、16進数の数字を表すときは、先頭に 0x
を付けて表す。「16進数41」==「0x41
」
表中、文字コード 0x00 - 0x1F
は、制御文字といい主に端末を制御するための文字コードが並んでいる。代表的なものとしては、
0x00 NUL
- 空文字 (文字列の終端を表すことが多い)
0x07 BEL
- 端末のベルを鳴らす
0x08 BS
- バックスペース (BackSpace)
0x09 HT
- 水平タブ (Horizontal Tab)
0x0A LF
- 改行 (Line Feed)
0x0D CR
- 復帰 (Carriage Return; リターン)
0x1B ESC
- エスケープ (Escape)
などがある。これらの制御文字をキーボードから入力するときは、制御文字の文字コードに 0x40
を足した文字を、Ctrlキーを押しながらタイプする。つまり、0x09
の HT
は、0x40
を足した 0x49
が I
なので、C-i が対応するキーとなる。また、慣習的に 0x00
の NUL
は C-SPC (Ctrlを押しながらスペース)に対応させることが多い。まとめると
0x00 NUL
0x01 SOH
- 0x1A SUB
0x1B ESC
- 0x1E RS
0x1F US
という風にキー入力できる。ただし、現在ではこれらの制御文字を直接画面に出すことはほとんどないので、C-英字 などのキー入力にはカーソル移動したり、画面を消したりするなどの機能が割り当てられていることが多い。
実際に、これらの制御文字そのものを入力したいときには
C-vを押してから制御文字
C-qを押してから制御文字
をタイプする。ためしに、BEL
コードを使って、端末(KTerm)のベルを鳴らしてみよう。KTermで次のようにタイプする。
% echo "^G"
(^Gの部分は C-v C-g とタイプする)
KTermで順に e c h o スペース " C-v C-g " とタイプすると、画面に echo "^G"
と出ているはずなので、それを確認したら最後に [Return] を押す。
ピッ
と音が出るはずである。ここで利用した echo
というコマンドは、後続する引数をそのまま出力するものである(シェルの内部コマンド)。
また、0x1B
の ESC
コードも端末制御をするのによく用いられる。ESC
コードに続いて一連の決まった文字を端末に出力すると、端末の文字の種類を変えたりすることができる。有名なものに、文字色を変えるエスケープシーケンスがある。
ESC [ 30m
→
文字色を黒に
ESC [ 31m
→
文字色を赤に
ESC [ 32m
→
文字色を緑に
ESC [ 33m
→
文字色を黄色に
ESC [ 34m
→
文字色を青に
ESC [ 35m
→
文字色をマジェンタに
ESC [ 36m
→
文字色をシアンに
ESC [ 37m
→
文字色を白に
ESC [ m
→
文字の属性を元に戻す
ためしに、画面に黄色の文字を出してみよう。ESC
コードはシェル上とRubyプログラムでは \e
で表せる。
C-v ESC とタイプしよう。
% echo "Hello \e[35mworld"
Hello world
のように色の変わったメッセージが表れる。
他のプログラミング言語と比較すると、Rubyでは文字コードのことを意識しなくてもプログラミングできる範囲が広くなっている。上記のように画面に出すメッセージの色を変えたいようなときは文字コードを意識する必要がある。
ある文字コードに該当する文字を画面に出力したい場合は、printf
のフォーマット文字列のところに %c
を指定する。たとえば、
printf("%c", 0x07)
とすると、文字コード 0x07
つまり BEL
が出力され、端末のベルが鳴る。やってみよう。
% irb irb(main):001:0> printf("%c", 0x07)
文字コードを実際の文字に変換するのが %c
であるならばその逆は何だろう。ある文字の文字コードを得るにはStringクラスに備わっているord
メソッドを使う。プログラム中に "a".ord
と書けば、それは 文字 'a' の文字コード(つまり0x61
)と書いたのと同じことになる。"9".ord
と書けば '9' の文字コード(つまり0x39
)と書いたのと同じことになる。次のプログラム ascii.rb
は、ASCIIコード表のうち、'!'
(0x21
) - 'z'
(0x7A
) の範囲の文字全てを出力するものである。
ascii.rb
#!/usr/koeki/bin/ruby
# -*- coding: utf-8 -*-
char = "!".ord # ! の文字コード(==0x21)
while char <= "Z".ord
printf("ascii-code %3d(0x%02x): %c\n", char, char, char)
char += 1
end
Rubyプログラム中の文字列は、あたかも文字コードの数値が並んで入っている配列のように考えることができる。たとえば、"Hello"
という文字列を考えよう。各文字の文字コードを調べると、H
は 0x48
、e
は 0x65
、l
は 0x6c
、o
は 0x6f
となる。これらの数値が並んで配列に入ったものをイメージしよう。
0 | 1 | 2 | 3 | 4 |
---|---|---|---|---|
H |
e |
l |
l |
o |
0x48 | 0x65 | 0x6c | 0x6c | 0x6f |
1行目に示されている0-4の数字はそれぞれの文字のインデックスである(配列と同じように0からはじまる)。文字列 "Hello"
の特定の文字を取り出すときは、配列のときと同様に大括弧 [ ]
を利用してその中には文字のインデックスを入れる。
% irb irb(main):001:0> printf("%c\n", "Hello"[0]) H => nil irb(main):002:0> printf("%x\n", "Hello"[0].ord) # .ordにより16進数の文字コードが得られる 48 => nil irb(main):002:0> printf("%x\n", "Hello"[1].ord) 65 => nil
これを文字列の先頭から、末尾まで繰り返すプログラムhellocode.rb
は以下のようになる。
hellocode.rb
#!/usr/koeki/bin/ruby
# -*- coding: utf-8 -*-
hello = "Hello"
i = 0
while i < hello.length
printf("%c → 0x%x\n", hello[i], hello[i].ord)
i += 1
end
結果:
H → 0x48
e → 0x65
l → 0x6c
l → 0x6c
o → 0x6f
計算機でASCII文字以外を含む文字列(たとえば日本語文字列)を表現する方法は複数ある。その方式のことをエンコーディングという。
日本語の処理をする場合,以下のエンコーディングを使い分ける必要がある。
utf-8
)Unicodeという国際的な文字コードの規格で定められたエンコーディングで、多国語を同時表現できる。
iso-2022-jp
)主に電子メールで用いられる。
euc-jp
)日本語に特化したプログラム処理を作成する場合に主に用いられる。
shift_jis
)Windows 系で用いられる。
同じ文字であっても、使用したエンコーディングによってその文字を表す数値が異なる。
文字 | utf-8 | iso-2022-jp | euc-jp | shift_jis |
---|---|---|---|---|
あ |
E38182 |
2422 |
A4A2 |
82A0 |
また逆に、エンコーディングによって同じ数値がまったく違う文字として表示される。
数値 | euc-jp | shift_jis |
---|---|---|
E0D1 |
猾 |
獏 |
この授業では、プログラムの最初に
# -*- coding: utf-8 -*-
というコメントを記述することによってプログラムのエンコーディングを指定している。このコメントはマジックコメントという。
open
によってRubyプログラムで読み込む(ASCII文字以外の)データが utf-8 以外のエンコーディングになっている場合、そのまま開こうとすると文字化けしてしまう。euc-jpエンコーディングで作成されたファイル eucjp_data.txt
を使って確かめよう。
read_eucjp_data.rb
#!/usr/koeki/bin/ruby
# -*- coding: utf-8 -*-
open("eucjp_data.txt", "r") do |f|
while line = f.gets
puts line
end
end
結果:
ASCII characters display normally
���������ܸ�ϡ������ǥ���
���������ꤷ�ʤ��ȡ�ʸ�������ˤʤ�
このようなデータを正しく処理するためには、以下のようにエンコーディングを指定してオープンする必要がある。
open(ファイル, "モード:エンコーディング")
なお、ターミナルのエンコーディング設定も utf-8 になっているので、出力する時それに変換しないとやはり文字化けしてしまう。文字列のエンコーディング変換にはencodeメソッドを用いる。
文字列.encode("utf-8")
| 元の文字列をUTF-8コードに変換した文字列を返す |
文字列.encode("iso-2022-jp")
| 元の文字列をJISコードに変換した文字列を返す |
文字列.encode("euc-jp")
| 元の文字列を日本語EUCコードに変換した文字列を返す |
文字列.encode("shift_jis")
| 元の文字列をシフトJISコードに変換した文字列を返す |
上記のプログラムを修正すると以下のようになる。
練習問題read_eucjp_data2.rb
#!/usr/koeki/bin/ruby
# -*- coding: utf-8 -*-
open("eucjp_data.txt", "r:euc-jp") do |f|
while line = f.gets
puts line.encode("utf-8")
end
end
結果:
ASCII characters display normally
しかし日本語は、エンコーディングを
正しく設定しないと、文字化けになる
また、Rubyプログラムで読み込むデータに対して、ASCII文字以外の文字を含む正規表現を使用したい場合は、正規表現マッチングの対象になる文字列とプログラムのエンコーディングを合わせる必要がある(そうしないとエラーになる)。
練習問題read_eucjp_data3.rb
#!/usr/koeki/bin/ruby
# -*- coding: utf-8 -*-
open("eucjp_data.txt", "r:euc-jp") do |f|
while line = f.gets # line のエンコーディングは euc-jp になっている
line_utf8 = line.encode("utf-8")
if /し/ =~ line_utf8
puts line_utf8
end
end
end
結果:
しかし日本語は、エンコーディングを
正しく設定しないと、文字化けになる
プログラムのエンコーディング(utf-8)以外のエンコーディングを使用してファイルに書き込みをしたい場合も同じように指定できる。
練習問題write_eucjp_data.rb
#!/usr/koeki/bin/ruby
# -*- coding: utf-8 -*-
open("eucjp_data.txt", "w:euc-jp") do |f|
f.puts("日本語の文字列です")
end
ユーザーが入力した数値の進数変換を行うプログラムnum_converter.rb
を作成せよ。
プログラム動作のながれ:
1)ユーザーが、入力数値の進法を指定する(選択肢:2進数, 10進数, 16進数)
2)ユーザーが、指定された進法の数値を入力する
3)ユーザーが、出力の進法を指定する(選択肢:2進数, 10進数, 16進数)
4)指定された進法で数値が出力される。
実行結果の例:
sime{c11xxxx}% ruby num_converter.rb
何進数の数値を入力しますか(1:2進数、2:10進数、3:16進数)
2
指定された進法で好きな数値を入力してください
1365
何進数に変換しますか(1:2進数、2:10進数、3:16進数)
3
16進数に変換すると 555 です。
本文は下記の通り記入してください.
氏名: 苗字名前
学籍番号: C11xxxxx
ソースコード:
...
実行結果:
...
発展課題
printf
を使うのではなく上記で説明した変換の手順(例えば2進数の各桁に決まった倍率を掛けて総和をとることで10進数を得るという方法や10進数を16で割り続けることで16進数を得るという方法)を行うプログラム num_converter2.rb
を実装してみよう。