private static ReverseStringBuilder StringWithGroups(int capacity, IEnumerable <char> builder, int[] groupingSizes, string groupingSeparator)
        {
            var newBuffer = new ReverseStringBuilder(capacity);

            using (var enumerator = builder.GetEnumerator())
            {
                if (!enumerator.MoveNext())
                {
                    return(newBuffer);
                }
                foreach (var size in groupingSizes)
                {
                    for (var count = size - 1; count >= 0; count--)
                    {
                        newBuffer.Prepend(enumerator.Current);
                        if (!enumerator.MoveNext())
                        {
                            return(newBuffer);
                        }
                    }
                    newBuffer.Prepend(groupingSeparator);
                }
                {
                    var size = groupingSizes[groupingSizes.Length - 1];
                    if (size != 0)
                    {
                        while (true)
                        {
                            for (var count = size - 1; count >= 0; count--)
                            {
                                newBuffer.Prepend(enumerator.Current);
                                if (!enumerator.MoveNext())
                                {
                                    return(newBuffer);
                                }
                            }
                            newBuffer.Prepend(groupingSeparator);
                        }
                    }
                    while (true)
                    {
                        newBuffer.Prepend(enumerator.Current);
                        if (!enumerator.MoveNext())
                        {
                            return(newBuffer);
                        }
                    }
                }
            }
        }
        private static ReverseStringBuilder CreateBuilder(BigInteger value, NumberFormatInfo info, bool decimalFmt, int digits)
        {
            // First convert to base 10^9.
            const uint NumericBase      = 1000000000; // 10^9
            const int  NumericBaseLog10 = 9;
            var        sourceLength     = Length(value.InternalBits);
            int        maxConvertedLength;

            try
            {
                maxConvertedLength = checked (sourceLength * 10 / 9 + 2);
            }
            catch (OverflowException e)
            {
                throw new FormatException("The value is too large to be represented by this format specifier.", e);
            }
            var converted       = new uint[maxConvertedLength];
            var convertedLength = 0;

            for (var sourceIndex = sourceLength; --sourceIndex >= 0;)
            {
                // Take a cipher from the source
                var carry = value.InternalBits[sourceIndex];
                // Add it to converted
                for (var convertedIndex = 0; convertedIndex < convertedLength; convertedIndex++)
                {
                    var cipherBlock = NumericsHelpers.MakeUlong(converted[convertedIndex], carry);
                    converted[convertedIndex] = (uint)(cipherBlock % NumericBase);
                    carry = (uint)(cipherBlock / NumericBase);
                }
                if (carry != 0)
                {
                    converted[convertedLength++] = carry % NumericBase;
                    carry /= NumericBase;
                    if (carry != 0)
                    {
                        converted[convertedLength++] = carry;
                    }
                }
            }
            int stringCapacity;

            try
            {
                // Each uint contributes at most 9 digits to the decimal representation.
                stringCapacity = checked (convertedLength * NumericBaseLog10);
            }
            catch (OverflowException e)
            {
                throw new FormatException("The value is too large to be represented by this format specifier.", e);
            }
            if (decimalFmt)
            {
                if (digits > 0 && stringCapacity < digits)
                {
                    stringCapacity = digits;
                }
                if (value.InternalSign < 0)
                {
                    try
                    {
                        // Leave an extra slot for a minus sign.
                        stringCapacity = checked (stringCapacity + info.NegativeSign.Length);
                    }
                    catch (OverflowException e)
                    {
                        throw new FormatException("The value is too large to be represented by this format specifier.", e);
                    }
                }
            }
            var result = new ReverseStringBuilder(stringCapacity);

            for (var stringIndex = 0; stringIndex < convertedLength - 1; stringIndex++)
            {
                var cipherBlock = converted[stringIndex];
                for (var cch = NumericBaseLog10; --cch >= 0;)
                {
                    result.Prepend((char)('0' + cipherBlock % 10));
                    cipherBlock /= 10;
                }
            }
            for (var cipherBlock = converted[convertedLength - 1]; cipherBlock != 0;)
            {
                result.Prepend((char)('0' + cipherBlock % 10));
                cipherBlock /= 10;
            }
            return(result);
        }
        internal static string FormatBigInteger(BigInteger value, string format, NumberFormatInfo info)
        {
            int digits;
            var fmt = ParseFormatSpecifier(format, out digits);

            if (fmt == 'x' || fmt == 'X')
            {
                return(FormatBigIntegerToHexString(value, fmt, digits, info));
            }

            if (fmt == 'e' || fmt == 'E')
            {
                var precision = digits != -1 ? digits : 6;

                if (value.InternalBits == null)
                {
                    return(value.InternalSign.ToString(format, info));
                }

                var scale = (int)Math.Floor(Log10(value));
                // ---
                if (scale > precision + 10)
                {
                    do
                    {
                        value /= 1000000000;
                    } while (Log10(value) > precision + 10);
                }
                while (Log10(value) > precision + 2)
                {
                    value /= 10;
                }
                if ((Log10(value) > precision + 1))
                {
                    var round = value % 10 >= 5;
                    value = (value / 10) + (round ? One : Zero);
                }

                ReverseStringBuilder builder;

                if (value.InternalBits == null)
                {
                    builder = new ReverseStringBuilder(10);
                    builder.Prepend(value.InternalSign.ToString("D"));
                }
                else
                {
                    builder = CreateBuilder(value, info, false, 0);
                }

                // ---
                var decimalSeparator = info.NumberDecimalSeparator;

                var result = new StringBuilder(builder.Length + 6);

                var extra = 0;

                if (precision >= builder.Length)
                {
                    extra     = precision - (builder.Length - 1);
                    precision = builder.Length - 1;
                }
                result.Append(builder.ToString(builder.Length, 1));
                result.Append(decimalSeparator);
                result.Append(builder.ToString(builder.Length - 1, precision));
                result.Append(new string('0', extra));
                result.Append(fmt);
                result.Append(info.PositiveSign);
                if (scale < 10)
                {
                    result.Append("00");
                }
                else if (scale < 100)
                {
                    result.Append('0');
                }
                result.Append(scale);

                return(result.ToString());
            }
            else
            {
                var decimalFmt = (fmt == 'g' || fmt == 'G' || fmt == 'd' || fmt == 'D' || fmt == 'r' || fmt == 'R');
                if (value.InternalBits == null)
                {
                    if (fmt == 'g' || fmt == 'G' || fmt == 'r' || fmt == 'R')
                    {
                        if (digits > 0)
                        {
                            format = "D" + digits.ToString(CultureInfo.InvariantCulture);
                        }
                        else
                        {
                            format = "D";
                        }
                    }
                    return(value.InternalSign.ToString(format, info));
                }
                var builder = CreateBuilder(value, info, decimalFmt, digits);
                if (decimalFmt)
                {
                    // Format Round-trip decimal
                    // This format is supported for integral types only. The number is converted to a string of
                    // decimal digits (0-9), prefixed by a minus sign if the number is negative. The precision
                    // specifier indicates the minimum number of digits desired in the resulting string. If required,
                    // the number is padded with zeros to its left to produce the number of digits given by the
                    // precision specifier.
                    while (digits > 0 && digits >= builder.Length)
                    {
                        builder.Prepend('0');
                        digits--;
                    }
                    if (value.InternalSign < 0)
                    {
                        builder.Prepend(info.NegativeSign);
                    }
                    return(builder.ToString());
                }
                // 'c', 'C', 'e', 'E', 'f', 'F', 'n', 'N', 'p', 'P', custom
                var precision         = -1;
                var groupingSizes     = new[] { 3 };
                var groupingSeparator = info.NumberGroupSeparator;
                var decimalSeparator  = info.NumberDecimalSeparator;
                var groups            = false;
                var type = 0;
                if (fmt == '\0')
                {
                    // parse custom
                }
                else
                {
                    if (fmt == 'c' || fmt == 'C')
                    {
                        decimalSeparator  = info.CurrencyDecimalSeparator;
                        precision         = digits != -1 ? digits : info.CurrencyDecimalDigits;
                        groupingSeparator = info.CurrencyGroupSeparator;
                        groupingSizes     = info.CurrencyGroupSizes;
                        groups            = true;
                        type = 1;
                    }
                    else if (fmt == 'f' || fmt == 'F')
                    {
                        precision = digits != -1 ? digits : info.NumberDecimalDigits;
                    }
                    else if (fmt == 'n' || fmt == 'N')
                    {
                        precision = digits != -1 ? digits : info.NumberDecimalDigits;
                        groups    = true;
                    }
                    else if (fmt == 'p' || fmt == 'P')
                    {
                        decimalSeparator = info.PercentDecimalSeparator;
                        precision        = digits != -1 ? digits : info.PercentDecimalDigits;
                        groups           = true;
                        type             = 2;
                    }
                    else
                    {
                        throw new NotImplementedException();
                    }
                }
                var result = new StringBuilder(builder.Length + 20);
                var close  = SetWrap(value, info, type, result);
                var append = builder;
                if (groups)
                {
                    var extra = groupingSizes.Length - 1;
                    if (groupingSizes[groupingSizes.Length - 1] != 0)
                    {
                        var totalDigits = builder.Length;
                        extra += (int)Math.Ceiling(totalDigits * 1.0 / groupingSizes[groupingSizes.Length - 1]);
                    }
                    var length = extra + builder.Length;
                    if (type == 2)
                    {
                        length += 2;
                        append  = StringWithGroups(length, new ExtendedEnumerable <char>(new[] { '0', '0' }, builder), groupingSizes, groupingSeparator);
                    }
                    else
                    {
                        append = StringWithGroups(extra + builder.Length, builder, groupingSizes, groupingSeparator);
                    }
                }
                result.Append(append);
                if (precision > 0)
                {
                    result.Append(decimalSeparator);
                    result.Append(new string('0', precision));
                }
                result.Append(close);
                return(result.ToString());
            }
        }