2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

KLab EngineerAdvent Calendar 2024

Day 10

インフレゲーム作ろうとした時のC#のBigIntegerについて

Last updated at Posted at 2024-12-09

インフレゲームを作るとき

放置ゲーなどにある数値がインフレして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

差があるときは結構差がでますが、一致するときもある感じ。

まとめ

簡単なゲーム作るにあたってはこれくらいあれば後は標準の計算で足りそうでした。
今回は日本語的な数字表現にしましたが、調整すれば無限に対応できると思うので参考にしていただければ幸いです。

これを使ってゲーム作ってみたので遊んでみてください。

2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?