12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112 |
- using System;
- using System.Diagnostics;
- using System.Runtime.CompilerServices;
-
- #if !BURST_COMPILER_SHARED
- using Unity.Collections.LowLevel.Unsafe;
- #endif
-
- namespace Unity.Burst
- {
- #if BURST_COMPILER_SHARED
- internal static partial class BurstStringInternal
- #else
- internal static partial class BurstString
- #endif
- {
- /// <summary>
- /// Copies a Burst managed UTF8 string prefixed by a ushort length to a FixedString with the specified maximum length.
- /// </summary>
- /// <param name="dest">Pointer to the fixed string.</param>
- /// <param name="destLength">Maximum number of UTF8 the fixed string supports without including the zero character.</param>
- /// <param name="src">The UTF8 Burst managed string prefixed by a ushort length and zero terminated.
- /// <param name="srcLength">Number of UTF8 the fixed string supports without including the zero character.</param>
- /// </param>
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static unsafe void CopyFixedString(byte* dest, int destLength, byte* src, int srcLength)
- {
- // TODO: should we throw an exception instead if the string doesn't fit?
- var finalLength = srcLength > destLength ? destLength : srcLength;
- // Write the length and zero null terminated
- *((ushort*)dest - 1) = (ushort)finalLength;
- dest[finalLength] = 0;
- #if BURST_COMPILER_SHARED
- Unsafe.CopyBlock(dest, src, (uint)finalLength);
- #else
- UnsafeUtility.MemCpy(dest, src, finalLength);
- #endif
- }
-
- /// <summary>
- /// Format a UTF-8 string (with a specified source length) to a destination buffer.
- /// </summary>
- /// <param name="dest">Destination buffer.</param>
- /// <param name="destIndex">Current index in destination buffer.</param>
- /// <param name="destLength">Maximum length of destination buffer.</param>
- /// <param name="src">The source buffer of the string to copy from.</param>
- /// <param name="srcLength">The length of the string from the source buffer.</param>
- /// <param name="formatOptionsRaw">Formatting options encoded in raw format.</param>
- public static unsafe void Format(byte* dest, ref int destIndex, int destLength, byte* src, int srcLength, int formatOptionsRaw)
- {
- var options = *(FormatOptions*)&formatOptionsRaw;
-
- // Align left
- if (AlignLeft(dest, ref destIndex, destLength, options.AlignAndSize, srcLength)) return;
-
- int maxToCopy = destLength - destIndex;
- int toCopyLength = srcLength > maxToCopy ? maxToCopy : srcLength;
- if (toCopyLength > 0)
- {
- #if BURST_COMPILER_SHARED
- Unsafe.CopyBlock(dest + destIndex, src, (uint)toCopyLength);
- #else
- UnsafeUtility.MemCpy(dest + destIndex, src, toCopyLength);
- #endif
- destIndex += toCopyLength;
-
- // Align right
- AlignRight(dest, ref destIndex, destLength, options.AlignAndSize, srcLength);
- }
- }
-
- /// <summary>
- /// Format a float value to a destination buffer.
- /// </summary>
- /// <param name="dest">Destination buffer.</param>
- /// <param name="destIndex">Current index in destination buffer.</param>
- /// <param name="destLength">Maximum length of destination buffer.</param>
- /// <param name="value">The value to format.</param>
- /// <param name="formatOptionsRaw">Formatting options encoded in raw format.</param>
- public static unsafe void Format(byte* dest, ref int destIndex, int destLength, float value, int formatOptionsRaw)
- {
- var options = *(FormatOptions*)&formatOptionsRaw;
- ConvertFloatToString(dest, ref destIndex, destLength, value, options);
- }
-
- /// <summary>
- /// Format a double value to a destination buffer.
- /// </summary>
- /// <param name="dest">Destination buffer.</param>
- /// <param name="destIndex">Current index in destination buffer.</param>
- /// <param name="destLength">Maximum length of destination buffer.</param>
- /// <param name="value">The value to format.</param>
- /// <param name="formatOptionsRaw">Formatting options encoded in raw format.</param>
- public static unsafe void Format(byte* dest, ref int destIndex, int destLength, double value, int formatOptionsRaw)
- {
- var options = *(FormatOptions*)&formatOptionsRaw;
- ConvertDoubleToString(dest, ref destIndex, destLength, value, options);
- }
-
- /// <summary>
- /// Format a bool value to a destination buffer.
- /// </summary>
- /// <param name="dest">Destination buffer.</param>
- /// <param name="destIndex">Current index in destination buffer.</param>
- /// <param name="destLength">Maximum length of destination buffer.</param>
- /// <param name="value">The value to format.</param>
- /// <param name="formatOptionsRaw">Formatting options encoded in raw format.</param>
- [MethodImpl(MethodImplOptions.NoInlining)]
- public static unsafe void Format(byte* dest, ref int destIndex, int destLength, bool value, int formatOptionsRaw)
- {
- var length = value ? 4 : 5; // True = 4 chars, False = 5 chars
- var options = *(FormatOptions*)&formatOptionsRaw;
-
- // Align left
- if (AlignLeft(dest, ref destIndex, destLength, options.AlignAndSize, length)) return;
-
- if (value)
- {
- if (destIndex >= destLength) return;
- dest[destIndex++] = (byte)'T';
- if (destIndex >= destLength) return;
- dest[destIndex++] = (byte)'r';
- if (destIndex >= destLength) return;
- dest[destIndex++] = (byte)'u';
- if (destIndex >= destLength) return;
- dest[destIndex++] = (byte)'e';
- }
- else
- {
- if (destIndex >= destLength) return;
- dest[destIndex++] = (byte)'F';
- if (destIndex >= destLength) return;
- dest[destIndex++] = (byte)'a';
- if (destIndex >= destLength) return;
- dest[destIndex++] = (byte)'l';
- if (destIndex >= destLength) return;
- dest[destIndex++] = (byte)'s';
- if (destIndex >= destLength) return;
- dest[destIndex++] = (byte)'e';
- }
-
- // Align right
- AlignRight(dest, ref destIndex, destLength, options.AlignAndSize, length);
- }
-
- /// <summary>
- /// Format a char value to a destination buffer.
- /// </summary>
- /// <param name="dest">Destination buffer.</param>
- /// <param name="destIndex">Current index in destination buffer.</param>
- /// <param name="destLength">Maximum length of destination buffer.</param>
- /// <param name="value">The value to format.</param>
- /// <param name="formatOptionsRaw">Formatting options encoded in raw format.</param>
- [MethodImpl(MethodImplOptions.NoInlining)]
- public static unsafe void Format(byte* dest, ref int destIndex, int destLength, char value, int formatOptionsRaw)
- {
- var length = value <= 0x7f ? 1 : value <= 0x7FF ? 2 : 3;
- var options = *(FormatOptions*)&formatOptionsRaw;
-
- // Align left - Special case for char, make the length as it was always one byte (one char)
- // so that alignment is working fine (on a char basis)
- if (AlignLeft(dest, ref destIndex, destLength, options.AlignAndSize, 1)) return;
-
- // Basic encoding of UTF16 to UTF8, doesn't handle high/low surrogate as we are given only one char
- if (length == 1)
- {
- if (destIndex >= destLength) return;
- dest[destIndex++] = (byte)value;
- }
- else if (length == 2)
- {
- if (destIndex >= destLength) return;
- dest[destIndex++] = (byte)((value >> 6) | 0xC0);
-
- if (destIndex >= destLength) return;
- dest[destIndex++] = (byte)((value & 0x3F) | 0x80);
- }
- else if (length == 3)
- {
- // We don't handle high/low surrogate, so we replace the char with the replacement char
- // 0xEF, 0xBF, 0xBD
- bool isHighOrLowSurrogate = value >= '\xD800' && value <= '\xDFFF';
- if (isHighOrLowSurrogate)
- {
- if (destIndex >= destLength) return;
- dest[destIndex++] = 0xEF;
-
- if (destIndex >= destLength) return;
- dest[destIndex++] = 0xBF;
-
- if (destIndex >= destLength) return;
- dest[destIndex++] = 0xBD;
- }
- else
- {
- if (destIndex >= destLength) return;
- dest[destIndex++] = (byte)((value >> 12) | 0xE0);
-
- if (destIndex >= destLength) return;
- dest[destIndex++] = (byte)(((value >> 6) & 0x3F) | 0x80);
-
- if (destIndex >= destLength) return;
- dest[destIndex++] = (byte)((value & 0x3F) | 0x80);
- }
- }
-
- // Align right - Special case for char, make the length as it was always one byte (one char)
- // so that alignment is working fine (on a char basis)
- AlignRight(dest, ref destIndex, destLength, options.AlignAndSize, 1);
- }
-
- /// <summary>
- /// Format a byte value to a destination buffer.
- /// </summary>
- /// <param name="dest">Destination buffer.</param>
- /// <param name="destIndex">Current index in destination buffer.</param>
- /// <param name="destLength">Maximum length of destination buffer.</param>
- /// <param name="value">The value to format.</param>
- /// <param name="formatOptionsRaw">Formatting options encoded in raw format.</param>
- public static unsafe void Format(byte* dest, ref int destIndex, int destLength, byte value, int formatOptionsRaw)
- {
- Format(dest, ref destIndex, destLength, (ulong)value, formatOptionsRaw);
- }
-
- /// <summary>
- /// Format an ushort value to a destination buffer.
- /// </summary>
- /// <param name="dest">Destination buffer.</param>
- /// <param name="destIndex">Current index in destination buffer.</param>
- /// <param name="destLength">Maximum length of destination buffer.</param>
- /// <param name="value">The value to format.</param>
- /// <param name="formatOptionsRaw">Formatting options encoded in raw format.</param>
- public static unsafe void Format(byte* dest, ref int destIndex, int destLength, ushort value, int formatOptionsRaw)
- {
- Format(dest, ref destIndex, destLength, (ulong)value, formatOptionsRaw);
- }
-
- /// <summary>
- /// Format an uint value to a destination buffer.
- /// </summary>
- /// <param name="dest">Destination buffer.</param>
- /// <param name="destIndex">Current index in destination buffer.</param>
- /// <param name="destLength">Maximum length of destination buffer.</param>
- /// <param name="value">The value to format.</param>
- /// <param name="formatOptionsRaw">Formatting options encoded in raw format.</param>
- public static unsafe void Format(byte* dest, ref int destIndex, int destLength, uint value, int formatOptionsRaw)
- {
- var options = *(FormatOptions*)&formatOptionsRaw;
- ConvertUnsignedIntegerToString(dest, ref destIndex, destLength, value, options);
- }
-
- /// <summary>
- /// Format a ulong value to a destination buffer.
- /// </summary>
- /// <param name="dest">Destination buffer.</param>
- /// <param name="destIndex">Current index in destination buffer.</param>
- /// <param name="destLength">Maximum length of destination buffer.</param>
- /// <param name="value">The value to format.</param>
- /// <param name="formatOptionsRaw">Formatting options encoded in raw format.</param>
- public static unsafe void Format(byte* dest, ref int destIndex, int destLength, ulong value, int formatOptionsRaw)
- {
- var options = *(FormatOptions*)&formatOptionsRaw;
- ConvertUnsignedIntegerToString(dest, ref destIndex, destLength, value, options);
- }
-
- /// <summary>
- /// Format a sbyte value to a destination buffer.
- /// </summary>
- /// <param name="dest">Destination buffer.</param>
- /// <param name="destIndex">Current index in destination buffer.</param>
- /// <param name="destLength">Maximum length of destination buffer.</param>
- /// <param name="value">The value to format.</param>
- /// <param name="formatOptionsRaw">Formatting options encoded in raw format.</param>
- public static unsafe void Format(byte* dest, ref int destIndex, int destLength, sbyte value, int formatOptionsRaw)
- {
- var options = *(FormatOptions*)&formatOptionsRaw;
- if (options.Kind == NumberFormatKind.Hexadecimal)
- {
- ConvertUnsignedIntegerToString(dest, ref destIndex, destLength, (byte)value, options);
- }
- else
- {
- ConvertIntegerToString(dest, ref destIndex, destLength, value, options);
- }
- }
-
- /// <summary>
- /// Format a short value to a destination buffer.
- /// </summary>
- /// <param name="dest">Destination buffer.</param>
- /// <param name="destIndex">Current index in destination buffer.</param>
- /// <param name="destLength">Maximum length of destination buffer.</param>
- /// <param name="value">The value to format.</param>
- /// <param name="formatOptionsRaw">Formatting options encoded in raw format.</param>
- public static unsafe void Format(byte* dest, ref int destIndex, int destLength, short value, int formatOptionsRaw)
- {
- var options = *(FormatOptions*)&formatOptionsRaw;
- if (options.Kind == NumberFormatKind.Hexadecimal)
- {
- ConvertUnsignedIntegerToString(dest, ref destIndex, destLength, (ushort)value, options);
- }
- else
- {
- ConvertIntegerToString(dest, ref destIndex, destLength, value, options);
- }
-
- }
-
- /// <summary>
- /// Format an int value to a destination buffer.
- /// </summary>
- /// <param name="dest">Destination buffer.</param>
- /// <param name="destIndex">Current index in destination buffer.</param>
- /// <param name="destLength">Maximum length of destination buffer.</param>
- /// <param name="value">The value to format.</param>
- /// <param name="formatOptionsRaw">Formatting options encoded in raw format.</param>
- [MethodImpl(MethodImplOptions.NoInlining)]
- public static unsafe void Format(byte* dest, ref int destIndex, int destLength, int value, int formatOptionsRaw)
- {
- var options = *(FormatOptions*)&formatOptionsRaw;
- if (options.Kind == NumberFormatKind.Hexadecimal)
- {
- ConvertUnsignedIntegerToString(dest, ref destIndex, destLength, (uint)value, options);
- }
- else
- {
- ConvertIntegerToString(dest, ref destIndex, destLength, value, options);
- }
- }
-
- /// <summary>
- /// Format a long value to a destination buffer.
- /// </summary>
- /// <param name="dest">Destination buffer.</param>
- /// <param name="destIndex">Current index in destination buffer.</param>
- /// <param name="destLength">Maximum length of destination buffer.</param>
- /// <param name="value">The value to format.</param>
- /// <param name="formatOptionsRaw">Formatting options encoded in raw format.</param>
- public static unsafe void Format(byte* dest, ref int destIndex, int destLength, long value, int formatOptionsRaw)
- {
- var options = *(FormatOptions*)&formatOptionsRaw;
- if (options.Kind == NumberFormatKind.Hexadecimal)
- {
- ConvertUnsignedIntegerToString(dest, ref destIndex, destLength, (ulong)value, options);
- }
- else
- {
- ConvertIntegerToString(dest, ref destIndex, destLength, value, options);
- }
- }
-
- [MethodImpl(MethodImplOptions.NoInlining)]
- private static unsafe void ConvertUnsignedIntegerToString(byte* dest, ref int destIndex, int destLength, ulong value, FormatOptions options)
- {
- var basis = (uint)options.GetBase();
- if (basis < 2 || basis > 36) return;
-
- // Calculate the full length (including zero padding)
- int length = 0;
- var tmp = value;
- do
- {
- tmp /= basis;
- length++;
- } while (tmp != 0);
-
- // Write the characters for the numbers to a temp buffer
- int tmpIndex = length - 1;
- byte* tmpBuffer = stackalloc byte[length + 1];
-
- tmp = value;
- do
- {
- tmpBuffer[tmpIndex--] = ValueToIntegerChar((int)(tmp % basis), options.Uppercase);
- tmp /= basis;
- } while (tmp != 0);
-
- tmpBuffer[length] = 0;
-
- var numberBuffer = new NumberBuffer(NumberBufferKind.Integer, tmpBuffer, length, length, false);
- FormatNumber(dest, ref destIndex, destLength, ref numberBuffer, options.Specifier, options);
- }
-
- private static int GetLengthIntegerToString(long value, int basis, int zeroPadding)
- {
- int length = 0;
- var tmp = value;
- do
- {
- tmp /= basis;
- length++;
- } while (tmp != 0);
-
- if (length < zeroPadding)
- {
- length = zeroPadding;
- }
-
- if (value < 0) length++;
- return length;
- }
-
- [MethodImpl(MethodImplOptions.NoInlining)]
- private static unsafe void ConvertIntegerToString(byte* dest, ref int destIndex, int destLength, long value, FormatOptions options)
- {
- var basis = options.GetBase();
- if (basis < 2 || basis > 36) return;
-
- // Calculate the full length (including zero padding)
- int length = 0;
- var tmp = value;
- do
- {
- tmp /= basis;
- length++;
- } while (tmp != 0);
-
- // Write the characters for the numbers to a temp buffer
- byte* tmpBuffer = stackalloc byte[length + 1];
-
- tmp = value;
- int tmpIndex = length - 1;
- do
- {
- tmpBuffer[tmpIndex--] = ValueToIntegerChar((int)(tmp % basis), options.Uppercase);
- tmp /= basis;
- } while (tmp != 0);
- tmpBuffer[length] = 0;
-
- var numberBuffer = new NumberBuffer(NumberBufferKind.Integer, tmpBuffer, length, length, value < 0);
- FormatNumber(dest, ref destIndex, destLength, ref numberBuffer, options.Specifier, options);
- }
-
- private static unsafe void FormatNumber(byte* dest, ref int destIndex, int destLength, ref NumberBuffer number, int nMaxDigits, FormatOptions options)
- {
- bool isCorrectlyRounded = (number.Kind == NumberBufferKind.Float);
-
- // If we have an integer, and the rendering is the default `G`, then use Decimal rendering which is faster
- if (number.Kind == NumberBufferKind.Integer && options.Kind == NumberFormatKind.General && options.Specifier == 0)
- {
- options.Kind = NumberFormatKind.Decimal;
- }
-
- int length;
- switch (options.Kind)
- {
- case NumberFormatKind.DecimalForceSigned:
- case NumberFormatKind.Decimal:
- case NumberFormatKind.Hexadecimal:
- length = number.DigitsCount;
-
- var zeroPadding = (int)options.Specifier;
- int actualZeroPadding = 0;
- if (length < zeroPadding)
- {
- actualZeroPadding = zeroPadding - length;
- length = zeroPadding;
- }
-
- bool outputPositiveSign = options.Kind == NumberFormatKind.DecimalForceSigned;
- length += number.IsNegative || outputPositiveSign ? 1 : 0;
-
- // Perform left align
- if (AlignLeft(dest, ref destIndex, destLength, options.AlignAndSize, length)) return;
-
- FormatDecimalOrHexadecimal(dest, ref destIndex, destLength, ref number, actualZeroPadding, outputPositiveSign);
-
- // Perform right align
- AlignRight(dest, ref destIndex, destLength, options.AlignAndSize, length);
-
- break;
-
- default:
- case NumberFormatKind.General:
-
- if (nMaxDigits < 1)
- {
- // This ensures that the PAL code pads out to the correct place even when we use the default precision
- nMaxDigits = number.DigitsCount;
- }
-
- RoundNumber(ref number, nMaxDigits, isCorrectlyRounded);
-
- // Calculate final rendering length
- length = GetLengthForFormatGeneral(ref number, nMaxDigits);
-
- // Perform left align
- if (AlignLeft(dest, ref destIndex, destLength, options.AlignAndSize, length)) return;
-
- // Format using general formatting
- FormatGeneral(dest, ref destIndex, destLength, ref number, nMaxDigits, options.Uppercase ? (byte)'E' : (byte)'e');
-
- // Perform right align
- AlignRight(dest, ref destIndex, destLength, options.AlignAndSize, length);
- break;
- }
- }
-
- private static unsafe void FormatDecimalOrHexadecimal(byte* dest, ref int destIndex, int destLength, ref NumberBuffer number, int zeroPadding, bool outputPositiveSign)
- {
- if (number.IsNegative)
- {
- if (destIndex >= destLength) return;
- dest[destIndex++] = (byte)'-';
- }
- else if (outputPositiveSign)
- {
- if (destIndex >= destLength) return;
- dest[destIndex++] = (byte)'+';
- }
-
- // Zero Padding
- for (int i = 0; i < zeroPadding; i++)
- {
- if (destIndex >= destLength) return;
- dest[destIndex++] = (byte)'0';
- }
-
- var digitCount = number.DigitsCount;
- byte* digits = number.GetDigitsPointer();
- for (int i = 0; i < digitCount; i++)
- {
- if (destIndex >= destLength) return;
- dest[destIndex++] = digits[i];
- }
- }
-
- private static byte ValueToIntegerChar(int value, bool uppercase)
- {
- value = value < 0 ? -value : value;
- if (value <= 9)
- return (byte)('0' + value);
- if (value < 36)
- return (byte)((uppercase ? 'A' : 'a') + (value - 10));
-
- return (byte)'?';
- }
-
- private static readonly char[] SplitByColon = new char[] { ':' };
-
- #if !NET_DOTS
- private static void OptsSplit(string fullFormat, out string padding, out string format)
- {
- var split = fullFormat.Split(SplitByColon, StringSplitOptions.RemoveEmptyEntries);
- format = split[0];
- padding = null;
- if (split.Length == 2)
- {
- padding = format;
- format = split[1];
- }
- else if (split.Length == 1)
- {
- if (format[0] == ',')
- {
- padding = format;
- format = null;
- }
- }
- else
- {
- throw new ArgumentException($"Format `{format}` not supported. Invalid number {split.Length} of :. Expecting no more than one.");
- }
- }
- #else
- // Tiny BCL is missing StringSplitOptions
- private static void OptsSplit(string fullFormat, out string padding, out string format)
- {
- var idx0 = 0;
- var idx1 = 1;
- var length = 0;
- var split = fullFormat.Split(SplitByColon);
- for (int chk=0;chk<split.Length;chk++)
- {
- if (split[chk].Length>0)
- length++;
- }
- while (idx0<split.Length)
- {
- if (split[idx0].Length>0)
- {
- idx1=idx0+1;
- break;
- }
-
- idx0++;
- }
- while (idx1<split.Length)
- {
- if (split[idx1].Length>0)
- {
- break;
- }
-
- idx1++;
- }
- format = split[idx0];
- padding = null;
- if (length == 2)
- {
- padding = format;
- format=split[idx1];
- }
- else if (length == 1)
- {
- if (format[0]==',')
- {
- padding = format;
- format = null;
- }
- }
- else
- {
- throw new ArgumentException($"Format `{format}` not supported. Invalid number {length} of :. Expecting no more than one.");
- }
- }
- #endif
-
- /// <summary>
- /// Parse a format string as specified .NET string.Format https://docs.microsoft.com/en-us/dotnet/api/system.string.format?view=netframework-4.8
- /// - Supports only Left/Right Padding (e.g {0,-20} {0, 8})
- /// - 'G' 'g' General formatting for numbers with precision specifier (e.g G4 or g4)
- /// - 'D' 'd' General formatting for numbers with precision specifier (e.g D5 or d5)
- /// - 'X' 'x' General formatting for integers with precision specifier (e.g X8 or x8)
- /// </summary>
- /// <param name="fullFormat"></param>
- /// <returns></returns>
- public static FormatOptions ParseFormatToFormatOptions(string fullFormat)
- {
- if (string.IsNullOrWhiteSpace(fullFormat)) return new FormatOptions();
-
- OptsSplit(fullFormat, out var padding, out var format);
-
- format = format?.Trim();
- padding = padding?.Trim();
-
- int alignAndSize = 0;
- var formatKind = NumberFormatKind.General;
- bool lowercase = false;
- int specifier = 0;
-
- if (!string.IsNullOrEmpty(format))
- {
- switch (format[0])
- {
- case 'G':
- formatKind = NumberFormatKind.General;
- break;
- case 'g':
- formatKind = NumberFormatKind.General;
- lowercase = true;
- break;
- case 'D':
- formatKind = NumberFormatKind.Decimal;
- break;
- case 'd':
- formatKind = NumberFormatKind.Decimal;
- lowercase = true;
- break;
- case 'X':
- formatKind = NumberFormatKind.Hexadecimal;
- break;
- case 'x':
- formatKind = NumberFormatKind.Hexadecimal;
- lowercase = true;
- break;
- default:
- throw new ArgumentException($"Format `{format}` not supported. Only G, g, D, d, X, x are supported.");
- }
-
- if (format.Length > 1)
- {
- var specifierString = format.Substring(1);
- #if !NET_DOTS
- if (!uint.TryParse(specifierString, out var unsignedSpecifier))
- #else
- // Tiny BCL is missing string->uint
- if (!ParseUnsigned(specifierString, out var unsignedSpecifier))
- #endif
- {
- throw new ArgumentException($"Expecting an unsigned integer for specifier `{format}` instead of {specifierString}.");
- }
- specifier = (int)unsignedSpecifier;
- }
- }
-
- if (!string.IsNullOrEmpty(padding))
- {
- if (padding[0] != ',')
- {
- throw new ArgumentException($"Invalid padding `{padding}`, expecting to start with a leading `,` comma.");
- }
-
- var numberStr = padding.Substring(1);
- #if !NET_DOTS
- if (!int.TryParse(numberStr, out alignAndSize))
- #else
- // Tiny BCL is missing string->int
- if (!ParseSigned(numberStr, out alignAndSize))
- #endif
- {
- throw new ArgumentException($"Expecting an integer for align/size padding `{numberStr}`.");
- }
- }
-
- return new FormatOptions(formatKind, (sbyte)alignAndSize, (byte)specifier, lowercase);
- }
-
- #if NET_DOTS
- // Won't handle anything but simple ascii unsigned integers
- private static bool ParseUnsigned(string inputString, out uint unsignedValue, int startIdx = 0)
- {
- var length = inputString.Length;
- unsignedValue = 0;
- for (int i = startIdx; i < inputString.Length; i++)
- {
- var c = inputString[i];
- if (c>='0' && c<='9')
- {
- if (unsignedValue>=0x19999999) // overflow
- return false;
- unsignedValue*=10;
- unsignedValue+=(uint)(c-'0');
- }
- else
- {
- return false;
- }
- }
-
- return true;
- }
-
- // Won't handle anything but simple ascii signed integers
- private static bool ParseSigned(string inputString, out int signedValue)
- {
- signedValue = 0;
- int startIdx = 0;
- bool negative = false;
- if (inputString[0] == '-' || inputString[0] == '+')
- {
- negative = inputString[0] == '-';
- startIdx++;
- }
-
- if (!ParseUnsigned(inputString, out var unsignedValue, startIdx))
- {
- return false;
- }
-
- if (negative)
- {
- if (unsignedValue > 0x80000000)
- return false;
- signedValue = (int) ((~unsignedValue) + 1);
- }
- else
- {
- if (unsignedValue > 0x7FFFFFFF)
- return false;
- signedValue = (int) unsignedValue;
- }
-
- return true;
- }
- #endif
- private static unsafe bool AlignRight(byte* dest, ref int destIndex, int destLength, int align, int length)
- {
- // right align
- if (align < 0)
- {
- align = -align;
- return AlignLeft(dest, ref destIndex, destLength, align, length);
- }
-
- return false;
- }
-
- private static unsafe bool AlignLeft(byte* dest, ref int destIndex, int destLength, int align, int length)
- {
- // left align
- if (align > 0)
- {
- while (length < align)
- {
- if (destIndex >= destLength) return true;
- dest[destIndex++] = (byte)' ';
- length++;
- }
- }
-
- return false;
- }
-
- private static unsafe int GetLengthForFormatGeneral(ref NumberBuffer number, int nMaxDigits)
- {
- // NOTE: Must be kept in sync with FormatGeneral!
- int length = 0;
- int scale = number.Scale;
- int digPos = scale;
- bool scientific = false;
-
- // Don't switch to scientific notation
- if (digPos > nMaxDigits || digPos < -3)
- {
- digPos = 1;
- scientific = true;
- }
-
- byte* dig = number.GetDigitsPointer();
-
- if (number.IsNegative)
- {
- length++; // (byte)'-';
- }
-
- if (digPos > 0)
- {
- do
- {
- if (*dig != 0)
- {
- dig++;
- }
- length++;
- } while (--digPos > 0);
- }
- else
- {
- length++;
- }
-
- if (*dig != 0 || digPos < 0)
- {
- length++; // (byte)'.';
-
- while (digPos < 0)
- {
- length++; // (byte)'0';
- digPos++;
- }
-
- while (*dig != 0)
- {
- length++; // *dig++;
- dig++;
- }
- }
-
- if (scientific)
- {
- length++; // e or E
- int exponent = number.Scale - 1;
- if (exponent >= 0) length++;
- length += GetLengthIntegerToString(exponent, 10, 2);
- }
-
- return length;
- }
-
- [MethodImpl(MethodImplOptions.NoInlining)]
- private static unsafe void FormatGeneral(byte* dest, ref int destIndex, int destLength, ref NumberBuffer number, int nMaxDigits, byte expChar)
- {
- int scale = number.Scale;
- int digPos = scale;
- bool scientific = false;
-
- // Don't switch to scientific notation
- if (digPos > nMaxDigits || digPos < -3)
- {
- digPos = 1;
- scientific = true;
- }
-
- byte* dig = number.GetDigitsPointer();
-
- if (number.IsNegative)
- {
- if (destIndex >= destLength) return;
- dest[destIndex++] = (byte)'-';
- }
-
- if (digPos > 0)
- {
- do
- {
- if (destIndex >= destLength) return;
- dest[destIndex++] = (*dig != 0) ? (byte)(*dig++) : (byte)'0';
- } while (--digPos > 0);
- }
- else
- {
- if (destIndex >= destLength) return;
- dest[destIndex++] = (byte)'0';
- }
-
- if (*dig != 0 || digPos < 0)
- {
- if (destIndex >= destLength) return;
- dest[destIndex++] = (byte)'.';
-
- while (digPos < 0)
- {
- if (destIndex >= destLength) return;
- dest[destIndex++] = (byte)'0';
- digPos++;
- }
-
- while (*dig != 0)
- {
- if (destIndex >= destLength) return;
- dest[destIndex++] = *dig++;
- }
- }
-
- if (scientific)
- {
- if (destIndex >= destLength) return;
- dest[destIndex++] = expChar;
-
- int exponent = number.Scale - 1;
- var exponentFormatOptions = new FormatOptions(NumberFormatKind.DecimalForceSigned, 0, 2, false);
-
- ConvertIntegerToString(dest, ref destIndex, destLength, exponent, exponentFormatOptions);
- }
- }
-
- private static unsafe void RoundNumber(ref NumberBuffer number, int pos, bool isCorrectlyRounded)
- {
- byte* dig = number.GetDigitsPointer();
-
- int i = 0;
- while (i < pos && dig[i] != (byte)'\0')
- i++;
-
- if ((i == pos) && ShouldRoundUp(dig, i, isCorrectlyRounded))
- {
- while (i > 0 && dig[i - 1] == (byte)'9')
- i--;
-
- if (i > 0)
- {
- dig[i - 1]++;
- }
- else
- {
- number.Scale++;
- dig[0] = (byte)('1');
- i = 1;
- }
- }
- else
- {
- while (i > 0 && dig[i - 1] == (byte)'0')
- i--;
- }
-
- if (i == 0)
- {
- number.Scale = 0; // Decimals with scale ('0.00') should be rounded.
- }
-
- dig[i] = (byte)('\0');
- number.DigitsCount = i;
- }
-
- private static unsafe bool ShouldRoundUp(byte* dig, int i, bool isCorrectlyRounded)
- {
- // We only want to round up if the digit is greater than or equal to 5 and we are
- // not rounding a floating-point number. If we are rounding a floating-point number
- // we have one of two cases.
- //
- // In the case of a standard numeric-format specifier, the exact and correctly rounded
- // string will have been produced. In this scenario, pos will have pointed to the
- // terminating null for the buffer and so this will return false.
- //
- // However, in the case of a custom numeric-format specifier, we currently fall back
- // to generating Single/DoublePrecisionCustomFormat digits and then rely on this
- // function to round correctly instead. This can unfortunately lead to double-rounding
- // bugs but is the best we have right now due to back-compat concerns.
-
- byte digit = dig[i];
-
- if ((digit == '\0') || isCorrectlyRounded)
- {
- // Fast path for the common case with no rounding
- return false;
- }
-
- // Values greater than or equal to 5 should round up, otherwise we round down. The IEEE
- // 754 spec actually dictates that ties (exactly 5) should round to the nearest even number
- // but that can have undesired behavior for custom numeric format strings. This probably
- // needs further thought for .NET 5 so that we can be spec compliant and so that users
- // can get the desired rounding behavior for their needs.
-
- return digit >= '5';
- }
-
- private enum NumberBufferKind
- {
- Integer,
- Float,
- }
-
- /// <summary>
- /// Information about a number: pointer to digit buffer, scale and if negative.
- /// </summary>
- private unsafe struct NumberBuffer
- {
- private readonly byte* _buffer;
-
- public NumberBuffer(NumberBufferKind kind, byte* buffer, int digitsCount, int scale, bool isNegative)
- {
- Kind = kind;
- _buffer = buffer;
- DigitsCount = digitsCount;
- Scale = scale;
- IsNegative = isNegative;
- }
-
- public NumberBufferKind Kind;
-
- public int DigitsCount;
-
- public int Scale;
-
- public readonly bool IsNegative;
-
- public byte* GetDigitsPointer() => _buffer;
- }
-
- /// <summary>
- /// Type of formatting
- /// </summary>
- public enum NumberFormatKind : byte
- {
- /// <summary>
- /// General 'G' or 'g' formatting.
- /// </summary>
- General,
-
- /// <summary>
- /// Decimal 'D' or 'd' formatting.
- /// </summary>
- Decimal,
-
- /// <summary>
- /// Internal use only. Decimal 'D' or 'd' formatting with a `+` positive in front of the decimal if positive
- /// </summary>
- DecimalForceSigned,
-
- /// <summary>
- /// Hexadecimal 'X' or 'x' formatting.
- /// </summary>
- Hexadecimal,
- }
-
- /// <summary>
- /// Formatting options. Must be sizeof(int)
- /// </summary>
- public struct FormatOptions
- {
- public FormatOptions(NumberFormatKind kind, sbyte alignAndSize, byte specifier, bool lowercase) : this()
- {
- Kind = kind;
- AlignAndSize = alignAndSize;
- Specifier = specifier;
- Lowercase = lowercase;
- }
-
- public NumberFormatKind Kind;
- public sbyte AlignAndSize;
- public byte Specifier;
- public bool Lowercase;
-
- public bool Uppercase => !Lowercase;
-
- /// <summary>
- /// Encode this options to a single integer.
- /// </summary>
- /// <returns></returns>
- public unsafe int EncodeToRaw()
- {
- #if !NET_DOTS
- Debug.Assert(sizeof(FormatOptions) == sizeof(int));
- #endif
- var value = this;
- return *(int*)&value;
- }
-
- /// <summary>
- /// Get the base used for formatting this number.
- /// </summary>
- /// <returns></returns>
- public int GetBase()
- {
- switch (Kind)
- {
- case NumberFormatKind.Hexadecimal:
- return 16;
- default:
- return 10;
- }
- }
-
- public override string ToString()
- {
- return $"{nameof(Kind)}: {Kind}, {nameof(AlignAndSize)}: {AlignAndSize}, {nameof(Specifier)}: {Specifier}, {nameof(Uppercase)}: {Uppercase}";
- }
- }
- }
- }
|