internal static unsafe string NumberToStringFormat(NumberBuffer number, string format, NumberFormatInfo info)
{
int digitCount;
int decimalPos;
int firstDigit;
int lastDigit;
int digPos;
bool scientific;
int thousandPos;
int thousandCount = 0;
bool thousandSeps;
int scaleAdjust;
int adjust;
int section;
int src;
char* dig = number.digits;
char ch;
section = FindSection(format, dig[0] == 0 ? 2 : number.sign ? 1 : 0);
while (true)
{
digitCount = 0;
decimalPos = -1;
firstDigit = 0x7FFFFFFF;
lastDigit = 0;
scientific = false;
thousandPos = -1;
thousandSeps = false;
scaleAdjust = 0;
src = section;
fixed (char* pFormat = format)
{
while ((ch = pFormat[src++]) != 0 && ch != ';')
{
switch (ch)
{
case '#':
digitCount++;
break;
case '0':
if (firstDigit == 0x7FFFFFFF)
firstDigit = digitCount;
digitCount++;
lastDigit = digitCount;
break;
case '.':
if (decimalPos < 0)
decimalPos = digitCount;
break;
case ',':
if (digitCount > 0 && decimalPos < 0)
{
if (thousandPos >= 0)
{
if (thousandPos == digitCount)
{
thousandCount++;
break;
}
thousandSeps = true;
}
thousandPos = digitCount;
thousandCount = 1;
}
break;
case '%':
scaleAdjust += 2;
break;
case '\x2030':
scaleAdjust += 3;
break;
case '\'':
case '"':
while (pFormat[src] != 0 && pFormat[src++] != ch)
;
break;
case '\\':
if (pFormat[src] != 0)
src++;
break;
case 'E':
case 'e':
if (pFormat[src] == '0' || ((pFormat[src] == '+' || pFormat[src] == '-') && pFormat[src + 1] == '0'))
{
while (pFormat[++src] == '0')
;
scientific = true;
}
break;
}
}
}
if (decimalPos < 0)
decimalPos = digitCount;
if (thousandPos >= 0)
{
if (thousandPos == decimalPos)
scaleAdjust -= thousandCount * 3;
else
thousandSeps = true;
}
if (dig[0] != 0)
{
number.scale += scaleAdjust;
int pos = scientific ? digitCount : number.scale + digitCount - decimalPos;
RoundNumber(ref number, pos);
if (dig[0] == 0)
{
src = FindSection(format, 2);
if (src != section)
{
section = src;
continue;
}
}
}
else
{
number.sign = false; // We need to format -0 without the sign set.
number.scale = 0; // Decimals with scale ('0.00') should be rounded.
}
break;
}
firstDigit = firstDigit < decimalPos ? decimalPos - firstDigit : 0;
lastDigit = lastDigit > decimalPos ? decimalPos - lastDigit : 0;
if (scientific)
{
digPos = decimalPos;
adjust = 0;
}
else
{
digPos = number.scale > decimalPos ? number.scale : decimalPos;
adjust = number.scale - decimalPos;
}
src = section;
// Adjust can be negative, so we make this an int instead of an unsigned int.
// Adjust represents the number of characters over the formatting e.g. format string is "0000" and you are trying to
// format 100000 (6 digits). Means adjust will be 2. On the other hand if you are trying to format 10 adjust will be
// -2 and we'll need to fixup these digits with 0 padding if we have 0 formatting as in this example.
int[] thousandsSepPos = new int[4];
int thousandsSepCtr = -1;
if (thousandSeps)
{
// We need to precompute this outside the number formatting loop
if (info.NumberGroupSeparator.Length > 0)
{
// We need this array to figure out where to insert the thousands separator. We would have to traverse the string
// backwards. PIC formatting always traverses forwards. These indices are precomputed to tell us where to insert
// the thousands separator so we can get away with traversing forwards. Note we only have to compute up to digPos.
// The max is not bound since you can have formatting strings of the form "000,000..", and this
// should handle that case too.
int[] groupDigits = info.NumberGroupSizes;
int groupSizeIndex = 0; // Index into the groupDigits array.
int groupTotalSizeCount = 0;
int groupSizeLen = groupDigits.Length; // The length of groupDigits array.
if (groupSizeLen != 0)
groupTotalSizeCount = groupDigits[groupSizeIndex]; // The current running total of group size.
int groupSize = groupTotalSizeCount;
int totalDigits = digPos + ((adjust < 0) ? adjust : 0); // Actual number of digits in o/p
int numDigits = (firstDigit > totalDigits) ? firstDigit : totalDigits;
while (numDigits > groupTotalSizeCount)
{
if (groupSize == 0)
break;
++thousandsSepCtr;
if (thousandsSepCtr >= thousandsSepPos.Length)
Array.Resize(ref thousandsSepPos, thousandsSepPos.Length * 2);
thousandsSepPos[thousandsSepCtr] = groupTotalSizeCount;
if (groupSizeIndex < groupSizeLen - 1)
{
groupSizeIndex++;
groupSize = groupDigits[groupSizeIndex];
}
groupTotalSizeCount += groupSize;
}
}
}
StringBuilder sb = new StringBuilder(MIN_SB_BUFFER_SIZE);
if (number.sign && section == 0)
sb.Append(info.NegativeSign);
bool decimalWritten = false;
fixed (char* pFormat = format)
{
char* cur = dig;
while ((ch = pFormat[src++]) != 0 && ch != ';')
{
if (adjust > 0)
{
switch (ch)
{
case '#':
case '0':
case '.':
while (adjust > 0)
{
// digPos will be one greater than thousandsSepPos[thousandsSepCtr] since we are at
// the character after which the groupSeparator needs to be appended.
sb.Append(*cur != 0 ? *cur++ : '0');
if (thousandSeps && digPos > 1 && thousandsSepCtr >= 0)
{
if (digPos == thousandsSepPos[thousandsSepCtr] + 1)
{
sb.Append(info.NumberGroupSeparator);
thousandsSepCtr--;
}
}
digPos--;
adjust--;
}
break;
}
}
switch (ch)
{
case '#':
case '0':
{
if (adjust < 0)
{
adjust++;
ch = digPos <= firstDigit ? '0' : '\0';
}
else
{
ch = *cur != 0 ? *cur++ : digPos > lastDigit ? '0' : '\0';
}
if (ch != 0)
{
sb.Append(ch);
if (thousandSeps && digPos > 1 && thousandsSepCtr >= 0)
{
if (digPos == thousandsSepPos[thousandsSepCtr] + 1)
{
sb.Append(info.NumberGroupSeparator);
thousandsSepCtr--;
}
}
}
digPos--;
break;
}
case '.':
{
if (digPos != 0 || decimalWritten)
{
// For compatibility, don't echo repeated decimals
break;
}
// If the format has trailing zeros or the format has a decimal and digits remain
if (lastDigit < 0 || (decimalPos < digitCount && *cur != 0))
{
sb.Append(info.NumberDecimalSeparator);
decimalWritten = true;
}
break;
}
case '\x2030':
sb.Append(info.PerMilleSymbol);
break;
case '%':
sb.Append(info.PercentSymbol);
break;
case ',':
break;
case '\'':
case '"':
while (pFormat[src] != 0 && pFormat[src] != ch)
sb.Append(pFormat[src++]);
if (pFormat[src] != 0)
src++;
break;
case '\\':
if (pFormat[src] != 0)
sb.Append(pFormat[src++]);
break;
case 'E':
case 'e':
{
bool positiveSign = false;
int i = 0;
if (scientific)
{
if (pFormat[src] == '0')
{
// Handles E0, which should format the same as E-0
i++;
}
else if (pFormat[src] == '+' && pFormat[src + 1] == '0')
{
// Handles E+0
positiveSign = true;
}
else if (pFormat[src] == '-' && pFormat[src + 1] == '0')
{
// Handles E-0
// Do nothing, this is just a place holder s.t. we don't break out of the loop.
}
else
{
sb.Append(ch);
break;
}
while (pFormat[++src] == '0')
i++;
if (i > 10)
i = 10;
int exp = dig[0] == 0 ? 0 : number.scale - decimalPos;
FormatExponent(sb, info, exp, ch, i, positiveSign);
scientific = false;
}
else
{
sb.Append(ch); // Copy E or e to output
if (pFormat[src] == '+' || pFormat[src] == '-')
sb.Append(pFormat[src++]);
while (pFormat[src] == '0')
sb.Append(pFormat[src++]);
}
break;
}
default:
sb.Append(ch);
break;
}
}
}
return sb.ToString();
}
}