どうしてこんなことに...
まえおき
C++ には次の型があります:
signed char
unsigned char
char
short signed int
(=short
)short unsigned int
(=unsigned short
)signed int
(=int
)unsigned int
(=unsigned
)signed long int
(=long
)unsigned long int
(=unsigned long
)signed long long int
(=long long
)unsigned long long int
(=unsigned long long
)
char
の signedness(符号つきかどうか)は処理系定義なのですが、signed char
とも unsigned char
とも異なる型であることが規定されています。
また、signed long long int
などについては、順番はどうでもよくて、たとえば long int signed long
なども同じ型として扱われます。
ともかく、signedness などを無視すると、次の 5 つがあることになります。
char
short
int
long
long long
ところで、扱えるビット長は 8, 16, 32, 64 は 4 種類です*1。 鳩ノ巣原理から、上記の型について、少なくとも 1 組はビット長が同じものが存在しますね。
よくある環境では、次のようになっているはずです。
char
→ 8 bitshort
→ 16 bitint
→ 32 bitlong
→ 32 bit OR 64 bitlong long
→ 64 bit
(char
が 64 bit の処理系とか、short
が 32 bit の処理系とかも規格上は許されてはいますね。これらの型同士の大小関係とか、最低限のビット長くらいしか規定されていません。)
long
が 64 bit の環境をみんなが使っていれば「64 bit 整数を使いたいなら long
を使いましょう。おわり」にできるんですが...
多くのジャッジ環境(Codeforces は別)は long
が 64 bit なので、手元環境が Windows でない人は long
と書けばいい気がします。
本題
「そもそも 64 bit 整数を使っている」みたいなことを明示したいのに「long
」とかの名前を使いたくないという感情はあるので、ビット長で名付けられた型を使いたい気分になります。
そこで、std::int64_t
みたいなのを使うようになるのですが、これは罠が多いので、それに関する記事です。
まず、intXX_t
は上記の型のどれかの typedef
である(あれらと別の型として存在しているわけではない)ことに注意です。
前述の通り、long
が 64 bit の処理系とそうでない処理系があるので、int64_t
は long
だったり long long
だったりします。
このことは、意外と厄介です。
リテラルのつくりかた
では、int64_t
の定数を自分で定義するときはどうすればいいでしょう。
unsigned
の 0u
とか、long
の 0L
のような suffix は用意されていません。
auto x = INT64_C(0);
INT64_C
というマクロが定義されており、これを使うとよいです(めんどくさいね)。
このマクロは処理系が定義しておいてくれているもので、適宜 0L
とか 0LL
とかに置き換えてくれます。
int64_t(0)
や (int64_t)0
のようなキャストでも別にいいです。
多用するなら、リテラルを定義しちゃいましょう。
ユーザ定義リテラルは _
で始めないと未定義動作(予約されている識別子を勝手に使ったことになる)なので、そういう感じにします*2。
int64_t operator ""_i64(unsigned long long) { return n; }
これを定義しておけば、0_i64
のようにして作ることができます。
次のように適当に決め打ちしていいじゃんと侮っていると、面倒なことになります。
int64_t x = 0LL;
std::max
もちろんこれに限るわけではないですが、ありがちなので。
int64_t x = /* 計算で出てきた値 */; int64_t y = std::max(0L, x); // ???
int64_t
が long
の処理系では 0L
と x
の型が同じなのでコンパイルが通りますが、long long
の処理系では異なるので通りません。
Note: std::max
は 2 つの引数の型が同じである必要があります。
scanf
/ printf
std::cin
を何らかの理由で使いたがらない人向けです。
int64_t x; scanf("%ld", &x);
これは、int64_t
が long long
である環境では未定義動作になるはずです(scanf
はフォーマット文字列で指定される型と実際の型が異なると未定義動作になるので)。
そこで、これもやっぱりマクロが用意されていて、次のようにすると安全です。 受け取った 2 整数を順番を入れ替えてスペース区切りで出力する例です。
#include <cinttypes> int main() { int64_t x, y; scanf("%" SCNd64 "%" SCNd64, &x, &y); printf("%" PRId64 " %" PRId64 "\n", y, x); }
SCNd64
などが "ld"
とかに展開されるように処理系が定義してくれています。
"%ld"
となっていないのは、"%02ld"
のようなのを作りやすいようにするためだと思います。
Note: C++ の文字列リテラルはくっつけると文字列の意味で連結することができ、"%" "ld" "%" "ld"
と書けば "%ld%ld"
と書いたのと同じことになります。
どうせ競プロで使うぶんにはジャッジ環境であってさえいればいいので、"%ld"
とか書いてしまえばいいんですが、気持ち悪さを抱えながら書くことになります。
そもそも long
とかを考えたくないからこういう型を使っているのに... という気分になります。
余談:intmax_t
という型もあって、これについては %jd
というのが使えます。
int8_t
これは std::cin
/ std::cout
で入出力する人向けです。
前述のように、これらの型は typedef
であって、「専用に用意された整数型」ではないです。
つまるところ、int8_t
は char
だったりします。
int8_t x = 65; std::cout << x << '\n';
のように書くと...? A
とかが出ちゃいます。あーあ。
ACL の話
これ忘れてました。追記です。
ACL が今のところ int64_t をサポートしてないので vector<int64_t> を vector<long long> に変換して渡さないといけなかったりするとおもう
— keijak (@keijak) 2021年5月3日
ACL が int64_t
をサポートしていないというか、long
をサポートしていないというのが正しい気はします(ジャッジ環境で int64_t == long
なので)。
そもそも定義されない処理系もある
こんな処理系を触ることはないでしょうので、これは気にしません。
そのほか
int_fast32_t
とか int_least64_t
とかについても同じような問題があります。
これらはそもそもどのビット長なのかすらわからないので、参りがちです。
int_fast32_t
は別に「普段の 32 bit 整数とは異なるすごい整数型」ではなく、「処理系が適当に決めた最低 32 bit ある整数型」なので、必ずしも常に速いとすら限りません。
どうせ人々は自作ライブラリをぺたりしてるのでしょうから、自分で定義する方がいいんじゃないかなと思います。好きな名前をつけられますし。
using i32 = int; using u64 = unsigned long long; ...
むかしの
めちゃくちゃ後の人類、i16777216 とか i2147483648 とかで型名を宣言しなきゃいけなくなったらかわいそう
— えびちゃん (@rsk0315_h4x) 2020年10月25日