C#格式任意大的BigInteger,用于无休止的游戏
我正在尝试创建一个无休止的游戏,如点击泰坦、点击英雄等。我有一个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) 我的班级情况如下: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。然
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++;
}
}
}
}