数値型で扱える値の最小値や最大値を取得する - C++ プログラミング
PROGRAM
.auto-style1 {
white-space: nowrap;
}
数値型で扱える値の最小値や最大値を取得する
C++ では、int 型や char 型などの数値型のサイズは処理系に依存するため、コンパイラによってそれらが扱う値の範囲が違ってくる可能性があります。特に char は、8 ビットとは限らなかったり、signed 扱いか unsigned 扱いかも違ってきます。
そういった違いを吸収できるように、C++ では扱える値の範囲や桁数を取得する機能が用意されています。
std::numeric_limits<> を使用する方法
値の範囲や桁数を知る方法の一つとして std::numeric_limits<> というテンプレートクラスを使用する方法があります。
これを使うためには、まず <limits> ヘッダーをインクルードします。
#include <limits>
こうすることで std::numeric_limits<> が使えるようになります。
これを使って、たとえば char 型が扱える情報の範囲は次のようにして取得することができます。
char 型ではなく int 型の情報を取得したい場合は <char> のところを <int> に、unsigned long の情報を取得したい場合は <unsigned long> にすれば同じように取得できます。
std::numeric_limits<char>::min() | char 型の最小値を取得します。値は char 型で取得されます。 |
---|---|
std::numeric_limits<char>::max() | char 型の最大値を取得します。値は char 型で取得されます。 |
std::numeric_limits<char>::is_signed | char 型が符号を扱えるとき (signed char) に true を、符号を扱えないとき (unsigned char) に false を返します。 |
std::numeric_limits<char>::digits | char 型で数値として扱えるビット数を int 型で取得します。符号用のビットは含みません。たとえば 8 ビットなら、signed char は 7 に、unsigned char は 8 になります。(厳密にはビット数ではなく std::numeric_limits<char>::radix を基数として「確実に」扱える桁数を取得しますが、標準の数値型は基数が 2 なのでビット数と同じです) |
std::numeric_limits<char>::digits10 | char 型で「確実に」扱える 10 進数での桁数を取得します。符号は含みません。この型で扱える最大値の桁数ではありません。 |
注意したいところとしては、まず、std::numeric_limits<>::digits10 が返す値についてです。
これは「この変数に格納されている値が 10 進数で最大何桁の値を表現するか」ではなく、「この変数に代入しても値が壊れない最大の桁数」を意味しています。
これらの違いや仕組みについては 数値型の変数で扱える値の桁数を求める で詳しく記してありますが、たとえば、符号なし 2 バイト整数であれば、表現できる数字の範囲は 0 から 65,535 までなので、最大桁数は 5 桁ですけど、たとえばこの変数に 90,000 を代入すると、桁数は 5 桁であるにも関わらず、表現し切れずに値が壊れてしまいます。
よって、符号なし 2 バイト整数であれば 4 桁が、その変数で問題なく扱うことのできる 10 進数の最大桁数ということになります。
ちなみに std::numeric_limits<>::digits についても考え方は同じなのですが、2 進数の場合はそういう中途半端な値が出てこないので、格納されている値の最大桁数と、変数が表現できる最大の桁数とが一致します。
また、最小値を取得する min 関数や最大値を取得する max 関数は、戻り値として目的の型と同じ型で値を返すところにも注意です。
たとえば std::numeric_limits<char>::min() なら char 型で値が帰ってくるので、目的の型の値を正確に取得できる反面、別の型に変換するときには char と int の変換キャストの留意点 でお話したような値の範囲による影響を受けがちです。
同じ型で符号の有り無し(たとえば signed char と unsigned char など)を相互変換したり、符号なし変数(たとえば size_t など)に代入したりすると、値がおかしくなることがあることには注意です。
もっとも、こういった注意は常に必要なところですけど。
この std::numeric_limits<> を使うメリットは、テンプレート引数によって調べたい型を変更できるところです。
クラステンプレート や 関数テンプレート ととても相性が良いので、さまざまな型の差異を中和しながら汎用的なテンプレートを実装をすることも可能になります。
定義済みマクロを使う方法
値の範囲を知る方法として、もうひとつ、定義済みのマクロを使う方法があります。
値の範囲が定義されたマクロは <climits> ヘッダーに定義されているので、まずはそれをインクルードする必要があります。
#include <climits>
これで、次のマクロを使って、それぞれの型の最小値や最大値を取得できます。
対象の型 | 最小値 | 最大値 | 補足 |
---|---|---|---|
char | CHAR_MIN | CHAR_MAX | char 型が何ビットで構成されているかは CHAR_BIT で取得できます。この CHAR_BIT は std::numeric_limits<char>::digit とは違って、符号ビットも含んだ「バイト当たりのビット数」です。 |
signed char | SCHAR_MIN | SCHAR_MAX | |
unsigned char | UCHAR_MAX | UCHAR_MIN は定義されていませんが、最小値は 0 です。 | |
short | SHRT_MIN | SHRT_MAX | |
unsigned short | USHRT_MAX | USHRT_MIN は定義されていませんが、最小値は 0 です。 | |
int | INT_MIN | INT_MAX | |
unsigned int | UINT_MAX | UINT_MIN は定義されていませんが、最小値は 0 です。 | |
long | LONG_MIN | LONG_MAX | |
unsigned long | ULONG_MAX | ULONG_MIN は定義されていませんが、最小値は 0 です。 | |
long long | LLONG_MIN | LLONG_MAX | |
unsigned long long | ULLONG_MAX | ULLONG_MIN は定義されていませんが、最小値は 0 です。 |
また、次のように <cstdint> をインクルードすることで、もう少し細かな判定もできるようになります。
#include <cstdint>
対象の型 | 最小値 | 最大値 | 補足 |
---|---|---|---|
std::int8_t | INT8_MIN | INT8_MAX | |
std::int16_t | INT16_MIN | INT16_MAX | |
std::int32_t | INT32_MIN | INT32_MAX | |
std::int64_t | INT64_MIN | INT64_MAX | |
std::uint8_t | UINT8_MAX | UINT8_MIN は定義されていませんが、最小値は 0 です。 | |
std::uint16_t | UINT16_MAX | UINT16_MIN は定義されていませんが、最小値は 0 です。 | |
std::uint32_t | UINT32_MAX | UINT32_MIN は定義されていませんが、最小値は 0 です。 | |
std::uint64_t | UINT64_MAX | UINT64_MIN は定義されていませんが、最小値は 0 です。 | |
std::int_least8_t | INT_LEAST8_MIN | INT_LEAST8_MAX | |
std::int_least16_t | INT_LEAST16_MIN | INT_LEAST16_MAX | |
std::int_least32_t | INT_LEAST32_MIN | INT_LEAST32_MAX | |
std::int_least64_t | INT_LEAST64_MIN | INT_LEAST64_MAX | |
std::uint_least8_t | UINT_LEAST8_MAX | UINT_LEAST8_MIN は定義されていませんが、最小値は 0 です。 | |
std::uint_least16_t | UINT_LEAST16_MAX | UINT_LEAST16_MIN は定義されていませんが、最小値は 0 です。 | |
std::uint_least32_t | UINT_LEAST32_MAX | UINT_LEAST32_MIN は定義されていませんが、最小値は 0 です。 | |
std::uint_least64_t | UINT_LEAST64_MAX | UINT_LEAST64_MIN は定義されていませんが、最小値は 0 です。 | |
std::int_fast8_t | INT_FAST8_MIN | INT_FAST8_MAX | |
std::int_fast16_t | INT_FAST16_MIN | INT_FAST16_MAX | |
std::int_fast32_t | INT_FAST32_MIN | INT_FAST32_MAX | |
std::int_fast64_t | INT_FAST64_MIN | INT_FAST64_MAX | |
std::uint_fast8_t | UINT_FAST8_MAX | UINT_FAST8_MIN は定義されていませんが、最小値は 0 です。 | |
std::uint_fast16_t | UINT_FAST16_MAX | UINT_FAST16_MIN は定義されていませんが、最小値は 0 です。 | |
std::uint_fast32_t | UINT_FAST32_MAX | UINT_FAST32_MIN は定義されていませんが、最小値は 0 です。 | |
std::uint_fast64_t | UINT_FAST64_MAX | UINT_FAST64_MIN は定義されていませんが、最小値は 0 です。 | |
std::intptr_t | INTPTR_MIN | INTPTR_MAX | |
std::uintptr_t | UINTPTR_MAX | UINTPTR_MIN は定義されていませんが、最小値は 0 です。 | |
std::intmax_t | INTMAX_MIN | INTMAX_MAX | |
std::uintmax_t | UINTMAX_MAX | UINTMAX_MIN は定義されていませんが、最小値は 0 です。 | |
std::ptrdiff_t | PTRDIFF_MIN | PTRDIFF_MAX | |
std::sig_atmic_t | SIG_ATOMIC_MIN | SIG_ATOMIC_MAX | |
wchar_t | WCHAR_MIN | WCHAR_MAX | |
std::wint_t | WINT_MIN | WINT_MAX |
また、余談ですけど std::int8_t 型などの数値リテラルを記述するためのマクロも用意されるようでした。
対象の型 | リテラル表記 (たとえば 100) |
---|---|
std::int8_t | INT8_C(100) |
std::int16_t | INT16_C(100) |
std::int32_t | INT32_C(100) |
std::int64_t | INT64_C(100) |
std::uint8_t | UINT8_C(100) |
std::uint16_t | UINT16_C(100) |
std::uint32_t | UINT32_C(100) |
std::uint64_t | UINT64_C(100) |
std::intmax_t | INTMAX_C(100) |
std::uintmax_t | UINTMAX_C(100) |
これらのマクロでやっていることは、指定された値に適切なサフィックスを追加しています。型のサイズは処理系によって依存するので、これらのマクロで中和している感じですね。
さて、これらのマクロで注意したいところは、これらのマクロがプリプロセッサによって数値リテラルに置き換えられるところでしょうか。
適切なサフィックスは付けられているものの、たとえば CHAR_MIN なら -128 に置き換えられます。厳密な型情報が抜け落ちるので、たとえばテンプレート関数やオーバーロードされた関数の引数に直接これらを指定する必要がある場合などには、思っているのと違う動作をするかもしれません。
型を厳密に扱うためには "(char)CHAR_MAX" などと、明示的にキャストする必要があります。
マクロを使う利点としては、プリプロセッサで処理できる点が挙げられます。
std::numeric_limits<> は実行時の処理になりますけど、マクロならコンパイル前のプリプロセスの段階で処理されるので、処理系に依る差異を吸収したいときには、マクロを使った方法の方が最適な場合が考えられます。
たとえば char 型が符号を持つかどうかを知りたい場合は、次のように判定できます。
#if CHAR_MIN != 0
// 符号付き char の場合の処理をここに記載します。
#else
// 符号なし char の場合の処理をここに記載します。
#endif
このとき、条件に合った方のコードしかコンパイルされないため、実行時に判定処理をしなくて済み、バイナリレベルではコードがスマートになります。ソースコードのレベルでは見難くなりますけれど。
処理系依存を中和したい場合、実行時に処理系のルールが変わることは普通はないでしょうから、マクロを使う方が最適でしょう。
[ もどる ]