C#格式任意大的BigInteger,用于无休止的游戏

C#格式任意大的BigInteger,用于无休止的游戏,c#,formatting,biginteger,procedural-generation,C#,Formatting,Biginteger,Procedural Generation,我正在尝试创建一个无休止的游戏,如点击泰坦、点击英雄等。我有一个BigInteger类,它能够表示任意大的整数,只要它们适合内存 现在我有了一个类,它将BigInteger格式化为特定格式。它使用K(千)、M(百万)、B(十亿)、T(万亿)、Q(万亿)来表示“较小”的数字,但在这之后,速记符号变得模棱两可且不直观。Q已经因为五百万而模棱两可,但我可以接受 在Q之后,我想从字母a开始。所以1000Q=1.000a,然后1000a=1.000b等。当达到1000z时,应将其格式化为1.000aa。然

我正在尝试创建一个无休止的游戏,如点击泰坦、点击英雄等。我有一个BigInteger类,它能够表示任意大的整数,只要它们适合内存

现在我有了一个类,它将BigInteger格式化为特定格式。它使用K(千)、M(百万)、B(十亿)、T(万亿)、Q(万亿)来表示“较小”的数字,但在这之后,速记符号变得模棱两可且不直观。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;
    }
}
以上所有测试目前均通过。缺少的测试是检查
1000^(26^3+5)
(+5是因为
a
格式化在
Q
之后开始)是否可以格式化为“1.000aaa”


我如何按照上面描述的方式格式化大整数

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

为了便于讨论,让我们看看您还有哪些其他选择


表面上看,您的请求归结为一个直接的数字基值到文本的转换。正如我们可以将数值转换为其文本表示形式,例如,基数2、基数10、基数16等。我们可以使用基数26将数值转换为文本表示形式,只使用字母
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()
扩展方法如下:

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;
    }
}
下面是一个完整的程序,演示了这两种技术,显示了“有趣”输入的文本(即输出中的字符数发生变化的地方):

类程序
{
静态void Main(字符串[]参数)
{
int[]值={0,25,26,675,676};
foreach(值中的int值)
{
WriteLine(“{0}:{1}”,value,ToBase26AlphaString(value));
}
Console.WriteLine();
List hackedValues=新列表();
对于(int i=0;i<531441;i++)
{
字符串文本=字母字符串(i);
如果(!text.Contains('`'))
{
hackedValues.Add(Tuple.Create(i,text));
}
}
元组prev=null;
for(int i=0;i0)
{
整数位数=数值%numericBase;
sb.追加((字符)(baseChar+数字));
值/=数值基数;
}
如果(sb.Length==0)
{
sb.Append(baseChar);
}
使某人倒转;
使某人返回字符串();
}
}
静态类扩展
{
公共静态无效反转(此StringBuilder sb)
{
对于(inti=0,j=sb.Length-1;i
经过长时间的思考(实际上是逃避工作一个月)和几个小时的编码,我使用了您代码的一部分创建了自己的解决方案

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

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

使用System.Collections.Generic;
使用系统数字;
/// 
///用于格式化大整数的静态类。
/// 
公共静态类BigIntegratPerformatter
{
私有静态列表后缀=新列表();
/// 
///如果它等于0,则后缀列表中只有从空字符串到Q的后缀。
///如果它等于1,则会添加a-z后缀。
///如果它等于2,则会添加aa-zz后缀,以此类推。
/// 
私有静态int-subfixesUnterforgeneration=0;
/// 
///使用科学记数法格式化BigInteger。如果长度
///该数字的最大值小于4。
/// 
///要格式化的数字。
///返回包含使用科学表示法格式化的BigInteger的字符串。
公共静态字符串FormatScientific(BigInteger数字)
{
ret
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;
    }
}
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();
}
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;
        }
    }
}
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++;
            }
        }
    }
}