比 long long int 還大的存在?探索 128 位元的整數:int128

前陣子寫程式的時候,想要減少 hash 碰撞的機率。
所以想要用比 uint64_t
更大的整數型態來計算。
但又不想用陣列存多個整數然後自己計算。
於是花了點時間研究了 __int128
和 unsigned __int128
。
int128 / uint128
GCC 編譯器內建支援 128 位元有符號整數 和 128 位元無符號整數。
根據查詢到的資料 1,GCC 至少從 4.1.0 版本開始就有支援,但是第一次釋出版本已經不可考了。
不同版本的名稱也不同,最一開始出來有帶 typedef
意思的 _t
結尾,在 4.1.0 版本,其名稱分別為 __int128_t
和 unsigned __int128_t
,從 4.6.0 版本之後,可以使用 __int128
和 unsigned __int128
來宣告了。
雖然 4.6.0 版本之後一樣可以用帶有 _t
的來宣告,但是我在版本 12.1 版本上測試時,發現使用 unsigned __int128_t
來宣告的時候,sizeof 只有 4 bytes,使用 unsigned __int128
時一切正常,因此建議 4.6.0 版本之後都不要使用帶有 _t
的。
以下為建議的使用名稱:
GCC Version | 128 bits integer | unsigned 128 bits integer |
---|---|---|
$<$ 4.6.0 | __int128_t | unsigned __int128_t |
$\geq$ 4.6.0 | __int128 | unsigned __int128 |
以下為範例:
__int128 a;
unsigned __int128 b;
如果喜歡用其他名稱的可以改為:
typedef __int128 INT128;
typedef unsigned __int128 U128;
// or
typedef __int128 int128_t;
typedef unsigned __int128 uint128_t;
如果要確認編譯器有沒有支援,可以用 macro 來測試。
#ifdef __SIZEOF_INT128__
#endif
其餘編譯器 (像是 clang) 的支援,可以參考這篇文章
輸入輸出
由於 std::cin
和 std::cout
並不支援 128 位元的型態,因此我們需要自行撰寫輸出的函式。
輸入
輸入的方式就是直接輸入成字串,再一個 digit 一個 digit 放到 __int128
,另外記得處理負號的部分。
#include <string>
#include <iostream>
using namespace std;
template<typename T>
T str2int(string str) {
T num = 0;
bool is_negative = (str.size() > 0 && str[0] == '-');
for(unsigned i = (is_negative ? 1 : 0); i < str.size(); ++i) {
num *= 10;
num += str[i] - '0';
}
return (is_negative ? -num : num);
}
istream& operator>>(istream &in, __int128 &num){
string str;
in >> str;
num = str2int<typeof(__int128)>(str);
return in;
}
輸出
輸出的方式為先將數字轉成字串 (std::string),然後再給 std::cout
輸出字串,這種方法也考慮到了正負號。
#include <ostream>
#include <string>
#include <vector>
using namespace std;
string decimal_string(__int128 num) {
if(num == 0) return "0";
vector<char> vec;
bool is_negative = num < 0;
__int128 value = is_negative ? -num : num;
while(value != 0){
vec.push_back((value % 10) + '0');
value /= 10;
}
string str = is_negative ? "-" : "";
return str + string(vec.rbegin(), vec.rend());
}
ostream& operator<<(ostream &out, __int128 &num){
out << decimal_string(num);
return out;
}
如果我們不想要每次都拿一個十進位的 digit,可以用 template 自訂成其他基數,這樣就可以一次拿出很多個十進位的 digit。
decimal_string
中,typename T
為被轉換的型態,這邊就是__int128
T base
為拿出來的基數,但因為我們要輸出的是十進位,因此基數也要是 10 的次方數typename Q
是要儲存 digit 的型態,注意型態一定要可以被std::to_string
接受
#include <string>
#include <ostream>
#include <vector>
#include <limits>
using namespace std;
template<typename T, T base, typename Q>
string decimal_string(T num) {
if(num == 0) return "0";
static_assert(numeric_limits<Q>::max() >= base-1);
static_assert(base % 10 == 0);
vector<Q> vec;
bool is_negative = num < 0;
T value = is_negative ? -num : num;
while(value != 0){
vec.push_back(value % base);
value /= base;
}
string str = is_negative ? "-" : "";
for(int i = vec.size()-1; i >= 0; --i){
str += to_string(vec[i]);
}
return str;
}
ostream& operator<<(ostream &out, __int128 &num){
out << decimal_string<typeof(num), 1000000000000000ULL, uint64_t>(num);
return out;
}
Reference
如果你覺得這篇文章有用 可以考慮贊助飲料給大貓咪