えびちゃんの日記

えびちゃん(競プロ)の日記です。

intXX_t に関して

どうしてこんなことに...

まえおき

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 bit
  • short → 16 bit
  • int → 32 bit
  • long → 32 bit OR 64 bit
  • long 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_tlong だったり long long だったりします。 このことは、意外と厄介です。

リテラルのつくりかた

では、int64_t の定数を自分で定義するときはどうすればいいでしょう。

unsigned0u とか、long0L のような 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_tlong の処理系では 0Lx の型が同じなのでコンパイルが通りますが、long long の処理系では異なるので通りません。

Note: std::max は 2 つの引数の型が同じである必要があります。

scanf / printf

std::cin を何らかの理由で使いたがらない人向けです。

int64_t x;
scanf("%ld", &x);

これは、int64_tlong 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_tchar だったりします。

int8_t x = 65;
std::cout << x << '\n';

のように書くと...? A とかが出ちゃいます。あーあ。

ACL の話

これ忘れてました。追記です。

ACLint64_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;
...

むかしの

*1:すごい処理系であれば別にこれに縛られはしないと思うのですが、とりあえず現在よくある環境たちについての話ということにします。

*2:グローバルでアンダースコアで始めるのは、リテラルなら許されるんですかね?