128ビット整数をいい感じに使えるライブラリを作ってみた(C++)
作ってみたとは書いたものの、肝心の入出力の部分はこちらの記事のコードをがっつりコピペをさせていただきました。
この場で感謝します。感謝。
さて、では何を作ったのかというと、入出力に加えて四則演算と剰余を行えるようにしてみました。
それ元々できるよって言われたら実は返す言葉がありません。
なぜ作ったのかというと、struct
やclass
にしたかったという曖昧な動機なので特に深い理由はないです。
#include <bits/stdc++.h> using namespace std; class i128 { private: __int128_t v; public: i128() {} i128(const long long &a) { v = a; } i128(const string &s) { parse(s); } long long long_val() { return (long long)v; } __int128_t val() { return v; } __int128_t abs() { return v < 0 ? -v : v; } void set(const long long &a) { v = a; } void parse(const string &s) { v = 0; for ( char c : s ) { if ( isdigit(c) ) v = v * 10 + (c - '0'); } if ( s[0] == '-' ) v *= -1; } // clang-format off i128 operator+(const i128 &a) { return v + a.v; } i128 operator-(const i128 &a) { return v - a.v; } i128 operator*(const i128 &a) { return v * a.v; } i128 operator/(const i128 &a) { return v / a.v; } i128 operator%(const i128 &a) { return v % a.v; } void operator+=(const i128 &a) { v += a.v; } void operator-=(const i128 &a) { v -= a.v; } void operator*=(const i128 &a) { v *= a.v; } void operator/=(const i128 &a) { v /= a.v; } void operator%=(const i128 &a) { v %= a.v; } template <typename T> i128 operator+(const T &a) { i128 res = *this; res.v += a; return res; } template <typename T> i128 operator-(const T &a) { i128 res = *this; res.v -= a; return res; } template <typename T> i128 operator*(const T &a) { i128 res = *this; res.v *= a; return res; } template <typename T> i128 operator/(const T &a) { i128 res = *this; res.v /= a; return res; } template <typename T> i128 operator%(const T &a) { i128 res = *this; res.v %= a; return res; } template <typename T> void operator+=(const T &a) { v += a; } template <typename T> void operator-=(const T &a) { v -= a; } template <typename T> void operator*=(const T &a) { v *= a; } template <typename T> void operator/=(const T &a) { v /= a; } template <typename T> void operator%=(const T &a) { v %= a; } template <typename T> i128 operator-() { i128 res = *this; res.v *= -1; return res; } bool operator<(const i128 &a) { return v < a.v; } bool operator>(const i128 &a) { return v > a.v; } bool operator<=(const i128 &a) { return v <= a.v; } bool operator>=(const i128 &a) { return v >= a.v; } bool operator==(const i128 &a) { return v == a.v; } bool operator!=(const i128 &a) { return v != a.v; } template <typename T> bool operator<(const T &a) { return v < a; } template <typename T> bool operator>(const T &a) { return v > a; } template <typename T> bool operator<=(const T &a) { return v <= a; } template <typename T> bool operator>=(const T &a) { return v >= a; } template <typename T> bool operator==(const T &a) { return v == a; } template <typename T> bool operator!=(const T &a) { return v != a; } // clang-format on friend istream &operator>>(istream &is, i128 &v) { string s; is >> s; v.parse(s); return is; } friend ostream &operator<<(ostream &os, const i128 &v) { if ( !ostream::sentry(os) ) return os; char buf[64]; char *d = end(buf); __uint128_t tmp = (v.v < 0 ? -v.v : v.v); do { d--; *d = char(tmp % 10 + '0'); tmp /= 10; } while ( tmp ); if ( v.v < 0 ) { d--; *d = '-'; } int len = end(buf) - d; if ( os.rdbuf()->sputn(d, len) != len ) { os.setstate(ios_base::badbit); } return os; } };
int main() { i128 n; cin >> n; n *= 100100100100; cout << n << endl; }
int main() { string s = "100100100100100"; i128 n(s); cout << n * 102010 << endl; }
あとがき
冷静になって考えてみると、やっぱり使い道がないような...?
入出力の部分だけべた貼りすればよい気がしてきたけど気にしない。
vectorと単項の演算を作ってみた
0-indexedのものを1-indexedにして出力したいなぁってなったときにわざわざループを回して+1したりvalarrayにおきかえたりするのはひと手間がかかると感じたので演算子を作った
operator+=
template <typename T> void operator+=(vector<T> &vs, T x){ for(T &v:vs) v += x; }
他の四則演算もほとんど同じ
operator+
template <typename T> vector<T> operator+(const vector<T> &vs, T x){ vector<T> res=vs; for(T &v:res) v += x; return res; }
他の四則演算もほとんど同じ
完成形
template <typename T> void operator+=(vector<T> &vs, T x){ for(T &v:vs) v += x; } template <typename T> void operator-=(vector<T> &vs, T x){ for(T &v:vs) v -= x; } template <typename T> void operator*=(vector<T> &vs, T x){ for(T &v:vs) v *= x; } template <typename T> void operator/=(vector<T> &vs, T x){ for(T &v:vs) v /= x; } template <typename T> void operator%=(vector<T> &vs, T x){ for(T &v:vs) v %= x; } template <typename T> vector<T> operator+(const vector<T> &vs, T x){ vector<T> res=vs; for(T &v:res) v += x; return res; } template <typename T> vector<T> operator-(const vector<T> &vs, T x){ vector<T> res=vs; for(T &v:res) v -= x; return res; } template <typename T> vector<T> operator*(const vector<T> &vs, T x){ vector<T> res=vs; for(T &v:res) v *= x; return res; } template <typename T> vector<T> operator/(const vector<T> &vs, T x){ vector<T> res=vs; for(T &v:res) v /= x; return res; } template <typename T> vector<T> operator%(const vector<T> &vs, T x){ vector<T> res=vs; for(T &v:res) v %= x; return res; }
template <typename T> void operator+=(vector<T> &vs, T x) { for ( T &v : vs ) v += x; } template <typename T> void operator-=(vector<T> &vs, T x) { for ( T &v : vs ) v -= x; } template <typename T> void operator*=(vector<T> &vs, T x) { for ( T &v : vs ) v *= x; } template <typename T> void operator/=(vector<T> &vs, T x) { for ( T &v : vs ) v /= x; } template <typename T> void operator%=(vector<T> &vs, T x) { for ( T &v : vs ) v %= x; } template <typename T> vector<T> operator+(const vector<T> &vs, T x) { vector<T> res = vs; for ( T &v : res ) v += x; return res; } template <typename T> vector<T> operator-(const vector<T> &vs, T x) { vector<T> res = vs; for ( T &v : res ) v -= x; return res; } template <typename T> vector<T> operator*(const vector<T> &vs, T x) { vector<T> res = vs; for ( T &v : res ) v *= x; return res; } template <typename T> vector<T> operator/(const vector<T> &vs, T x) { vector<T> res = vs; for ( T &v : res ) v /= x; return res; } template <typename T> vector<T> operator%(const vector<T> &vs, T x) { vector<T> res = vs; for ( T &v : res ) v %= x; return res; }
あとがき
これでほんの少し便利になったと思う
参考
デバッグ用の可変長引数関数を作ってみた(C++)
目標の形
int main(){ int abc=10; int hoge=5; string str="aiueo"; debug(abc,hoge,str); } // 出力結果 abc:10 hoge:5 str:aiueo
debugはマクロでも関数でもなんでも良いから変数名とその値を空白区切りで出力してほしかった(できれば標準エラー出力)
結論
#define debug(...) debug_func(0, #__VA_ARGS__, __VA_ARGS__) template <typename T> void debug_func(int i, T name) { cerr << endl; } template <typename T1, typename T2, typename... T3> void debug_func(int i, const T1 &name, const T2 &a, const T3 &...b) { for ( ; name[i] != ',' && name[i] != '\0'; i++ ) cerr << name[i]; cerr << ":" << a << " "; debug_func(i + 1, name, b...); }
2つの関数とマクロを定義することで実現できた
改行の手前に余分な空白が入るのはまあデバッグ用だし...
それぞれの説明
まずは
#define debug(...) debug_func(0, #__VA_ARGS__, __VA_ARGS__)
から
これは可変長引数をマクロで読み取っている
なんでマクロなのかというと変数名を表示するのに#
演算子が必要だったから
※#
演算子で変数名を文字列として取得できる
例として、debug(a,bb,ccc)
と呼び出すと、"a,bb,ccc"
という文字列が取得できるので変数名を出力するときは,
を読み飛ばす必要がある
次に
template <typename T> void debug_func(int cnt, T name) { cerr << endl; }
これはdebug_func
の終了のための関数
最後の関数と名前は同じだけど引数が違う
再帰的に呼び出しながら出力していって、最後に改行するという流れ
最後に
template <typename T1, typename T2, typename... T3> void debug_func(int i, const T1 &name, const T2 &a, const T3 &...b) { for ( ; name[i] != ',' && name[i] != '\0'; i++ ) cerr << name[i]; cerr << ":" << a << " "; debug_func(i + 1, name, b...); }
この関数を見た人は全員次のような疑問ですえ!?ってなる(と思う)
Q.#define debug(...) debug_func(0, #__VA_ARGS__, __VA_ARGS__)
だと引数が3つなのにこれは引数が4つだから間違ってるんじゃないか?
A.間違ってないです
実は可変長引数を関数の引数に取るときは...
(pack演算子)を使える
pack演算子は前につけるか後につけるかで変わってくる
前につける
int ...b
可変長引数を一つの変数に受け取る後につける
b...
変数から可変長引数を取り出す
可変長引数の内、一つを変数に入れて残りをpack演算子でもう一つの変数にまとめているイメージ
int int_sum() { return 0; } template <typename T1, typename... T2> int int_sum(const T1 &a, const T2&... b){ return a + int_sum(b...); } int main(){ cout<<int_sum(10,20,5)<<endl; } // 結果 35
また、下記のように#define sum(...) int_sum(__VA_ARGS__)
のようにして呼び出しても良い
#define sum(...) int_sum(__VA_ARGS__) int int_sum() { return 0; } template <typename T1, typename... T2> int int_sum(const T1 &a, const T2&... b){ return a + int_sum(b...); } int main(){ cout<<sum(10,20,5)<<endl; }
余談
for ( ; name[i] != ',' && name[i] != '\0'; i++ ) cerr << name[i];
↑この部分は変数名が○○,○○○,....
という風にコンマ区切りで渡されるのでコンマまで一文字ずつ出力している
name
の型を調べてみるとconst char*
だったので最後に\0
がある
#include <iostream> #include <typeinfo> using namespace std; #define debug(...) debug_func(0, #__VA_ARGS__, __VA_ARGS__) template <typename T> void debug_func(int i, T name) { cerr << endl; cerr << "typename:" << typeid(name).name() << endl; cerr << "const char* same?:" << boolalpha << (typeid(name) == typeid(const char *)) << endl; } template <typename T1, typename T2, typename... T3> void debug_func(int i, const T1 &name, const T2 &a, const T3 &...b) { for ( ; name[i] != ',' && name[i] != '\0'; i++ ) cerr << name[i]; cerr << ":" << a << " "; debug_func(i + 1, name, b...); } int main() { int hoge = 10; int foo = 101391; long long hofoo = 101010; debug(hoge, foo, hofoo); }
hoge:10 foo:101391 hofoo:101010 typename:PKc const char* same?:true
typename
がPKc
となっているのはP
がポインタ、K
がconst
、c
がchar
を表していて、mangled nameと呼ばれているらしい
参考
詳しくはこちらをご覧ください
goto文は使ってもいいんじゃないかと思い始めてきた話
goto文は多重ループから抜け出す場合は使用することを推奨されている
でもほんとにgoto文を使ってもいいのか?とおもって多重ループを抜け出すときの別の案を考えてみた
条件を満たしたらたくさんbreak
int n=10; bool flg=false; for(int i=0;i<n;i++){ for(int j=0;j<n;j++){ for(int k=0;k<n;k++){ if(i+j+k==n){ cout<<i<<" "<<j<<" "<<k<<endl; flg=true; } if(flg) break; } if(flg) break; }
うーん、微妙な気がする
breakを書く手間とそのための変数を追加するのがもったいない
ラムダ式の形にまとめる
int n=10; [&]{ for(int i=0;i<n;i++){ for(int j=0;j<n;j++){ for(int k=0;k<n;k++){ if(i+j+k==n){ cout<<i<<" "<<j<<" "<<k<<endl; return; } } } }();
これ始めて知った時はなるほど~ってなったけど階層が一段深くなるのが若干悩ましい気もする
ただ、breakに比べたらこちらの方が良い
やっぱりgoto
int n=10; for(int i=0;i<n;i++){ for(int j=0;j<n;j++){ for(int k=0;k<n;k++){ if(i+j+k==n){ cout<<i<<" "<<j<<" "<<k<<endl; goto EXIT_MULTI_LOOP; } } } EXIT_MULTI_LOOP:
追加するのが2行だけで、脳死で追加できるのでやっぱりgoto文がよいのでは?
C++ - ラムダ式を流用して多重ループから抜ける。有り?無し?|teratail
色んなとこで議論がなされているけどgoto文を使う人が多そう
結論
自分が納得しているものを使うのが一番!!!!!!
string::nposについて(c++)
知らずに使ってた
string::nposはstring型のfindで見つからなかったときに返ってくる値です。
int main(){ string s = "aaa"; if(s.find('b') == string::npos) cout << "Not found" << endl; }
Not found
この前、int型にstring::nposを入れたら-1だったので、おかしいなぁーって思ってたので実験してみました。
int main(){ bitset<100> bit(string::npos); if(string::npos==INT64_MAX) cout<<"Yes"<<endl; if(string::npos==UINT64_MAX) cout<<"OhYes"<<endl; cout<<bit<<endl; }
OhYes 0000000000000000000000000000000000001111111111111111111111111111111111111111111111111111111111111111
int型の-1は全ビットが1なのでint型に入れたら-1になるよねー。
using namespace std;を使わずに指定したものだけstd::を外す(c++)
using namespace std;はよろしくないから使いたくないけど、std::coutとかって書くのが嫌だなってとき
#include <bits/stdc++.h> using std::cin; using std::cout; using std::endl; int main(){ int n; cin >> n; cout << n << endl; }
これでよし