在實作一些演算法,如 Miller-Rabin 時,會出現 long long 整數相乘再取餘數的步驟。
1 2
longlong a, b, c; auto res = a * b % c;
然而若 a*b 的結果大於 long long 的表達範圍的話會溢位,便無法取得正確的結果。因此會需要自行實做乘法來避免溢位。具體方法可以參考維基百科,簡單來說就是模擬直式乘法:如果每次最多乘以 2,那麼一個 63 bit 的整數可以在 64 bit 的空間內計算出來不會發生溢位。因此實作上用 uint64_t 來儲存即可。
不過絕大多數的 OJ 與競賽編譯器都使用 GCC,而 GCC 帶有非標準的 128 bit 整數 __int128🔗,如果環境允許使用的話,那麼就只需要把 long long轉型到__int128運算即可。
inline ll mul_int128(ll a, ll b, ll m) { ll res = 0; __int128 _a = a; __int128 _b = b; _a = (_a * _b) % m; return _a; }
longlongmul_morris(unsignedlonglong a, unsignedlonglong b, unsignedlonglong mod){ longlong ret = 0; for (a %= mod, b %= mod; b != 0; b >>= 1, a <<= 1, a = a >= mod ? a - mod : a) { if (b&1) { ret += a; if (ret >= mod) ret -= mod; } } return ret; }
uint64_tmul_wiki(uint64_t a, uint64_t b, uint64_t m) { uint64_t d = 0, mp2 = m >> 1; int i; if (a >= m) a %= m; if (b >= m) b %= m; for (i = 0; i < 64; ++i) { d = (d > mp2) ? (d << 1) - m : d << 1; if (a & 0x8000000000000000ULL) d += b; if (d > m) d -= m; a <<= 1; } return d%m; }
#define Test(Expr) \ []() \ { \ std::mt19937_64 mt(7122); \ ll hashv = 0; \ auto TimeStart = std::chrono::high_resolution_clock::now(); \ for (int i = 0; i < 10; ++i) \ for (int j = 0; j < 1000000; ++j) \ { \ ll a = mt() % LLONG_MAX; \ ll b = mt() % LLONG_MAX; \ ll c = 1 + mt() % (LLONG_MAX - 1); \ hashv ^= (Expr); \ } \ auto TimeEnd = std::chrono::high_resolution_clock::now(); \ auto ms = std::chrono::duration_cast<std::chrono::microseconds>(TimeEnd - TimeStart); \ return std::make_tuple(ms.count(), hashv); \ }();