# 整数

始めに書いておくがこの章はユーモア欠落症患者によって書かれており極めて退屈だ。しかし、整数の詳細はすべてのプログラマーが理解すべきものだ。心して読むとよい。

# 整数リテラル

整数リテラルとは整数の値を直接ソースファイルに記述する機能だ。本書ではここまで何の説明もなくリテラルを使っていた。例えば以下のように。

int main()
{
    int a = 123 ;
    int b = 0 ;
    int c = -123 ;
}

ここでは、'123', '0'がリテラルだ。'-123'というのは演算子operator -に整数リテラル123を適用したものだ。リテラルは123だけだ。ただしこれは細かい詳細なのでいまはそれほど気にしなくてもよい。

# 10進数リテラル

10進数リテラルは最も簡単で我々が日常的に使っている数の表記方法と同じものだ。接頭語は何も使わず数字には0, 1, 2, 3, 4, 5, 6, 7, 8, 9が使える。

// 10進数で123
int decimal = 123 ;

ただし、10進数リテラルの先頭を0にしてはならない。これは8進数リテラルになってしまう。

// 10進数で83
int octal = 0123 ;

# 2進数リテラル

2進数リテラルは接頭語'0b', '0B'から始まる。数字には0, 1を使うことができる。

// 10進数で5
int binary = 0b1010 ;

// 0bと0Bは同じ
int a = 0B1010 ;

# 8進数リテラル

8進数リテラルは接頭語'0'から始まる。数字には0, 1, 2, 3, 4, 5, 6, 7を使うことができる。

// 10進数で83
int octal = 0123 ;

// 10進数で342391
int a = 01234567 ;

# 16進数リテラル

16進数リテラルは接頭語'0x', '0X'から始まる。数字には0, 1, 2, 3, 4, 5, 6, 7, 8, 9, a, b, c, d, e, f, A, B, C, D, E, Fが使える。ローマ字の大文字と小文字は意味が同じだ。a, b, c, d, e, fがそれぞれ10, 11, 12, 13, 14, 15を意味する。

// 10進数で291
int hexadecimal = 0x123 ;

// 0xと0Xは同じ
int a = 0X123 ;

// 10進数で10
int b = 0xa ;

// 10進数で15
int c = 0xf ;

# 数値区切り

長い整数リテラルは読みにくい。例えば10000000100000000はどちらが大きくて具体的にどのくらいの値なのかがわからない。C++には整数リテラルを読みやすいように区切ることのできる数値区切りという機能がある。整数リテラルはシングルクオート文字(')で区切ることができる。

int main()
{
    int a =   1000'0000 ;
    int b = 1'0000'0000 ;
}

区切り幅は何文字でもよい。

int main()
{
    int a = 1'22'333'4444'55555 ;
}

10進数整数リテラル以外でも使える。

int main()
{
    auto a = 0b10101010'11110000'00001111 ;
    auto b = 07'7'5 ;
    auto c = 0xde'ad'be'ef ;
}

# 整数の仕組み

# 情報の単位

0から100までの整数を表現するには101種類の状態を表現できる必要がある。コンピューターはどうやって整数を表現しているのかをここで学ぶ。

情報の最小単位はビット(bit)だ。ビットは2種類の状態を表現できる。たとえばbool型はtrue/falseという2種類の状態を表現できる。

しかし、2種類の状態しか表現できない整数は使いづらい。0もしくは1しか表現できない整数とか、100もしく1000しか表現できない整数は使い物にならない。

また、ビットという単位も扱いづらい。コンピューターは膨大な情報を扱うので、ビットをいくつかまとめたバイト(byte)を単位として情報を扱っている。1バイトが何ビットであるかは環境により異なる。本書では最も普及している1バイトは8ビットを前提にする。

1ビットは2種類の状態を表現できるので、1バイトの中の8ビットは$2^8 = 256$種類の状態を表現できる。2バイトならば16ビットとなり、$2^{16} = 65536$種類の状態を表現できる。

# 1バイトで表現された整数

整数の表現方法について理解するために、1バイトで表現された整数を考えよう。

1バイトは8ビットであり256種類の状態を表現できる。整数を0から正の方向の数だけ表現したいとすると、0から255までの値を表現できることになる。

その場合、1バイトの整数の中の8ビットはちょうど2進数8桁で表現できる。

// 0
auto zero = 0b00000000 ;
// 255
auto max  = 0b11111111 ;

一番左側の桁が最上位桁で、一番右側の桁が最下位桁だ。これを最上位ビット、最下位ビットともいう。

正数だけを表現するならば話は簡単だ。1バイトの整数は0から255までの値を表現できる。これを符号なし整数(unsigned integer)という。

では負数を表現するにはどうしたらいいだろう。正数と負数を両方扱える整数表現のことを、符号付き整数(signed integer)という。1バイトは256種類の状態しか表現できないので、もし$-1$を表現したい場合、$-1$から254までの値を扱えることになる。

$-1$しか扱えないのでは実用的ではないので、負数と正数を同じ種類ぐらい表現したい。256の半分は128だが、1バイトで表現された整数は$-128$から128までを表現することはできない。0があるからだ。0を含めると、1バイトの整数は最大で$-128$から127までか、$-127$から128までを表現できる。どちらかに偏ってしまう。

では実際に1バイトで負数も表現できる整数表現を考えてみよう。

# 符号ビット

誰でも思いつきそうな表現方法に、符号ビットがある。これは最上位ビットを符号の有無を管理するフラグとして用いることにより、下位7ビットの値の符号を指定する方法だ。

符号ビット表現では$-1$と1は以下のように表現できる。

// 1
0b0'0000001
// -1
0b1'0000001

最上位ビットが0であれば正数、1であれば負数だ。

この一見わかりやすい表現方法には問題がある。まず表現できる値の範囲は$-127$から$+127$だ。先ほど、1バイトで正負になるべく均等に値を割り振る場合、$-128$から$+127$、もしくは$-127$から$+128$までを扱えると書いた。しかし符号ビット表現では$-127$から$+127$しか扱えない。残りの1はどこにいったのか。

答えはゼロにある。符号ビット表現ではゼロに2通りの表現がある。$+0$と$-0$だ。

// +0
0b0'0000000
// -0
0b1'0000000

$+0$も$-0$もゼロには違いない。しかし符号ビットが独立して存在しているために、ゼロが2種類ある。

符号ビットは電子回路で実装するには複雑という問題もある。

# 1の補数

1の補数は負数を絶対値を2進数で表したときの各ビットを反転させた値で表現する。たとえば$-1$は1(0b00000001)の1の補数の0b11111110で表現される。

// -1
0b11111110

// -2
0b11111101

$-1$と$-2$を足すと結果は$-3$だ。この計算を1の補数で行うとどうなるか。

まず1の補数表現による$-1$と$-2$を足す。

   11111110
+) 11111101
-----------
 1'11111011

この結果は9ビットになる。この整数は8ビットなので、9ビット目を表現することはできない。ただし1の補数表現の計算では、もし9ビット目が繰り上がった場合は、演算結果に1を足す取り決めがある。

   11111011
+)        1
-----------
   11111100

1の補数による$-3$は3の各ビットを反転したものだ。3は0b00000011で、そのビットを反転させたものは0b11111100だ。上の計算結果は$-3$の1の補数表現になった。

もう1つ例を見てみよう。5と$-2$を足すと3になる。

   00000101
+) 11111101
-----------
 1'00000010

繰り上がりが発生したので1を足すと

   00000010
+)        1
-----------
   00000011

3になった。

1の補数は引き算も足し算で表現できるので電子回路での実装が符号ビットよりもやや簡単になる。

ただし、1の補数にも問題がある。0の表現だ。0というのは0b00000000だが1の補数では$-x$は$x$の各ビット反転ということを適用すると、$-0$は0b11111111になる。すると、符号ビット表現と同じく、$+0$と$-0$が存在することになる。したがって、1の補数8ビットで表現できる範囲は$-127$から$+127$になる。

# 2の補数

符号ビットと1の補数による負数表現にある問題は、2の補数表現で解決できる。

2の補数表現による負数は1の補数表現の負数に、繰り上がり時に足すべき1を加えた値になる。

$-1$は1の補数表現では、1(0b00000001)の各ビットを反転させた値になる(0b11111110)。2の補数表現では、1の補数表現に1を加えた値になるので、0b11111111になる。

同様に、$-2$は0b11111110に、$-3$は0b11111101になる。

2の補数表現の$-1$と$-2$を足すと以下のようになる。

   11111111
+) 11111110
-----------
 1'11111101

9ビット目の繰り上がりを無視すると、計算結果は0b11111101になる。これは2の補数表現による$-3$と同じだ。

5と$-2$の計算も見てみよう。

   00000101
+) 11111110
-----------
 1'00000011

結果は3(0b00000011)だ。

2の補数表現は引き算も足し算で実装できる上に、ゼロの表現方法は1つで、$+0$と$-0$が存在しない。8ビットの2の補数表現された整数の範囲は$-128$から$+127$になる。とても便利な負数の表現方法なのでほとんどのコンピューターで採用されている。

# 整数型

C++にはさまざまな整数型が存在する。C++はCから引き継いだ歴史的な経緯により、整数型の文法がわかりにくくなっている。

基本的には、符号付き整数型と符号なし整数型に分かれている。

符号付き整数型としては、signed char, short int, int, long int, long long intが存在する。符号付き整数型は負数を表現できる。

符号なし整数型としては、unsigned char, unsigned short int, unsigned int, unsigned long int, unsigned long long intが存在する。符号なし整数型は負数を表現できない。

# int型

int型は最も基本となる整数型だ。C++で数値を扱う場合、多くはint型になる。

int x = 123 ;

整数リテラルの型は通常はint型になる。

// int
auto x = 123 ;

unsigned int型は符号のないint型だ。

unsigned int x = 123 ;

整数リテラルの末尾にu/Uと書いた場合、unsigned int型になる。

// int
auto x = 123 ;
// unsigned int
auto y = 123u ;

特殊なルールとして、単にsignedと書いた場合、それはintになる。unsignedと書いた場合は、unsigned intになる。

// int
signed a = 1 ;
// unsigned int
unsigned b = 1 ;

signed intと書いた場合、int型になる。signed intintの冗長な書き方だ。

# long int型

long int型int型以上の範囲の整数を扱える型だ。具体的な整数型の値の範囲は実装依存だが、long int型int型の表現できる整数の範囲はすべて表現でき、かつint型以上の範囲の整数型を表現できるかもしれない型だ。

unsigned long int型は符号なしのlong intだ。

long int a = 123 ;
unsigned long int b = 123 ;

特殊なルールとして、単にlongと書いた場合、それはlong intになる。unsigned longと書いた場合、unsigned long intになる。

// long int
long a = 1 ;
// unsigned long int
unsigned long b = 1 ;

通常、intを省略して単にlongと書くことが多い。

整数リテラルの値がint型で表現できない場合、long型になる。例えば、int型で100億を表現できないが、long型では表現できる実装の場合、以下の変数along型になる。

// 100億
auto a = 100'0000'0000 ;

整数リテラルの値がlongでは表現できないがunsigned longでは表現できる場合、unsigned long型になる。

整数リテラルの末尾にl/Lと書いた場合、値にかかわらずlong型になる。

// int
auto a = 123 ;
// long
auto b = 123l ;
// long
auto c = 123L ;

符号なし整数型を意味するu/Uと組み合わせることもできる。

// unsigned long
auto a = 123ul ;
auto b = 123lu ;

順番と大文字小文字の組み合わせは自由だ。

# long long int型

long long int型long int型以上の範囲の整数を扱える型だ。longと同じくlong longlong long intと同じで、unsigned long long intもある。

// long long int
long long a = 1 ;
// unsigned long long int
unsigned long long b = 1 ;

整数リテラルの値がlong型でも表現できないときは、long longが使われる。long longでも表現できない場合はunsigned long longが使われる。

整数リテラルの末尾にll/LLと書くとlong long int型になる。

// long long int
auto a = 123ll ;
// long long int
auto b = 123LL ;
// unsigned long long int
auto c = 123ull ;

# short int型

short int型int型より小さい範囲の値を扱う整数型だ。long, long longと同様に、unsigned short int型もある。単にshortと書くと、short intと同じ意味になる。

整数リテラルでshort int型を表現する方法はない。

# char型

char型はやや特殊で、char, signed char, unsigned charの3種類の型がある。signed charcharは別物だ。char型は整数型であり、あとで説明するように文字型でもある。char型の符号の有無は実装ごとに異なる。

# 整数型のサイズ

整数型を含む変数のサイズは、sizeof演算子で確認することができる。sizeof(T)Tに型名や変数名を入れることで、サイズを取得することができる。

int main()
{
    std::cout << sizeof(int) << "\n"s ;

    int x{} ;
    std::cout << sizeof(x) ;
}

sizeof演算子std::size_t型を返す。vectorの章でも出てきたこの型は実装依存の符号なし型であると定義されている。単位はバイトだ。

以下が各種整数型のサイズを出力するプログラムだ。

int main()
{
    auto print = []( std::size_t s )
    { std::cout << s << "\n"s ; } ;

    print( sizeof(char) ) ;
    print( sizeof(short) ) ;
    print( sizeof(int) ) ;
    print( sizeof(long) ) ;
    print( sizeof(long long ) ) ;
}

このプログラムを筆者の環境で実行した結果が以下になる。

1
2
4
8
8

どうやら筆者の環境では、charが1バイト、shortが2バイト、intが4バイト、longlong longが8バイトのようだ。この結果は環境ごとに異なるので読者も自分でsizeof演算子をさまざまな型に適用して試してほしい。

# 整数型の表現できる値の範囲

整数型の表現できる値の最小値と最大値はstd::numeric_limits<T>で取得できる。最小値は::min()を、最大値は::max()で得られる。

int main()
{
    std::cout
        << std::numeric_limits<int>::min() << "\n"s
        << std::numeric_limits<int>::max() ;
}

実行結果

-2147483648
2147483647

どうやら筆者の環境ではint型は$−21億4748万3648$から21億4748万3647までの範囲の値を表現できるようだ。

unsigned intはどうだろうか。

int main()
{
    std::cout
        << std::numeric_limits<unsigned int>::min() << "\n"s
        << std::numeric_limits<unsigned int>::max() ;
}

実行結果

0
4294967295

どうやら筆者の環境ではunsigned int型は0から42億9496万7295までの範囲の値を表現できるようだ。sizeof(int)が4バイトであり、1バイトが8ビットの筆者の環境では自然な値だ。符号なしの4バイト整数型は0から$2^{32}-1$までの範囲の値を表現できる。符号付き4バイト整数型は$-2^{31}$から$2^{31}-1$までの範囲の値を表現できる。

整数の最小値を$-1$したり、最大値を$+1$した場合、何が起こるのだろうか。

符号なし整数型の場合は簡単だ。最小値$-1$は最大値になる。最大値$+1$は最小値になる。

int main()
{
    unsigned int min = std::numeric_limits<unsigned int>::min() ;
    unsigned int max = std::numeric_limits<unsigned int>::max() ;

    unsigned int min_minus_one = min - 1u ;
    unsigned int max_plus_one = max + 1u ;

    std::cout << min << "\n"s << max << "\n"s
        << min_minus_one << "\n"s << max_plus_one ;
}

8ビットの符号なし整数型があるとして、最小値は0b00000000(0)になるが、この値を$-1$すると0b11111111(255)となり、これは最大値になる。逆に、最大値である0b11111111(255)に$+1$すると0b00000000(0)となり、これは最小値になる。

これを数学的に厳密に書くと、「符号なし整数は算術モジュロ$2^n$の法に従う。ただし$n$は整数を表現する値のビット数である」となる。

符号付き整数型の場合、挙動は定められていない。ただし、一般に普及している2の補数表現の場合は、以下のような挙動になることが多い。

符号付き整数型の最小値を$-1$すると最大値になり、最大値を$+1$すると最小値になる。

int main()
{
     int min = std::numeric_limits<int>::min() ;
     int max = std::numeric_limits<int>::max() ;

     int min_minus_one = min - 1 ;
     int max_plus_one = max + 1 ;

    std::cout << min << "\n"s << max << "\n"s
        << min_minus_one << "\n"s << max_plus_one ;
}

これはなぜか。2の補数表現の8ビットの符号付き整数の最小値は0b10000000($-128$)だが、これを$-1$すると0b01111111(127)となり、これは最大値となる。逆に最大値0b01111111(127)を$+1$すると0b10000000($-128$)となり、これは最小値となる。

# 整数型の変換

整数型にはここで紹介しただけでも、さまざまな型がある。同じ型同士を使った方がよい。

以下は型が一致している例だ。

int main()
{
    int a = 123 ;
    long b = 123l ;
    long long c = 123ll ;

    unsigned int d = 123u ; 
}

以下は型が一致していない例だ。

int main()
{
    // intからshort
    short a = 123 ;
    // longからint
    int b = 123l ;

    // intからunsigned int
    unsigned int c = 123 ;
    // unsigned intからint
    int d = 123u ;
}

代入や演算で整数型が一致しない場合、整数型の変換が行われる。

整数型の変換で注意すべきこととしては、変換元の値を変換先の型で表現できない場合の挙動だ。

たとえばshort型とint型の表現できる最大値を調べるプログラムを書いてみよう。

int main()
{
    std::cout << "short: "s << std::numeric_limits<short>::max() << "\n"s
        << "int: "s << std::numeric_limits<int>::max() ;
}

これを実行すると筆者の環境では以下のようになる。

short: 32767
int: 2147483647

どうやら筆者の環境ではshort型は約3万、int型は約21億ぐらいの値を表現できるようだ。

では約3万までしか表現できないshort型に4万を代入しようとするとどうなるのか。これは1つ前の整数型の表現できる値の範囲で説明したものと同じことが起こる。

int main()
{
    short x = 40000 ;
    std::cout << x ;
}

このプログラムを実行した結果は実装ごとに異なる。例えば筆者の環境では以下のようになる。

-25536

整数型の変換は暗黙的に行われるが、明示的に行うこともできる。明示的な変換にはstatic_cast<T>(e)を使う。static_castは値eを型Tの値に変換する。

int main()
{
    int x = 123 ;
    short y = static_cast<short>(x) ;
}