C# 格式任意大的 BigInteger 用于无尽的游戏
C# format arbitrarily large BigInteger for endless game
我正在尝试创建一款无穷无尽的游戏,例如 Tap Titans、Clicker Heroes 等。我有一个 BigInteger class,它能够表示任意大的整数,只要它们适合内存。
现在我有一个 class 可以将 BigInteger 格式化为特定格式。它使用 K(千)、M(百万)、B(十亿)、T(万亿)、Q(万亿)表示 'smaller' 数字,但之后的简写符号变得模棱两可且不直观。由于 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, 等等
到目前为止,我已经实现了上述目标,但是我的class 无法格式化 1000zz 之后的数字。我还没有想出一个通用算法来自动确定需要多少个字符(对于非常大的数字可能是 aaaz)。
我的 class 看起来如下:
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。
我还有一个单元测试来验证此 class 的功能(准备一些横向滚动):
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
之后开始)是否可以格式化为“1.000aaa”。
如何按照我上面描述的方式格式化程序上的大整数。
在数字符号的世界里,这实际上是一个已解决的问题。也就是说,您可以改为使用 scientific notation 来表示这些特别大的数字。科学计数法是紧凑的,尾数允许任意精度,并且易于理解。就个人而言,这就是我会采取的方法。
为了便于讨论,让我们看看您还有哪些其他选择……
从表面上看,您的请求归结为直接将数字基值转换为文本。正如我们可以将数值转换为以 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;
}
}
但是,上述内容存在一个小问题。在以这种方式表示的 26 进制中,数字 a
对应于 0
,因此您的后缀永远不会以字母 a
开头,至少不会在第一个字母之后(这是一种特殊情况) ,就像在使用十进制表示法时,我们使用数字 0
本身来表示零值)。相反,例如,您将在 z
之后获得 ba
,在 zz
之后获得 baa
。
就我个人而言,我认为这很好。它会排除像 aaaz
这样的后缀,但这只是因为后缀符号系统是合乎逻辑的、可预测的并且容易逆转(即给定一个后缀,很容易弄清楚它在数字上的含义)。
但是,如果你坚持像a
…z
、aa
…zz
、aaa
…zzz
这样的顺序, aaaa
… 等等,您可以使用 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();
}
这是一个完整的程序,说明了这两种技术,显示了 "interesting" 输入的文本(即输出中字符数发生变化的地方):
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;
}
}
}
经过长时间的思考(实际上是避免工作一个月)和几个小时的编码,我使用了您的部分代码来创建我自己的解决方案。
它使用前缀顺序:空字符串,k,M,B,Q,a,b ... z (不包括k,因为千),aa,bb , ..., zz, aaa, bbb, ..., zzz, 等等。它 trim 是数字末尾的零,例如1000 = 1k.
(也可以使用科学计数法,但它不会 trim 零。)
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++;
}
}
}
}
我正在尝试创建一款无穷无尽的游戏,例如 Tap Titans、Clicker Heroes 等。我有一个 BigInteger class,它能够表示任意大的整数,只要它们适合内存。
现在我有一个 class 可以将 BigInteger 格式化为特定格式。它使用 K(千)、M(百万)、B(十亿)、T(万亿)、Q(万亿)表示 'smaller' 数字,但之后的简写符号变得模棱两可且不直观。由于 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, 等等
到目前为止,我已经实现了上述目标,但是我的class 无法格式化 1000zz 之后的数字。我还没有想出一个通用算法来自动确定需要多少个字符(对于非常大的数字可能是 aaaz)。
我的 class 看起来如下:
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。
我还有一个单元测试来验证此 class 的功能(准备一些横向滚动):
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
之后开始)是否可以格式化为“1.000aaa”。
如何按照我上面描述的方式格式化程序上的大整数。
在数字符号的世界里,这实际上是一个已解决的问题。也就是说,您可以改为使用 scientific notation 来表示这些特别大的数字。科学计数法是紧凑的,尾数允许任意精度,并且易于理解。就个人而言,这就是我会采取的方法。
为了便于讨论,让我们看看您还有哪些其他选择……
从表面上看,您的请求归结为直接将数字基值转换为文本。正如我们可以将数值转换为以 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;
}
}
但是,上述内容存在一个小问题。在以这种方式表示的 26 进制中,数字 a
对应于 0
,因此您的后缀永远不会以字母 a
开头,至少不会在第一个字母之后(这是一种特殊情况) ,就像在使用十进制表示法时,我们使用数字 0
本身来表示零值)。相反,例如,您将在 z
之后获得 ba
,在 zz
之后获得 baa
。
就我个人而言,我认为这很好。它会排除像 aaaz
这样的后缀,但这只是因为后缀符号系统是合乎逻辑的、可预测的并且容易逆转(即给定一个后缀,很容易弄清楚它在数字上的含义)。
但是,如果你坚持像a
…z
、aa
…zz
、aaa
…zzz
这样的顺序, aaaa
… 等等,您可以使用 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();
}
这是一个完整的程序,说明了这两种技术,显示了 "interesting" 输入的文本(即输出中字符数发生变化的地方):
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;
}
}
}
经过长时间的思考(实际上是避免工作一个月)和几个小时的编码,我使用了您的部分代码来创建我自己的解决方案。
它使用前缀顺序:空字符串,k,M,B,Q,a,b ... z (不包括k,因为千),aa,bb , ..., zz, aaa, bbb, ..., zzz, 等等。它 trim 是数字末尾的零,例如1000 = 1k.
(也可以使用科学计数法,但它不会 trim 零。)
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++;
}
}
}
}