// Copyright (c) 2015-2017 Michael Popoloski // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. using System.Globalization; using System.Reflection; using System.Runtime.CompilerServices; using UnsafeIL; namespace System.Text.Formatting { /// /// Specifies an interface for types that act as a set of formatting arguments. /// public interface IArgSet { /// /// The number of arguments in the set. /// int Count { get; } /// /// Format one of the arguments in the set into the given string buffer. /// /// The buffer to which to append the argument. /// The index of the argument to format. /// A specifier indicating how the argument should be formatted. void Format (StringBuffer buffer, int index, StringView format); } /// /// Defines an interface for types that can be formatted into a string buffer. /// public interface IStringFormattable { /// /// Format the current instance into the given string buffer. /// /// The buffer to which to append. /// A specifier indicating how the argument should be formatted. void Format (StringBuffer buffer, StringView format); } /// /// A low-allocation version of the built-in type. /// public unsafe sealed partial class StringBuffer { CachedCulture culture; char[] buffer; int currentCount; /// /// The number of characters in the buffer. /// public int Count { get { return currentCount; } } /// /// The culture used to format string data. /// public CultureInfo Culture { get { return culture.Culture; } set { if (culture.Culture == value) return; if (value == CultureInfo.InvariantCulture) culture = CachedInvariantCulture; else if (value == CachedCurrentCulture.Culture) culture = CachedCurrentCulture; else culture = new CachedCulture(value); } } /// /// Initializes a new instance of the class. /// public StringBuffer () : this(DefaultCapacity) { } /// /// Initializes a new instance of the class. /// /// The initial size of the string buffer. public StringBuffer (int capacity) { buffer = new char[capacity]; culture = CachedCurrentCulture; } /// /// Sets a custom formatter to use when converting instances of a given type to a string. /// /// The type for which to set the formatter. /// A delegate that will be called to format instances of the specified type. public static void SetCustomFormatter(Action formatter) { ValueHelper.Formatter = formatter; } /// /// Clears the buffer. /// public void Clear () { currentCount = 0; } /// /// Copies the contents of the buffer to the given array. /// /// The index within the buffer to begin copying. /// The destination array. /// The index within the destination array to which to begin copying. /// The number of characters to copy. public void CopyTo (int sourceIndex, char[] destination, int destinationIndex, int count) { if (destination == null) throw new ArgumentNullException(nameof(destination)); if (destinationIndex + count > destination.Length || destinationIndex < 0) throw new ArgumentOutOfRangeException(nameof(destinationIndex)); fixed (char* destPtr = &destination[destinationIndex]) CopyTo(destPtr, sourceIndex, count); } /// /// Copies the contents of the buffer to the given array. /// /// A pointer to the destination array. /// The index within the buffer to begin copying. /// The number of characters to copy. public void CopyTo (char* dest, int sourceIndex, int count) { if (count < 0) throw new ArgumentOutOfRangeException(nameof(count)); if (sourceIndex + count > currentCount || sourceIndex < 0) throw new ArgumentOutOfRangeException(nameof(sourceIndex)); fixed (char* s = buffer) { var src = s + sourceIndex; for (int i = 0; i < count; i++) *dest++ = *src++; } } /// /// Copies the contents of the buffer to the given byte array. /// /// A pointer to the destination byte array. /// The index within the buffer to begin copying. /// The number of characters to copy. /// The encoding to use to convert characters to bytes. /// The number of bytes written to the destination. public int CopyTo (byte* dest, int sourceIndex, int count, Encoding encoding) { if (count < 0) throw new ArgumentOutOfRangeException(nameof(count)); if (sourceIndex + count > currentCount || sourceIndex < 0) throw new ArgumentOutOfRangeException(nameof(sourceIndex)); if (encoding == null) throw new ArgumentNullException(nameof(encoding)); fixed (char* s = buffer) return encoding.GetBytes(s, count, dest, count); } /// /// Converts the buffer to a string instance. /// /// A new string representing the characters currently in the buffer. public override string ToString () { return new string(buffer, 0, currentCount); } /// /// Appends a character to the current buffer. /// /// The character to append. public void Append (char c) { Append(c, 1); } /// /// Appends a character to the current buffer several times. /// /// The character to append. /// The number of times to append the character. public void Append (char c, int count) { if (count < 0) throw new ArgumentOutOfRangeException(nameof(count)); CheckCapacity(count); fixed (char* b = &buffer[currentCount]) { var ptr = b; for (int i = 0; i < count; i++) *ptr++ = c; currentCount += count; } } /// /// Appends the specified string to the current buffer. /// /// The value to append. public void Append (string value) { if (value == null) throw new ArgumentNullException(nameof(value)); Append(value, 0, value.Length); } /// /// Appends a string subset to the current buffer. /// /// The string to append. /// The starting index within the string to begin reading characters. /// The number of characters to append. public void Append (string value, int startIndex, int count) { if (value == null) throw new ArgumentNullException(nameof(value)); if (startIndex < 0 || startIndex + count > value.Length) throw new ArgumentOutOfRangeException(nameof(startIndex)); fixed (char* s = value) Append(s + startIndex, count); } /// /// Appends an array of characters to the current buffer. /// /// The characters to append. /// The starting index within the array to begin reading characters. /// The number of characters to append. public void Append (char[] values, int startIndex, int count) { if (values == null) throw new ArgumentNullException(nameof(values)); if (startIndex < 0 || startIndex + count > values.Length) throw new ArgumentOutOfRangeException(nameof(startIndex)); fixed (char* s = &values[startIndex]) Append(s, count); } /// /// Appends an array of characters to the current buffer. /// /// A pointer to the array of characters to append. /// The number of characters to append. public void Append (char* str, int count) { CheckCapacity(count); fixed (char* b = &buffer[currentCount]) { var dest = b; for (int i = 0; i < count; i++) *dest++ = *str++; currentCount += count; } } /// /// Appends the specified value as a string to the current buffer. /// /// The value to append. public void Append (bool value) { if (value) Append(TrueLiteral); else Append(FalseLiteral); } /// /// Appends the specified value as a string to the current buffer. /// /// The value to append. /// A format specifier indicating how to convert to a string. public void Append (sbyte value, StringView format) { Numeric.FormatSByte(this, value, format, culture); } /// /// Appends the specified value as a string to the current buffer. /// /// The value to append. /// A format specifier indicating how to convert to a string. public void Append (byte value, StringView format) { // widening here is fine Numeric.FormatUInt32(this, value, format, culture); } /// /// Appends the specified value as a string to the current buffer. /// /// The value to append. /// A format specifier indicating how to convert to a string. public void Append (short value, StringView format) { Numeric.FormatInt16(this, value, format, culture); } /// /// Appends the specified value as a string to the current buffer. /// /// The value to append. /// A format specifier indicating how to convert to a string. public void Append (ushort value, StringView format) { // widening here is fine Numeric.FormatUInt32(this, value, format, culture); } /// /// Appends the specified value as a string to the current buffer. /// /// The value to append. /// A format specifier indicating how to convert to a string. public void Append (int value, StringView format) { Numeric.FormatInt32(this, value, format, culture); } /// /// Appends the specified value as a string to the current buffer. /// /// The value to append. /// A format specifier indicating how to convert to a string. public void Append (uint value, StringView format) { Numeric.FormatUInt32(this, value, format, culture); } /// /// Appends the specified value as a string to the current buffer. /// /// The value to append. /// A format specifier indicating how to convert to a string. public void Append (long value, StringView format) { Numeric.FormatInt64(this, value, format, culture); } /// /// Appends the specified value as a string to the current buffer. /// /// The value to append. /// A format specifier indicating how to convert to a string. public void Append (ulong value, StringView format) { Numeric.FormatUInt64(this, value, format, culture); } /// /// Appends the specified value as a string to the current buffer. /// /// The value to append. /// A format specifier indicating how to convert to a string. public void Append (float value, StringView format) { Numeric.FormatSingle(this, value, format, culture); } /// /// Appends the specified value as a string to the current buffer. /// /// The value to append. /// A format specifier indicating how to convert to a string. public void Append (double value, StringView format) { Numeric.FormatDouble(this, value, format, culture); } /// /// Appends the specified value as a string to the current buffer. /// /// The value to append. /// A format specifier indicating how to convert to a string. public void Append (decimal value, StringView format) { Numeric.FormatDecimal(this, (uint*)&value, format, culture); } /// /// Appends the string returned by processing a composite format string, which contains zero or more format items, to this instance. /// Each format item is replaced by the string representation of a single argument. /// /// The type of argument set being formatted. /// A composite format string. /// The set of args to insert into the format string. public void AppendArgSet(string format, ref T args) where T : IArgSet { if (format == null) throw new ArgumentNullException(nameof(format)); fixed (char* formatPtr = format) { var curr = formatPtr; var end = curr + format.Length; var segmentsLeft = false; var prevArgIndex = 0; do { CheckCapacity((int)(end - curr) + 1); fixed (char* bufferPtr = &buffer[currentCount]) segmentsLeft = AppendSegment(ref curr, end, bufferPtr, ref prevArgIndex, ref args); } while (segmentsLeft); } } [MethodImpl(MethodImplOptions.AggressiveInlining)] void CheckCapacity (int count) { int newCount = currentCount + count; if (newCount > buffer.Length) { int newCapacity = buffer.Length * 2; if (newCapacity < newCount) newCapacity = newCount; char[] newBuffer = new char[newCapacity]; fixed (void* nptr = newBuffer, ptr = buffer) { Unsafe.CopyBlock(nptr, ptr, (uint)(currentCount * sizeof(char))); } buffer = newBuffer; } } bool AppendSegment(ref char* currRef, char* end, char* dest, ref int prevArgIndex, ref T args) where T : IArgSet { char* curr = currRef; char c = '\x0'; while (curr < end) { c = *curr++; if (c == '}') { // check for escape character for }} if (curr < end && *curr == '}') curr++; else ThrowError(); } else if (c == '{') { // check for escape character for {{ if (curr == end) ThrowError(); else if (*curr == '{') curr++; else break; } *dest++ = c; currentCount++; } if (curr == end) return false; int index; if (*curr == '}') index = prevArgIndex; else index = ParseNum(ref curr, end, MaxArgs); if (index >= args.Count) throw new FormatException(string.Format(SR.ArgIndexOutOfRange, index)); // check for a spacing specifier c = SkipWhitespace(ref curr, end); var width = 0; var leftJustify = false; var oldCount = currentCount; if (c == ',') { curr++; c = SkipWhitespace(ref curr, end); // spacing can be left-justified if (c == '-') { leftJustify = true; curr++; if (curr == end) ThrowError(); } width = ParseNum(ref curr, end, MaxSpacing); c = SkipWhitespace(ref curr, end); } // check for format specifier curr++; if (c == ':') { var specifierBuffer = stackalloc char[MaxSpecifierSize]; var specifierEnd = specifierBuffer + MaxSpecifierSize; var specifierPtr = specifierBuffer; while (true) { if (curr == end) ThrowError(); c = *curr++; if (c == '{') { // check for escape character for {{ if (curr < end && *curr == '{') curr++; else ThrowError(); } else if (c == '}') { // check for escape character for }} if (curr < end && *curr == '}') curr++; else { // found the end of the specifier // kick off the format job var specifier = new StringView(specifierBuffer, (int)(specifierPtr - specifierBuffer)); args.Format(this, index, specifier); break; } } if (specifierPtr == specifierEnd) ThrowError(); *specifierPtr++ = c; } } else { // no specifier. make sure we're at the end of the format block if (c != '}') ThrowError(); // format without any specifier args.Format(this, index, StringView.Empty); } // finish off padding, if necessary var padding = width - (currentCount - oldCount); if (padding > 0) { if (leftJustify) Append(' ', padding); else { // copy the recently placed chars up in memory to make room for padding CheckCapacity(padding); for (int i = currentCount - 1; i >= oldCount; i--) buffer[i + padding] = buffer[i]; // fill in padding for (int i = 0; i < padding; i++) buffer[i + oldCount] = ' '; currentCount += padding; } } prevArgIndex = index + 1; currRef = curr; return true; } [MethodImpl(MethodImplOptions.AggressiveInlining)] internal void AppendGeneric(T value, StringView format) { // this looks gross, but T is known at JIT-time so this call tree // gets compiled down to a direct call with no branching if (typeof(T) == typeof(sbyte)) Append(*(sbyte*)Unsafe.AsPointer(ref value), format); else if (typeof(T) == typeof(byte)) Append(*(byte*)Unsafe.AsPointer(ref value), format); else if (typeof(T) == typeof(short)) Append(*(short*)Unsafe.AsPointer(ref value), format); else if (typeof(T) == typeof(ushort)) Append(*(ushort*)Unsafe.AsPointer(ref value), format); else if (typeof(T) == typeof(int)) Append(*(int*)Unsafe.AsPointer(ref value), format); else if (typeof(T) == typeof(uint)) Append(*(uint*)Unsafe.AsPointer(ref value), format); else if (typeof(T) == typeof(long)) Append(*(long*)Unsafe.AsPointer(ref value), format); else if (typeof(T) == typeof(ulong)) Append(*(ulong*)Unsafe.AsPointer(ref value), format); else if (typeof(T) == typeof(float)) Append(*(float*)Unsafe.AsPointer(ref value), format); else if (typeof(T) == typeof(double)) Append(*(double*)Unsafe.AsPointer(ref value), format); else if (typeof(T) == typeof(decimal)) Append(*(decimal*)Unsafe.AsPointer(ref value), format); else if (typeof(T) == typeof(bool)) Append(*(bool*)Unsafe.AsPointer(ref value)); else if (typeof(T) == typeof(char)) Append(*(char*)Unsafe.AsPointer(ref value), format); else if (typeof(T) == typeof(string)) Append(Unsafe.As(value)); else { // first, check to see if it's a value type implementing IStringFormattable var formatter = ValueHelper.Formatter; if (formatter != null) formatter(this, value, format); else { // We could handle this case by calling ToString() on the object and paying the // allocation, but presumably if the user is using us instead of the built-in // formatting utilities they would rather be notified of this case, so we'll throw. throw new InvalidOperationException(string.Format(SR.TypeNotFormattable, typeof(T))); } } } static int ParseNum (ref char* currRef, char* end, int maxValue) { char* curr = currRef; char c = *curr; if (c < '0' || c > '9') ThrowError(); int value = 0; do { value = value * 10 + c - '0'; curr++; if (curr == end) ThrowError(); c = *curr; } while (c >= '0' && c <= '9' && value < maxValue); currRef = curr; return value; } [MethodImpl(MethodImplOptions.AggressiveInlining)] static char SkipWhitespace (ref char* currRef, char* end) { char* curr = currRef; while (curr < end && *curr == ' ') curr++; if (curr == end) ThrowError(); currRef = curr; return *curr; } static void ThrowError () { throw new FormatException(SR.InvalidFormatString); } static StringBuffer Acquire (int capacity) { if (capacity <= MaxCachedSize) { var buffer = CachedInstance; if (buffer != null) { CachedInstance = null; buffer.Clear(); buffer.CheckCapacity(capacity); return buffer; } } return new StringBuffer(capacity); } static void Release (StringBuffer buffer) { if (buffer.buffer.Length <= MaxCachedSize) CachedInstance = buffer; } [ThreadStatic] static StringBuffer CachedInstance; static readonly CachedCulture CachedInvariantCulture = new CachedCulture(CultureInfo.InvariantCulture); static readonly CachedCulture CachedCurrentCulture = new CachedCulture(CultureInfo.CurrentCulture); const int DefaultCapacity = 32; const int MaxCachedSize = 360; // same as BCL's StringBuilderCache const int MaxArgs = 256; const int MaxSpacing = 1000000; const int MaxSpecifierSize = 32; const string TrueLiteral = "True"; const string FalseLiteral = "False"; // The point of this class is to allow us to generate a direct call to a known // method on an unknown, unconstrained generic value type. Normally this would // be impossible; you'd have to cast the generic argument and introduce boxing. // Instead we pay a one-time startup cost to create a delegate that will forward // the parameter to the appropriate method in a strongly typed fashion. static class ValueHelper { public static Action Formatter = Prepare(); static Action Prepare () { // we only use this class for value types that also implement IStringFormattable var type = typeof(T); if (!typeof(IStringFormattable).GetTypeInfo().IsAssignableFrom(type.GetTypeInfo())) return null; var result = typeof(ValueHelper) .GetTypeInfo() .GetDeclaredMethod("Assign") .MakeGenericMethod(type) .Invoke(null, null); return (Action)result; } public static Action Assign() where U : IStringFormattable { return (f, u, v) => u.Format(f, v); } } } // TODO: clean this up public unsafe struct StringView { public static readonly StringView Empty = new StringView(); public readonly char* Data; public readonly int Length; public bool IsEmpty { get { return Length == 0; } } public StringView (char* data, int length) { Data = data; Length = length; } public static bool operator ==(StringView lhs, string rhs) { var count = lhs.Length; if (count != rhs.Length) return false; fixed (char* r = rhs) { var lhsPtr = lhs.Data; var rhsPtr = r; for (int i = 0; i < count; i++) { if (*lhsPtr++ != *rhsPtr++) return false; } } return true; } public static bool operator !=(StringView lhs, string rhs) { return !(lhs == rhs); } } // caches formatting information from culture data // some of the accessors on NumberFormatInfo allocate copies of their data sealed class CachedCulture { public readonly CultureInfo Culture; public readonly NumberFormatData CurrencyData; public readonly NumberFormatData FixedData; public readonly NumberFormatData NumberData; public readonly NumberFormatData ScientificData; public readonly NumberFormatData PercentData; public readonly string CurrencyNegativePattern; public readonly string CurrencyPositivePattern; public readonly string CurrencySymbol; public readonly string NumberNegativePattern; public readonly string NumberPositivePattern; public readonly string PercentNegativePattern; public readonly string PercentPositivePattern; public readonly string PercentSymbol; public readonly string PerMilleSymbol; public readonly string NegativeSign; public readonly string PositiveSign; public readonly string NaN; public readonly string PositiveInfinity; public readonly string NegativeInfinity; public readonly int DecimalBufferSize; public CachedCulture (CultureInfo culture) { Culture = culture; var info = culture.NumberFormat; CurrencyData = new NumberFormatData( info.CurrencyDecimalDigits, info.NegativeSign, info.CurrencyDecimalSeparator, info.CurrencyGroupSeparator, info.CurrencyGroupSizes, info.CurrencySymbol.Length ); FixedData = new NumberFormatData( info.NumberDecimalDigits, info.NegativeSign, info.NumberDecimalSeparator, null, null, 0 ); NumberData = new NumberFormatData( info.NumberDecimalDigits, info.NegativeSign, info.NumberDecimalSeparator, info.NumberGroupSeparator, info.NumberGroupSizes, 0 ); ScientificData = new NumberFormatData( 6, info.NegativeSign, info.NumberDecimalSeparator, null, null, info.NegativeSign.Length + info.PositiveSign.Length * 2 // for number and exponent ); PercentData = new NumberFormatData( info.PercentDecimalDigits, info.NegativeSign, info.PercentDecimalSeparator, info.PercentGroupSeparator, info.PercentGroupSizes, info.PercentSymbol.Length ); CurrencyNegativePattern = NegativeCurrencyFormats[info.CurrencyNegativePattern]; CurrencyPositivePattern = PositiveCurrencyFormats[info.CurrencyPositivePattern]; CurrencySymbol = info.CurrencySymbol; NumberNegativePattern = NegativeNumberFormats[info.NumberNegativePattern]; NumberPositivePattern = PositiveNumberFormat; PercentNegativePattern = NegativePercentFormats[info.PercentNegativePattern]; PercentPositivePattern = PositivePercentFormats[info.PercentPositivePattern]; PercentSymbol = info.PercentSymbol; PerMilleSymbol = info.PerMilleSymbol; NegativeSign = info.NegativeSign; PositiveSign = info.PositiveSign; NaN = info.NaNSymbol; PositiveInfinity = info.PositiveInfinitySymbol; NegativeInfinity = info.NegativeInfinitySymbol; DecimalBufferSize = NumberFormatData.MinBufferSize + info.NumberDecimalSeparator.Length + (NegativeSign.Length + PositiveSign.Length) * 2; } static readonly string[] PositiveCurrencyFormats = { "$#", "#$", "$ #", "# $" }; static readonly string[] NegativeCurrencyFormats = { "($#)", "-$#", "$-#", "$#-", "(#$)", "-#$", "#-$", "#$-", "-# $", "-$ #", "# $-", "$ #-", "$ -#", "#- $", "($ #)", "(# $)" }; static readonly string[] PositivePercentFormats = { "# %", "#%", "%#", "% #" }; static readonly string[] NegativePercentFormats = { "-# %", "-#%", "-%#", "%-#", "%#-", "#-%", "#%-", "-% #", "# %-", "% #-", "% -#", "#- %" }; static readonly string[] NegativeNumberFormats = { "(#)", "-#", "- #", "#-", "# -", }; static readonly string PositiveNumberFormat = "#"; } // contains format information for a specific kind of format string // e.g. (fixed, number, currency) sealed class NumberFormatData { readonly int bufferLength; readonly int perDigitLength; public readonly int DecimalDigits; public readonly string NegativeSign; public readonly string DecimalSeparator; public readonly string GroupSeparator; public readonly int[] GroupSizes; public NumberFormatData (int decimalDigits, string negativeSign, string decimalSeparator, string groupSeparator, int[] groupSizes, int extra) { DecimalDigits = decimalDigits; NegativeSign = negativeSign; DecimalSeparator = decimalSeparator; GroupSeparator = groupSeparator; GroupSizes = groupSizes; bufferLength = MinBufferSize; bufferLength += NegativeSign.Length; bufferLength += DecimalSeparator.Length; bufferLength += extra; if (GroupSeparator != null) perDigitLength = GroupSeparator.Length; } public int GetBufferSize (ref int maxDigits, int scale) { if (maxDigits < 0) maxDigits = DecimalDigits; var digitCount = scale >= 0 ? scale + maxDigits : 0; long len = bufferLength; // calculate buffer size len += digitCount; len += perDigitLength * digitCount; return checked((int)len); } internal const int MinBufferSize = 105; } // Most of the implementation of this file was ported from the native versions built into the CLR // See: https://github.com/dotnet/coreclr/blob/838807429a0828a839958e3b7d392d65886c8f2e/src/classlibnative/bcltype/number.cpp // Also see: https://github.com/dotnet/coreclr/blob/02084af832c2900cf6eac2a168c41f261409be97/src/mscorlib/src/System/Number.cs // Standard numeric format string reference: https://msdn.microsoft.com/en-us/library/dwhawy9k%28v=vs.110%29.aspx unsafe static partial class Numeric { public static void FormatSByte (StringBuffer formatter, sbyte value, StringView specifier, CachedCulture culture) { if (value < 0 && !specifier.IsEmpty) { // if we're negative and doing a hex format, mask out the bits for the conversion char c = specifier.Data[0]; if (c == 'X' || c == 'x') { FormatUInt32(formatter, (uint)(value & 0xFF), specifier, culture); return; } } FormatInt32(formatter, value, specifier, culture); } public static void FormatInt16 (StringBuffer formatter, short value, StringView specifier, CachedCulture culture) { if (value < 0 && !specifier.IsEmpty) { // if we're negative and doing a hex format, mask out the bits for the conversion char c = specifier.Data[0]; if (c == 'X' || c == 'x') { FormatUInt32(formatter, (uint)(value & 0xFFFF), specifier, culture); return; } } FormatInt32(formatter, value, specifier, culture); } public static void FormatInt32 (StringBuffer formatter, int value, StringView specifier, CachedCulture culture) { int digits; var fmt = ParseFormatSpecifier(specifier, out digits); // ANDing with 0xFFDF has the effect of uppercasing the character switch (fmt & 0xFFDF) { case 'G': if (digits > 0) goto default; else goto case 'D'; case 'D': Int32ToDecStr(formatter, value, digits, culture.NegativeSign); break; case 'X': // fmt-('X'-'A'+1) gives us the base hex character in either // uppercase or lowercase, depending on the casing of fmt Int32ToHexStr(formatter, (uint)value, fmt - ('X' - 'A' + 10), digits); break; default: var number = new Number(); var buffer = stackalloc char[MaxNumberDigits + 1]; number.Digits = buffer; Int32ToNumber(value, ref number); if (fmt != 0) NumberToString(formatter, ref number, fmt, digits, culture); else NumberToCustomFormatString(formatter, ref number, specifier, culture); break; } } public static void FormatUInt32 (StringBuffer formatter, uint value, StringView specifier, CachedCulture culture) { int digits; var fmt = ParseFormatSpecifier(specifier, out digits); // ANDing with 0xFFDF has the effect of uppercasing the character switch (fmt & 0xFFDF) { case 'G': if (digits > 0) goto default; else goto case 'D'; case 'D': UInt32ToDecStr(formatter, value, digits); break; case 'X': // fmt-('X'-'A'+1) gives us the base hex character in either // uppercase or lowercase, depending on the casing of fmt Int32ToHexStr(formatter, value, fmt - ('X' - 'A' + 10), digits); break; default: var number = new Number(); var buffer = stackalloc char[MaxNumberDigits + 1]; number.Digits = buffer; UInt32ToNumber(value, ref number); if (fmt != 0) NumberToString(formatter, ref number, fmt, digits, culture); else NumberToCustomFormatString(formatter, ref number, specifier, culture); break; } } public static void FormatInt64 (StringBuffer formatter, long value, StringView specifier, CachedCulture culture) { int digits; var fmt = ParseFormatSpecifier(specifier, out digits); // ANDing with 0xFFDF has the effect of uppercasing the character switch (fmt & 0xFFDF) { case 'G': if (digits > 0) goto default; else goto case 'D'; case 'D': Int64ToDecStr(formatter, value, digits, culture.NegativeSign); break; case 'X': // fmt-('X'-'A'+1) gives us the base hex character in either // uppercase or lowercase, depending on the casing of fmt Int64ToHexStr(formatter, (ulong)value, fmt - ('X' - 'A' + 10), digits); break; default: var number = new Number(); var buffer = stackalloc char[MaxNumberDigits + 1]; number.Digits = buffer; Int64ToNumber(value, ref number); if (fmt != 0) NumberToString(formatter, ref number, fmt, digits, culture); else NumberToCustomFormatString(formatter, ref number, specifier, culture); break; } } public static void FormatUInt64 (StringBuffer formatter, ulong value, StringView specifier, CachedCulture culture) { int digits; var fmt = ParseFormatSpecifier(specifier, out digits); // ANDing with 0xFFDF has the effect of uppercasing the character switch (fmt & 0xFFDF) { case 'G': if (digits > 0) goto default; else goto case 'D'; case 'D': UInt64ToDecStr(formatter, value, digits); break; case 'X': // fmt-('X'-'A'+1) gives us the base hex character in either // uppercase or lowercase, depending on the casing of fmt Int64ToHexStr(formatter, value, fmt - ('X' - 'A' + 10), digits); break; default: var number = new Number(); var buffer = stackalloc char[MaxNumberDigits + 1]; number.Digits = buffer; UInt64ToNumber(value, ref number); if (fmt != 0) NumberToString(formatter, ref number, fmt, digits, culture); else NumberToCustomFormatString(formatter, ref number, specifier, culture); break; } } public static void FormatSingle (StringBuffer formatter, float value, StringView specifier, CachedCulture culture) { int digits; int precision = FloatPrecision; var fmt = ParseFormatSpecifier(specifier, out digits); // ANDing with 0xFFDF has the effect of uppercasing the character switch (fmt & 0xFFDF) { case 'G': if (digits > 7) precision = 9; break; case 'E': if (digits > 6) precision = 9; break; } var number = new Number(); var buffer = stackalloc char[MaxFloatingDigits + 1]; number.Digits = buffer; DoubleToNumber(value, precision, ref number); if (number.Scale == ScaleNaN) { formatter.Append(culture.NaN); return; } if (number.Scale == ScaleInf) { if (number.Sign > 0) formatter.Append(culture.NegativeInfinity); else formatter.Append(culture.PositiveInfinity); return; } if (fmt != 0) NumberToString(formatter, ref number, fmt, digits, culture); else NumberToCustomFormatString(formatter, ref number, specifier, culture); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void FormatDouble (StringBuffer formatter, double value, StringView specifier, CachedCulture culture) { int digits; int precision = DoublePrecision; var fmt = ParseFormatSpecifier(specifier, out digits); // ANDing with 0xFFDF has the effect of uppercasing the character switch (fmt & 0xFFDF) { case 'G': if (digits > 15) precision = 17; break; case 'E': if (digits > 14) precision = 17; break; } var number = new Number(); var buffer = stackalloc char[MaxFloatingDigits + 1]; number.Digits = buffer; DoubleToNumber(value, precision, ref number); if (number.Scale == ScaleNaN) { formatter.Append(culture.NaN); return; } if (number.Scale == ScaleInf) { if (number.Sign > 0) formatter.Append(culture.NegativeInfinity); else formatter.Append(culture.PositiveInfinity); return; } if (fmt != 0) NumberToString(formatter, ref number, fmt, digits, culture); else NumberToCustomFormatString(formatter, ref number, specifier, culture); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void FormatDecimal (StringBuffer formatter, uint* value, StringView specifier, CachedCulture culture) { int digits; var fmt = ParseFormatSpecifier(specifier, out digits); var number = new Number(); var buffer = stackalloc char[MaxNumberDigits + 1]; number.Digits = buffer; DecimalToNumber(value, ref number); if (fmt != 0) NumberToString(formatter, ref number, fmt, digits, culture, isDecimal: true); else NumberToCustomFormatString(formatter, ref number, specifier, culture); } static void NumberToString (StringBuffer formatter, ref Number number, char format, int maxDigits, CachedCulture culture, bool isDecimal = false) { // ANDing with 0xFFDF has the effect of uppercasing the character switch (format & 0xFFDF) { case 'C': { var cultureData = culture.CurrencyData; var bufferSize = cultureData.GetBufferSize(ref maxDigits, number.Scale); RoundNumber(ref number, number.Scale + maxDigits); var buffer = stackalloc char[bufferSize]; var ptr = FormatCurrency( buffer, ref number, maxDigits, cultureData, number.Sign > 0 ? culture.CurrencyNegativePattern : culture.CurrencyPositivePattern, culture.CurrencySymbol ); formatter.Append(buffer, (int)(ptr - buffer)); break; } case 'F': { var cultureData = culture.FixedData; var bufferSize = cultureData.GetBufferSize(ref maxDigits, number.Scale); RoundNumber(ref number, number.Scale + maxDigits); var buffer = stackalloc char[bufferSize]; var ptr = buffer; if (number.Sign > 0) AppendString(&ptr, cultureData.NegativeSign); ptr = FormatFixed(ptr, ref number, maxDigits, cultureData); formatter.Append(buffer, (int)(ptr - buffer)); break; } case 'N': { var cultureData = culture.NumberData; var bufferSize = cultureData.GetBufferSize(ref maxDigits, number.Scale); RoundNumber(ref number, number.Scale + maxDigits); var buffer = stackalloc char[bufferSize]; var ptr = FormatNumber( buffer, ref number, maxDigits, number.Sign > 0 ? culture.NumberNegativePattern : culture.NumberPositivePattern, cultureData ); formatter.Append(buffer, (int)(ptr - buffer)); break; } case 'E': { var cultureData = culture.ScientificData; var bufferSize = cultureData.GetBufferSize(ref maxDigits, number.Scale); maxDigits++; RoundNumber(ref number, maxDigits); var buffer = stackalloc char[bufferSize]; var ptr = buffer; if (number.Sign > 0) AppendString(&ptr, cultureData.NegativeSign); ptr = FormatScientific( ptr, ref number, maxDigits, format, // TODO: fix casing cultureData.DecimalSeparator, culture.PositiveSign, culture.NegativeSign ); formatter.Append(buffer, (int)(ptr - buffer)); break; } case 'P': { number.Scale += 2; var cultureData = culture.PercentData; var bufferSize = cultureData.GetBufferSize(ref maxDigits, number.Scale); RoundNumber(ref number, number.Scale + maxDigits); var buffer = stackalloc char[bufferSize]; var ptr = FormatPercent( buffer, ref number, maxDigits, cultureData, number.Sign > 0 ? culture.PercentNegativePattern : culture.PercentPositivePattern, culture.PercentSymbol ); formatter.Append(buffer, (int)(ptr - buffer)); break; } case 'G': { var enableRounding = true; if (maxDigits < 1) { if (isDecimal && maxDigits == -1) { // if we're formatting a decimal, default to 29 digits precision // only for G formatting without a precision specifier maxDigits = DecimalPrecision; enableRounding = false; } else maxDigits = number.Precision; } var bufferSize = maxDigits + culture.DecimalBufferSize; var buffer = stackalloc char[bufferSize]; var ptr = buffer; // round for G formatting only if a precision is given // we need to handle the minus zero case also if (enableRounding) RoundNumber(ref number, maxDigits); else if (isDecimal && number.Digits[0] == 0) number.Sign = 0; if (number.Sign > 0) AppendString(&ptr, culture.NegativeSign); ptr = FormatGeneral( ptr, ref number, maxDigits, (char)(format - ('G' - 'E')), culture.NumberData.DecimalSeparator, culture.PositiveSign, culture.NegativeSign, !enableRounding ); formatter.Append(buffer, (int)(ptr - buffer)); break; } default: throw new FormatException(string.Format(SR.UnknownFormatSpecifier, format)); } } static char* FormatCurrency (char* buffer, ref Number number, int maxDigits, NumberFormatData data, string currencyFormat, string currencySymbol) { for (int i = 0; i < currencyFormat.Length; i++) { char c = currencyFormat[i]; switch (c) { case '#': buffer = FormatFixed(buffer, ref number, maxDigits, data); break; case '-': AppendString(&buffer, data.NegativeSign); break; case '$': AppendString(&buffer, currencySymbol); break; default: *buffer++ = c; break; } } return buffer; } static char* FormatNumber (char* buffer, ref Number number, int maxDigits, string format, NumberFormatData data) { for (int i = 0; i < format.Length; i++) { char c = format[i]; switch (c) { case '#': buffer = FormatFixed(buffer, ref number, maxDigits, data); break; case '-': AppendString(&buffer, data.NegativeSign); break; default: *buffer++ = c; break; } } return buffer; } static char* FormatPercent (char* buffer, ref Number number, int maxDigits, NumberFormatData data, string format, string percentSymbol) { for (int i = 0; i < format.Length; i++) { char c = format[i]; switch (c) { case '#': buffer = FormatFixed(buffer, ref number, maxDigits, data); break; case '-': AppendString(&buffer, data.NegativeSign); break; case '%': AppendString(&buffer, percentSymbol); break; default: *buffer++ = c; break; } } return buffer; } static char* FormatGeneral ( char* buffer, ref Number number, int maxDigits, char expChar, string decimalSeparator, string positiveSign, string negativeSign, bool suppressScientific) { var digitPos = number.Scale; var scientific = false; if (!suppressScientific) { if (digitPos > maxDigits || digitPos < -3) { digitPos = 1; scientific = true; } } var digits = number.Digits; if (digitPos <= 0) *buffer++ = '0'; else { do { *buffer++ = *digits != 0 ? *digits++ : '0'; } while (--digitPos > 0); } if (*digits != 0 || digitPos < 0) { AppendString(&buffer, decimalSeparator); while (digitPos < 0) { *buffer++ = '0'; digitPos++; } while (*digits != 0) *buffer++ = *digits++; } if (scientific) buffer = FormatExponent(buffer, number.Scale - 1, expChar, positiveSign, negativeSign, 2); return buffer; } static char* FormatScientific ( char* buffer, ref Number number, int maxDigits, char expChar, string decimalSeparator, string positiveSign, string negativeSign) { var digits = number.Digits; *buffer++ = *digits != 0 ? *digits++ : '0'; if (maxDigits != 1) AppendString(&buffer, decimalSeparator); while (--maxDigits > 0) *buffer++ = *digits != 0 ? *digits++ : '0'; int e = number.Digits[0] == 0 ? 0 : number.Scale - 1; return FormatExponent(buffer, e, expChar, positiveSign, negativeSign, 3); } static char* FormatExponent (char* buffer, int value, char expChar, string positiveSign, string negativeSign, int minDigits) { *buffer++ = expChar; if (value < 0) { AppendString(&buffer, negativeSign); value = -value; } else if (positiveSign != null) AppendString(&buffer, positiveSign); var digits = stackalloc char[11]; var ptr = Int32ToDecChars(digits + 10, (uint)value, minDigits); var len = (int)(digits + 10 - ptr); while (--len >= 0) *buffer++ = *ptr++; return buffer; } static char* FormatFixed (char* buffer, ref Number number, int maxDigits, NumberFormatData data) { var groups = data.GroupSizes; var digits = number.Digits; var digitPos = number.Scale; if (digitPos <= 0) *buffer++ = '0'; else if (groups != null) { var groupIndex = 0; var groupSizeCount = groups[0]; var groupSizeLen = groups.Length; var newBufferSize = digitPos; var groupSeparatorLen = data.GroupSeparator.Length; var groupSize = 0; // figure out the size of the result if (groupSizeLen != 0) { while (digitPos > groupSizeCount) { groupSize = groups[groupIndex]; if (groupSize == 0) break; newBufferSize += groupSeparatorLen; if (groupIndex < groupSizeLen - 1) groupIndex++; groupSizeCount += groups[groupIndex]; if (groupSizeCount < 0 || newBufferSize < 0) throw new ArgumentOutOfRangeException(SR.InvalidGroupSizes); } if (groupSizeCount == 0) groupSize = 0; else groupSize = groups[0]; } groupIndex = 0; var digitCount = 0; var digitLength = StrLen(digits); var digitStart = digitPos < digitLength ? digitPos : digitLength; var ptr = buffer + newBufferSize - 1; for (int i = digitPos - 1; i >= 0; i--) { *(ptr--) = i < digitStart ? digits[i] : '0'; // check if we need to add a group separator if (groupSize > 0) { digitCount++; if (digitCount == groupSize && i != 0) { for (int j = groupSeparatorLen - 1; j >= 0; j--) *(ptr--) = data.GroupSeparator[j]; if (groupIndex < groupSizeLen - 1) { groupIndex++; groupSize = groups[groupIndex]; } digitCount = 0; } } } buffer += newBufferSize; digits += digitStart; } else { do { *buffer++ = *digits != 0 ? *digits++ : '0'; } while (--digitPos > 0); } if (maxDigits > 0) { AppendString(&buffer, data.DecimalSeparator); while (digitPos < 0 && maxDigits > 0) { *buffer++ = '0'; digitPos++; maxDigits--; } while (maxDigits > 0) { *buffer++ = *digits != 0 ? *digits++ : '0'; maxDigits--; } } return buffer; } static void Int32ToDecStr (StringBuffer formatter, int value, int digits, string negativeSign) { if (digits < 1) digits = 1; var maxDigits = digits > 15 ? digits : 15; var bufferLength = maxDigits > 100 ? maxDigits : 100; var negativeLength = 0; if (value < 0) { negativeLength = negativeSign.Length; if (negativeLength > bufferLength - maxDigits) bufferLength = negativeLength + maxDigits; } var buffer = stackalloc char[bufferLength]; var p = Int32ToDecChars(buffer + bufferLength, value >= 0 ? (uint)value : (uint)-value, digits); if (value < 0) { // add the negative sign for (int i = negativeLength - 1; i >= 0; i--) *(--p) = negativeSign[i]; } formatter.Append(p, (int)(buffer + bufferLength - p)); } static void UInt32ToDecStr (StringBuffer formatter, uint value, int digits) { var buffer = stackalloc char[100]; if (digits < 1) digits = 1; var p = Int32ToDecChars(buffer + 100, value, digits); formatter.Append(p, (int)(buffer + 100 - p)); } static void Int32ToHexStr (StringBuffer formatter, uint value, int hexBase, int digits) { var buffer = stackalloc char[100]; if (digits < 1) digits = 1; var p = Int32ToHexChars(buffer + 100, value, hexBase, digits); formatter.Append(p, (int)(buffer + 100 - p)); } static void Int64ToDecStr (StringBuffer formatter, long value, int digits, string negativeSign) { if (digits < 1) digits = 1; var sign = (int)High32((ulong)value); var maxDigits = digits > 20 ? digits : 20; var bufferLength = maxDigits > 100 ? maxDigits : 100; if (sign < 0) { value = -value; var negativeLength = negativeSign.Length; if (negativeLength > bufferLength - maxDigits) bufferLength = negativeLength + maxDigits; } var buffer = stackalloc char[bufferLength]; var p = buffer + bufferLength; var uv = (ulong)value; while (High32(uv) != 0) { p = Int32ToDecChars(p, Int64DivMod(ref uv), 9); digits -= 9; } p = Int32ToDecChars(p, Low32(uv), digits); if (sign < 0) { // add the negative sign for (int i = negativeSign.Length - 1; i >= 0; i--) *(--p) = negativeSign[i]; } formatter.Append(p, (int)(buffer + bufferLength - p)); } static void UInt64ToDecStr (StringBuffer formatter, ulong value, int digits) { if (digits < 1) digits = 1; var buffer = stackalloc char[100]; var p = buffer + 100; while (High32(value) != 0) { p = Int32ToDecChars(p, Int64DivMod(ref value), 9); digits -= 9; } p = Int32ToDecChars(p, Low32(value), digits); formatter.Append(p, (int)(buffer + 100 - p)); } static void Int64ToHexStr (StringBuffer formatter, ulong value, int hexBase, int digits) { var buffer = stackalloc char[100]; char* ptr; if (High32(value) != 0) { Int32ToHexChars(buffer + 100, Low32(value), hexBase, 8); ptr = Int32ToHexChars(buffer + 100 - 8, High32(value), hexBase, digits - 8); } else { if (digits < 1) digits = 1; ptr = Int32ToHexChars(buffer + 100, Low32(value), hexBase, digits); } formatter.Append(ptr, (int)(buffer + 100 - ptr)); } static char* Int32ToDecChars (char* p, uint value, int digits) { while (value != 0) { *--p = (char)(value % 10 + '0'); value /= 10; digits--; } while (--digits >= 0) *--p = '0'; return p; } static char* Int32ToHexChars (char* p, uint value, int hexBase, int digits) { while (--digits >= 0 || value != 0) { var digit = value & 0xF; *--p = (char)(digit + (digit < 10 ? '0' : hexBase)); value >>= 4; } return p; } static char ParseFormatSpecifier (StringView specifier, out int digits) { if (specifier.IsEmpty) { digits = -1; return 'G'; } char* curr = specifier.Data; char first = *curr++; if ((first >= 'A' && first <= 'Z') || (first >= 'a' && first <= 'z')) { int n = -1; char c = *curr++; if (c >= '0' && c <= '9') { n = c - '0'; c = *curr++; while (c >= '0' && c <= '9') { n = n * 10 + c - '0'; c = *curr++; if (n >= 10) break; } } if (c == 0) { digits = n; return first; } } digits = -1; return (char)0; } static void Int32ToNumber (int value, ref Number number) { number.Precision = Int32Precision; if (value >= 0) number.Sign = 0; else { number.Sign = 1; value = -value; } var buffer = stackalloc char[Int32Precision + 1]; var ptr = Int32ToDecChars(buffer + Int32Precision, (uint)value, 0); var len = (int)(buffer + Int32Precision - ptr); number.Scale = len; var dest = number.Digits; while (--len >= 0) *dest++ = *ptr++; *dest = '\0'; } static void UInt32ToNumber (uint value, ref Number number) { number.Precision = UInt32Precision; number.Sign = 0; var buffer = stackalloc char[UInt32Precision + 1]; var ptr = Int32ToDecChars(buffer + UInt32Precision, value, 0); var len = (int)(buffer + UInt32Precision - ptr); number.Scale = len; var dest = number.Digits; while (--len >= 0) *dest++ = *ptr++; *dest = '\0'; } static void Int64ToNumber (long value, ref Number number) { number.Precision = Int64Precision; if (value >= 0) number.Sign = 0; else { number.Sign = 1; value = -value; } var buffer = stackalloc char[Int64Precision + 1]; var ptr = buffer + Int64Precision; var uv = (ulong)value; while (High32(uv) != 0) ptr = Int32ToDecChars(ptr, Int64DivMod(ref uv), 9); ptr = Int32ToDecChars(ptr, Low32(uv), 0); var len = (int)(buffer + Int64Precision - ptr); number.Scale = len; var dest = number.Digits; while (--len >= 0) *dest++ = *ptr++; *dest = '\0'; } static void UInt64ToNumber (ulong value, ref Number number) { number.Precision = UInt64Precision; number.Sign = 0; var buffer = stackalloc char[UInt64Precision + 1]; var ptr = buffer + UInt64Precision; while (High32(value) != 0) ptr = Int32ToDecChars(ptr, Int64DivMod(ref value), 9); ptr = Int32ToDecChars(ptr, Low32(value), 0); var len = (int)(buffer + UInt64Precision - ptr); number.Scale = len; var dest = number.Digits; while (--len >= 0) *dest++ = *ptr++; *dest = '\0'; } static void DoubleToNumber (double value, int precision, ref Number number) { number.Precision = precision; uint sign, exp, mantHi, mantLo; ExplodeDouble(value, out sign, out exp, out mantHi, out mantLo); if (exp == 0x7FF) { // special value handling (infinity and NaNs) number.Scale = (mantLo != 0 || mantHi != 0) ? ScaleNaN : ScaleInf; number.Sign = (int)sign; number.Digits[0] = '\0'; } else { // convert the digits of the number to characters if (value < 0) { number.Sign = 1; value = -value; } var digits = number.Digits; var end = digits + MaxFloatingDigits; var p = end; var shift = 0; double intPart; double reducedInt; var fracPart = ModF(value, out intPart); if (intPart != 0) { // format the integer part while (intPart != 0) { reducedInt = ModF(intPart / 10, out intPart); *--p = (char)((int)((reducedInt + 0.03) * 10) + '0'); shift++; } while (p < end) *digits++ = *p++; } else if (fracPart > 0) { // normalize the fractional part while ((reducedInt = fracPart * 10) < 1) { fracPart = reducedInt; shift--; } } // concat the fractional part, padding the remainder with zeros p = number.Digits + precision; while (digits <= p && digits < end) { fracPart *= 10; fracPart = ModF(fracPart, out reducedInt); *digits++ = (char)((int)reducedInt + '0'); } // round the result if necessary digits = p; *p = (char)(*p + 5); while (*p > '9') { *p = '0'; if (p > number.Digits) ++*--p; else { *p = '1'; shift++; } } number.Scale = shift; *digits = '\0'; } } static void DecimalToNumber (uint* value, ref Number number) { // bit 31 of the decimal is the sign bit // bits 16-23 contain the scale number.Sign = (int)(*value >> 31); number.Scale = (int)((*value >> 16) & 0xFF); number.Precision = DecimalPrecision; // loop for as long as the decimal is larger than 32 bits var buffer = stackalloc char[DecimalPrecision + 1]; var p = buffer + DecimalPrecision; var hi = *(value + 1); var lo = *(value + 2); var mid = *(value + 3); while ((mid | hi) != 0) { // keep dividing down by one billion at a time ulong n = hi; hi = (uint)(n / OneBillion); n = (n % OneBillion) << 32 | mid; mid = (uint)(n / OneBillion); n = (n % OneBillion) << 32 | lo; lo = (uint)(n / OneBillion); // format this portion of the number p = Int32ToDecChars(p, (uint)(n % OneBillion), 9); } // finish off with the low 32-bits of the decimal, if anything is left over p = Int32ToDecChars(p, lo, 0); var len = (int)(buffer + DecimalPrecision - p); number.Scale = len - number.Scale; var dest = number.Digits; while (--len >= 0) *dest++ = *p++; *dest = '\0'; } static void RoundNumber (ref Number number, int pos) { var digits = number.Digits; int i = 0; while (i < pos && digits[i] != 0) i++; if (i == pos && digits[i] >= '5') { while (i > 0 && digits[i - 1] == '9') i--; if (i > 0) digits[i - 1]++; else { number.Scale++; digits[0] = '1'; i = 1; } } else { while (i > 0 && digits[i - 1] == '0') i--; } if (i == 0) { number.Scale = 0; number.Sign = 0; } digits[i] = '\0'; } static void AppendString (char** buffer, string value) { fixed (char* pinnedString = value) { var length = value.Length; for (var src = pinnedString; src < pinnedString + length; (*buffer)++, src++) **buffer = *src; } } static int StrLen (char* str) { int count = 0; while (*str++ != 0) count++; return count; } static uint Int64DivMod (ref ulong value) { var rem = (uint)(value % 1000000000); value /= 1000000000; return rem; } static double ModF (double value, out double intPart) { intPart = Math.Truncate(value); return value - intPart; } static void ExplodeDouble (double value, out uint sign, out uint exp, out uint mantHi, out uint mantLo) { var bits = *(ulong*)&value; if (BitConverter.IsLittleEndian) { mantLo = (uint)(bits & 0xFFFFFFFF); // bits 0 - 31 mantHi = (uint)((bits >> 32) & 0xFFFFF); // bits 32 - 51 exp = (uint)((bits >> 52) & 0x7FF); // bits 52 - 62 sign = (uint)((bits >> 63) & 0x1); // bit 63 } else { sign = (uint)(bits & 0x1); // bit 0 exp = (uint)((bits >> 1) & 0x7FF); // bits 1 - 11 mantHi = (uint)((bits >> 12) & 0xFFFFF); // bits 12 - 31 mantLo = (uint)(bits >> 32); // bits 32 - 63 } } static uint Low32 (ulong value) { return (uint)value; } static uint High32 (ulong value) { return (uint)((value & 0xFFFFFFFF00000000) >> 32); } struct Number { public int Precision; public int Scale; public int Sign; public char* Digits; // useful for debugging public override string ToString () { return new string(Digits); } } const int MaxNumberDigits = 50; const int MaxFloatingDigits = 352; const int Int32Precision = 10; const int UInt32Precision = 10; const int Int64Precision = 19; const int UInt64Precision = 20; const int FloatPrecision = 7; const int DoublePrecision = 15; const int DecimalPrecision = 29; const int ScaleNaN = unchecked((int)0x80000000); const int ScaleInf = 0x7FFFFFFF; const int OneBillion = 1000000000; } // currently just contains some hardcoded exception messages static class SR { public const string InvalidGroupSizes = "Invalid group sizes in NumberFormatInfo."; public const string UnknownFormatSpecifier = "Unknown format specifier '{0}'."; public const string ArgIndexOutOfRange = "No format argument exists for index '{0}'."; public const string TypeNotFormattable = "Type '{0}' is not a built-in type, does not implement IStringFormattable, and no custom formatter was found for it."; public const string InvalidFormatString = "Invalid format string."; } /// /// A low-allocation version of the built-in type. /// partial class StringBuffer { /// /// Appends the string returned by processing a composite format string, which contains zero or more format items, to this instance. Each format item is replaced by the string representation of a single argument. /// /// A composite format string. /// A value to format. public void AppendFormat(string format, T0 arg0) { var args = new Arg1(arg0); AppendArgSet(format, ref args); } /// /// Converts the value of objects to strings based on the formats specified and inserts them into another string. /// /// A composite format string. /// A value to format. public static string Format(string format, T0 arg0) { var buffer = Acquire(format.Length + 8); buffer.AppendFormat(format, arg0); var result = buffer.ToString(); Release(buffer); return result; } /// /// Appends the string returned by processing a composite format string, which contains zero or more format items, to this instance. Each format item is replaced by the string representation of a single argument. /// /// A composite format string. /// A value to format. /// A value to format. public void AppendFormat(string format, T0 arg0, T1 arg1) { var args = new Arg2(arg0, arg1); AppendArgSet(format, ref args); } /// /// Converts the value of objects to strings based on the formats specified and inserts them into another string. /// /// A composite format string. /// A value to format. /// A value to format. public static string Format(string format, T0 arg0, T1 arg1) { var buffer = Acquire(format.Length + 16); buffer.AppendFormat(format, arg0, arg1); var result = buffer.ToString(); Release(buffer); return result; } /// /// Appends the string returned by processing a composite format string, which contains zero or more format items, to this instance. Each format item is replaced by the string representation of a single argument. /// /// A composite format string. /// A value to format. /// A value to format. /// A value to format. public void AppendFormat(string format, T0 arg0, T1 arg1, T2 arg2) { var args = new Arg3(arg0, arg1, arg2); AppendArgSet(format, ref args); } /// /// Converts the value of objects to strings based on the formats specified and inserts them into another string. /// /// A composite format string. /// A value to format. /// A value to format. /// A value to format. public static string Format(string format, T0 arg0, T1 arg1, T2 arg2) { var buffer = Acquire(format.Length + 24); buffer.AppendFormat(format, arg0, arg1, arg2); var result = buffer.ToString(); Release(buffer); return result; } /// /// Appends the string returned by processing a composite format string, which contains zero or more format items, to this instance. Each format item is replaced by the string representation of a single argument. /// /// A composite format string. /// A value to format. /// A value to format. /// A value to format. /// A value to format. public void AppendFormat(string format, T0 arg0, T1 arg1, T2 arg2, T3 arg3) { var args = new Arg4(arg0, arg1, arg2, arg3); AppendArgSet(format, ref args); } /// /// Converts the value of objects to strings based on the formats specified and inserts them into another string. /// /// A composite format string. /// A value to format. /// A value to format. /// A value to format. /// A value to format. public static string Format(string format, T0 arg0, T1 arg1, T2 arg2, T3 arg3) { var buffer = Acquire(format.Length + 32); buffer.AppendFormat(format, arg0, arg1, arg2, arg3); var result = buffer.ToString(); Release(buffer); return result; } /// /// Appends the string returned by processing a composite format string, which contains zero or more format items, to this instance. Each format item is replaced by the string representation of a single argument. /// /// A composite format string. /// A value to format. /// A value to format. /// A value to format. /// A value to format. /// A value to format. public void AppendFormat(string format, T0 arg0, T1 arg1, T2 arg2, T3 arg3, T4 arg4) { var args = new Arg5(arg0, arg1, arg2, arg3, arg4); AppendArgSet(format, ref args); } /// /// Converts the value of objects to strings based on the formats specified and inserts them into another string. /// /// A composite format string. /// A value to format. /// A value to format. /// A value to format. /// A value to format. /// A value to format. public static string Format(string format, T0 arg0, T1 arg1, T2 arg2, T3 arg3, T4 arg4) { var buffer = Acquire(format.Length + 40); buffer.AppendFormat(format, arg0, arg1, arg2, arg3, arg4); var result = buffer.ToString(); Release(buffer); return result; } /// /// Appends the string returned by processing a composite format string, which contains zero or more format items, to this instance. Each format item is replaced by the string representation of a single argument. /// /// A composite format string. /// A value to format. /// A value to format. /// A value to format. /// A value to format. /// A value to format. /// A value to format. public void AppendFormat(string format, T0 arg0, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5) { var args = new Arg6(arg0, arg1, arg2, arg3, arg4, arg5); AppendArgSet(format, ref args); } /// /// Converts the value of objects to strings based on the formats specified and inserts them into another string. /// /// A composite format string. /// A value to format. /// A value to format. /// A value to format. /// A value to format. /// A value to format. /// A value to format. public static string Format(string format, T0 arg0, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5) { var buffer = Acquire(format.Length + 48); buffer.AppendFormat(format, arg0, arg1, arg2, arg3, arg4, arg5); var result = buffer.ToString(); Release(buffer); return result; } /// /// Appends the string returned by processing a composite format string, which contains zero or more format items, to this instance. Each format item is replaced by the string representation of a single argument. /// /// A composite format string. /// A value to format. /// A value to format. /// A value to format. /// A value to format. /// A value to format. /// A value to format. /// A value to format. public void AppendFormat(string format, T0 arg0, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6) { var args = new Arg7(arg0, arg1, arg2, arg3, arg4, arg5, arg6); AppendArgSet(format, ref args); } /// /// Converts the value of objects to strings based on the formats specified and inserts them into another string. /// /// A composite format string. /// A value to format. /// A value to format. /// A value to format. /// A value to format. /// A value to format. /// A value to format. /// A value to format. public static string Format(string format, T0 arg0, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6) { var buffer = Acquire(format.Length + 56); buffer.AppendFormat(format, arg0, arg1, arg2, arg3, arg4, arg5, arg6); var result = buffer.ToString(); Release(buffer); return result; } /// /// Appends the string returned by processing a composite format string, which contains zero or more format items, to this instance. Each format item is replaced by the string representation of a single argument. /// /// A composite format string. /// A value to format. /// A value to format. /// A value to format. /// A value to format. /// A value to format. /// A value to format. /// A value to format. /// A value to format. public void AppendFormat(string format, T0 arg0, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7) { var args = new Arg8(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7); AppendArgSet(format, ref args); } /// /// Converts the value of objects to strings based on the formats specified and inserts them into another string. /// /// A composite format string. /// A value to format. /// A value to format. /// A value to format. /// A value to format. /// A value to format. /// A value to format. /// A value to format. /// A value to format. public static string Format(string format, T0 arg0, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7) { var buffer = Acquire(format.Length + 64); buffer.AppendFormat(format, arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7); var result = buffer.ToString(); Release(buffer); return result; } } unsafe struct Arg1 : IArgSet { T0 t0; public int Count => 1; public Arg1 (T0 t0) { this.t0 = t0; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Format (StringBuffer buffer, int index, StringView format) { switch (index) { case 0: buffer.AppendGeneric(t0, format); break; } } } unsafe struct Arg2 : IArgSet { T0 t0; T1 t1; public int Count => 2; public Arg2 (T0 t0, T1 t1) { this.t0 = t0; this.t1 = t1; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Format (StringBuffer buffer, int index, StringView format) { switch (index) { case 0: buffer.AppendGeneric(t0, format); break; case 1: buffer.AppendGeneric(t1, format); break; } } } unsafe struct Arg3 : IArgSet { T0 t0; T1 t1; T2 t2; public int Count => 3; public Arg3 (T0 t0, T1 t1, T2 t2) { this.t0 = t0; this.t1 = t1; this.t2 = t2; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Format (StringBuffer buffer, int index, StringView format) { switch (index) { case 0: buffer.AppendGeneric(t0, format); break; case 1: buffer.AppendGeneric(t1, format); break; case 2: buffer.AppendGeneric(t2, format); break; } } } unsafe struct Arg4 : IArgSet { T0 t0; T1 t1; T2 t2; T3 t3; public int Count => 4; public Arg4 (T0 t0, T1 t1, T2 t2, T3 t3) { this.t0 = t0; this.t1 = t1; this.t2 = t2; this.t3 = t3; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Format (StringBuffer buffer, int index, StringView format) { switch (index) { case 0: buffer.AppendGeneric(t0, format); break; case 1: buffer.AppendGeneric(t1, format); break; case 2: buffer.AppendGeneric(t2, format); break; case 3: buffer.AppendGeneric(t3, format); break; } } } unsafe struct Arg5 : IArgSet { T0 t0; T1 t1; T2 t2; T3 t3; T4 t4; public int Count => 5; public Arg5 (T0 t0, T1 t1, T2 t2, T3 t3, T4 t4) { this.t0 = t0; this.t1 = t1; this.t2 = t2; this.t3 = t3; this.t4 = t4; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Format (StringBuffer buffer, int index, StringView format) { switch (index) { case 0: buffer.AppendGeneric(t0, format); break; case 1: buffer.AppendGeneric(t1, format); break; case 2: buffer.AppendGeneric(t2, format); break; case 3: buffer.AppendGeneric(t3, format); break; case 4: buffer.AppendGeneric(t4, format); break; } } } unsafe struct Arg6 : IArgSet { T0 t0; T1 t1; T2 t2; T3 t3; T4 t4; T5 t5; public int Count => 6; public Arg6 (T0 t0, T1 t1, T2 t2, T3 t3, T4 t4, T5 t5) { this.t0 = t0; this.t1 = t1; this.t2 = t2; this.t3 = t3; this.t4 = t4; this.t5 = t5; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Format (StringBuffer buffer, int index, StringView format) { switch (index) { case 0: buffer.AppendGeneric(t0, format); break; case 1: buffer.AppendGeneric(t1, format); break; case 2: buffer.AppendGeneric(t2, format); break; case 3: buffer.AppendGeneric(t3, format); break; case 4: buffer.AppendGeneric(t4, format); break; case 5: buffer.AppendGeneric(t5, format); break; } } } unsafe struct Arg7 : IArgSet { T0 t0; T1 t1; T2 t2; T3 t3; T4 t4; T5 t5; T6 t6; public int Count => 7; public Arg7 (T0 t0, T1 t1, T2 t2, T3 t3, T4 t4, T5 t5, T6 t6) { this.t0 = t0; this.t1 = t1; this.t2 = t2; this.t3 = t3; this.t4 = t4; this.t5 = t5; this.t6 = t6; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Format (StringBuffer buffer, int index, StringView format) { switch (index) { case 0: buffer.AppendGeneric(t0, format); break; case 1: buffer.AppendGeneric(t1, format); break; case 2: buffer.AppendGeneric(t2, format); break; case 3: buffer.AppendGeneric(t3, format); break; case 4: buffer.AppendGeneric(t4, format); break; case 5: buffer.AppendGeneric(t5, format); break; case 6: buffer.AppendGeneric(t6, format); break; } } } unsafe struct Arg8 : IArgSet { T0 t0; T1 t1; T2 t2; T3 t3; T4 t4; T5 t5; T6 t6; T7 t7; public int Count => 8; public Arg8 (T0 t0, T1 t1, T2 t2, T3 t3, T4 t4, T5 t5, T6 t6, T7 t7) { this.t0 = t0; this.t1 = t1; this.t2 = t2; this.t3 = t3; this.t4 = t4; this.t5 = t5; this.t6 = t6; this.t7 = t7; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Format (StringBuffer buffer, int index, StringView format) { switch (index) { case 0: buffer.AppendGeneric(t0, format); break; case 1: buffer.AppendGeneric(t1, format); break; case 2: buffer.AppendGeneric(t2, format); break; case 3: buffer.AppendGeneric(t3, format); break; case 4: buffer.AppendGeneric(t4, format); break; case 5: buffer.AppendGeneric(t5, format); break; case 6: buffer.AppendGeneric(t6, format); break; case 7: buffer.AppendGeneric(t7, format); break; } } } }