C#、BigintegerにSI接頭語をつける拡張メソッドを追加

## はじめに
クリッカーゲームなどで使う、桁数が増えた場合に1.0Kの様に途中の桁をSI接頭語に変換する拡張メソッドをC#で作りたかったので、桁数とSIを調査する

## Bigintegerについて
任意の大きさの整数を表すための型であり、以下のような特徴がある

* 任意の大きさの整数: BigInteger は、通常の整数型（int, long など）が持つ固定のサイズ制限を持たないため、非常に大きな整数値も表現できます。変換時に小数点以下は切り捨てられるので注意
```c#
BigInteger assignedFromDouble = (BigInteger) 179032.6541;
Console.WriteLine(assignedFromDouble);
BigInteger assignedFromDecimal = (BigInteger) 64312.65m;
Console.WriteLine(assignedFromDecimal);
// The example displays the following output:
//   179032
//   64312
```
* 動的なサイズ: BigInteger のサイズは動的に変化し、必要に応じてメモリを確保します。このため、非常に大きな数を扱う場合には、それに伴ってメモリ使用量も増加します。typeScriptの
* 不変性: BigInteger は不変型 (immutable) です。したがって、一度 BigInteger オブジェクトが作成されると、その値を変更することはできません。代わりに、新しい BigInteger オブジェクトが返されます。
* パフォーマンス: 通常の整数型と比べると、BigInteger の計算は遅くなり得ます。特に非常に大きな数の演算を行う場合、計算時間やメモリ使用量が増加することを考慮する必要があります。

## SI接頭語とは
> k（キロ）、M（メガ）、G（ギガ）、m（ミリ）、µ（マイクロ）、n（ナノ）など、大きな量あるいは小さな量を端的に記述するために、10のべき乗を表し、SI単位と共に用いられるものをSI接頭語と呼びます。

トピックとして、割と最近(2022年)にクエタ(10^30),ロナ(10^27)が正式に加わっている

| 接頭語 | 記号 | 10n | 十進数表記 | 漢数字表記 | short scale | メートル法への導入年 | 国際単位系における制定年 |
|-------|------|-----|----------|-----------|-------------|----------------------|-------------------------|
| クエタ (quetta) | Q | 10^30 | 1000000000000000000000000000000 | 百穣 | nonillion | 2022年 | - |
| ロナ (ronna) | R | 10^27 | 1000000000000000000000000000 | 千𥝱 | octillion | 2022年 | - |
| ヨタ (yotta) | Y | 10^24 | 1000000000000000000000000 | 一𥝱 | septillion | 1991年 | - |
| ゼタ (zetta) | Z | 10^21 | 1000000000000000000000 | 十垓 | sextillion | 1991年 | - |
| エクサ (exa) | E | 10^18 | 1000000000000000000 | 百京 | quintillion | 1975年 | - |
| ペタ (peta) | P | 10^15 | 1000000000000000 | 千兆 | quadrillion | 1975年 | - |
| テラ (tera) | T | 10^12 | 1000000000000 | 一兆 | trillion | 1960年 | - |
| ギガ (giga) | G | 10^9 | 1000000000 | 十億 | billion | 1960年 | - |
| メガ (mega) | M | 10^6 | 1000000 | 百万 | million | 1874年 | 1960年 |
| キロ (kilo) | k | 10^3 | 1000 | 千 | thousand | 1795年 | 1960年 |

## 以上を踏まえてコーディング
```c#
using System.Numerics;

namespace Sample
{
    /// <summary>
    /// SI接頭語の指数(10^nのnの部分)
    /// </summary>
    public enum SiPrefixesPowerExponent
    {
        Q = 30,
        R = 27,
        Y = 24,
        Z = 21,
        E = 18,
        P = 15,
        T = 12,
        G = 9,
        M = 6,
        K = 3,
    }

    public static class BigIntegerExtensions
    {
        /// <summary>
        /// BigIntegerの桁数を取得する
        /// マイナスの場合は絶対値に変換して桁数を返却する
        /// </summary>
        public static int GetNumberOfDigits(this BigInteger self)
        {
            return BigInteger.Abs(self).ToString().Length;
        }

        /// <summary>
        /// SI接頭語を付けた文字列に変換する
        /// </summary>
        public static string ToSiPrefixString(this BigInteger self)
        {
            var numberOfDigit = self.GetNumberOfDigits();
            var siPrefix = "";
            var displayIntegerLenght = 0; // 実数部分の表示範囲
            var displayDecimalLenght = 2; // 少数部分の表示範囲

            switch (numberOfDigit)
            {
                case < 4:
                    // 1000未満の場合は変換しない
                    return self.ToString();
                case > (int)SiPrefixesPowerExponent.Q + 3:
                    // SI接頭語の範囲外だった場合の表示対応
                    displayIntegerLenght = 1;
                    siPrefix = $" * 10^{numberOfDigit}";
                    break;
                default:
                {
                    var remainder = numberOfDigit % 3;
                    displayIntegerLenght = remainder == 0 ? 3 : remainder;
                    siPrefix = $"{(SiPrefixesPowerExponent)numberOfDigit - displayIntegerLenght}";
                    break;
                }
            }

            var displayString = self
                .ToString()
                .Substring(self.Sign == -1 ? 1 : 0, displayIntegerLenght + displayDecimalLenght) //マイナスの場合は"-"部分を飛ばす
                .Insert(displayIntegerLenght, ".");

            return self > 0 ? $"{displayString}{siPrefix}" : $"-{displayString}{siPrefix}";
        }
    }
}
```

## おわりに
Bigintegerの計算は単純にインクリメントするだけでもそこそこになるらしく、ループ内で使用したい場合などはパフォーマンスに無視できない負荷がかかるらしいので、注意したい

## 参考
https://ja.wikipedia.org/wiki/SI%E6%8E%A5%E9%A0%AD%E8%AA%9E
https://unit.aist.go.jp/nmij/library/SI_prefixes/