こんにちは、ナナです。
本記事では、進数の違いによる数の表現方法について学びましょう。
皆さんが普段の生活の中で利用する数は「10進数」という表現方法です。
しかし、プログラミングの世界では、10進数以外の「2進数」や「16進数」といった表現方法を利用することも多いのです。
進数とはいったい何なのか?その仕組みと考え方を学んでいきましょう。
2進数や16進数を意識した「ビット演算」に関して知りたい方は、『C言語 ビット演算を扱うための本当の視点と実践的な使用例を図解』の記事を参照してください。
本記事では次の疑問点を解消する内容となっています。
では、「2進数」「16進数」の扱い方を学んでいきましょう。
進数とは何か?「2進数」「16進数」の考え方
押忍!プログラミングをする時に変わった数値を見かけたっす。「0x3A」みたいなやつっす。
こんなの人生で初めて見たっすよ。これは一体なんなんっすか?
それは、C言語において数を「16進数」という表現方法で表したものだね。
プログラミングの世界ではよく使われる表現なんだよ。じゃあ、「2進数」と「16進数」ってものを学んでいこうね。
普段の皆さんは「10進数」という数の表現に慣れすぎているため、改めて「10進数ってなんなんだっけ?」と考える機会はおそらくないでしょう。
ここで新たに「2進数」と「16進数」という進数が出てきたことで、
「10進数とはそもそも何なのか?」
を再度考え直してみましょう。
それを知ることで「2進数」と「16進数」というものが、何なのかを知ることができるのです。
進数の基本ルール:数を表現するための記号とは
「進数」とは、数値を表す時の記号の数を示します。
「10進数」というものを改めて観察すると、「0」から「9」までの全10個の記号から構成される数の表現方法であること気づきますね。
このように「進数」というのは、数を表す際の記号の個数のことなのです。
「2進数」の場合は2つの記号、「16進数」の場合は16個の記号で数を表現する、ということです。これが進数の考え方です。
進数における桁上がりの概念
10進数では桁上がりという仕組みがあります。「9」という最大の数を表す記号に+1をすることで「桁上がり」という現象が発生します。
この桁上がりの仕組みは「2進数」、「16進数」でも同様のルールが適用されます。
「2進数」「10進数」「16進数」の一覧表
「2進数」と「16進数」の表記に慣れていない方もいることでしょう。「10進数」との表現の違いは、次のものになります。
2進数 | 10進数 | 16進数 |
---|---|---|
0000 b | 0 | 0x0 |
0001 b | 1 | 0x1 |
0010 b | 2 | 0x2 |
0011 b | 3 | 0x3 |
0100 b | 4 | 0x4 |
0101 b | 5 | 0x5 |
0110 b | 6 | 0x6 |
0111 b | 7 | 0x7 |
1000 b | 8 | 0x8 |
1001 b | 9 | 0x9 |
1010 b | 10 | 0xA |
1011 b | 11 | 0xB |
1100 b | 12 | 0xC |
1101 b | 13 | 0xD |
1110 b | 14 | 0xE |
1111 b | 15 | 0xF |
10000 b | 16 | 0x10 |
このように2進数では「0」「1」という2つの記号で数を表現します。16進数の場合は「A」~「F」の英字を使って16個の数を表現するのです。
よく見ると桁上がりの仕組みが「2進数」でも「16進数」でも起きているのがわかりますね。
最初は戸惑うかもしれませんが、使っていくことで少しづつ慣れていくことです。ルールを覚えてしまえば、それほど難しくはないんですよ!
「2進数」「10進数」「16進数」の表記を区別する方法
ソフトウェア開発を行う上で、設計書やプログラムの中には様々な数が飛び交います。
これらの数を表現する際には、進数の見分けがつくように数を記載しなければなりません。
このように「10」という数があったときに、進数の違いによって意味が変わってしまいます。
何かルールを決めておかないと、読み手には正しい数値が伝わりませんね。進数の違いを表すルールは次のものになります。
このように「2進数」では「b」、「16進数」では「0x」という特別な文字を付与することで、「10進数」との違いを表現します。
0xはゼロエックスです。この表現方法は設計書などのドキュメントでも適切に使いましょう。
補足しておきますが、C言語のプログラムでは「10進数」と「16進数」による数値の記載は認められていますが、「2進数」は基本的には記載できないことは知っておきましょう。
int num = 10b; // 2進数による代入はビルドエラー
2進数では「B’10」、16進数では「H’10」といった「B」と「H」を使った表現方法もあるから覚えておきましょう。
組み込み開発で利用するメーカーのマニュアルは、この表記が結構使われます。
進数の違いは表現方法の違い
「数」というものを表現する際に、進数の違いにより表現される文字が変わります。ただし、数としての重みが変わるわけではありません。
勘違いしないように注意しましょう。
これらの数値は全て同じ数を表現しています。
次のプログラムで、num変数への代入は全く同じ数を代入していることになります。
#include <stdio.h>
int main(void)
{
long num;
// 10進数による代入
num = 10;
// 16進数による代入
num = 0x0A;
return 0;
}
このプログラムは、すごく大事なことを示しています。「表現は違うが、値としては同じ」この意味をしっかりと理解しましょう。
「表現方法は違うが、値は同じ」。この考え方を理解できれば、進数を正しく理解したと言えるでしょう!
これを奇妙に感じるかもしれませんが、普通のことなんです。数字の「1」と漢数字の「一」は表現方法が違いますが、数の重みは同じですよね。
「2進数」と「10進数」「16進数」を相互に変換する方法
押忍っ!進数はとある数の表現方法の違いってことなんすね。ってことは、「16進数」の数を「2進数」の数でも表現できるってことっすよね?
でも、そんなことどうやったらできるんすか?
各進数同士は数は、相互に変換できるんだよ。
これはやり方のコツを身に付ければできるよ。ややこしいやつもあるから、暗算が苦手な子は大変かもね。
「2進数」から「10進数」への変換方法
2進数の数は、人の目には「0」と「1」の羅列でしかなく、管理がしづらいデータですね。
しかし、「この2進数のデータは、10進数ではいくつだっけ?」といったシーンは開発でよく出てきます。
2進数と10進数はあまり相性がよくないため、暗算で計算するにはコツが必要です。
2進数から10進数への変換は、次のように考えます。
2進数の各桁には、10進数としての重みの数があります。
そして、2進数で「1」になっている部分の「10進数の重み」を全て足すと、10進数の数に変換ができます。
「10進数」から「2進数」への変換方法
逆に10進数の「50」を2進数に変換するには、上から順に重みを振り分けていくイメージです。
振り分けた結果、10進数の数は最後に「0」になります。これで2進数の並びの出来上がりですね。
「2進数」と「16進数」を相互に変換する方法
2進数と16進数は非常に仲の良い進数のため、変換が簡単に行えます。
例として2バイト(16ビット)のデータが2進数で並んでいたとします。16進数に変換するためには次のように考えます。
特徴的なのは「2進数の4桁が、16進数の1桁」に対応することです。
変換は、2進数の4桁単位で16進数の数に置き換えていきます。
置き換えた16進数を横並びに結合すれば、2バイトの「0x5D61」のできあがりです。
16進数から2進数への変換は逆順に行えばできます。この法則は1バイトでも4バイトでも同じように変換ができます。
「2進数」と「16進数」の変換はよく出てきます。4桁単位で独立して考えればよいので、比較的簡単に相互変換ができますね。
これこそが「2進数」と「16進数」の相性がいいってことなんです。
「2進数」「10進数」「16進数」のメリット・デメリット
3種類も進数が出てきて混乱してるっす。なんでこんなに種類があるんすか?自分は一途に1人だけを好きになりたいっすよ。
それはね、それぞれに良いところも悪いところもあって、どれが1番っていうものじゃないんだね。
この子のこの部分が好きだけど、こっちの子は別の部分が好きなんだよね、なんてことあるでしょ。それと同じだね!
プログラムを作る上で、「2進数」「10進数」「16進数」の3つの進数表現はどれも使いこなせる必要があります。
でも、プログラミングにおいて、進数はなぜ3種類も必要なのでしょうか?
「2進数」と「16進数」を扱う必要性
皆さんはすでに、使い慣れた10進数を習得していますよね。
なぜ、新たに「2進数」や「16進数」なんてものを知る必要があるのか疑問に思うかもしれません。
それは、各進数によってメリット/デメリットの個性があるからです。
メリット
デメリット
メリット
デメリット
メリット
デメリット
大事なのは使い分けです。それぞれが得意とするシーンにおいて適切に表現する進数を皆さんが選べば、数値を扱いやすくできるんです。
「16進数」はメモリの値の表記に利用される
皆さんがここまで定義してきた変数は、メモリというハードウェアに値を記憶しています。
実は、「16進数」はメモリの数値を表現する上でよく利用されます。「16進数」はメモリを表現する上で非常に都合の良い特性を持っているのが理由です。
次のように複数のメモリを使用するshort型データが存在するとします。
short型やlong型のような複数バイトのメモリで構成される数は、どのようにメモリの中で管理しているのでしょうか。
#include <stdio.h>
int main(void)
{
// 2Byteのshort型変数
short num;
// メモリにどのように値を覚える?
num = 18921;
return 0;
}
メモリは1バイトという単位で情報を覚えます。
つまり、2バイトの大きさであるshort型の数値であっても、最終的には1バイト単位で管理しなければなりません。
それでは「18921」という数値は、いったいどのように1バイト単位の情報に分割して管理するのでしょう。皆さん、わかりますか?
この問題は「2進数」と「16進数」で考えてみるとシンプルな答えになります。
2バイトのメモリとは、16ビットで構成されています。
「18921」という10進数の数値は、2進数で表現すると次のものになり、8ビット毎に分割すればメモリで管理する2バイトの情報になります。
つまり、10進数の「18921」は、16進数では「0x49E9」で表現されます。
このデータをメモリに記憶したければ「0x49」と「0xE9」という2桁単位で分割すればメモリに格納する1バイト単位のデータに早変わりするのです。
10進数の表現方法では、このような考え方ではできません。
メモリに記憶する数字は「16進数」で捉えると非常に都合の良い表現方法なのです。
Q&A:進数に関するよくある質問
2進数や16進数に関する質問なんでもいいよ?
Q:2進数の「b」、16進数の「0x」という記号の意味はなんなの?
押忍!2進数や16進数の数字につける「b」や「0x」ってなんなんすか?適当なものなら「osu48」みたいに「osu」を採用してほしかったっす。
「osu」は君しか使わないからね。こういうのはね、やっぱり英語の頭文字からルールが決められるんだね。
2進数の「b」は「binary」の略で2進法を意味しています。
16進数は「hexadecimal」という英語が16進法を意味しており、数値を表す0とhexadecimalのxを組み合わせた表現です。
Q:16進数で数値を表現する際に0x0005といった上位桁に0を指定するけど、書かないと間違いなの?
16進数の数を扱うときに数字の頭に「0」を付けてるっすけど、これは書かないと数字の意味が変わるんすか?気になるっす。
short num;
num = 0x5; // 0を付けない
num = 0x0005; // 0を付けた
「0」を付けても付けなくても数値の扱いは一緒だよ。だからどっちも正解だよ。
間違いではありません。「0x0005」と「0x5」は全く同じ数値として処理が行われます。
これは慣習なのですが、16進数での数値表現は1バイトのメモリ表現と直結しているため、表現したい数値のメモリサイズに合わせて上位桁を0埋めすることがあります。
つまり、short型は2バイトの情報のため「0x0005」として表現することがありますね。
Q:暗算で2進数、10進数、16進数を変換していると間違えてしまうことがある?どうしたらよいのか?
自分、数の計算が少し苦手っす。「2進数」「10進数」「16進数」の変換を頭の中でやっても、いっつも間違うっす。悲しいっす、自分ダメなヤツっす。
暗算はできたらいいけど必須ではないね。間違えやすい自覚があるのであれば、値があっているかを検証すればいいだけだよ。
検証するための便利なツールを紹介するね。
進数の変換は暗算で計算すると、時々間違えてしまうことはよくやってしまうことです。
不安な時は「電卓アプリ」を使って検証するフェーズを入れてみるのもよいでしょう。
Windowsの場合は、標準で電卓アプリが入っています。プログラミングをしたことがない方は馴染みがないかもしれませんが、実はこの電卓アプリには「プログラマー電卓」という機能がついているんです。
メニューの [プログラマー] を選択することで2進数や16進数をサポートする電卓に変わります。すごく便利なので使い方を覚えておくとよいでしょう。
僕も変換をいつも間違えちゃうから、電卓すぐ使っちゃいます。各進数の数字が出ててわかりやすいですよね。
電卓アプリに、「こんな機能があることを知らないかった!」って人は結構います。実際使うとすごく便利なんですよ。
課題:2進数と16進数が学べたかを確認しよう
課題1
課題内容
2進数における「1001 0101 b」が10進数と16進数でどのように表現されるのかを手計算で算出せよ。
10進数 ⇒ 149
16進数 ⇒ 0x95
本記事の手順に従って変換すれば解けますね。電卓アプリの使い方も慣れておくとよいですね。
課題2
課題内容
16進数における「0x7B8A」が2進数でどのように表現されるのかを手計算で算出せよ。
2進数 ⇒ 0111 1011 1000 1010 b
16進数の1桁を2進数の4桁に変換すればよかったですね。落ち着いて変換すれば解けますよ。
課題3
課題内容
次の数値をprintf関数を利用して、出力期待値に従い画面に出力せよ。
10進数の「198」を使って、10進数と16進数で表示せよ。
16進数の「0x9A」を使って、10進数と16進数で表示せよ。
ヒント:printf関数の表記方法は次の記事に記載されています。
出力期待結果
10進数:198 16進数:0xC6
10進数:154 16進数:0x9A
main.c
#include <stdio.h>
int main(void)
{
// 10進数の「198」を表示
printf("10進数:%d 16進数:0x%X\n", 198, 198);
// 16進数の「0x9A」を表示
printf("10進数:%d 16進数:0x%X\n", 0x9A, 0x9A);
return 0;
}
printf関数は%dを利用すると10進数表記、%xを利用すると16進数で数値を画面に表示してくれます。16進数の英語表記を大文字にしたい時は%Xとして「X」を大文字に指定することでできますよ。