インフレゲームを作るとき
放置ゲーなどにある数値がインフレしてintやlongに収まらない数値を使ってゲームを作りたいときありませんか?
各データ型の最大値を確認してみると以下のようになります。
データ型 | 最大値 | だいたいの値 |
---|---|---|
int | 2147483647 | 21.4億 |
uint | 4294967295 | 42.9億 |
long | 9223372036854775807 | 922京 |
ulong | 18446744073709551615 | 1844京 |
decimal | 79228162514264337593543950335 | 7.92穣 |
float | 3.4028235E+38 | 340澗 |
double | 1.7976931348623157E+308 | 1.797E+240無量大数 |
doubleで足りそうじゃんって思いますが、浮動小数の誤差が気になるのでfloatとdoubleは除外しました。
decimalでも穣まで使えますがせっかくなら無量大数まで対応してみたいので、今回の表題であるBigIntegerを使おうと思います。
BigIntegerとは
任意の大きい符号付き整数を表します。
多倍長整数と呼ばれるデータで、無限の大きさの整数を管理できます。
原理などは多倍長整数で調べていただければ情報があると思うのでここでは割愛。
BigIntegerをゲーム上で扱うために欲しい機能
省略されたString取得
UIや演出で数値を出したいが、当然すべての桁を出すわけにもいかないので、ある程度省略した数値情報として変換する処理が必要です。
今回は拡張メソッドとして実装しました。
internal static class Extension
{
private static string[] digitString =
{
"","万", "億", "兆", "京", "垓",
"秭", "穣", "溝", "澗", "正",
"載", "極", "恒河沙", "阿僧祇",
"那由他", "不可思議", "無量大数"
};
const int PunctuateCount = 4;
const int ViewDigit = 4;
/// <summary>
/// 単位付きで表示する文字列に変換
/// </summary>
/// <param name="value">BigInteger</param>
/// <returns>単位付き文字列(有効桁数4桁)</returns>
public static string ToStringShort(this BigInteger value)
{
var digitCount = value.DigitCount();
if (digitCount <= PunctuateCount)
{
return $"{value}";
}
// 無量大数以上
if (digitCount >= 72)
{
// 無量大数で桁を伸ばしていく表示
return $"{value.GetTopDigits(digitCount - 68)}{digitString[17]}";
}
var index = Mathf.CeilToInt(digitCount / (float)PunctuateCount) - 1;
var remainder = digitCount % PunctuateCount;
var viewNum = value.GetTopDigits(ViewDigit);
switch (remainder)
{
case 0: // 9999万・1000万など
return $"{viewNum}{digitString[index]}";
case 1: // 9.99万・1.00万など
return $"{viewNum / 1000:F0}.{viewNum % 1000 / 10:D2}{digitString[index]}";
case 2: // 99.9万・10.0万など
return $"{viewNum / 100:F0}.{viewNum % 100 / 10}{digitString[index]}";
case 3: // 999万・100万など
return $"{viewNum / 10:F0}{digitString[index]}";
default:
return $"{value}";
}
}
/// <summary>
/// 桁数取得
/// </summary>
/// <param name="number">BigInteger</param>
/// <returns>桁数</returns>
public static int DigitCount(this BigInteger number) => (int)Math.Floor(BigInteger.Log10(number) + 1);
/// <summary>
/// 有効桁数の数値取得
/// </summary>
/// <param name="number">BigInteger</param>
/// <param name="digitCount">有効桁数</param>
/// <returns>BigInteger</returns>
public static BigInteger GetTopDigits(this BigInteger number, int digitCount)
{
if (number == 0)
{
return 0;
}
var shiftAmount = number.DigitCount() - digitCount;
return number / BigInteger.Pow(10, shiftAmount);
}
}
これで簡単に省略表示できるようになりました!
new BigInteger(decimal.MaxValue).ToStringShot();
// 7.92穣になる
sqrt(平方根)
数字を軸にしたゲームでバランス調整のために平方根が使いたいときありますよね。
なんとBigIntegerには標準ではsqrtが用意されていません。
自前で平方根を計算する必要がありますが、個人的にはここまで大きい数値の平方根でバランス調整するなら大体の値でいいと思うので計算量を抑えて大体の平方根を取得する処理を用意しました。
public static BigInteger SqrtApproximate(this BigInteger value)
{
if (value < 0) throw new ArgumentException("負の数の平方根は計算できません。");
// nが0または1の場合はnをそのまま返す
if (value == 0 || value == 1) return value;
// 初期値を設定(nのビット長の半分を利用)
var x0 = value >> (int)(value.GetBitLength() / 2);
var x1 = (x0 + value / x0) >> 1;
// ニュートン法の反復計算
while (x1 < x0)
{
x0 = x1;
x1 = (x0 + value / x0) >> 1;
}
return x0;
}
どれくらい大体かというと。
元の値 | 正しい平方根 | 計算結果 |
---|---|---|
10 | 約3.16 | 2 |
100 | 10 | 10 |
1000 | 約31.6 | 31 |
10000 | 100 | 78 |
100000 | 約316 | 316 |
1000000 | 1000 | 976 |
10000000 | 約3162 | 2441 |
100000000 | 10000 | 10000 |
1000000000 | 約31622 | 30517 |
10000000000 | 100000 | 76293 |
100000000000 | 約316227 | 316227 |
差があるときは結構差がでますが、一致するときもある感じ。
まとめ
簡単なゲーム作るにあたってはこれくらいあれば後は標準の計算で足りそうでした。
今回は日本語的な数字表現にしましたが、調整すれば無限に対応できると思うので参考にしていただければ幸いです。
これを使ってゲーム作ってみたので遊んでみてください。