ulongより大きい数値扱えないの??

お久しぶりです、こんにちは〜

更新頻度が本当に低いこのサイトですが、久々にネタが思いついたので記述です〜。

今回は、unityで128bitの数値とか256bitとか、大きな数値扱えないの?と言う、

大半の人はそんな大きな値どこで使うねんwwっていうサイズの数値を扱ってみよう!と言うお話です。

今回使うもの、それは「BigInteger」と言うものになります。

ナニソレ?型(正確には構造体?)です。

using System.Numerics;

を定義すると、使えるようになります。

(※少し前のC#だと使えないみたいなので、出てこない人はバージョンを上げてみてください。。自分は2019.2.9使ってますが、ここでは出てきてます。)

さて、どれくらいの大きさを扱えるのか、見てみましょう。

BigInteger val = 1;
int Loop = 256;
for (int i = 0; i < Loop; i++) {
    val *= 2;
    print(val);
}

普通にいけそうですね。ではLongを1024にしてみますか。。

桁数がすごい。。でもいけてそうですね。(多分)

ならLong=16384ならどうだ・・・?

ここまでくるとループが重い。。。

2のn乗の一の位は、2→4→8→6→2をループするので、

16384 % 4 = 0 (=6)

どうやら正確に表示されているのでは・・・?(メモリどういうやり方してるんだろう。。)

まぁこんな4933桁も扱う変人はそういないと思う(居たらごめんなさい。。)ので、実質全人類が満足するような数値でも扱える、それがBigIntegerです!

まぁそれでおしまい。と言うわけには行かず。これだけ大きな値を扱えるわけですから、何かデメリットがあるはず。色々と検証しましょう。

○少数は扱えるの?

コード:

BigInteger val01 = (BigInteger)0.1;
BigInteger val02 = (BigInteger)0.5;
BigInteger val03 = (BigInteger)0.8;
BigInteger val04 = 3 / 2;
print(val01 + "\n" + val02 + "\n" + val03 + "\n" + val04);

キャスト変換しないと使えないみたいなので、全部キャスト変換してます。

ダメですね。まぁIntegerって名前に入っているのでお察しです。

みた感じ小数点以下は切り捨てなようです。

ちなみに、マイナスでも0方向への切り捨てです。(バッサリ切る感じ。)

○int型とかに変換できるの?

コード:

BigInteger val01 = 1000;
int val02 = (int)val01;
print(val01 + "\n" + val02);
BigInteger val03 = 2147483648; //intMax + 1
int val04 = (int)val03;
print(val03 + "\n" + val04);

こちらもキャストは必要なようです。

あ、はい。一応変換はできますが、オーバーフローしたらエラー吐いて止まります。

なのでtry-catchは必須ですね。

他の型でも同じみたいです。

余談ですが、ulongで実験しようと思った時に、こんなエラーが。

定数として扱うときでも、64bitが最大みたいですね。初知りです。。

○速度は?

ゲームクリエイターなるもの(中にはゲーム以外で使っている方もいますが)、実行速度を気にせずには使えません。おそらくこれが一番大事なのでは。。?

と言うことで検証です。

コード:

BigInteger val01 = 0;
var sw01= new System.Diagnostics.Stopwatch();
sw01.Start();
for (int i = 1; i <= 1000000; i++) { //足し算するのであえて1から
val01 += i;
} sw01.Stop();
print("BigInteger(int plus)"+(sw01.Elapsed.Ticks / 10000f).ToString("f4") + "ms");
BigInteger val02 = 18446744073709551615;
val02 *= 2; //ulongの最大値の2倍
var sw02 = new System.Diagnostics.Stopwatch();
sw02.Start(); 
for (int i = 1; i <= 1000000; i++) { //足し算するのであえて1から 
val02 += i; 
} 
sw02.Stop(); 
print("BigInteger(over_ulong plus)" + (sw02.Elapsed.Ticks / 10000f).ToString("f4") + "ms"); 
int val03 = 0; 
var sw03 = new System.Diagnostics.Stopwatch(); 
sw03.Start(); 
for (int i = 1; i <= 1000000; i++) { //足し算するのであえて1から 
val03 += i; 
} 
sw03.Stop(); 
print("int(int plus)" + (sw03.Elapsed.Ticks / 10000f).ToString("f4") + "ms");

(どうでもいいんですけど、wordpressのバグか知らないんですけどソースコード入力するボタンが一回使うと反応しなくなるのめんどくさいんやが。。)

上から順にBigIntegerのintで扱える範囲での足し算、ulongでも扱えない範囲での足し算、int型で足し算(比較用)です。

速度計算にはSystem.Diagnostics.Stopwatchを使っています。これ何?と言うのは調べてください〜(気が向いたら記事作るかもしれませんが。)

結果は・・・?

え、重たっ!?普通にint型の100倍ありますね。

ちなみにint範囲とover_ulong範囲で実行速度が違いますが、何回やってもこれくらいの速度差なので、多分仕様ですね。

ちなみにulongの最大値の3乗(58桁)から順番に足しても400秒くらいなので、若干遅くなるものの、over_ulongとさほど変わらないので、もしかしたら64bit範囲内での計算は早くなるアルゴリズムが使用されているかもしれないですね。

では続いて四則演算を使ってみます。

コード:

BigInteger val01 = 18446744073709551615;
val01 *= 2; //ulongの最大値の2倍
var sw01= new System.Diagnostics.Stopwatch();
sw01.Start();
for (int i = 1; i <= 100000; i++) { //足し算
	val01 += i;
}
sw01.Stop();
print("BigInteger(plus)"+(sw01.Elapsed.Ticks / 10000f).ToString("f4") + "ms");

BigInteger val02 = val01 + 18446744073709551615;//単純にulong_maxを使用すると、64bit内での計算になるので。
var sw02 = new System.Diagnostics.Stopwatch();
sw02.Start();
for (int i = 1; i <= 100000; i++) { //引き算
	val02 -= i;
}
sw02.Stop();
print("BigInteger(minus)" + (sw02.Elapsed.Ticks / 10000f).ToString("f4") + "ms");



BigInteger val03 = 18446744073709551615;
val02 *= 2;
print(val02);
var sw03 = new System.Diagnostics.Stopwatch();
sw03.Start();
for (int i = 1; i <= 100000; i++) { //掛け算
	val03 *= i;
}
sw03.Stop();
print("BigInteger(multiplication)" + (sw03.Elapsed.Ticks / 10000f).ToString("f4") + "ms");

BigInteger val04 = val03 + 18446744073709551615; //単純にulong_maxを使用すると、64bit内での計算になるので。
var sw04 = new System.Diagnostics.Stopwatch();
sw04.Start();
for (int i = 1; i <= 100000; i++) { //割り算
	val04 /= i;
}
sw04.Stop();
print("BigInteger(division)" + (sw04.Elapsed.Ticks / 10000f).ToString("f4") + "ms");

当たり前ですが10万回掛け算したら64bit余裕で超えるので、今回は64bit超えの足し算を基準に計測していきたいと思います。

(※足し算の時は100万回でやったんですけど、今回は諸事情により10万回ループです)

まぁ掛け算と割り算がそうなりますよね。

でも今回の計算方法では、繰り返し掛け算(割り算)してしまったおかげで桁数がかなり高くなった(16300桁は余裕で超えてます。出力の都合上全桁は表示できないですが。)のが原因なのでは・・・?

と言うことでコードを少し変更。。。

BigInteger val01 = 18446744073709551615;
val01 *= 2; //ulongの最大値の2倍
var sw01= new System.Diagnostics.Stopwatch();
sw01.Start();
for (int i = 1; i <= 100000; i++) { //足し算
	val01 += i;
}
sw01.Stop();
print("BigInteger(plus)"+(sw01.Elapsed.Ticks / 10000f).ToString("f4") + "ms");

//引き算はさっきと変わらないので省略。

BigInteger val03 = 18446744073709551615;
val03 *= 2;
BigInteger ans03 = 0;
var sw03 = new System.Diagnostics.Stopwatch();
sw03.Start();
for (int i = 1; i <= 100000; i++) { //掛け算
	ans03 = val03 * i;
}
sw03.Stop();
val03 += ans03;//不要なコードと認識されて実行されないのを防ぐため。
print("BigInteger(multiplication)" + (sw03.Elapsed.Ticks / 10000f).ToString("f4") + "ms");

BigInteger val04 = 18446744073709551615; //単純にulong_maxを使用すると、64bit内での計算になるので。
val04 *= 18446744073709551615;
BigInteger ans04 = 0;
var sw04 = new System.Diagnostics.Stopwatch();
sw04.Start();
for (int i = 1; i <= 100000; i++) { //割り算
	ans04 = val04 / i;
}
sw04.Stop();
val04 += ans04;//不要なコードと認識されて実行されないのを防ぐため。
print("BigInteger(division)" + (sw04.Elapsed.Ticks / 10000f).ToString("f4") + "ms");

さて、どうなるでしょう。

桁数のせいでした。すみません。

ちなみに割り算が若干遅いのは、割り算だけ割るからと言う理由で初期値(val04)を二倍ではなく二乗しているからですね。ulong maxの100000倍くらいでやるとほぼ変わりません。

と言うことで、実行速度ですが

・intより100倍遅い。

・桁数が上がれば上がるほど速度は落ちていく。

という感じですかね〜。

いくらでも大きい数値は扱えるけど、その分速度は御察しのようです。

今回は以上です!いかがだったでしょう?

まぁそもそも、ulongで満足しないこと自体稀なので、使うことはほぼ無いでしょうが、もし使うことがありましたら参考にしてください〜

また、間違ってたりこれどうなの?とかありましたらコメントしていってくれると助かります・・!

それでは、またね〜





↓もしこの記事が参考になったら、下のいいねボタンを押していただけると非常に助かります!(ログイン不要)↓

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です