C#格式任意大的BigInteger为无尽的游戏

时间:2016-06-19 12:49:36

标签: c# formatting biginteger procedural-generation

我正在尝试创建一个无尽的游戏,如Tap Titans,Clicker Heroes等。我有一个BigInteger类,只要它们适合内存就能够表示任意大整数。

现在我有一个将BigInteger格式化为特定格式的类。它使用K(千),M(百万),B(十亿),T(万亿),Q(千万亿)用于“小”和“小”。数字,但在此之后,简写符号变得模棱两可,不直观。由于Quintillion,Q已经模糊不清了,但我可以接受。

Q之后,我想从字母a开始。因此1000Q = 1.000a,然后1000a = 1.000b等。当达到1000z时,应将其格式化为1.000aa。然后1000aa = 1.000 ab,1000 az = 1.000 ba,1000 bz = 1.000 ca等。

到目前为止,我已经实现了上述目标,但是我的班级无法在1000zz之后格式化数字。我无法想出一个自动确定需要多少个字符的通用算法(对于极大数字可能是aaaz)。

我的课程如下:

public class NumericalFormatter : BigIntegerFormatter
{
    public string Format(BigInteger number)
    {
        return FormatNumberString(number.ToString());
    }

    private string FormatNumberString(string number)
    {
        if (number.Length < 5)
        {
            return number;
        }

        if (number.Length < 7)
        {
            return FormatThousands(number);
        }

        return FormatGeneral(number);
    }

    private string FormatThousands(string number)
    {
        string leadingNumbers = number.Substring(0, number.Length - 3);
        string decimals = number.Substring(number.Length - 3);

        return CreateNumericalFormat(leadingNumbers, decimals, "K");
    }

    private string CreateNumericalFormat(string leadingNumbers, string decimals, string suffix)
    {
        return String.Format("{0}.{1}{2}", leadingNumbers, decimals, suffix);
    }

    private string FormatGeneral(string number)
    {
        int amountOfLeadingNumbers = (number.Length - 7) % 3 + 1;
        string leadingNumbers = number.Substring(0, amountOfLeadingNumbers);
        string decimals = number.Substring(amountOfLeadingNumbers, 3);

        return CreateNumericalFormat(leadingNumbers, decimals, GetSuffixForNumber(number));
    }

    private string GetSuffixForNumber(string number)
    {
        int numberOfThousands = (number.Length - 1) / 3;

        switch (numberOfThousands)
        {
            case 1:
                return "K";
            case 2:
                return "M";
            case 3:
                return "B";
            case 4:
                return "T";
            case 5:
                return "Q";
            default:
                return GetProceduralSuffix(numberOfThousands - 5);
        }
    }

    private string GetProceduralSuffix(int numberOfThousandsAfterQ)
    {
        if (numberOfThousandsAfterQ < 27)
        {
            return ((char)(numberOfThousandsAfterQ + 96)).ToString();
        }

        int rightChar = (numberOfThousandsAfterQ % 26);
        string right = rightChar == 0 ? "z" : ((char)(rightChar + 96)).ToString();
        string left = ((char)(((numberOfThousandsAfterQ - 1) / 26) + 96)).ToString();

        return left + right;
    }
}

正如您所看到的,getProceduralSuffix()方法无法处理会导致两个以上字符后缀的BigIntegers。

我还有一个单元测试来验证这个类的功能(准备一些侧滚动):

namespace UnitTestProject.BigIntegerTest
{
    [TestClass]
    public class NumericalformatterTest
    {
        [TestMethod]
        public void TestFormatReturnsNumericalFormat()
        {
            BigIntegerFormatter numericalFormatter = new NumericalFormatter();

            foreach (string[] data in DpNumbersAndNumericalFormat())
            {
                BigInteger number = new BigInteger(data[0]);
                string expectedNumericalFormat = data[1];

                Assert.AreEqual(expectedNumericalFormat, numericalFormatter.Format(number));
            }
        }

        private string[][] DpNumbersAndNumericalFormat()
        {
            return new string[][]
            {
                new string[] { "0", "0" },
                new string[] { "1", "1" },
                new string[] { "15", "15" },
                new string[] { "123", "123" },
                new string[] { "999", "999" },
                new string[] { "1000", "1000" },
                new string[] { "9999", "9999" },
                new string[] { "10000", "10.000K" },
                new string[] { "78456", "78.456K" },
                new string[] { "134777", "134.777K" },
                new string[] { "999999", "999.999K" },
                new string[] { "1000000", "1.000M" },
                new string[] { "12345000", "12.345M" },
                new string[] { "999999000", "999.999M" },
                new string[] { "1000000000", "1.000B" },
                new string[] { "12345678900", "12.345B" },
                new string[] { "123345678900", "123.345B" },
                new string[] { "1233000000000", "1.233T" },
                new string[] { "9999000000000", "9.999T" },
                new string[] { "12233000000000", "12.233T" },
                new string[] { "99999000000000", "99.999T" },
                new string[] { "100000000000000", "100.000T" },
                new string[] { "456789000000000", "456.789T" },
                new string[] { "999999000000000", "999.999T" },
                new string[] { "1000000000000000", "1.000Q" },
                new string[] { "10000000000000000", "10.000Q" },
                new string[] { "100000000000000000", "100.000Q" },
                new string[] { "999999000000000000", "999.999Q" },
                new string[] { "1000000000000000000", "1.000a" },
                new string[] { "10000000000000000000", "10.000a" },
                new string[] { "100000000000000000000", "100.000a" },
                new string[] { "1000000000000000000000", "1.000b" },
                new string[] { "1000000000000000000000000", "1.000c" },
                new string[] { "1000000000000000000000000000", "1.000d" },
                new string[] { "1000000000000000000000000000000", "1.000e" },
                new string[] { "1000000000000000000000000000000000", "1.000f" },
                new string[] { "1000000000000000000000000000000000000", "1.000g" },
                new string[] { "1000000000000000000000000000000000000000", "1.000h" },
                new string[] { "1000000000000000000000000000000000000000000", "1.000i" },
                new string[] { "1000000000000000000000000000000000000000000000", "1.000j" },
                new string[] { "1000000000000000000000000000000000000000000000000", "1.000k" },
                new string[] { "1000000000000000000000000000000000000000000000000000", "1.000l" },
                new string[] { "1000000000000000000000000000000000000000000000000000000", "1.000m" },
                new string[] { "1000000000000000000000000000000000000000000000000000000000", "1.000n" },
                new string[] { "1000000000000000000000000000000000000000000000000000000000000", "1.000o" },
                new string[] { "1000000000000000000000000000000000000000000000000000000000000000", "1.000p" },
                new string[] { "1000000000000000000000000000000000000000000000000000000000000000000", "1.000q" },
                new string[] { "1000000000000000000000000000000000000000000000000000000000000000000000", "1.000r" },
                new string[] { "1000000000000000000000000000000000000000000000000000000000000000000000000", "1.000s" },
                new string[] { "1000000000000000000000000000000000000000000000000000000000000000000000000000", "1.000t" },
                new string[] { "1000000000000000000000000000000000000000000000000000000000000000000000000000000", "1.000u" },
                new string[] { "1000000000000000000000000000000000000000000000000000000000000000000000000000000000", "1.000v" },
                new string[] { "1000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "1.000w" },
                new string[] { "1000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "1.000x" },
                new string[] { "1000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "1.000y" },
                new string[] { "1000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "1.000z" },
                new string[] { "1000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "1.000aa" },
                new string[] { "1000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "1.000ab" },
                new string[] { "1000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "1.000ac" },
                new string[] { "1000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "1.000ad" },
                new string[] { "1000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "1.000ae" },
                new string[] { "1000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "1.000af" },
                new string[] { "1000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "1.000ag" },
                new string[] { "1000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "1.000ah" },
                new string[] { "1000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "1.000ai" },
                new string[] { "1000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "1.000aj" },
                new string[] { "1000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "1.000ak" },
                new string[] { "1000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "1.000al" },
                new string[] { "1000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "1.000am" },
                new string[] { "1000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "1.000an" },
                new string[] { "1000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "1.000ao" },
                new string[] { "1000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "1.000ap" },
                new string[] { "1000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "1.000aq" },
                new string[] { "1000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "1.000ar" },
                new string[] { "1000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "1.000as" },
                new string[] { "1000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "1.000at" },
                new string[] { "1000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "1.000au" },
                new string[] { "1000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "1.000av" },
                new string[] { "1000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "1.000aw" },
                new string[] { "1000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "1.000ax" },
                new string[] { "1000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "1.000ay" },
                new string[] { "1000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "1.000az" },
                new string[] { "1000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "1.000ba" },
                new string[] { "1000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "1.000bb" },
                new string[] { "1000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "1.000bc" },
                new string[] { "1000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "1.000bd" },
                new string[] { "1000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "1.000be" },
                new string[] { "1000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "1.000bf" },
                new string[] { "1000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "1.000bg" },
                new string[] { "1000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "1.000bh" },
                new string[] { "1000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "1.000bi" },
                new string[] { "1000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "1.000bj" },
                new string[] { "1000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "1.000bt" },
                new string[] { "1000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "1.000by" },
                new string[] { "1000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "1.000bz" },
                new string[] { "1000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "1.000ca" },
                new string[] { "1000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "1.000cb" },
                new string[] { "1000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "1.000cc" },
                new string[] { "1000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "1.000cd" },
                new string[] { "1000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "1.000ce" },
                new string[] { "1000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "1.000ct" },
                new string[] { "1000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "1.000cy" },
                new string[] { "1000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "1.000cz" },
                new string[] { "1000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "1.000da" },
                new string[] { "1234000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "1.234da" },
                new string[] { "123456000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "123.456da" },
                new string[] { "1000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "1.000db" },
                new string[] { "1000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "1.000dr" },
                new string[] { "1000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "1.000dz" },
            };
        }
    }
}

此时所有的测试都通过了。缺少的测试是检查1000 ^ (26 ^ 3 + 5)(+5是因为a格式化开始后Q)可以格式化为&#34; 1.000aaa&#34;的测试。

如何按照上面描述的方式编写程序大整数的格式。

2 个答案:

答案 0 :(得分:1)

在数字符号的世界中,这实际上是一个已解决的问题。也就是说,您可以使用scientific notation来表示这些特别大的数字。科学记数法是紧凑的,允许尾数的任意精度,并且易于理解。就个人而言,这就是我采取的方法。

为了便于讨论,让我们看看你还有其他选择......


从表面上看,您的请求可归结为文本转换的直接数字基值。正如我们可以将数值转换为其文本表示形式,例如base 2,base 10,base 16等。我们可以使用base 26将数值转换为文本表示,只使用字母{{1} }通过a作为数字。

然后你的z方法看起来像这样:

GetProceduralSuffix()

static string GetProceduralSuffix(int value) { StringBuilder sb = new StringBuilder(); while (value > 0) { int digit = value % 26; sb.Append((char)('a'+ digit)); value /= 26; } if (sb.Length == 0) { sb.Append('a'); } sb.Reverse(); return sb.ToString(); } 扩展方法是这样的:

Reverse()

然而,上述问题存在轻微问题。在以这种方式表示的基数26中,数字public static void Reverse(this StringBuilder sb) { for (int i = 0, j = sb.Length - 1; i < sb.Length / 2; i++, j--) { char chT = sb[i]; sb[i] = sb[j]; sb[j] = chT; } } 对应于a,因此您的后缀将永远不会以字母0开头,至少不会在第一个字母a之后(&#39;特殊情况,就像使用十进制表示法时一样,我们使用数字0来表示零值)。相反,您可以在bazbaa之后zz获得aaaz

就个人而言,我认为这很好。它会排除像a这样的后缀,但仅仅因为后缀符号系统是合乎逻辑的,可预测的,并且容易被逆转(即给出后缀,它可以很容易地弄清楚数字意味着什么)。

但是,如果您坚持使用z ... aazz ... aaazzz ... aaaa,{{ 1}} ...依此类推,您可以使用基数27而不是26,a ... z以外的字符作为0数字,并预先计算跳过后缀的值你去了一个0位数,然后索引结果。例如:

List<string> _hackedValues = new List<string>();

static void PrecomputeValues()
{
    // 531441 = 27 ^ 4, i.e. the first 5-digit base 27 number.
    // That's a large enough number to ensure that the output
    // include "aaaz", and indeed almost all of the 4-digit
    // base 27 numbers
    for (int i = 0; i < 531441; i++)
    {
        string text = ToBase27AlphaString(i);

        if (!text.Contains('`'))
        {
            _hackedValues.Add(text);
        }
    }
}

static string GetProceduralSuffix(int value)
{
    if (hackedValues.Count == 0)
    {
        PrecomputeValues();
    }

    return _hackedValues[value];
}

static string ToBase27AlphaString(int value)
{
    StringBuilder sb = new StringBuilder();

    while (value > 0)
    {
        int digit = value % 27;

        sb.Append((char)('`'+ digit));
        value /= 27;
    }

    if (sb.Length == 0)
    {
        sb.Append('`');
    }

    sb.Reverse();
    return sb.ToString();
}

这是一个完整的程序,用于说明这两种技术,显示&#34;有趣的&#34;输入(即输出中字符数改变的地方):

class Program
{
    static void Main(string[] args)
    {
        int[] values = { 0, 25, 26, 675, 676 };

        foreach (int value in values)
        {
            Console.WriteLine("{0}: {1}", value, ToBase26AlphaString(value));
        }

        Console.WriteLine();

        List<Tuple<int, string>> hackedValues = new List<Tuple<int, string>>();

        for (int i = 0; i < 531441; i++)
        {
            string text = ToBase27AlphaString(i);

            if (!text.Contains('`'))
            {
                hackedValues.Add(Tuple.Create(i, text));
            }
        }

        Tuple<int, string> prev = null;

        for (int i = 0; i < hackedValues.Count; i++)
        {
            Tuple<int, string> current = hackedValues[i];

            if (prev == null || prev.Item2.Length != current.Item2.Length)
            {
                if (prev != null)
                {
                    DumpHackedValue(prev, i - 1);
                }
                DumpHackedValue(current, i);
            }

            prev = current;
        }
    }

    private static void DumpHackedValue(Tuple<int, string> hackedValue, int i)
    {
        Console.WriteLine("{0}: {1} (actual value: {2})", i, hackedValue.Item2, hackedValue.Item1);
    }

    static string ToBase26AlphaString(int value)
    {
        return ToBaseNAlphaString(value, 'a', 26);
    }

    static string ToBase27AlphaString(int value)
    {
        return ToBaseNAlphaString(value, '`', 27);
    }

    static string ToBaseNAlphaString(int value, char baseChar, int numericBase)
    {
        StringBuilder sb = new StringBuilder();

        while (value > 0)
        {
            int digit = value % numericBase;

            sb.Append((char)(baseChar + digit));
            value /= numericBase;
        }

        if (sb.Length == 0)
        {
            sb.Append(baseChar);
        }

        sb.Reverse();
        return sb.ToString();
    }
}

static class Extensions
{
    public static void Reverse(this StringBuilder sb)
    {
        for (int i = 0, j = sb.Length - 1; i < sb.Length / 2; i++, j--)
        {
            char chT = sb[i];

            sb[i] = sb[j];
            sb[j] = chT;
        }
    }
}

答案 1 :(得分:1)

经过长时间的思考(实际上避免了一个月的工作)和几个小时的编码,我用了一部分代码来创建自己的解决方案。

按顺序使用前缀:空字符串,k,M,B,Q,a,b ... z (不包括k,因为成千上万),aa,bb,..., zz,aaa,bbb,...,zzz等。它从数字的末尾修剪零,例如1000 = 1k。

(也有可能使用科学计数法,但它不会修剪零。)

using System.Collections.Generic;
using System.Numerics;

/// <summary>
/// Static class used to format the BigIntegers.
/// </summary>
public static class BigIntegerFormatter
{
    private static List<string> suffixes = new List<string>();

    /// <summary>
    /// If it's equal to 0, there are only suffixes from an empty string to Q on the suffixes list.
    /// If it's equal to 1, there are a - z suffixes added.
    /// If it's equal to 2, there are aa - zz suffixes added and so on.
    /// </summary>
    private static int suffixesCounterForGeneration = 0;

    /// <summary>
    /// Formats BigInteger using scientific notation. Returns a number without the exponent if the length
    /// of the number is smaller than 4.
    /// </summary>
    /// <param name="number">Number to format.</param>
    /// <returns>Returns string that contains BigInteger formatted using scientific notation.</returns>
    public static string FormatScientific(BigInteger number)
    {
        return FormatNumberScientificString(number.ToString());
    }

    /// <summary>
    /// Formats BigInteger using engineering notation - with a suffix. Returns a number without the
    /// suffix if the length of the number is smaller than 4.
    /// </summary>
    /// <param name="number">Number to format.</param>
    /// <returns>Returns string that contains BigInteger formatted using engineering notation.</returns>
    public static string FormatWithSuffix(BigInteger number)
    {
        return FormatNumberWithSuffixString(number.ToString());
    }

    private static string FormatNumberScientificString(string numberString)
    {
        // if number length is smaller than 4, just returns the number
        if (numberString.Length < 4) return numberString;

        // Exponent counter. E.g. for 1000 it will be 3 and the number will
        // be presented as 1.000e3 because 1000.Length = 4
        var exponent = numberString.Length - 1;

        // Digit before a comma. Always only one.
        var leadingDigit = numberString.Substring(0, 1);

        // Digits after a comma. Always three of them.
        var decimals = numberString.Substring(1, 3);

        // Returns the number in scientific format. 
        // Example: 12345 -> 1.234e4
        return $"{leadingDigit}.{decimals}e{exponent}";
    }

    private static string FormatNumberWithSuffixString(string numberAsString)
    {
        // if number length is smaller than 4, just returns the number
        if (numberAsString.Length < 4) return numberAsString;

        // Counts scientific exponent. This will be used to determine which suffix from the 
        // suffixes List should be used. 
        var exponentIndex = numberAsString.Length - 1;

        // Digits before a comma. Can be one, two or three of them - that depends on the exponentsIndex.
        var leadingDigit = "";

        // Digits after a comma. Always three of them or less, if the formatted number will have zero 
        // on its end.
        var decimals = "";

        // Example: if the number the methods is formatting is 12345, exponentsIndex is 4, 4 % 3 = 1. 
        // There will be two leading digits. There will be three decimals. Formatted number will look like:
        // 12.345k
        switch (exponentIndex % 3)
        {
            case 0:
                leadingDigit = numberAsString.Substring(0, 1);
                decimals = numberAsString.Substring(1, 3);
                break;

            case 1:
                leadingDigit = numberAsString.Substring(0, 2);
                decimals = numberAsString.Substring(2, 3);
                break;

            case 2:
                leadingDigit = numberAsString.Substring(0, 3);
                decimals = numberAsString.Substring(3, 3);
                break;
        }

        // Trims zeros from the number's end.
        var numberWithoutSuffix = $"{leadingDigit}.{decimals}";
        numberWithoutSuffix = numberWithoutSuffix.TrimEnd('0').TrimEnd('.');

        var suffix = GetSuffixForNumber(exponentIndex / 3);

        // Returns number in engineering format.
        // return $"{numberWithoutSuffix}{suffixes[exponentIndex / 3]}";

        return $"{numberWithoutSuffix}{suffix}";
    }

    /// <summary>
    /// Gets suffix under a given index which is actually a number of thousands.
    /// </summary>
    /// <param name="suffixIndex">Suffix index. Number of thousands.</param>
    /// <returns>Suffix under a given index - suffix for a given number of thousands.</returns>
    private static string GetSuffixForNumber(int suffixIndex)
    {
        // Creates initial suffixes List with an empty string, k, M, B and Q
        if (suffixes.Count == 0) suffixes = CreateSuffixesList();

        // Fills the suffixes list if there's a need to
        if (suffixes.Count - 1 < suffixIndex) FillSuffixesList(suffixes, suffixIndex);

        return suffixes[suffixIndex];
    }

    private static List<string> CreateSuffixesList()
    {
        var suffixesList = new List<string>
        {
            "", "k", "M", "B", "Q"
        };

        return suffixesList;
    }

    private static void FillSuffixesList(List<string> suffixesList, int suffixIndex)
    {
        // while the suffixes list length - 1 is smaller than the suffix index of the suffix that we need
        // (e.g.: when there's a need for an 'a' suffix:
        // when suffixesList = "", "k", "M", "B", "Q"
        // suffixesList.Count = 5, suffixIndex for a 'Q' is 4,
        // suffixIndex for an 'a' is 5)
        while (suffixesList.Count - 1 < suffixIndex)
        {
            // happens only once, when suffixList is filled only with 
            // initial values
            if (suffixesCounterForGeneration == 0)
            {
                for (int i = 97; i <= 122; i++)
                {
                    // k excluded because of thousands suffix
                    if (i == 107) continue;

                    // cache the character a - z
                    char character = (char)i;
                    suffixesList.Add(char.ToString(character));
                }

                suffixesCounterForGeneration++;
            }
            else
            {
                // for every character (a - z) counts how many times the character should be generated as the suffix
                for (var i = 97; i <= 122; i++)
                {
                    // cache the character a - z
                    char character = (char)i;

                    // placeholder for a generated suffix
                    string generatedSuffix = "";

                    // counts how many times one character should be used as one suffix and adds them
                    // basing on the suffixesCounterForGeneration which is the number telling us how many times 
                    // the suffixes were generated
                    for (var counter = 1; counter <= suffixesCounterForGeneration + 1; counter++)
                    {
                        generatedSuffix += character.ToString();
                    }

                    // adds the generated suffix to the suffixes list
                    suffixesList.Add(generatedSuffix);
                }

                suffixesCounterForGeneration++;
            }
        }
    }
}