タイトル通りの記事なので、#define int long long
アレルギーの方はそっ閉じしてください。
Twitter(X)上で#define int long long
について悩んでいる人を見かけたので、プロ#define int long long
ユーザーの私がちょっとだけ解説してみようと思います。
そもそもなぜ#deifne int long long
しようと思うのか
多くの場合、int
は4byte、long long
は8byteなので、本来#define int long long
は必要ないならばしたくないものです。
CPUのキャッシュは有限でかなり小さいので倍の容量が必要になるとメモリアクセスがボトルネックになって実行時間が遅くなることは十分にあり得ますし、現在(2024/10/20)のAtCoderではあまり遭遇しませんが問題のメモリ制限に引っかかってMLEになる可能性だってあります。
では、long long
が必要な場合はどのようなときでしょうか。
単純な例では、入力long long
が必要になります。
競プロ初心者がたまに引っかかるものは、int
だとオーバーフローしてしまうことや、
問題の解法は合っているのにオーバーフローでペナを食らったり、コンテスト終了までにオーバーフローに気づかずACできなかったときなんかは、後で気づいた時に悔しくて発狂しますよね。
そういう経験をしたポンコツな私のような皆さんはその次にこう思うのです。
#define int long long
してればこんなことにならなかったのに……と。
#define int long long
のデメリット
まぁデメリットがあろうが何だろうがACできないよりはマシだと思えば、発狂経験のある諸氏は納得していただけると思うが、それでも書かない訳にはいかないので書きます。
メモリを多く消費する
何よりも当たり前の事実としてメモリを食います。
前述の通り、キャッシュミスヒットで効率が下がることもあれば、MLEになる可能正だって(AtCoderでは低いけど)あります。
ただ、問題の正しい解法を思いついていた場合は高々メモリ使用量が2倍になったくらいでTLEやMLEになることは無いと信じてもいいと思います。
(そもそもメモリ使用量が2倍になった程度でTLEするんだったら定数倍高速化の前に解法の計算量が大きすぎる可能性のほうが高いので)
32bit整数を使いにくい
当たり前ですが、int
と書いて64bitになるなら32bitの整数の型の名前をちゃんと思い出しておかないといけません。signed
とunsigned
です。こいつらはint
を省略してもいいよという雑な取り決めのおかげで#define int long long
しても32bitのまま使えます。
一つ注意点として、今までint main()
としていたところはsigned main()
にしなければなりません。unsigned
はコンパイルエラーになります。
ライブラリのコンパイルが通らない
コンパイルエラーのコードの読み方がわからないとなぜかライブラリを使ったときにエラーが出てどうしようもないことになります。コンパイルエラーが出るときは、ライブラリの中でint(~~~~)
のようにint
のコンストラクタが呼び出されているなどの場合や、C++20以降で導入されたconcepts
でrequires(sizeof(T)==4)
のように型のサイズを固定している場合などです。コンパイルエラーが出ないエラーがあるとさらに面倒で、例えば最上位bitの場所を教えてくれる関数はcountl_zero<_TP>()
なので、bit数が違うと頭のゼロの数が変わって動作が狂います。
しかし、これは#define int long long
をすべての#include
より下に書くことで解決できます。私はテンプレを別ファイルにして提出時には#include <local.h>
をテンプレファイルで置き換えるプログラムでクリップボードにコピーしていますが、そのような場合でも#include <local.h>
の上の行に新たに必要なヘッダを#include
することで問題なく動作します。
using ll = long long;
ではダメなのか
ダメではないです。ですが、私たちはなぜ#define int long long
したいのですか?
うっかりオーバーフローするのを未然に防ぎたいからです。
必要なときにll
を使うのであればうっかりは防げません。
では、すべての整数をll
で扱えばいいのではないか。はい、その通りです。
ll
のほうが文字数も少ないし、int
との区別もできていいじゃないか。はい、その通りです。
しかし、一つ問題点を上げるとするとll
は右手薬指二連打で少し打ちにくいのです。
コメントで指摘してもらいましたが、ll
以外に打ちやすいエイリアスを用いれば連打力は必要なくなります。
なので、もうちょっと#define int long long
擁護派として追記しておくと、新しいエイリアスを作ると、短い変数名と衝突しやすいという問題が発生します。
ll
なら二つl
,r
を管理するような場面でll
が使えなくなりますし、ii
なら4重ループでループ変数をi,j,ii,jj
とできなくなります。
もちろん探せば衝突しない良いエイリアスがあるのかもしれませんが、探すのめんどくさいコンテスト中に変数名を考えるのに時間を消費するなんて時間の無駄じゃないですか(建前)。
でも、ちゃんと変数名を考える人はusing
でエイリアスするかlong
使うかでいいと思います。
というか、windows環境でプログラミングしてないならlong
でいいと思います。
追記終わり
ここで、私のもう一つのおすすめも紹介しておきます。
#define itn long long
です。
これも追加しておくとint
をitn
と間違えてしまっても何も問題なく動きます。
int
はすべて違う指で押すのでタイピングはこちらの方が前後も合わせて早く打ちやすいです。
特にこだわりが無いのであればusing ll = long long;
のほうが良いと思います。
補足 int
以外もエイリアスしよう
C/C++では環境によってint
、long
、long long
などの型のサイズは違います。
規格で定められているのは決められた文字をすべて収めるのに十分なchar
のサイズを1byteとして、それの整数倍の大きさでchar
short
int
long
long long
の大小関係を持つことだけだったはずなので、int
とlong
が同じ大きさだったりlong
とlong long
が同じ大きさだったりします。
Unix系のOSでは多くがLP64
という規格で、long
が64bitです。AtCoderもこの大きさです。
なので本当は#define int long long
ではなく#define int long
でいいのです。
例えば、ローカルの環境がWindows
ならばlong long
でないと32bitになってしまうこともあると思います。
(無いとは思いますがまだ32bitのWin7とかを使ってるならlong long
じゃないといけません)
なので、すべての整数型を明示的にbit数がわかる形で使った方がいいです。
しかし、そのような型は名前が長くて打ちにくいのでusing
して楽をしようというのがここの趣旨です。
#include <cstdint>
using i8 = int8_t;
using u8 = uint8_t;
using i16 = int16_t;
using u16 = uint16_t;
using i32 = int32_t;
using u32 = uint32_t;
using i64 = int64_t;
using u64 = uint64_t;
using intw = __int128_t;
using uintw = __uint128_t;
#define int long long
#define itn long long
余計なものが混じっているように見えますが、これも必要なんです。ワイドなint
でintw
ですが、これは64bitでもオーバーフローするときに使います。私はオーバーフロー対策なんてできないので、__int128_t
でゴリ押します。異論は認めません。例えばどの問題で使うのかと言うと、典型90問-082 - Counting Numbers(★3)です。d
で持つとします。この時制約でlong long
ではオーバーフローしますが、unsigned long long
ではオーバーフローしません。でもそんな細かい数字覚えてられませんよね?そんなあなたに__int128_t
です。確かにこの問題では必要ないかもしれませんが、あったら考えることが一つ減ります。intw
で宣言してしまえばいいのです。
ただ、__int128_t
と__uint128_t
はstd::cin
,std::cout
で入出力できないので、演算子のオーバーロードを定義する必要があります。
以下に私が使ってるテンプレの、一部をエイリアスをできるだけ除いて置いておきます。
使う場合はグローバルに貼り付けてください。バッファに使ってるstatic char _O128B[128];
がグローバル変数なので……。
// C++20?(もしかしたら23かも)
// #include <bits/stdc++.h> か以下のヘッダを入れる
// #include <concepts> // concept
// #include <type_traits> // is_integral_v<>
// #include <limits> // numeric_limits<>::is_signed
// #include <iterator> // end()
// #include <cstddef> // size_t
// #include <ranges> // views::drop()
// #include <utility> // pair<>
// #include <cassert> // assert()
// #include <fstream> // istream,ostream
// #include <iostream> // cin,cout
// using namespace std;
#define template _TP // 許せ
#define uintw __uint128_t
_TP<class T> concept Lint = is_integral_v<T> && sizeof(T)>8;
static char _O128B[128];
pair<char*, ssize_t> _O128(uintw tmp) {
char*d=end(_O128B);
do {
*(--d) = "0123456789"[tmp % 10];
tmp /= 10;
} while (tmp != 0);
return {d,end(_O128B) - d};
}
_TP<Lint T>
ostream &operator<<(ostream &dst, T val) {
ostream::sentry s(dst);
if (s) {
auto [d, len] = _O128(val < 0 ? -val : val);
if (val < 0)
*(--d) = '-', len++;
if (dst.rdbuf()->sputn(d, len) != len)
dst.setstate(ios_base::badbit);
}
return dst;
}
_TP<Lint T>
istream &operator>>(istream &src, T &val) {
str s;src>>s;
bool is_neg=numeric_limits<T>::is_signed&&s.size()>0&&s[0]=='-';
for(val=0;const auto&x:s|views::drop(is_neg)){
// assert('0'<=x&&x<='9');
val=10*val+x-'0';
}
if (is_neg) val*=-1;
return src;
}
Comments
Let's comment your feelings that are more than good