C++ on MSVC講習/整数と小数

あらすじと概要

前回は整数、小数、文字列を実行時に保存したり、入力できるようになりました。
今回は、整数と小数の内部表現と、計算をやっていきましょう。

重要語

ビット

1/0を表現する情報の最小単位

バイト

概ね8bitである、情報の基本的な単位

1の補数表現

1の補数でマイナスの整数を表現する方法

2の補数表現

2の補数でマイナスの整数を表現する方法

符号ビット

値が正であるか負であるかを判定できるビット

オーバーフロー

扱える値の範囲を超えてしまうこと

ゼロ除算

値を0で割ること

固定小数点数

予め小数点の位置を決定して整数で小数を表す方法

浮動小数点数

符号と仮数と指数で小数を表す方法

IEEE 754

一般的に用いられる浮動小数点数の規格

inf

infinityとも、無限大になる浮動小数点数演算の値

NaN

Not a Number、数値にならない浮動小数点数演算の値

正規化(正規表現)

指数表現の仮数を0以上10未満の表現にすること

丸め

値を表現できる値に近似すること、またその近似方法

サフィックス

接尾辞

プレフィックス

接頭辞

評価

式を実際に計算などして実行すること

ゼロ方向への丸め

小数部分を破棄し、ゼロに向かって丸めること

暗黙の型変換

型が違う場合で自動的に行われる型変換

キャスト

型変換、型を変更すること

整数の内部表現

組み込み型での計算を正確にするには、組み込み型の内部表現を知っているべきです。
ですからまず、一番基本的な整数の内部表現を学んでいきましょう。
前回保留していた、変数のサイズについても触れていきます。

ビットとバイト

コンピューターでは、電気的なOn/Offで1/0を表現し、2進数として扱っています。
最小となる、その1/0を表現する単位をビット、そして8bitビットでバイトと扱われます。
なお、8bitで1Byteバイトとは決まっているわけでは無く、そうでない場合も極々稀にあります。

変数のサイズ

変数のサイズというのは、変数が使うビットの量のことになります。
ただし、ビットは物理的に存在している以上、無限に存在しているわけではありません。
また、変数のサイズは固定されていると何かと便利ですし、効率がよくなります。
以上のことから、変数にサイズを決める、あるいは決まってしまうのが普通です。

負の数を扱いたくなりました

負の数を扱いたくなりました。この時一番簡単に思いつくのは符号ビットを使うことです。
例えば、8bitの整数があった時、0これ000 0000を符号ビットということにしてみましょう。
符号ビットが0/1の時、正/負とし、残り7bitは絶対値で表現してみましょう。
0000 00011に対して、1000 0001-1のようになり、-127から127が表現できます。

1の補数表現

符号ビットと絶対値で表す以外にも、正の数の表現をビット反転各ビットの0と1を逆にするする方法もあります。
例えば、上と同じく8bitの整数で、0000 00011をビット反転して1111 1110-1といった具合。
これもやっぱり-127から127まで表現でき、0これ000 0000は符号ビットの役割を果たします。
これは、数学の「補数」という考え方に由来するため、1の補数表現と言います。

ダメなのよ。

上の2つの表現、実はどっちもダメなところがあります。例えば、0が2種類あるのです。
1つの数値に対して2つ以上表現があると、一意に表現できていないということになるのです。
でも、符号ビットが1の時は0を表現しない、とすると表現が1個捨てられて無駄です。

2の補数表現

どうすればいいのか。人類はやっぱり数学に由来して、2の補数表現にたどり着きました。
内容としては、負の数は、1の補数表現に+1をした表現で表すことにしたのです。
つまり、8bitの-1なら、0000 000111111 11101の補数の-11111 11112の補数の-1ということになりました。
これなら、例えば8bitであれば、127から-128までを表せるのです。

何がいいのさ

実は、2の補数表現は減算を加算で処理できるという素晴らしい性質を持っています。
36-12→24なら、12-12にして足すと0010 010036 + 1111 0100-12 → 0001 100024、あらあら。
桁あふれした分は見なかったことにして、2進法で計算すると減算が加算で処理できるのです。

C++の整数

C++17までは、C++はC言語と同様に2の補数以外の整数表現を許可していました。
しかし、C++20からは整数は2の補数表現に限られることになりました。
ただ、ほとんどは2の補数表現が用いられているので、気にするほどでもないかもしれません。
この講習では、整数は2の補数表現であることを前提にして進めていきます。

オーバーフローと符号付き整数

さて、負の値の扱い方まで分かると、整数が扱える値の範囲が分かると思います。
扱える値の範囲に制限があるということは、範囲を超えてしまったときはまずいのです。
そのことをオーバーフローといい、符号ありの変数では未定義動作となります。
なお、実行時に起こりうるエラーを検出するサニタイザーなどで検出することが出来ます。

オーバーフローと符号なし整数

符号なしの変数の場合には、その符号なし変数が表せる最大の値で剰余じょうよした値になります。
なお剰余、つまりあまり算は、割られる数を割る数で割った余りが求める値になる計算です。
例えば、8bitの符号なし整数扱える値の範囲は0~255に対して、256になる演算をしたら、1になります。
以上のように、C++は単純な計算でも、変数でどの程度の値を扱うか把握するのが重要です。

ゼロ除算

オーバーフローの解説をしたので、同時にゼロ除算についても解説します。
名前の通り、値を0で割ることをゼロ除算と言い、C++では整数の場合未定義動作です。

MSVCにおける整数のサイズ

short

16bit

int(サイズに関する修飾子なし)

32bit

long

32bit

long long

64bit

MSVCにおける整数が扱える値の範囲

short (signed / unsigned)

-32,768 ~ 32,767 / 0 ~ 65,535 

int (signed / unsigned)

-2,147,483,648 ~ 2,147,483,647 / 0 ~ 4,294,967,295

long (signed / unsigned)

-2,147,483,648 ~ 2,147,483,647 / 0 ~ 4,294,967,295

long long (signed / unsigned)

-9,223,372,036,854,775,808 ~ 9,223,372,036,854,775,807 
/ 0 ~ 18,446,744,073,709,551,615

小数の内部表現

さて、整数が分かりましたが、小数はどうなっているのでしょうか。

固定小数点数

これは、小数点の位置を予め決めておくことで、整数で小数を表す方法です。
整数で整数/小数部分を表現することから、基本的に誤差は発生しません。
その代わりに、扱える値の範囲が狭かったり、無駄が発生したりすることが多いのです。
そのため、一般的には使われず、特殊な分野で使用されることが多いです。

浮動小数点数

前回指数表記を解説しましたが、その発想に近いのが浮動小数点数です。
すなわち、符号と仮数と指数で小数を表す表現をするのが浮動小数点数です。
現在の多くのコンピューターでは、専用のプロセッサFPU, Floating Point Unitがあり、主流となっています。

IEEEあいとりぷるいー 754

浮動小数点数の実装や扱いとして一般的に用いられるのが、IEEE 754です。
IEEE 754の中でも、C++で使われているのはbinary32/binary64ばいなりー32/ばいなりー64, 俗に単精度/倍精度の2つです。
binary32は合計32bit、binary64は合計64bitを用いる表現方法です。
以下では、特に言及がない場合はIEEE 754に基づいて解説します。

特殊な値

浮動小数点数には、整数にはない特殊な値、infinfinity, いんふぃにてぃーNaNなん, Not a Numberがあります。
infは無限大のことで、非0のゼロ除算などの演算で発生し、正負が存在します。
NaNは0のゼロ除算や、infと0の乗算、inf同士の除算、異符号infの加算などで発生します。
なおinfは0の対数を取った時、NaNは結果が虚数になる演算などでも設定される事があります。

指数表記のおさらい

指数表記は、1.25e5などのように表記する方法で、符号/仮数/基数/指数に分けられます。
例えば、-1.25e5なら符号/仮数/基数/指数-/1.25/e/5となります。
符号が+の場合は+が省略されますが符号は+です。また、e10として扱います。
基数がeではない場合は、紙に書く時と同じようにします。例えば-1.25*2**5のように。

正規化

指数表記において、仮数を0以上10未満にした表現に変換することを正規化と言います。
例えば、12.345e2という値であれば、1.2345e3という形に正規化することが出来ます。

基数と丸め

binary32/64では、基数は2と定められます。つまり、2進数の小数ということになります。
そのため、10進数の小数を2進数で表すときや、除算で循環小数になる事があるのです。
この場合に、何らかの値に近似するする必要があり、その近似方法を丸めと言います。
主に、最も近くの表現できる値へ、丁度中間の値なら偶数へ丸める方法が用いられます。

精度

binary32では、2進数で23~24bit程度、10進数で6~7桁程度の精度を持っています。
binary64では、2進数で52~53bit程度、10進数で15桁程度の精度を持っています。
ここで示す精度より大きい桁については、不確かな値が含まれることになります。

C++における小数の扱い

C++の標準規格では、固定小数点数か浮動小数点数かすら決められていません。
ただし、主要な処理系では、floatをbinary32、doubleをbinary64で実装しています。
long doubleについては、MSVCはdoubleと同様binary64で実装していますが、
他の処理系ではより高精度かもしれません。ただ、通常doubleを使用すればよいでしょう。

MSVCにおける小数の扱い

float

binary32

double

binary64

long double

binary64

MSVCにおける小数が扱える値の範囲

float

3.4E±38

double

1.7E±308

long double

1.7E±308

整数と小数の演算

それでは、やっと今回初めてのサンプルコードです。
整数と小数の演算
#include <iostream>

int main()
{
    std::cout << 0b1010 + 020 << "\n"
        << 0xFF - 1.5 << "\n"
        << 2.5 * 8 << "\n"
        << 1ul / 3ull << "\n"
        << (int)1.5 << "\n"
        << (double)1 / 3 << "\n"
        << 1 / static_cast<double>(3) << "\n"
        << 15 % 8;
}

実行結果例
26
253.5
20
0
1
0.333333
0.333333
7

解説

解説します

リテラルの型

既に分かっているかもしれませんが、リテラルもやはり型を持っています。
整数/小数リテラルそれぞれ標準で、int/double型になります。
整数/小数リテラルは、それぞれ、以下のサフィックスsuffix, 接尾辞の総称を付けると型を変えられます。
整数リテラルのサフィックスは、符号とサイズについては組み合わせることが出来ます。
ただしリテラルの値が、指定されている型で表せない場合、より大きい型になります。

整数リテラルのサフィックス

signed

なし

unsigned

u または U

int

なし

long

l または L

long long

ll または LL

小数リテラルのサフィックス

float

f または F

double

なし

long double

l または L

整数リテラルの進法選択

更に、整数リテラルは2/8/10/16進法の何れかを使用できます。
これらは、以下のプレフィックスprefix, 接頭辞の総称を使用して選択することが出来ます。
16進法では、a/Af/Fまで、順番に1015を表します。
特に、始めを0にすると、8進法となってしまうことに注意しましょう。

整数リテラルのプレフィックス

2進法

0b

8進法

0

10進法

なし

16進法

0x

各進法で使用できる数字と文字

2進法

0/1

8進法

0/1/2/3/4/5/6/7

10進法

0/1/2/3/4/5/6/7/8/9

16進法

0/1/2/3/4/5/6/7/8/9/a/A/b/B/c/C/d/D/e/E/f/F

式と評価

式は演算子とオペランド(被演算子)の並びですが、結果的には値になります。
式を計算することを評価すると言うので、式は評価されると値になると言えます。

演算子について

演算子は式を構成する要素ですが、演算子は主オペランドを1/2/3つ持つものがあります。
1つのオペランドを取るものは、オペランドの前/後に置くかで、更に前置/後置と区別します。
なお、オペランドは値に評価されるものであればよいので、式も取ることが出来ます。

今回用いた演算子

+

加算

-

減算

*

乗算

/

除算

%

剰余じょうよ、モジュロ、余り算とも

問題の箇所と0方向への丸め

1ulunsigned longの1 / 3ullunsigned long longの3が0に評価されるのは驚いたでしょうか。
これは、C++では整数同士の計算結果は、やはり整数になるからなのです。
整数同士の計算が数学的に小数になる場合、0方向に丸められます。
つまり、小数部分を見なかったことにして、整数部分が計算結果になるということです。

暗黙の型変換

一般に、式のオペランドの型が違う場合、暗黙の型変換というものが行われる事があります。
組み込み型では、2つのオペランドのうち、より表せる値の範囲が広い方へ変換されます。
整数/小数同士では、単純により大きい方へと、整数と小数では、小数へ変換されます。
ただし、整数のsignedとunsignedが一致しない場合、unsignedへと変換されます。

整数と小数についての暗黙の型変換

整数同士

short < int < long < long longsigned < unsigned

小数同士

float < double < long double

整数と小数

整数 < 小数

キャスト(型変換、明示的な型変換)

暗黙の型変換以外にも明示的に型を変換でき、それをキャストと言います。
キャストは、まずCスタイルのキャストと、C++で追加されたキャストがあります。
C++で追加されたキャストは、Cスタイルのキャストをより役割ごとに分割したものです。

Cスタイルのキャスト

Cスタイルのキャストの中で一番使われる構文は以下のようになります。
Cスタイルのキャストは、C++で追加されたキャスト演算子を組み合わせてキャストされます。
そのため、記述するのは楽ですが、余りにも強力な為、あまり推奨されません。
Cスタイルのキャストの構文
( 型名 ) 値

static_cast

static_castは演算子で、暗黙の型変換で行われるようなキャストを明示的に行えます。
構文は以下のようで、<>の中に型名、()の中にキャストしたい値を入れます。
static_cast
static_cast < 型名 > ( 値 )

問題の箇所2

(int)1.51に評価されます。小数から整数へのキャストでも、0方向へ丸められるのです。
そして、(double)1 / 31 / static_cast<double>(3)は、片方をdoubleにしています。
その結果、もう片方が暗黙の型変換でdoubleになり、評価はdoubleとなるのです。

剰余

前半でも説明しましたが、割られる数を割る数で割った時の余りが、剰余で得られる値です。
剰余演算子は挙げた5つの演算子の中で唯一、オペランドに取れるのが整数だけとなります。
まあ、剰余はnの倍数かの判定くらいでしか使用しないので[要検証]いいでしょう。
余剰は除算同様に、ゼロの余剰を取ろうとすると未定義動作になります。

参照、出典

参照や出典です

参照

ビット - Wikipedia

https://ja.wikipedia.org/wiki/%E3%83%93%E3%83%83%E3%83%88

バイト (情報) - Wikipedia

https://ja.wikipedia.org/wiki/
%E3%83%90%E3%82%A4%E3%83%88_(%E6%83%85%E5%A0%B1)

補数 - Wikipedia

https://ja.wikipedia.org/wiki/%E8%A3%9C%E6%95%B0

符号付数値表現 - Wikipedia

https://ja.wikipedia.org/wiki/
%E7%AC%A6%E5%8F%B7%E4%BB%98%E6%95%B0%E5%80%A4%E8%A1%A8%E7%8F%BE

2の補数 - Wikipedia

https://ja.wikipedia.org/wiki/2%E3%81%AE%E8%A3%9C%E6%95%B0

符号付き整数型が2の補数表現であることを規定 - cpprefjp C++日本語リファレンス

https://cpprefjp.github.io/lang/cpp20/signed_integers_are_twos_complement.html

算術演算子 - cppreference.com

https://ja.cppreference.com/w/cpp/language/operator_arithmetic

固定小数点数 - Wikipedia

https://ja.wikipedia.org/wiki/
%E5%9B%BA%E5%AE%9A%E5%B0%8F%E6%95%B0%E7%82%B9%E6%95%B0

データ型の範囲 | Microsoft Docs

https://docs.microsoft.com/ja-jp/cpp/cpp/data-type-ranges?view=msvc-160

浮動小数点数 - Wikipedia

https://ja.wikipedia.org/wiki/
%E6%B5%AE%E5%8B%95%E5%B0%8F%E6%95%B0%E7%82%B9%E6%95%B0

IEEE 754 - Wikipedia

https://ja.wikipedia.org/wiki/IEEE_754

小数の表現

http://www.cc.kyoto-su.ac.jp/~kbys/kiso/number/fraction.html

端数処理 - Wikipedia

https://ja.wikipedia.org/wiki/%E7%AB%AF%E6%95%B0%E5%87%A6%E7%90%86

整数リテラル - cppreference.com

https://ja.cppreference.com/w/cpp/language/integer_literal

浮動小数点リテラル - cppreference.com

https://ja.cppreference.com/w/cpp/language/floating_literal

評価順序 - cppreference.com

https://ja.cppreference.com/w/cpp/language/eval_order

C++の演算子の優先順位 - cppreference.com

https://ja.cppreference.com/w/cpp/language/operator_precedence

暗黙の変換 - cppreference.com

https://ja.cppreference.com/w/cpp/language/implicit_conversion

明示的な型変換 - cppreference.com

https://ja.cppreference.com/w/cpp/language/explicit_cast

static_cast 変換 - cppreference.com

https://ja.cppreference.com/w/cpp/language/static_cast