このエントリーをはてなブックマークに追加

 

C言語 C++ 2進数 16進数 入門
Copyright (C) 2014 by Nobuhide Tsuda

 

※ イラスト提供:かわいいフリー素材集「いらすとや」様

2進数 16進数 とは

我々が日常生活で使用するのは10進数である。
なぜ10進数なのかというと、手の指が合計10本あるからではないかと言われている。 もし手の指が4本だったなら、8進数が使われていて、読者が2進数・16進数に混乱することももっと少なかったかもしれない。

コンピュータ内部では、通常2進数が使用される。何故かというと、10進数よりも2進数の方がコンピュータにとって扱いやすいからである。 なので、気がつかないかもしれないが、入出力の時に10進数←→2進数の変換が陰でこっそり行われているのだ。

コンピュータは 0 または 1 のデータを取り扱う。これを「ビット(bit)」と呼ぶ。
ひとつのビットだけでは、表現できる状態が2つしかない。

各要素の値が 0 または 1 の配列で2進数を表すことが出来る。例えば、2進数の 1010 は以下のように表現出来る。

    int b[] = {0, 1, 0, 1};     // 2進数の 1010 を表す配列、順序が逆になっていることに注意

順序が逆になっているのは、計算を楽にするためだ。N進数のi番目の桁の値は N^i の重みを持つ(10進数であれば、i番目の桁は 10^i の重みでしょ)ので、 b[] の値は Σ(b[i] * 2^i) = b[0] + b[1] * 2 + b[2] * 4 + b[3] * 8 + ... となる。

2進数の一桁では状態数が少なすぎるし、ひとつの値を表すのに桁数が多くなるので、通常は8個のビットをひとまとまりとして取り扱う。
これを「バイト(byte)」と呼ぶ。

1バイトで表現可能な範囲は、2進数で書けば 00000000 から 11111111 まで。
10進数で書けば 0 から 255 までとなる。

2進数で表記すると桁が多くなりすぎるので、2進数の4桁をひとまとまりにして、通常は16進数を用いる。
0から9までは通常の数を用い、10~15は 'A', 'B', 'C', 'D', 'E', 'F' を用いる。
小文字('a', 'b', 'c', 'd', 'e', 'f')で表記してもよい。

16進数1桁は4ビットに相当する。

 10進数  2進数  16進数 
000000
100011
200102
300113
401004
501015
601106
701117
810008
910019
101010A
111011B
121100C
131101D
141110E
151111F

Nビットの数値を16進数に変換すると、(Nが4の倍数であれば、)N/4桁の16進数で表記できる。

演習問題

  1. 10進数の100を、2進数、16進数に変換してみなさい
  2.     100 = 0b01100100 = 0x64
    
  3. 2進数の 01011010 を 10進数、16進数に変換してみなさい
  4.     0b01011010 = 0x5a = 16*5 + 10 = 90
    
  5. 32ビットの数値を16進数に変換すると何桁になるか?
  6.     32/4 = 8桁
    
  7. 4桁の16進数を2進数に変換すると何桁になるか?
  8.     4*4 = 16桁
    

C言語、C++ での 2進数、16進数

C言語、C++ による2進数表記

Visual Studio 2013 までは、C/C++で2進数をソースコードを記述することはできなかったが、VS2015 で C++14 対応が進み、 0b プレフィックスで2進数表記が可能になった。
clang は 3.2 から、gcc は 4.9 から対応(予定?)のようだ。

    //  C++14 で導入、VS2015以上で記述可能:
    int a = 0b1010;         //  2進数の 1010、すなわち10進数の 10 を a に代入

では、古いコンパイラの C/C++ で、ソースコードに2進数を書きたい場合はどうしたいいのか?

それには、諦めて16進数で記述するか、または、2進数文字列→数値変換関数をコールするという方法がある。

    int a = 0xa;       // 0xa = 0b1010
    int b = binToUInt("1010");

※ binToUInt() 関数の実装方法は後述する

Boost やテンプレート機能を駆使すると、C++11 でも、2進数リテラルを記述することは可能のようだ (参照:C++ で 2 進数を書く)。 が、初級者にはちょっとむずかしいかもしれないので、素直に VS2015等の C++14 対応コンパイラを使うのがいいだろう。

C言語、C++ による16進数表記

C言語、C++ において、ソースコード中に直接16進数を使用したい場合は 0x または 0X に続けて16進数を記述する。
10以上の値は 'a'~'f' または 'A'~'F' で記述する。

    int a = 0x100;       //  a に 16進数の 100, すなわち 10進数の 256 を代入
    int b = 0xab;         // b に 16進数の ab, すなわち 10進数の 171 を代入

C言語、C++ による2進数表示

おそらく、C++14 で 2進数表示がサポートされると思われるが、C++11 以下ではサポートされていない。
なので、数値を2進数文字列に変換し、表示しなくてはいけない。

後述する to_binString() を使って、以下のようにするとよい。

    std::cout << to_binString(100) << "\n";      // 10進数の100を2進数に変換し出力

C言語、C++ による16進数表示

  • C/C++ の printf文で変数の値を10進数表示する場合は %d を使用する。
  •     int x = 100;
        printf("x = %d\n", x);        //    10進数表記
    

    実行結果:

    x = 100
    
  • C/C++ の printf文で変数の値を16進数表示する場合は %x を使用する。
  •     int x = 100;
        printf("x = 0x%x\n", x);        //    16進数表記
    

    実行結果:

    x = 0x64
    
  • 場合によっては、16進数を固定桁数にし、先頭に0を付けたい場合がある。
    そんな場合は %0桁数x を使うといいぞ。
  •     int x = 100;
        printf("x = 0x%04x\n", x);        //    16進数4桁表記
    

    実行結果:

    x = 0x0064
    
  • C++ の iostream の cout を使って変数の値を10進数表示する場合は << を使用する。
  • #include <iostream>     //    for std::cout
        .....
        int x = 100;
        std::cout << "x = " << x << "\n";        //    デフォルトは10進数表記
    

    実行結果:

    x = 100
    
  • C++ の iostream の cout を使って変数の値を16進数表示する場合は、<< std::hex を使用して、16進数モードに切り替える。
  • #include <iostream>     //    for std::cout
        .....
        int x = 100;
        std::cout << "x = 0x" << std::hex << x << "\n";        //    16進数表記に切り替え
    

    実行結果:

    x = 0x64
    

    ※ 一度16進数モードに切り替えると、その後も16進数表示される。
      10進数モードに切り替えるには << std::dec を記述する。

  • cout で桁数を指定するには <iomanip> をインクルードし、std::setw(wd) を使う。
    さらに、桁を埋める文字指定には std::setfill(c) を使う。
  • #include <iostream>     //    for std::cout
    #include <iomanip>      // for std::setw, std::setfill>
        ....
        int x = 100;
        std::cout << "x = 0x" << std::hex << std::setw(4)
                        << std::setfill('0') << x << "\n";
    

    実行結果:

    x = 0x0064
    
  • 正直言って、printf より cout が好きな筆者でもこの表記はめんどくさいと考える。
    下記のように、16進数出力のためのマクロを定義しておけばいいのではないだろうか?
  • #include <iostream>     //    for std::cout
    #include <iomanip>      // for std::setw, std::setfill>
    #define  hexformat(fill, wd)    std::hex<<std::setfill(fill)<<std::setw(wd)
        ....
        int x = 100;
        std::cout << "x = 0x" << hexformat('0', 4) << x << "\n";
    

    実行結果:

    x = 0x0064
    

C言語、C++ による整数・2進数表記変換

"1010" のような2進数文字列を整数値に変換する関数は以下のように定義できる。

// "1010" のような2進数文字列を整数値に変換
// '0' '1' 以外の文字は無視する
// オーバーフローチェックは行わない
unsigned int binToUInt(const char *ptr)
{
    unsigned int val = 0;
    while( *ptr != '\0' ) {
        switch( *ptr++ ) {
            case '0':
                val *= 2;
                break;
            case '1':
                val = val * 2 + 1;
                break;
        }
    }
    return val;
}

上記関数は、引数に文字列へのポインタをとるが、引数が const std::string & の場合は、以下のように定義出来る。

unsigned int binToUInt(const std::string &str)
{
    unsigned int val = 0;
    for(int i = 0; i < (int)str.size(); ++i) {
        switch (str[i]) {
        case '0':
            val *= 2;
            break;
        case '1':
            val = val * 2 + 1;
            break;
        }
    }
    return val;
}

以下の関数は、整数値を2進数文字列に変換する。

// 数値を2進数文字列に変換
std::string to_binString(unsigned int val)
{
    if( !val )
        return std::string("0");
    std::string str;
    while( val != 0 ) {
        if( (val & 1) == 0 )  // val は偶数か?
            str.insert(str.begin(), '0');  //  偶数の場合
        else
            str.insert(str.begin(), '1');  //  奇数の場合
        val >>= 1;
    }
    return str;
}

C言語、C++ による整数・16進数表記変換

"19ab" のような16進数文字列を整数値に変換する関数は以下のように定義できる。

// "ab12" のような16進数文字列を整数値に変換
// '0'~'9', 'A'~'F', 'a'~'f' 以外の文字は無視する
// オーバーフローチェックは行わない
unsigned int hexToUInt(const char *ptr)
{
    unsigned int val = 0;
    unsigned int v;
    char ch;
    while( (ch = *ptr++) != '\0' ) {
        if( ch >= '0' && ch <= '9' )
            v = ch - '0';
        else if( ch >= 'A' && ch <= 'F' )
            v = ch - 'A' + 10;
        else if( ch >= 'a' && ch <= 'f' )
            v = ch - 'a' + 10;
        else
            continue;    // 16進数文字ではない場合
        val = val * 16 + v;
    }
    return val;
}

上記関数は、引数に文字列へのポインタをとるが、引数が const std::string & の場合は、以下のように定義出来る。

unsigned int hexToUInt(const std::string &str)
{
    unsigned int val = 0;
    unsigned int v;
    for(int ix = 0; ix != str.size(); ++ix) {
        char ch = str[ix];
        if( ch >= '0' && ch <= '9' )
            v = ch - '0';
        else if( ch >= 'A' && ch <= 'F' )
            v = ch - 'A' + 10;
        else if( ch >= 'a' && ch <= 'f' )
            v = ch - 'a' + 10;
        else
            continue;    // 16進数文字ではない場合
        val = val * 16 + v;
    }
    return val;
}
以下の関数は、整数値を16進数文字列に変換する。
// 数値を16進数文字列に変換
std::string to_hexString(unsigned int val, bool lower = true)
{
    if( !val )
        return std::string("0");
    std::string str;
    const char hc = lower ? 'a' : 'A';     // 小文字 or 大文字表記
    while( val != 0 ) {
        int d = val & 15;     // 16進数一桁を取得
        if( d < 10 )
            str.insert(str.begin(), d + '0');  //  10未満の場合
        else //  10以上の場合
            str.insert(str.begin(), d - 10 + hc);
        val >>= 4;
    }
    return str;
}

演習問題

  1. int型変数 a に、2進数 0b00010010001101000101011001111000 を代入し、それを2進数表記で表示するプログラムを書きなさい。
  2. 	int a = binToUInt("00010010001101000101011001111000");
    	cout << "a = 0b" << to_binString(a) << "\n";
    
  3. int型変数 a に、2進数 0b00010010001101000101011001111000 を代入し、それを16進数表記で表示するプログラムを書きなさい。
  4. 	int a = binToUInt("00010010001101000101011001111000");
    	cout << "a = 0x" << std::hex << a << "\n";
    

参考