寒い日が続きますね。
今日は整数型の最大値・最小値を汎用的に得る方法を書いていきたいと思います。
実のところ、整数型の最大値・最小値はすべて<climits>(limit.h)に載っていますので、必要ないと言えばないのですが、マクロなのが気に入らないのと、汎用的な定義が可能だろうと思ったので書くことにいたしました。また、ビット演算を多用するので、あまりビット演算を使わないという方に見て頂けると多少は参考になったり……参考になるように書いていきたいと思います。
@_wlfx さんより、<climits>ヘッダにstd::numeric_limits::max()などテンプレートによって定義されているものがあることを教えていただきました。( コメントはこちらです。 )
前提として、char型が1バイト、short型が2バイト、int型が4バイトだとして説明を進めていますが、そうでなくてもちゃんと動くことを保証します。
ただし、1バイトが8ビットであることを想定しているので、( ほぼないとは思いますが )もし異なる環境の場合はbitofの定義を書き換えてください。
# include <type_traits> // for std::is_unsigned
# define bitof( arg ) ( sizeof( arg ) * 8 ) // 1バイトは8ビット
template < class Ty > static constexpr
Ty max_value()
{
return std::is_unsigned < Ty >::value ?
~static_cast < Ty > ( 0 ) :
~static_cast < Ty > ( 0 ) & ~( 1 << bitof( Ty ) - 1 );
}
template < class Ty > static constexpr
Ty min_value()
{
return std::is_unsigned < Ty >::value ?
0 :
~static_cast < Ty >( 0 ) & ( 1 << bitof( Ty ) - 1 );
}
# undef bitof
まずあんなに毛嫌いしていたマクロを使っています。ごめんなさい。
といっても大したものではなく、型や値を入れるとその型のビットの大きさを返します。8ビットなことを想定して8倍していますが、組み込みなどの環境によっては9ビットだったりするかもしれませんので注意してください。
max_value関数から見ていきます。
constexpr関数なのでif文は使えないので(C++14からは可能になっています。)、三項演算子を使います。
符号なしの型の最大値は、常に全ビットが1で埋まっている状態です。
unsigned char型の最大値は2進数で0b11111111ですよね。16進数では0xffです。
よって、0をテンプレート型Tyにキャストしてあげて、ビット反転を行えば全てのビットに1が立ちます。
符号ありの型の最大値は、最上位ビットを除く全てのビットが1で埋まっている状態です。
short型の最大値は0b0111'1111'1111'1111となります。( 区切り文字にシングルクウォートを使っています。 )
これを表すには、まずは全てのビットを立たせた状態から、最上位ビットだけを反転してあげればOKです。
最上位ビットはbitof( Ty )番目のビットです。( short型であれば、16ビット目です。 )
つまり、1をbitof( Ty ) - 1シフトしてあげれば、最上位ビットのみに1を立てることができます。
short型の例では、1をシフトしていくと
0シフト => 0b0000'0000'0000'0000'0001
1シフト => 0b0000'0000'0000'0000'0010
2シフト => 0b0000'0000'0000'0000'0100
...
15シフト=> 0b1000'0000'0000'0000'0000
15シフトで最上位ビットが立つことがわかります。
このフラグのみを取り除きたいので、反転して、全ビット1のものと論理積を取ってあげればよいことがわかります。
上のを反転 => 0b0111'1111'1111'1111'1111
論理反転 =>&0b1111'1111'1111'1111'1111
論理積 ==========================
最大値 => 0b0111'1111'1111'11111'1111
峠は越えました。サクッと下山しましょう。
min_valueに入ります。
またもやstd::is_unsignedで符号の有無を調べています。
もちろん符号なしの最小値は0です。
( climitsヘッダにはUCHAR_MINなどは用意されていませんので注意してください。 )
符号ありの最小値はビットではどうなっているのでしょうか。オーバーフローを思い出すと簡単だと思います。
符号ありの最小値は、ビット的には符号ありの上限値 + 1なのです。つまり、最上位ビットは1で、その他が0の値を符号ありとして読みだすと符号ありの最小値となります。
int型の下限値は0b1000'0000'0000'0000'0000'0000'0000'0000です。
最大値と操作はさほど変わりません。
まずはビット反転したものを作り、1をbitof( Ty ) - 1シフトしたものとの論理積を取ってあげればよいことになります。
関数の説明は以上です。
ビット単位の操作では、型( 特にそのサイズ )が問題になります。( char ) 0
は0が8つ並んでいて、 ( int ) 0
は0が32個並んでいることを意識しなければなりません。
// 利用例
constexpr int int_max = max_value < int >();
constexpr unsigned long long ullong_min = min_value < unsigned long long >();
以降はオマケです。よく使うビット操作のイディオム的なものを並べておきます。上3つは最も基本的な操作なので言語としてサポートされています。
-
論理和
c = a | b; // aに立っているビットとbに立っているビットがcに立てられる。
-
論理積
c = a & b; // aにもbにも立っているビットだけがcに立てられる。
-
排他的論理和
c = a ^ b; // aかbのどちらかにしか立っていないビットだけがcに立てられる。
-
ビット反転
c = ~a; // aの全てのビットが反転される。
-
ビット除去
c = a & ~b; // aに立っているビットのうち、bに立っているビットを除いたものがcに立てられる。
-
ビットマスク
c = ( ( ~static_cast < decltype( a ) > ( 0 ) ) << b) & a; // aに立っているビットのうち、bビット以下の全てのビットを0にする。
以上です。何かのお役に立てばと思います。