From 1a3c5d20c0f8af22b4276c9dc6de0e24c6912e18 Mon Sep 17 00:00:00 2001 From: Stephen Toub Date: Mon, 10 Apr 2023 17:00:17 -0400 Subject: [PATCH 1/3] Implement IUtf8SpanFormattable on all the numeric types in corelib Augments SByte, Byte, Int16, UInt16, Int32, UInt32, Int64, UInt64, IntPtr, UIntPtr, Int128, UInt128, Half, Single, Double, NFloat, and Decimal. Also fixes corelib TODOs around using IUtf8SpanFormattable. And removes some duplicate code from Utf8Formatter. There is still more consolidation to be done between FormattingHelpers and Number.Formatting. --- .../Formatter/FormatterTests.Negative.cs | 34 - .../tests/System.Memory.Tests.csproj | 1 - .../System.Private.CoreLib.Shared.projitems | 12 - .../src/System/Buffers/StandardFormat.cs | 24 +- .../src/System/Buffers/Text/Utf8Constants.cs | 5 - .../Text/Utf8Formatter/FormattingHelpers.cs | 151 +- .../Utf8Formatter/Utf8Formatter.Boolean.cs | 30 +- .../Utf8Formatter/Utf8Formatter.Date.G.cs | 16 +- .../Utf8Formatter/Utf8Formatter.Date.L.cs | 38 +- .../Text/Utf8Formatter/Utf8Formatter.Date.cs | 48 +- .../Utf8Formatter/Utf8Formatter.Decimal.E.cs | 103 -- .../Utf8Formatter/Utf8Formatter.Decimal.F.cs | 100 -- .../Utf8Formatter/Utf8Formatter.Decimal.G.cs | 106 -- .../Utf8Formatter/Utf8Formatter.Decimal.cs | 88 +- .../Text/Utf8Formatter/Utf8Formatter.Float.cs | 75 +- .../Text/Utf8Formatter/Utf8Formatter.Guid.cs | 3 +- .../Utf8Formatter.Integer.Signed.D.cs | 26 - .../Utf8Formatter.Integer.Signed.Default.cs | 147 -- .../Utf8Formatter.Integer.Signed.N.cs | 26 - .../Utf8Formatter.Integer.Signed.cs | 55 - .../Utf8Formatter.Integer.Unsigned.D.cs | 53 - .../Utf8Formatter.Integer.Unsigned.Default.cs | 134 -- .../Utf8Formatter.Integer.Unsigned.N.cs | 57 - .../Utf8Formatter.Integer.Unsigned.X.cs | 56 - .../Utf8Formatter.Integer.Unsigned.cs | 55 - .../Utf8Formatter/Utf8Formatter.Integer.cs | 32 +- .../System.Private.CoreLib/src/System/Byte.cs | 9 +- .../Collections/Generic/ValueListBuilder.cs | 31 + .../src/System/Decimal.cs | 9 +- .../src/System/Double.cs | 9 +- .../System/Globalization/DateTimeFormat.cs | 106 +- .../System/Globalization/NumberFormatInfo.cs | 163 ++ .../System/Globalization/TimeSpanFormat.cs | 10 +- .../System.Private.CoreLib/src/System/Half.cs | 9 +- .../src/System/Int128.cs | 9 +- .../src/System/Int16.cs | 9 +- .../src/System/Int32.cs | 9 +- .../src/System/Int64.cs | 9 +- .../src/System/IntPtr.cs | 7 +- .../src/System/Number.Formatting.cs | 1462 ++++++++++------- .../System/Runtime/InteropServices/NFloat.cs | 6 +- .../src/System/SByte.cs | 9 +- .../src/System/Single.cs | 9 +- .../src/System/UInt128.cs | 9 +- .../src/System/UInt16.cs | 9 +- .../src/System/UInt32.cs | 9 +- .../src/System/UInt64.cs | 9 +- .../src/System/UIntPtr.cs | 7 +- .../src/System/Version.cs | 2 +- .../ref/System.Runtime.InteropServices.cs | 3 +- .../System.Runtime/ref/System.Runtime.cs | 48 +- .../tests/System.Runtime.Tests.csproj | 1 + .../tests/System/BooleanTests.cs | 26 +- .../System.Runtime/tests/System/ByteTests.cs | 43 +- .../tests/System/DecimalTests.cs | 38 +- .../tests/System/DoubleTests.cs | 23 +- .../System.Runtime/tests/System/HalfTests.cs | 23 +- .../tests/System/Int128Tests.cs | 42 +- .../System.Runtime/tests/System/Int16Tests.cs | 42 +- .../System.Runtime/tests/System/Int32Tests.cs | 42 +- .../System.Runtime/tests/System/Int64Tests.cs | 42 +- .../tests/System/IntPtrTests.cs | 42 +- .../tests/System/NumberFormatTestHelper.cs | 91 + .../System.Runtime/tests/System/SByteTests.cs | 43 +- .../tests/System/SingleTests.cs | 23 +- .../tests/System/UInt128Tests.cs | 42 +- .../tests/System/UInt16Tests.cs | 43 +- .../tests/System/UInt32Tests.cs | 42 +- .../tests/System/UInt64Tests.cs | 42 +- .../tests/System/UIntPtrTests.cs | 43 +- 70 files changed, 1536 insertions(+), 2643 deletions(-) delete mode 100644 src/libraries/System.Memory/tests/ParsersAndFormatters/Formatter/FormatterTests.Negative.cs delete mode 100644 src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Utf8Formatter/Utf8Formatter.Decimal.E.cs delete mode 100644 src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Utf8Formatter/Utf8Formatter.Decimal.F.cs delete mode 100644 src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Utf8Formatter/Utf8Formatter.Decimal.G.cs delete mode 100644 src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Utf8Formatter/Utf8Formatter.Integer.Signed.D.cs delete mode 100644 src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Utf8Formatter/Utf8Formatter.Integer.Signed.Default.cs delete mode 100644 src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Utf8Formatter/Utf8Formatter.Integer.Signed.N.cs delete mode 100644 src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Utf8Formatter/Utf8Formatter.Integer.Signed.cs delete mode 100644 src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Utf8Formatter/Utf8Formatter.Integer.Unsigned.D.cs delete mode 100644 src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Utf8Formatter/Utf8Formatter.Integer.Unsigned.Default.cs delete mode 100644 src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Utf8Formatter/Utf8Formatter.Integer.Unsigned.N.cs delete mode 100644 src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Utf8Formatter/Utf8Formatter.Integer.Unsigned.X.cs delete mode 100644 src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Utf8Formatter/Utf8Formatter.Integer.Unsigned.cs create mode 100644 src/libraries/System.Runtime/tests/System/NumberFormatTestHelper.cs diff --git a/src/libraries/System.Memory/tests/ParsersAndFormatters/Formatter/FormatterTests.Negative.cs b/src/libraries/System.Memory/tests/ParsersAndFormatters/Formatter/FormatterTests.Negative.cs deleted file mode 100644 index 9abf2fb650ebc..0000000000000 --- a/src/libraries/System.Memory/tests/ParsersAndFormatters/Formatter/FormatterTests.Negative.cs +++ /dev/null @@ -1,34 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using Xunit; - -namespace System.Buffers.Text.Tests -{ - public static partial class FormatterTests - { - [Theory] - [MemberData(nameof(TestData.TypesThatCanBeFormatted), MemberType = typeof(TestData))] - public static void TestBadFormat(Type integerType) - { - if ((integerType == typeof(double)) || (integerType == typeof(float))) - { - // double and float support all the same formats as the UTF16 formatter - return; - } - - object value = Activator.CreateInstance(integerType); - Assert.Throws(() => TryFormatUtf8(value, Array.Empty(), out int bytesWritten, new StandardFormat('$', 1))); - } - - [Theory] - [MemberData(nameof(TestData.IntegerTypesTheoryData), MemberType = typeof(TestData))] - [InlineData(typeof(decimal))] - public static void TestGFormatWithPrecisionNotSupported(Type type) - { - object value = Activator.CreateInstance(type); - Assert.Throws(() => TryFormatUtf8(value, Array.Empty(), out int bytesWritten, new StandardFormat('G', 1))); - Assert.Throws(() => TryFormatUtf8(value, Array.Empty(), out int bytesWritten, new StandardFormat('g', 1))); - } - } -} diff --git a/src/libraries/System.Memory/tests/System.Memory.Tests.csproj b/src/libraries/System.Memory/tests/System.Memory.Tests.csproj index e35c06dfd3708..e7748a30e2488 100644 --- a/src/libraries/System.Memory/tests/System.Memory.Tests.csproj +++ b/src/libraries/System.Memory/tests/System.Memory.Tests.csproj @@ -248,7 +248,6 @@ - diff --git a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems index eaae7a28b9c46..8e40a73ad2f0c 100644 --- a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems +++ b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems @@ -131,21 +131,9 @@ - - - - - - - - - - - - diff --git a/src/libraries/System.Private.CoreLib/src/System/Buffers/StandardFormat.cs b/src/libraries/System.Private.CoreLib/src/System/Buffers/StandardFormat.cs index 784b08c5ec555..91dca61b2415a 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Buffers/StandardFormat.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Buffers/StandardFormat.cs @@ -43,7 +43,7 @@ namespace System.Buffers /// /// true if the StandardFormat == default(StandardFormat) /// - public bool IsDefault => _format == 0 && _precision == 0; + public bool IsDefault => (_format | _precision) == 0; /// /// Create a StandardFormat. @@ -144,12 +144,7 @@ private static bool ParseHelper(ReadOnlySpan format, out StandardFormat st /// /// Returns the format in classic .NET format. /// - public override string ToString() - { - Span buffer = stackalloc char[FormatStringLength]; - int charsWritten = Format(buffer); - return new string(buffer.Slice(0, charsWritten)); - } + public override string ToString() => new string(Format(stackalloc char[FormatStringLength])); /// The exact buffer length required by . internal const int FormatStringLength = 3; @@ -157,17 +152,15 @@ public override string ToString() /// /// Formats the format in classic .NET format. /// - internal int Format(Span destination) + internal Span Format(Span destination) { Debug.Assert(destination.Length == FormatStringLength); - int count = 0; char symbol = Symbol; if (symbol != default && destination.Length == FormatStringLength) { destination[0] = symbol; - count = 1; uint precision = Precision; if (precision != NoPrecision) @@ -185,15 +178,18 @@ internal int Format(Span destination) uint div; (div, precision) = Math.DivRem(precision, 10); destination[1] = (char)('0' + div % 10); - count = 2; + destination[2] = (char)('0' + precision); + return destination; } - destination[count] = (char)('0' + precision); - count++; + destination[1] = (char)('0' + precision); + return destination.Slice(0, 2); } + + return destination.Slice(0, 1); } - return count; + return default; } /// diff --git a/src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Utf8Constants.cs b/src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Utf8Constants.cs index 704d0d91e0b33..e4f226cd4d0b5 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Utf8Constants.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Utf8Constants.cs @@ -14,8 +14,6 @@ internal static partial class Utf8Constants public const byte Space = (byte)' '; public const byte Hyphen = (byte)'-'; - public const byte Separator = (byte)','; - // Invariant formatting uses groups of 3 for each number group separated by commas. // ex. 1,234,567,890 public const int GroupSize = 3; @@ -26,8 +24,5 @@ internal static partial class Utf8Constants public const int DateTimeNumFractionDigits = 7; // TimeSpan and DateTime formats allow exactly up to many digits for specifying the fraction after the seconds. public const int MaxDateTimeFraction = 9999999; // ... and hence, the largest fraction expressible is this. - - public const ulong BillionMaxUIntValue = (ulong)uint.MaxValue * Billion; // maximum value that can be split into two uint32 {1-10 digits}{9 digits} - public const uint Billion = 1000000000; // 10^9, used to split int64/uint64 into three uint32 {1-2 digits}{9 digits}{9 digits} } } diff --git a/src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Utf8Formatter/FormattingHelpers.cs b/src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Utf8Formatter/FormattingHelpers.cs index a3022370d88bd..7a86f1943ec46 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Utf8Formatter/FormattingHelpers.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Utf8Formatter/FormattingHelpers.cs @@ -1,18 +1,24 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System.Diagnostics; -using System.Numerics; +using System.Globalization; using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; namespace System.Buffers.Text { - // All the helper methods in this class assume that the by-ref is valid and that there is - // enough space to fit the items that will be written into the underlying memory. The calling - // code must have already done all the necessary validation. internal static partial class FormattingHelpers { + public static bool TryFormat(T value, Span utf8Destination, out int bytesWritten, StandardFormat format) where T : IUtf8SpanFormattable + { + scoped Span formatText = default; + if (!format.IsDefault) + { + formatText = format.Format(stackalloc char[StandardFormat.FormatStringLength]); + } + + return value.TryFormat(utf8Destination, out bytesWritten, formatText, CultureInfo.InvariantCulture); + } + /// /// Returns the symbol contained within the standard format. If the standard format /// has not been initialized, returns the provided fallback symbol. @@ -32,138 +38,5 @@ public static char GetSymbolOrDefault(in StandardFormat format, char defaultSymb } return symbol; } - - /// - /// Fills a buffer with the ASCII character '0' (0x30). - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void FillWithAsciiZeros(Span buffer) - { - // This is a faster implementation of Span.Fill() for very short buffers. - for (int i = 0; i < buffer.Length; i++) - { - buffer[i] = (byte)'0'; - } - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void WriteDigits(ulong value, Span buffer) where TChar : unmanaged, IBinaryInteger - { - // We can mutate the 'value' parameter since it's a copy-by-value local. - // It'll be used to represent the value left over after each division by 10. - - for (int i = buffer.Length - 1; i >= 1; i--) - { - ulong temp = '0' + value; - value /= 10; - buffer[i] = TChar.CreateTruncating(temp - (value * 10)); - } - - Debug.Assert(value < 10); - buffer[0] = TChar.CreateTruncating('0' + value); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void WriteDigitsWithGroupSeparator(ulong value, Span buffer) - { - // We can mutate the 'value' parameter since it's a copy-by-value local. - // It'll be used to represent the value left over after each division by 10. - - int digitsWritten = 0; - for (int i = buffer.Length - 1; i >= 1; i--) - { - ulong temp = '0' + value; - value /= 10; - buffer[i] = (byte)(temp - (value * 10)); - if (digitsWritten == Utf8Constants.GroupSize - 1) - { - buffer[--i] = Utf8Constants.Comma; - digitsWritten = 0; - } - else - { - digitsWritten++; - } - } - - Debug.Assert(value < 10); - buffer[0] = (byte)('0' + value); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void WriteDigits(uint value, Span buffer) where TChar : unmanaged, IBinaryInteger - { - Debug.Assert(buffer.Length > 0); - - for (int i = buffer.Length - 1; i >= 1; i--) - { - uint temp = '0' + value; - value /= 10; - buffer[i] = TChar.CreateTruncating(temp - (value * 10)); - } - - Debug.Assert(value < 10); - buffer[0] = TChar.CreateTruncating('0' + value); - } - - /// - /// Writes a value [ 00 .. 99 ] to the buffer starting at the specified offset. - /// This method performs best when the starting index is a constant literal. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static unsafe void WriteTwoDigits(uint value, Span buffer, int startingIndex = 0) where TChar : unmanaged, IBinaryInteger - { - Debug.Assert(typeof(TChar) == typeof(char) || typeof(TChar) == typeof(byte)); - Debug.Assert(value <= 99); - Debug.Assert(startingIndex <= buffer.Length - 2); - - fixed (TChar* bufferPtr = &MemoryMarshal.GetReference(buffer)) - { - Number.WriteTwoDigits(bufferPtr + startingIndex, value); - } - } - - /// - /// Writes a value [ 0000 .. 9999 ] to the buffer starting at the specified offset. - /// This method performs best when the starting index is a constant literal. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static unsafe void WriteFourDigits(uint value, Span buffer, int startingIndex = 0) where TChar : unmanaged, IBinaryInteger - { - Debug.Assert(typeof(TChar) == typeof(char) || typeof(TChar) == typeof(byte)); - Debug.Assert(value <= 9999); - Debug.Assert(startingIndex <= buffer.Length - 4); - - (value, uint remainder) = Math.DivRem(value, 100); - fixed (TChar* bufferPtr = &MemoryMarshal.GetReference(buffer)) - { - Number.WriteTwoDigits(bufferPtr + startingIndex, value); - Number.WriteTwoDigits(bufferPtr + startingIndex + 2, remainder); - } - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void CopyFour(ReadOnlySpan source, Span destination) where TChar : unmanaged, IBinaryInteger - { - if (typeof(TChar) == typeof(byte)) - { - Unsafe.WriteUnaligned(ref Unsafe.As(ref MemoryMarshal.GetReference(destination)), - Unsafe.ReadUnaligned(ref Unsafe.As(ref MemoryMarshal.GetReference(source)))); - } - else - { - Debug.Assert(typeof(TChar) == typeof(char)); - Unsafe.WriteUnaligned(ref Unsafe.As(ref MemoryMarshal.GetReference(destination)), - Unsafe.ReadUnaligned(ref Unsafe.As(ref MemoryMarshal.GetReference(source)))); - } - } - - /// Enable use of ThrowHelper from TryFormat() routines without introducing dozens of non-code-coveraged "bytesWritten = 0; return false" boilerplate. - public static bool TryFormatThrowFormatException(out int bytesWritten) - { - bytesWritten = 0; - ThrowHelper.ThrowFormatException_BadFormatSpecifier(); - return false; - } } } diff --git a/src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Utf8Formatter/Utf8Formatter.Boolean.cs b/src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Utf8Formatter/Utf8Formatter.Boolean.cs index 220a49308ff4e..70c0ba85887c2 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Utf8Formatter/Utf8Formatter.Boolean.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Utf8Formatter/Utf8Formatter.Boolean.cs @@ -1,8 +1,6 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System.Buffers.Binary; - namespace System.Buffers.Text { public static partial class Utf8Formatter @@ -34,19 +32,14 @@ public static bool TryFormat(bool value, Span destination, out int bytesWr { if (symbol == 'G') { - // By having each branch perform its own call to TryWriteUInt32BigEndian, we ensure that a - // constant value is passed to this routine, which means the compiler can reverse endianness - // at compile time instead of runtime if necessary. - const uint TrueValueUppercase = ('T' << 24) + ('r' << 16) + ('u' << 8) + ('e' << 0); - if (!BinaryPrimitives.TryWriteUInt32BigEndian(destination, TrueValueUppercase)) + if (!"True"u8.TryCopyTo(destination)) { goto BufferTooSmall; } } else if (symbol == 'l') { - const uint TrueValueLowercase = ('t' << 24) + ('r' << 16) + ('u' << 8) + ('e' << 0); - if (!BinaryPrimitives.TryWriteUInt32BigEndian(destination, TrueValueLowercase)) + if (!"true"u8.TryCopyTo(destination)) { goto BufferTooSmall; } @@ -63,42 +56,33 @@ public static bool TryFormat(bool value, Span destination, out int bytesWr { if (symbol == 'G') { - // This check can't be performed earlier because we need to throw if an invalid symbol is - // provided, even if the buffer is too small. - if (destination.Length <= 4) + if (!"False"u8.TryCopyTo(destination)) { goto BufferTooSmall; } - - const uint FalsValueUppercase = ('F' << 24) + ('a' << 16) + ('l' << 8) + ('s' << 0); - BinaryPrimitives.WriteUInt32BigEndian(destination, FalsValueUppercase); } else if (symbol == 'l') { - if (destination.Length <= 4) + if (!"false"u8.TryCopyTo(destination)) { goto BufferTooSmall; } - - const uint FalsValueLowercase = ('f' << 24) + ('a' << 16) + ('l' << 8) + ('s' << 0); - BinaryPrimitives.WriteUInt32BigEndian(destination, FalsValueLowercase); } else { goto BadFormat; } - destination[4] = (byte)'e'; bytesWritten = 5; return true; } + BadFormat: + ThrowHelper.ThrowFormatException_BadFormatSpecifier(); + BufferTooSmall: bytesWritten = 0; return false; - - BadFormat: - return FormattingHelpers.TryFormatThrowFormatException(out bytesWritten); } } } diff --git a/src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Utf8Formatter/Utf8Formatter.Date.G.cs b/src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Utf8Formatter/Utf8Formatter.Date.G.cs index 9c38cb08399c4..cd45fda493c07 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Utf8Formatter/Utf8Formatter.Date.G.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Utf8Formatter/Utf8Formatter.Date.G.cs @@ -43,22 +43,22 @@ private static bool TryFormatDateTimeG(DateTime value, TimeSpan offset, Span destination, out int bytesWritten) { - if (destination.Length <= 28) + if (((IUtf8SpanFormattable)value).TryFormat(destination, out bytesWritten, "r", CultureInfo.InvariantCulture)) { - bytesWritten = 0; - return false; + Debug.Assert(bytesWritten == 29); + Ascii.ToLowerInPlace(destination.Slice(0, bytesWritten), out bytesWritten); + return true; } - value.GetDate(out int year, out int month, out int day); - value.GetTime(out int hour, out int minute, out int second); - - FormattingHelpers.CopyFour("sun,mon,tue,wed,thu,fri,sat,"u8.Slice(4 * (int)value.DayOfWeek), destination); - destination[4] = Utf8Constants.Space; - - FormattingHelpers.WriteTwoDigits((uint)day, destination, 5); - destination[7] = Utf8Constants.Space; - - FormattingHelpers.CopyFour("jan feb mar apr may jun jul aug sep oct nov dec "u8.Slice(4 * (month - 1)), destination.Slice(8)); - - FormattingHelpers.WriteFourDigits((uint)year, destination, 12); - destination[16] = Utf8Constants.Space; - - FormattingHelpers.WriteTwoDigits((uint)hour, destination, 17); - destination[19] = Utf8Constants.Colon; - - FormattingHelpers.WriteTwoDigits((uint)minute, destination, 20); - destination[22] = Utf8Constants.Colon; - - FormattingHelpers.WriteTwoDigits((uint)second, destination, 23); - - FormattingHelpers.CopyFour(" gmt"u8, destination.Slice(25)); - - bytesWritten = 29; - return true; + return false; } } } diff --git a/src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Utf8Formatter/Utf8Formatter.Date.cs b/src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Utf8Formatter/Utf8Formatter.Date.cs index 7c4885eb432e0..3d8e68b9c4660 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Utf8Formatter/Utf8Formatter.Date.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Utf8Formatter/Utf8Formatter.Date.cs @@ -37,13 +37,23 @@ public static bool TryFormat(DateTimeOffset value, Span destination, out i offset = value.Offset; } - return symbol switch + switch (symbol) { - 'R' => DateTimeFormat.TryFormatR(value.UtcDateTime, new TimeSpan(DateTimeFormat.NullOffset), destination, out bytesWritten), - 'l' => TryFormatDateTimeL(value.UtcDateTime, destination, out bytesWritten), - 'O' => DateTimeFormat.TryFormatO(value.DateTime, value.Offset, destination, out bytesWritten), - 'G' => TryFormatDateTimeG(value.DateTime, offset, destination, out bytesWritten), - _ => FormattingHelpers.TryFormatThrowFormatException(out bytesWritten), + case 'R': + return DateTimeFormat.TryFormatR(value.UtcDateTime, new TimeSpan(DateTimeFormat.NullOffset), destination, out bytesWritten); + + case 'O': + return DateTimeFormat.TryFormatO(value.DateTime, value.Offset, destination, out bytesWritten); + + case 'l': + return TryFormatDateTimeL(value.UtcDateTime, destination, out bytesWritten); + + case 'G': + return TryFormatDateTimeG(value.DateTime, offset, destination, out bytesWritten); + + default: + ThrowHelper.ThrowFormatException_BadFormatSpecifier(); + goto case 'R'; }; } @@ -70,16 +80,24 @@ public static bool TryFormat(DateTimeOffset value, Span destination, out i /// public static bool TryFormat(DateTime value, Span destination, out int bytesWritten, StandardFormat format = default) { - char symbol = FormattingHelpers.GetSymbolOrDefault(format, 'G'); - - return symbol switch + switch (FormattingHelpers.GetSymbolOrDefault(format, 'G')) { - 'R' => DateTimeFormat.TryFormatR(value, new TimeSpan(DateTimeFormat.NullOffset), destination, out bytesWritten), - 'l' => TryFormatDateTimeL(value, destination, out bytesWritten), - 'O' => DateTimeFormat.TryFormatO(value, Utf8Constants.NullUtcOffset, destination, out bytesWritten), - 'G' => TryFormatDateTimeG(value, Utf8Constants.NullUtcOffset, destination, out bytesWritten), - _ => FormattingHelpers.TryFormatThrowFormatException(out bytesWritten), - }; + case 'R': + return DateTimeFormat.TryFormatR(value, new TimeSpan(DateTimeFormat.NullOffset), destination, out bytesWritten); + + case 'O': + return DateTimeFormat.TryFormatO(value, Utf8Constants.NullUtcOffset, destination, out bytesWritten); + + case 'l': + return TryFormatDateTimeL(value, destination, out bytesWritten); + + case 'G': + return TryFormatDateTimeG(value, Utf8Constants.NullUtcOffset, destination, out bytesWritten); + + default: + ThrowHelper.ThrowFormatException_BadFormatSpecifier(); + goto case 'R'; // unreachable + } } } } diff --git a/src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Utf8Formatter/Utf8Formatter.Decimal.E.cs b/src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Utf8Formatter/Utf8Formatter.Decimal.E.cs deleted file mode 100644 index b9cb146fe4819..0000000000000 --- a/src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Utf8Formatter/Utf8Formatter.Decimal.E.cs +++ /dev/null @@ -1,103 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Diagnostics; - -namespace System.Buffers.Text -{ - public static partial class Utf8Formatter - { - private static bool TryFormatDecimalE(ref Number.NumberBuffer number, Span destination, out int bytesWritten, byte precision, byte exponentSymbol) - { - const int NumExponentDigits = 3; - - int scale = number.Scale; - ReadOnlySpan digits = number.Digits; - - int numBytesNeeded = - ((number.IsNegative) ? 1 : 0) // minus sign - + 1 // digits before the decimal point (exactly 1) - + ((precision == 0) ? 0 : (precision + 1)) // period and the digits after the decimal point - + 2 // 'E' or 'e' followed by '+' or '-' - + NumExponentDigits; // exponent digits - - if (destination.Length < numBytesNeeded) - { - bytesWritten = 0; - return false; - } - - int dstIndex = 0; - int srcIndex = 0; - if (number.IsNegative) - { - destination[dstIndex++] = Utf8Constants.Minus; - } - - // - // Emit exactly one digit before the decimal point. - // - int exponent; - byte firstDigit = digits[srcIndex]; - if (firstDigit == 0) - { - destination[dstIndex++] = (byte)'0'; // Special case: number before the decimal point is exactly 0: Number does not store the zero in this case. - exponent = 0; - } - else - { - destination[dstIndex++] = firstDigit; - srcIndex++; - exponent = scale - 1; - } - - if (precision > 0) - { - destination[dstIndex++] = Utf8Constants.Period; - - // - // Emit digits after the decimal point. - // - int numDigitsEmitted = 0; - while (numDigitsEmitted < precision) - { - byte digit = digits[srcIndex]; - if (digit == 0) - { - while (numDigitsEmitted++ < precision) - { - destination[dstIndex++] = (byte)'0'; - } - break; - } - destination[dstIndex++] = digit; - srcIndex++; - numDigitsEmitted++; - } - } - - // Emit the exponent symbol - destination[dstIndex++] = exponentSymbol; - if (exponent >= 0) - { - destination[dstIndex++] = Utf8Constants.Plus; - } - else - { - destination[dstIndex++] = Utf8Constants.Minus; - exponent = -exponent; - } - - Debug.Assert(exponent < Number.DecimalPrecision, "If you're trying to reuse this routine for double/float, you'll need to review the code carefully for Decimal-specific assumptions."); - - // Emit exactly three digits for the exponent. - destination[dstIndex++] = (byte)'0'; // The exponent for Decimal can never exceed 28 (let alone 99) - destination[dstIndex++] = (byte)((exponent / 10) + '0'); - destination[dstIndex++] = (byte)((exponent % 10) + '0'); - - Debug.Assert(dstIndex == numBytesNeeded); - bytesWritten = numBytesNeeded; - return true; - } - } -} diff --git a/src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Utf8Formatter/Utf8Formatter.Decimal.F.cs b/src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Utf8Formatter/Utf8Formatter.Decimal.F.cs deleted file mode 100644 index b569dffce00b7..0000000000000 --- a/src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Utf8Formatter/Utf8Formatter.Decimal.F.cs +++ /dev/null @@ -1,100 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Diagnostics; - -namespace System.Buffers.Text -{ - public static partial class Utf8Formatter - { - private static bool TryFormatDecimalF(ref Number.NumberBuffer number, Span destination, out int bytesWritten, byte precision) - { - int scale = number.Scale; - ReadOnlySpan digits = number.Digits; - - int numBytesNeeded = - ((number.IsNegative) ? 1 : 0) // minus sign - + ((scale <= 0) ? 1 : scale) // digits before the decimal point (minimum 1) - + ((precision == 0) ? 0 : (precision + 1)); // if specified precision != 0, the decimal point and the digits after the decimal point (padded with zeroes if needed) - - if (destination.Length < numBytesNeeded) - { - bytesWritten = 0; - return false; - } - - int srcIndex = 0; - int dstIndex = 0; - if (number.IsNegative) - { - destination[dstIndex++] = Utf8Constants.Minus; - } - - // - // Emit digits before the decimal point. - // - if (scale <= 0) - { - destination[dstIndex++] = (byte)'0'; // The integer portion is 0 and not stored. The formatter, however, needs to emit it. - } - else - { - while (srcIndex < scale) - { - byte digit = digits[srcIndex]; - if (digit == 0) - { - int numTrailingZeroes = scale - srcIndex; - for (int i = 0; i < numTrailingZeroes; i++) - { - destination[dstIndex++] = (byte)'0'; - } - break; - } - - destination[dstIndex++] = digit; - srcIndex++; - } - } - - if (precision > 0) - { - destination[dstIndex++] = Utf8Constants.Period; - - // - // Emit digits after the decimal point. - // - int numDigitsEmitted = 0; - if (scale < 0) - { - int numLeadingZeroesToEmit = Math.Min((int)precision, -scale); - for (int i = 0; i < numLeadingZeroesToEmit; i++) - { - destination[dstIndex++] = (byte)'0'; - } - numDigitsEmitted += numLeadingZeroesToEmit; - } - - while (numDigitsEmitted < precision) - { - byte digit = digits[srcIndex]; - if (digit == 0) - { - while (numDigitsEmitted++ < precision) - { - destination[dstIndex++] = (byte)'0'; - } - break; - } - destination[dstIndex++] = digit; - srcIndex++; - numDigitsEmitted++; - } - } - - Debug.Assert(dstIndex == numBytesNeeded); - bytesWritten = numBytesNeeded; - return true; - } - } -} diff --git a/src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Utf8Formatter/Utf8Formatter.Decimal.G.cs b/src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Utf8Formatter/Utf8Formatter.Decimal.G.cs deleted file mode 100644 index ea14ef852e4c4..0000000000000 --- a/src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Utf8Formatter/Utf8Formatter.Decimal.G.cs +++ /dev/null @@ -1,106 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Diagnostics; - -namespace System.Buffers.Text -{ - public static partial class Utf8Formatter - { - private static bool TryFormatDecimalG(ref Number.NumberBuffer number, Span destination, out int bytesWritten) - { - int scale = number.Scale; - ReadOnlySpan digits = number.Digits; - int numDigits = number.DigitsCount; - - bool isFraction = scale < numDigits; - int numBytesNeeded; - if (isFraction) - { - numBytesNeeded = numDigits + 1; // A fraction. Must include one for the decimal point. - if (scale <= 0) - { - numBytesNeeded += 1 + (-scale); // A fraction of the form 0.ddd. Need to emit the non-stored 0 before the decimal point plus (-scale) leading 0's after the decimal point. - } - } - else - { - numBytesNeeded = ((scale <= 0) ? 1 : scale); // An integral. Just emit the digits before the decimal point (minimum 1) and no decimal point. - } - - if (number.IsNegative) - { - numBytesNeeded++; // And the minus sign. - } - - if (destination.Length < numBytesNeeded) - { - bytesWritten = 0; - return false; - } - - int srcIndex = 0; - int dstIndex = 0; - - if (number.IsNegative) - { - destination[dstIndex++] = Utf8Constants.Minus; - } - - // - // Emit digits before the decimal point. - // - if (scale <= 0) - { - destination[dstIndex++] = (byte)'0'; // The integer portion is 0 and not stored. The formatter, however, needs to emit it. - } - else - { - while (srcIndex < scale) - { - byte digit = digits[srcIndex]; - if (digit == 0) - { - int numTrailingZeroes = scale - srcIndex; - for (int i = 0; i < numTrailingZeroes; i++) - { - destination[dstIndex++] = (byte)'0'; - } - break; - } - - destination[dstIndex++] = digit; - srcIndex++; - } - } - - if (isFraction) - { - destination[dstIndex++] = Utf8Constants.Period; - - // - // Emit digits after the decimal point. - // - if (scale < 0) - { - int numLeadingZeroesToEmit = -scale; - for (int i = 0; i < numLeadingZeroesToEmit; i++) - { - destination[dstIndex++] = (byte)'0'; - } - } - - byte digit; - while ((digit = digits[srcIndex++]) != 0) - { - destination[dstIndex++] = digit; - } - } - - Debug.Assert(dstIndex == numBytesNeeded); - - bytesWritten = numBytesNeeded; - return true; - } - } -} diff --git a/src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Utf8Formatter/Utf8Formatter.Decimal.cs b/src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Utf8Formatter/Utf8Formatter.Decimal.cs index 16c102b89887e..1f619009c0594 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Utf8Formatter/Utf8Formatter.Decimal.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Utf8Formatter/Utf8Formatter.Decimal.cs @@ -1,8 +1,6 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System.Diagnostics; - namespace System.Buffers.Text { public static partial class Utf8Formatter @@ -27,89 +25,7 @@ public static partial class Utf8Formatter /// /// System.FormatException if the format is not valid for this data type. /// - public static unsafe bool TryFormat(decimal value, Span destination, out int bytesWritten, StandardFormat format = default) - { - if (format.IsDefault) - { - format = 'G'; - } - - switch (format.Symbol) - { - case 'g': - case 'G': - { - if (format.Precision != StandardFormat.NoPrecision) - throw new NotSupportedException(SR.Argument_GWithPrecisionNotSupported); - - Number.NumberBuffer number = new Number.NumberBuffer(Number.NumberBufferKind.Decimal, stackalloc byte[Number.DecimalNumberBufferLength]); - - Number.DecimalToNumber(ref value, ref number); - if (number.Digits[0] == 0) - { - number.IsNegative = false; // For Decimals, -0 must print as normal 0. - } - bool success = TryFormatDecimalG(ref number, destination, out bytesWritten); -#if DEBUG - // This DEBUG segment exists to close a code coverage hole inside TryFormatDecimalG(). Because we don't call RoundNumber() on this path, we have no way to feed - // TryFormatDecimalG() a number where trailing zeros before the decimal point have been cropped. So if the chance comes up, we'll crop the zeroes - // ourselves and make a second call to ensure we get the same outcome. - if (success) - { - Span digits = number.Digits; - int numDigits = number.DigitsCount; - if (numDigits != 0 && number.Scale == numDigits && digits[numDigits - 1] == '0') - { - while (numDigits != 0 && digits[numDigits - 1] == '0') - { - digits[numDigits - 1] = 0; - numDigits--; - } - - number.DigitsCount = numDigits; - number.CheckConsistency(); - - byte[] buffer2 = new byte[destination.Length]; - bool success2 = TryFormatDecimalG(ref number, buffer2, out int bytesWritten2); - Debug.Assert(success2); - Debug.Assert(bytesWritten2 == bytesWritten); - for (int i = 0; i < bytesWritten; i++) - { - Debug.Assert(destination[i] == buffer2[i]); - } - } - } -#endif // DEBUG - return success; - } - - case 'f': - case 'F': - { - Number.NumberBuffer number = new Number.NumberBuffer(Number.NumberBufferKind.Decimal, stackalloc byte[Number.DecimalNumberBufferLength]); - - Number.DecimalToNumber(ref value, ref number); - byte precision = (format.Precision == StandardFormat.NoPrecision) ? (byte)2 : format.Precision; - Number.RoundNumber(ref number, number.Scale + precision, isCorrectlyRounded: false); - Debug.Assert((number.Digits[0] != 0) || !number.IsNegative); // For Decimals, -0 must print as normal 0. As it happens, Number.RoundNumber already ensures this invariant. - return TryFormatDecimalF(ref number, destination, out bytesWritten, precision); - } - - case 'e': - case 'E': - { - Number.NumberBuffer number = new Number.NumberBuffer(Number.NumberBufferKind.Decimal, stackalloc byte[Number.DecimalNumberBufferLength]); - - Number.DecimalToNumber(ref value, ref number); - byte precision = (format.Precision == StandardFormat.NoPrecision) ? (byte)6 : format.Precision; - Number.RoundNumber(ref number, precision + 1, isCorrectlyRounded: false); - Debug.Assert((number.Digits[0] != 0) || !number.IsNegative); // For Decimals, -0 must print as normal 0. As it happens, Number.RoundNumber already ensures this invariant. - return TryFormatDecimalE(ref number, destination, out bytesWritten, precision, exponentSymbol: (byte)format.Symbol); - } - - default: - return FormattingHelpers.TryFormatThrowFormatException(out bytesWritten); - } - } + public static bool TryFormat(decimal value, Span destination, out int bytesWritten, StandardFormat format = default) => + FormattingHelpers.TryFormat(value, destination, out bytesWritten, format); } } diff --git a/src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Utf8Formatter/Utf8Formatter.Float.cs b/src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Utf8Formatter/Utf8Formatter.Float.cs index bbbcee7d0b1bf..3827e79a948a6 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Utf8Formatter/Utf8Formatter.Float.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Utf8Formatter/Utf8Formatter.Float.cs @@ -1,9 +1,6 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System.Globalization; -using System.Text; - namespace System.Buffers.Text { public static partial class Utf8Formatter @@ -28,10 +25,8 @@ public static partial class Utf8Formatter /// /// System.FormatException if the format is not valid for this data type. /// - public static bool TryFormat(double value, Span destination, out int bytesWritten, StandardFormat format = default) - { - return TryFormatFloatingPoint(value, destination, out bytesWritten, format); - } + public static bool TryFormat(double value, Span destination, out int bytesWritten, StandardFormat format = default) => + FormattingHelpers.TryFormat(value, destination, out bytesWritten, format); /// /// Formats a Single as a UTF8 string. @@ -53,69 +48,7 @@ public static bool TryFormat(double value, Span destination, out int bytes /// /// System.FormatException if the format is not valid for this data type. /// - public static bool TryFormat(float value, Span destination, out int bytesWritten, StandardFormat format = default) - { - return TryFormatFloatingPoint(value, destination, out bytesWritten, format); - } - - private static bool TryFormatFloatingPoint(T value, Span destination, out int bytesWritten, StandardFormat format) where T : ISpanFormattable - { - scoped Span formatText = default; - - if (!format.IsDefault) - { - formatText = stackalloc char[StandardFormat.FormatStringLength]; - int formatTextLength = format.Format(formatText); - formatText = formatText.Slice(0, formatTextLength); - } - - // We first try to format into a stack-allocated buffer, and if it succeeds, we can avoid - // all allocation. If that fails, we fall back to allocating strings. If it proves impactful, - // that allocation (as well as roundtripping from byte to char and back to byte) could be avoided by - // calling into a refactored Number.FormatSingle/Double directly. - - const int StackBufferLength = 128; // large enough to handle the majority cases - Span stackBuffer = stackalloc char[StackBufferLength]; - scoped ReadOnlySpan utf16Text; - - // Try to format into the stack buffer. If we're successful, we can avoid all allocations. - if (value.TryFormat(stackBuffer, out int formattedLength, formatText, CultureInfo.InvariantCulture)) - { - utf16Text = stackBuffer.Slice(0, formattedLength); - } - else - { - // The stack buffer wasn't large enough. If the destination buffer isn't at least as - // big as the stack buffer, we know the whole operation will eventually fail, so we - // can just fail now. - if (destination.Length <= StackBufferLength) - { - bytesWritten = 0; - return false; - } - - // Fall back to using a string format and allocating a string for the resulting formatted value. - utf16Text = value.ToString(new string(formatText), CultureInfo.InvariantCulture); - } - - // Copy the value to the destination, if it's large enough. - - if (utf16Text.Length > destination.Length) - { - bytesWritten = 0; - return false; - } - - try - { - bytesWritten = Encoding.UTF8.GetBytes(utf16Text, destination); - return true; - } - catch - { - bytesWritten = 0; - return false; - } - } + public static bool TryFormat(float value, Span destination, out int bytesWritten, StandardFormat format = default) => + FormattingHelpers.TryFormat(value, destination, out bytesWritten, format); } } diff --git a/src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Utf8Formatter/Utf8Formatter.Guid.cs b/src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Utf8Formatter/Utf8Formatter.Guid.cs index db604c2bc3ef8..8effbc24857b0 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Utf8Formatter/Utf8Formatter.Guid.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Utf8Formatter/Utf8Formatter.Guid.cs @@ -139,7 +139,8 @@ public static bool TryFormat(Guid value, Span destination, out int bytesWr break; default: - return FormattingHelpers.TryFormatThrowFormatException(out bytesWritten); + ThrowHelper.ThrowFormatException_BadFormatSpecifier(); + goto case 'D'; // unreachable } // At this point, the low byte of flags contains the minimum required length diff --git a/src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Utf8Formatter/Utf8Formatter.Integer.Signed.D.cs b/src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Utf8Formatter/Utf8Formatter.Integer.Signed.D.cs deleted file mode 100644 index 0e43b0537165b..0000000000000 --- a/src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Utf8Formatter/Utf8Formatter.Integer.Signed.D.cs +++ /dev/null @@ -1,26 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Runtime.CompilerServices; - -namespace System.Buffers.Text -{ - /// - /// Methods to format common data types as Utf8 strings. - /// - public static partial class Utf8Formatter - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static bool TryFormatInt64D(long value, byte precision, Span destination, out int bytesWritten) - { - bool insertNegationSign = false; - if (value < 0) - { - insertNegationSign = true; - value = -value; - } - - return TryFormatUInt64D((ulong)value, precision, destination, insertNegationSign, out bytesWritten); - } - } -} diff --git a/src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Utf8Formatter/Utf8Formatter.Integer.Signed.Default.cs b/src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Utf8Formatter/Utf8Formatter.Integer.Signed.Default.cs deleted file mode 100644 index 634f69e48fb85..0000000000000 --- a/src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Utf8Formatter/Utf8Formatter.Integer.Signed.Default.cs +++ /dev/null @@ -1,147 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Diagnostics; -using System.Runtime.CompilerServices; - -namespace System.Buffers.Text -{ - /// - /// Methods to format common data types as Utf8 strings. - /// - public static partial class Utf8Formatter - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static bool TryFormatInt64Default(long value, Span destination, out int bytesWritten) - { - if ((ulong)value < 10) - { - return TryFormatUInt32SingleDigit((uint)value, destination, out bytesWritten); - } - - if (IntPtr.Size == 8) // x64 - { - return TryFormatInt64MultipleDigits(value, destination, out bytesWritten); - } - else // x86 - { - if (value <= int.MaxValue && value >= int.MinValue) - { - return TryFormatInt32MultipleDigits((int)value, destination, out bytesWritten); - } - else - { - if (value <= (long)Utf8Constants.BillionMaxUIntValue && value >= -(long)Utf8Constants.BillionMaxUIntValue) - { - return value < 0 ? - TryFormatInt64MoreThanNegativeBillionMaxUInt(-value, destination, out bytesWritten) : - TryFormatUInt64LessThanBillionMaxUInt((ulong)value, destination, out bytesWritten); - } - else - { - return value < 0 ? - TryFormatInt64LessThanNegativeBillionMaxUInt(-value, destination, out bytesWritten) : - TryFormatUInt64MoreThanBillionMaxUInt((ulong)value, destination, out bytesWritten); - } - } - } - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static bool TryFormatInt32MultipleDigits(int value, Span destination, out int bytesWritten) - { - if (value < 0) - { - value = -value; - int digitCount = FormattingHelpers.CountDigits((uint)value); - // WriteDigits does not do bounds checks - if (digitCount >= destination.Length) - { - bytesWritten = 0; - return false; - } - destination[0] = Utf8Constants.Minus; - bytesWritten = digitCount + 1; - FormattingHelpers.WriteDigits((uint)value, destination.Slice(1, digitCount)); - return true; - } - else - { - return TryFormatUInt32MultipleDigits((uint)value, destination, out bytesWritten); - } - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static bool TryFormatInt64MultipleDigits(long value, Span destination, out int bytesWritten) - { - if (value < 0) - { - value = -value; - int digitCount = FormattingHelpers.CountDigits((ulong)value); - // WriteDigits does not do bounds checks - if (digitCount >= destination.Length) - { - bytesWritten = 0; - return false; - } - destination[0] = Utf8Constants.Minus; - bytesWritten = digitCount + 1; - FormattingHelpers.WriteDigits((ulong)value, destination.Slice(1, digitCount)); - return true; - } - else - { - return TryFormatUInt64MultipleDigits((ulong)value, destination, out bytesWritten); - } - } - - // Split long into two parts that can each fit in a uint - {1-10 digits}{9 digits} - private static bool TryFormatInt64MoreThanNegativeBillionMaxUInt(long value, Span destination, out int bytesWritten) - { - uint overNineDigits = (uint)(value / Utf8Constants.Billion); - uint lastNineDigits = (uint)(value - (overNineDigits * Utf8Constants.Billion)); - - int digitCountOverNineDigits = FormattingHelpers.CountDigits(overNineDigits); - Debug.Assert(digitCountOverNineDigits >= 1 && digitCountOverNineDigits <= 10); - int digitCount = digitCountOverNineDigits + 9; - // WriteDigits does not do bounds checks - if (digitCount >= destination.Length) - { - bytesWritten = 0; - return false; - } - destination[0] = Utf8Constants.Minus; - bytesWritten = digitCount + 1; - FormattingHelpers.WriteDigits(overNineDigits, destination.Slice(1, digitCountOverNineDigits)); - FormattingHelpers.WriteDigits(lastNineDigits, destination.Slice(digitCountOverNineDigits + 1, 9)); - return true; - } - - // Split long into three parts that can each fit in a uint - {1 digit}{9 digits}{9 digits} - private static bool TryFormatInt64LessThanNegativeBillionMaxUInt(long value, Span destination, out int bytesWritten) - { - // value can still be negative if value == long.MinValue - // Therefore, cast to ulong, since (ulong)value actually equals abs(long.MinValue) - ulong overNineDigits = (ulong)value / Utf8Constants.Billion; - uint lastNineDigits = (uint)((ulong)value - (overNineDigits * Utf8Constants.Billion)); - uint overEighteenDigits = (uint)(overNineDigits / Utf8Constants.Billion); - uint middleNineDigits = (uint)(overNineDigits - (overEighteenDigits * Utf8Constants.Billion)); - - int digitCountOverEighteenDigits = FormattingHelpers.CountDigits(overEighteenDigits); - Debug.Assert(digitCountOverEighteenDigits == 1); - int digitCount = digitCountOverEighteenDigits + 18; - // WriteDigits does not do bounds checks - if (digitCount >= destination.Length) - { - bytesWritten = 0; - return false; - } - destination[0] = Utf8Constants.Minus; - bytesWritten = digitCount + 1; - FormattingHelpers.WriteDigits(overEighteenDigits, destination.Slice(1, digitCountOverEighteenDigits)); - FormattingHelpers.WriteDigits(middleNineDigits, destination.Slice(digitCountOverEighteenDigits + 1, 9)); - FormattingHelpers.WriteDigits(lastNineDigits, destination.Slice(digitCountOverEighteenDigits + 1 + 9, 9)); - return true; - } - } -} diff --git a/src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Utf8Formatter/Utf8Formatter.Integer.Signed.N.cs b/src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Utf8Formatter/Utf8Formatter.Integer.Signed.N.cs deleted file mode 100644 index dcf8812c278c3..0000000000000 --- a/src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Utf8Formatter/Utf8Formatter.Integer.Signed.N.cs +++ /dev/null @@ -1,26 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Runtime.CompilerServices; - -namespace System.Buffers.Text -{ - /// - /// Methods to format common data types as Utf8 strings. - /// - public static partial class Utf8Formatter - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static bool TryFormatInt64N(long value, byte precision, Span destination, out int bytesWritten) - { - bool insertNegationSign = false; - if (value < 0) - { - insertNegationSign = true; - value = -value; - } - - return TryFormatUInt64N((ulong)value, precision, destination, insertNegationSign, out bytesWritten); - } - } -} diff --git a/src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Utf8Formatter/Utf8Formatter.Integer.Signed.cs b/src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Utf8Formatter/Utf8Formatter.Integer.Signed.cs deleted file mode 100644 index 3bb65faebde2e..0000000000000 --- a/src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Utf8Formatter/Utf8Formatter.Integer.Signed.cs +++ /dev/null @@ -1,55 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Runtime.CompilerServices; - -namespace System.Buffers.Text -{ - /// - /// Methods to format common data types as Utf8 strings. - /// - public static partial class Utf8Formatter - { - // - // Common worker for all signed integer TryFormat overloads - // - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static bool TryFormatInt64(long value, ulong mask, Span destination, out int bytesWritten, StandardFormat format) - { - if (format.IsDefault) - { - return TryFormatInt64Default(value, destination, out bytesWritten); - } - - switch (format.Symbol) - { - case 'G': - case 'g': - case 'R': - case 'r': // treat 'r' (which historically was only for floating-point) as being equivalent to 'g', rather than throwing - if (format.HasPrecision) - { - throw new NotSupportedException(SR.Argument_GWithPrecisionNotSupported); // With a precision, 'G' can produce exponential format, even for integers. - } - return TryFormatInt64D(value, format.Precision, destination, out bytesWritten); - - case 'd': - case 'D': - return TryFormatInt64D(value, format.Precision, destination, out bytesWritten); - - case 'n': - case 'N': - return TryFormatInt64N(value, format.Precision, destination, out bytesWritten); - - case 'x': - return TryFormatUInt64X((ulong)value & mask, format.Precision, true, destination, out bytesWritten); - - case 'X': - return TryFormatUInt64X((ulong)value & mask, format.Precision, false, destination, out bytesWritten); - - default: - return FormattingHelpers.TryFormatThrowFormatException(out bytesWritten); - } - } - } -} diff --git a/src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Utf8Formatter/Utf8Formatter.Integer.Unsigned.D.cs b/src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Utf8Formatter/Utf8Formatter.Integer.Unsigned.D.cs deleted file mode 100644 index 2a2059674f916..0000000000000 --- a/src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Utf8Formatter/Utf8Formatter.Integer.Unsigned.D.cs +++ /dev/null @@ -1,53 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -namespace System.Buffers.Text -{ - /// - /// Methods to format common data types as Utf8 strings. - /// - public static partial class Utf8Formatter - { - private static bool TryFormatUInt64D(ulong value, byte precision, Span destination, bool insertNegationSign, out int bytesWritten) - { - // Calculate the actual digit count and the number of padding zeroes requested. - // From all of this we can get the required buffer length. - - int digitCount = FormattingHelpers.CountDigits(value); - int leadingZeroCount = ((precision == StandardFormat.NoPrecision) ? 0 : (int)precision) - digitCount; - if (leadingZeroCount < 0) - { - leadingZeroCount = 0; - } - - int requiredBufferLength = digitCount + leadingZeroCount; - - if (insertNegationSign) - { - requiredBufferLength++; - } - - if (requiredBufferLength > destination.Length) - { - bytesWritten = 0; - return false; - } - - bytesWritten = requiredBufferLength; - - if (insertNegationSign) - { - destination[0] = Utf8Constants.Minus; - destination = destination.Slice(1); - } - - if (leadingZeroCount > 0) - { - FormattingHelpers.FillWithAsciiZeros(destination.Slice(0, leadingZeroCount)); - } - FormattingHelpers.WriteDigits(value, destination.Slice(leadingZeroCount, digitCount)); - - return true; - } - } -} diff --git a/src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Utf8Formatter/Utf8Formatter.Integer.Unsigned.Default.cs b/src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Utf8Formatter/Utf8Formatter.Integer.Unsigned.Default.cs deleted file mode 100644 index 505e818bd515f..0000000000000 --- a/src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Utf8Formatter/Utf8Formatter.Integer.Unsigned.Default.cs +++ /dev/null @@ -1,134 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Diagnostics; -using System.Runtime.CompilerServices; - -namespace System.Buffers.Text -{ - /// - /// Methods to format common data types as Utf8 strings. - /// - public static partial class Utf8Formatter - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static bool TryFormatUInt64Default(ulong value, Span destination, out int bytesWritten) - { - if (value < 10) - { - return TryFormatUInt32SingleDigit((uint)value, destination, out bytesWritten); - } - - if (IntPtr.Size == 8) // x64 - { - return TryFormatUInt64MultipleDigits(value, destination, out bytesWritten); - } - else // x86 - { - if (value <= uint.MaxValue) - { - return TryFormatUInt32MultipleDigits((uint)value, destination, out bytesWritten); - } - else - { - if (value <= Utf8Constants.BillionMaxUIntValue) - { - return TryFormatUInt64LessThanBillionMaxUInt(value, destination, out bytesWritten); - } - else - { - return TryFormatUInt64MoreThanBillionMaxUInt(value, destination, out bytesWritten); - } - } - } - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static bool TryFormatUInt32SingleDigit(uint value, Span destination, out int bytesWritten) - { - if (destination.Length == 0) - { - bytesWritten = 0; - return false; - } - destination[0] = (byte)('0' + value); - bytesWritten = 1; - return true; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static bool TryFormatUInt32MultipleDigits(uint value, Span destination, out int bytesWritten) - { - int digitCount = FormattingHelpers.CountDigits(value); - // WriteDigits does not do bounds checks - if (digitCount > destination.Length) - { - bytesWritten = 0; - return false; - } - bytesWritten = digitCount; - FormattingHelpers.WriteDigits(value, destination.Slice(0, digitCount)); - return true; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static bool TryFormatUInt64MultipleDigits(ulong value, Span destination, out int bytesWritten) - { - int digitCount = FormattingHelpers.CountDigits(value); - // WriteDigits does not do bounds checks - if (digitCount > destination.Length) - { - bytesWritten = 0; - return false; - } - bytesWritten = digitCount; - FormattingHelpers.WriteDigits(value, destination.Slice(0, digitCount)); - return true; - } - - // Split ulong into two parts that can each fit in a uint - {1-10 digits}{9 digits} - private static bool TryFormatUInt64LessThanBillionMaxUInt(ulong value, Span destination, out int bytesWritten) - { - uint overNineDigits = (uint)(value / Utf8Constants.Billion); - uint lastNineDigits = (uint)(value - (overNineDigits * Utf8Constants.Billion)); - - int digitCountOverNineDigits = FormattingHelpers.CountDigits(overNineDigits); - Debug.Assert(digitCountOverNineDigits >= 1 && digitCountOverNineDigits <= 10); - int digitCount = digitCountOverNineDigits + 9; - // WriteDigits does not do bounds checks - if (digitCount > destination.Length) - { - bytesWritten = 0; - return false; - } - bytesWritten = digitCount; - FormattingHelpers.WriteDigits(overNineDigits, destination.Slice(0, digitCountOverNineDigits)); - FormattingHelpers.WriteDigits(lastNineDigits, destination.Slice(digitCountOverNineDigits, 9)); - return true; - } - - // Split ulong into three parts that can each fit in a uint - {1-2 digits}{9 digits}{9 digits} - private static bool TryFormatUInt64MoreThanBillionMaxUInt(ulong value, Span destination, out int bytesWritten) - { - ulong overNineDigits = value / Utf8Constants.Billion; - uint lastNineDigits = (uint)(value - (overNineDigits * Utf8Constants.Billion)); - uint overEighteenDigits = (uint)(overNineDigits / Utf8Constants.Billion); - uint middleNineDigits = (uint)(overNineDigits - (overEighteenDigits * Utf8Constants.Billion)); - - int digitCountOverEighteenDigits = FormattingHelpers.CountDigits(overEighteenDigits); - Debug.Assert(digitCountOverEighteenDigits >= 1 && digitCountOverEighteenDigits <= 2); - int digitCount = digitCountOverEighteenDigits + 18; - // WriteDigits does not do bounds checks - if (digitCount > destination.Length) - { - bytesWritten = 0; - return false; - } - bytesWritten = digitCount; - FormattingHelpers.WriteDigits(overEighteenDigits, destination.Slice(0, digitCountOverEighteenDigits)); - FormattingHelpers.WriteDigits(middleNineDigits, destination.Slice(digitCountOverEighteenDigits, 9)); - FormattingHelpers.WriteDigits(lastNineDigits, destination.Slice(digitCountOverEighteenDigits + 9, 9)); - return true; - } - } -} diff --git a/src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Utf8Formatter/Utf8Formatter.Integer.Unsigned.N.cs b/src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Utf8Formatter/Utf8Formatter.Integer.Unsigned.N.cs deleted file mode 100644 index c7f564ae6c9f6..0000000000000 --- a/src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Utf8Formatter/Utf8Formatter.Integer.Unsigned.N.cs +++ /dev/null @@ -1,57 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -namespace System.Buffers.Text -{ - /// - /// Methods to format common data types as Utf8 strings. - /// - public static partial class Utf8Formatter - { - private static bool TryFormatUInt64N(ulong value, byte precision, Span destination, bool insertNegationSign, out int bytesWritten) - { - // Calculate the actual digit count, number of group separators required, and the - // number of trailing zeros requested. From all of this we can get the required - // buffer length. - - int digitCount = FormattingHelpers.CountDigits(value); - int commaCount = (digitCount - 1) / 3; - int trailingZeroCount = (precision == StandardFormat.NoPrecision) ? 2 /* default for 'N' */ : precision; - - int requiredBufferLength = digitCount + commaCount; - if (trailingZeroCount > 0) - { - requiredBufferLength += trailingZeroCount + 1; - } - - if (insertNegationSign) - { - requiredBufferLength++; - } - - if (requiredBufferLength > destination.Length) - { - bytesWritten = 0; - return false; - } - - bytesWritten = requiredBufferLength; - - if (insertNegationSign) - { - destination[0] = Utf8Constants.Minus; - destination = destination.Slice(1); - } - - FormattingHelpers.WriteDigitsWithGroupSeparator(value, destination.Slice(0, digitCount + commaCount)); - - if (trailingZeroCount > 0) - { - destination[digitCount + commaCount] = Utf8Constants.Period; - FormattingHelpers.FillWithAsciiZeros(destination.Slice(digitCount + commaCount + 1, trailingZeroCount)); - } - - return true; - } - } -} diff --git a/src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Utf8Formatter/Utf8Formatter.Integer.Unsigned.X.cs b/src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Utf8Formatter/Utf8Formatter.Integer.Unsigned.X.cs deleted file mode 100644 index 00ea1d2e29b5d..0000000000000 --- a/src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Utf8Formatter/Utf8Formatter.Integer.Unsigned.X.cs +++ /dev/null @@ -1,56 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -namespace System.Buffers.Text -{ - /// - /// Methods to format common data types as Utf8 strings. - /// - public static partial class Utf8Formatter - { - private static bool TryFormatUInt64X(ulong value, byte precision, bool useLower, Span destination, out int bytesWritten) - { - int actualDigitCount = FormattingHelpers.CountHexDigits(value); - int computedOutputLength = (precision == StandardFormat.NoPrecision) - ? actualDigitCount - : Math.Max(precision, actualDigitCount); - - if (destination.Length < computedOutputLength) - { - bytesWritten = 0; - return false; - } - - bytesWritten = computedOutputLength; - - // Writing the output backward in this manner allows the JIT to elide - // bounds checking on the output buffer. The JIT won't elide the bounds - // check on the hex table lookup, but we can live with that for now. - - // It doesn't quite make sense to use the fast hex conversion functionality - // for this method since that routine works on bytes, and here we're working - // directly with nibbles. There may be opportunity for improvement by special- - // casing output lengths of 2, 4, 8, and 16 and running them down optimized - // code paths. - - if (useLower) - { - while ((uint)(--computedOutputLength) < (uint)destination.Length) - { - destination[computedOutputLength] = (byte)HexConverter.ToCharLower((int)value); - value >>= 4; - } - } - else - { - while ((uint)(--computedOutputLength) < (uint)destination.Length) - { - destination[computedOutputLength] = (byte)HexConverter.ToCharUpper((int)value); - value >>= 4; - } - } - - return true; - } - } -} diff --git a/src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Utf8Formatter/Utf8Formatter.Integer.Unsigned.cs b/src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Utf8Formatter/Utf8Formatter.Integer.Unsigned.cs deleted file mode 100644 index c2414eb1e00c9..0000000000000 --- a/src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Utf8Formatter/Utf8Formatter.Integer.Unsigned.cs +++ /dev/null @@ -1,55 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Runtime.CompilerServices; - -namespace System.Buffers.Text -{ - /// - /// Methods to format common data types as Utf8 strings. - /// - public static partial class Utf8Formatter - { - // - // Common worker for all unsigned integer TryFormat overloads - // - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static bool TryFormatUInt64(ulong value, Span destination, out int bytesWritten, StandardFormat format) - { - if (format.IsDefault) - { - return TryFormatUInt64Default(value, destination, out bytesWritten); - } - - switch (format.Symbol) - { - case 'G': - case 'g': - case 'R': - case 'r': // treat 'R'/'r' (which historically was only for floating-point) as being equivalent to 'g', rather than throwing - if (format.HasPrecision) - { - throw new NotSupportedException(SR.Argument_GWithPrecisionNotSupported); // With a precision, 'G' can produce exponential format, even for integers. - } - return TryFormatUInt64D(value, format.Precision, destination, insertNegationSign: false, out bytesWritten); - - case 'd': - case 'D': - return TryFormatUInt64D(value, format.Precision, destination, insertNegationSign: false, out bytesWritten); - - case 'n': - case 'N': - return TryFormatUInt64N(value, format.Precision, destination, insertNegationSign: false, out bytesWritten); - - case 'x': - return TryFormatUInt64X(value, format.Precision, true /* useLower */, destination, out bytesWritten); - - case 'X': - return TryFormatUInt64X(value, format.Precision, false /* useLower */, destination, out bytesWritten); - - default: - return FormattingHelpers.TryFormatThrowFormatException(out bytesWritten); - } - } - } -} diff --git a/src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Utf8Formatter/Utf8Formatter.Integer.cs b/src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Utf8Formatter/Utf8Formatter.Integer.cs index d22e06e0a5cc1..303383a097e33 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Utf8Formatter/Utf8Formatter.Integer.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Utf8Formatter/Utf8Formatter.Integer.cs @@ -29,8 +29,8 @@ public static partial class Utf8Formatter /// /// System.FormatException if the format is not valid for this data type. /// - public static bool TryFormat(byte value, Span destination, out int bytesWritten, StandardFormat format = default) - => TryFormatUInt64(value, destination, out bytesWritten, format); + public static bool TryFormat(byte value, Span destination, out int bytesWritten, StandardFormat format = default) => + FormattingHelpers.TryFormat(value, destination, out bytesWritten, format); /// /// Formats an SByte as a UTF8 string. @@ -54,8 +54,8 @@ public static bool TryFormat(byte value, Span destination, out int bytesWr /// System.FormatException if the format is not valid for this data type. /// [CLSCompliant(false)] - public static bool TryFormat(sbyte value, Span destination, out int bytesWritten, StandardFormat format = default) - => TryFormatInt64(value, 0xff, destination, out bytesWritten, format); + public static bool TryFormat(sbyte value, Span destination, out int bytesWritten, StandardFormat format = default) => + FormattingHelpers.TryFormat(value, destination, out bytesWritten, format); /// /// Formats a Unt16 as a UTF8 string. @@ -79,8 +79,8 @@ public static bool TryFormat(sbyte value, Span destination, out int bytesW /// System.FormatException if the format is not valid for this data type. /// [CLSCompliant(false)] - public static bool TryFormat(ushort value, Span destination, out int bytesWritten, StandardFormat format = default) - => TryFormatUInt64(value, destination, out bytesWritten, format); + public static bool TryFormat(ushort value, Span destination, out int bytesWritten, StandardFormat format = default) => + FormattingHelpers.TryFormat(value, destination, out bytesWritten, format); /// /// Formats an Int16 as a UTF8 string. @@ -103,8 +103,8 @@ public static bool TryFormat(ushort value, Span destination, out int bytes /// /// System.FormatException if the format is not valid for this data type. /// - public static bool TryFormat(short value, Span destination, out int bytesWritten, StandardFormat format = default) - => TryFormatInt64(value, 0xffff, destination, out bytesWritten, format); + public static bool TryFormat(short value, Span destination, out int bytesWritten, StandardFormat format = default) => + FormattingHelpers.TryFormat(value, destination, out bytesWritten, format); /// /// Formats a UInt32 as a UTF8 string. @@ -128,8 +128,8 @@ public static bool TryFormat(short value, Span destination, out int bytesW /// System.FormatException if the format is not valid for this data type. /// [CLSCompliant(false)] - public static bool TryFormat(uint value, Span destination, out int bytesWritten, StandardFormat format = default) - => TryFormatUInt64(value, destination, out bytesWritten, format); + public static bool TryFormat(uint value, Span destination, out int bytesWritten, StandardFormat format = default) => + FormattingHelpers.TryFormat(value, destination, out bytesWritten, format); /// /// Formats an Int32 as a UTF8 string. @@ -152,8 +152,8 @@ public static bool TryFormat(uint value, Span destination, out int bytesWr /// /// System.FormatException if the format is not valid for this data type. /// - public static bool TryFormat(int value, Span destination, out int bytesWritten, StandardFormat format = default) - => TryFormatInt64(value, 0xffffffff, destination, out bytesWritten, format); + public static bool TryFormat(int value, Span destination, out int bytesWritten, StandardFormat format = default) => + FormattingHelpers.TryFormat(value, destination, out bytesWritten, format); /// /// Formats a UInt64 as a UTF8 string. @@ -177,8 +177,8 @@ public static bool TryFormat(int value, Span destination, out int bytesWri /// System.FormatException if the format is not valid for this data type. /// [CLSCompliant(false)] - public static bool TryFormat(ulong value, Span destination, out int bytesWritten, StandardFormat format = default) - => TryFormatUInt64(value, destination, out bytesWritten, format); + public static bool TryFormat(ulong value, Span destination, out int bytesWritten, StandardFormat format = default) => + FormattingHelpers.TryFormat(value, destination, out bytesWritten, format); /// /// Formats an Int64 as a UTF8 string. @@ -201,7 +201,7 @@ public static bool TryFormat(ulong value, Span destination, out int bytesW /// /// System.FormatException if the format is not valid for this data type. /// - public static bool TryFormat(long value, Span destination, out int bytesWritten, StandardFormat format = default) - => TryFormatInt64(value, 0xffffffffffffffff, destination, out bytesWritten, format); + public static bool TryFormat(long value, Span destination, out int bytesWritten, StandardFormat format = default) => + FormattingHelpers.TryFormat(value, destination, out bytesWritten, format); } } diff --git a/src/libraries/System.Private.CoreLib/src/System/Byte.cs b/src/libraries/System.Private.CoreLib/src/System/Byte.cs index 2a45415301196..d70797f876e56 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Byte.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Byte.cs @@ -22,7 +22,8 @@ public readonly struct Byte IEquatable, IBinaryInteger, IMinMaxValue, - IUnsignedNumber + IUnsignedNumber, + IUtf8SpanFormattable { private readonly byte m_value; // Do not rename (binary serialization) @@ -210,6 +211,12 @@ public bool TryFormat(Span destination, out int charsWritten, [StringSynta return Number.TryFormatUInt32(m_value, format, provider, destination, out charsWritten); } + /// + bool IUtf8SpanFormattable.TryFormat(Span utf8Destination, out int bytesWritten, ReadOnlySpan format, IFormatProvider? provider) + { + return Number.TryFormatUInt32(m_value, format, provider, utf8Destination, out bytesWritten); + } + // // IConvertible // diff --git a/src/libraries/System.Private.CoreLib/src/System/Collections/Generic/ValueListBuilder.cs b/src/libraries/System.Private.CoreLib/src/System/Collections/Generic/ValueListBuilder.cs index 01c19a05b9c58..85cb993fc0bb3 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Collections/Generic/ValueListBuilder.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Collections/Generic/ValueListBuilder.cs @@ -58,7 +58,24 @@ public void Append(T item) } } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Append(scoped ReadOnlySpan source) + { + int pos = _pos; + Span span = _span; + if (source.Length == 1 && (uint)pos < (uint)span.Length) + { + span[pos] = source[0]; + _pos = pos + 1; + } + else + { + AppendMultiChar(source); + } + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private void AppendMultiChar(scoped ReadOnlySpan source) { if ((uint)(_pos + source.Length) > (uint)_span.Length) { @@ -69,6 +86,20 @@ public void Append(scoped ReadOnlySpan source) _pos += source.Length; } + public void Insert(int index, scoped ReadOnlySpan source) + { + Debug.Assert(index >= 0 && index <= _pos); + + if ((uint)(_pos + source.Length) > (uint)_span.Length) + { + Grow(source.Length); + } + + _span.Slice(0, _pos).CopyTo(_span.Slice(source.Length)); + source.CopyTo(_span); + _pos += source.Length; + } + public Span AppendSpan(int length) { Debug.Assert(length >= 0); diff --git a/src/libraries/System.Private.CoreLib/src/System/Decimal.cs b/src/libraries/System.Private.CoreLib/src/System/Decimal.cs index 71895a2427352..a0d257376ca6a 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Decimal.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Decimal.cs @@ -68,7 +68,8 @@ public readonly partial struct Decimal ISerializable, IDeserializationCallback, IFloatingPoint, - IMinMaxValue + IMinMaxValue, + IUtf8SpanFormattable { // Sign mask for the flags field. A value of zero in this bit indicates a // positive Decimal value, and a value of one in this bit indicates a @@ -503,6 +504,12 @@ public bool TryFormat(Span destination, out int charsWritten, [StringSynta return Number.TryFormatDecimal(this, format, NumberFormatInfo.GetInstance(provider), destination, out charsWritten); } + /// + bool IUtf8SpanFormattable.TryFormat(Span utf8Destination, out int bytesWritten, ReadOnlySpan format, IFormatProvider? provider) + { + return Number.TryFormatDecimal(this, format, NumberFormatInfo.GetInstance(provider), utf8Destination, out bytesWritten); + } + // Converts a string to a Decimal. The string must consist of an optional // minus sign ("-") followed by a sequence of digits ("0" - "9"). The // sequence of digits may optionally contain a single decimal point (".") diff --git a/src/libraries/System.Private.CoreLib/src/System/Double.cs b/src/libraries/System.Private.CoreLib/src/System/Double.cs index 627e6e0cd9dfc..8af4921f6a7b2 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Double.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Double.cs @@ -32,7 +32,8 @@ public readonly struct Double IComparable, IEquatable, IBinaryFloatingPointIeee754, - IMinMaxValue + IMinMaxValue, + IUtf8SpanFormattable { private readonly double m_value; // Do not rename (binary serialization) @@ -365,6 +366,12 @@ public bool TryFormat(Span destination, out int charsWritten, [StringSynta return Number.TryFormatDouble(m_value, format, NumberFormatInfo.GetInstance(provider), destination, out charsWritten); } + /// + bool IUtf8SpanFormattable.TryFormat(Span utf8Destination, out int bytesWritten, ReadOnlySpan format, IFormatProvider? provider) + { + return Number.TryFormatDouble(m_value, format, NumberFormatInfo.GetInstance(provider), utf8Destination, out bytesWritten); + } + public static double Parse(string s) { if (s == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.s); diff --git a/src/libraries/System.Private.CoreLib/src/System/Globalization/DateTimeFormat.cs b/src/libraries/System.Private.CoreLib/src/System/Globalization/DateTimeFormat.cs index 5a58418c9d624..f376088560c3a 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Globalization/DateTimeFormat.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Globalization/DateTimeFormat.cs @@ -762,11 +762,13 @@ private static void AppendString(ref ValueListBuilder result, scop internal static void FormatFraction(ref ValueListBuilder result, int fraction, ReadOnlySpan fractionFormat) where TChar : unmanaged, IBinaryInteger { - // TODO https://github.com/dotnet/runtime/issues/84527: Update when Int32 implements IUtf8SpanFormattable - Span chars = stackalloc char[11]; - fraction.TryFormat(chars, out int charsWritten, fractionFormat, CultureInfo.InvariantCulture); - Debug.Assert(charsWritten != 0); - AppendString(ref result, chars.Slice(0, charsWritten)); + Span chars = stackalloc TChar[11]; + int charCount; + bool formatted = typeof(TChar) == typeof(char) ? + fraction.TryFormat(MemoryMarshal.Cast(chars), out charCount, fractionFormat, CultureInfo.InvariantCulture) : + ((IUtf8SpanFormattable)fraction).TryFormat(MemoryMarshal.Cast(chars), out charCount, fractionFormat, CultureInfo.InvariantCulture); + Debug.Assert(charCount != 0); + result.Append(chars.Slice(0, charCount)); } // output the 'z' family of formats, which output a the offset from UTC, e.g. "-07:30" @@ -817,11 +819,11 @@ private static void FormatCustomizedTimeZone(DateTime dateTime, TimeSpan else { // 'zz' or longer format e.g "-07" - FormattingHelpers.WriteTwoDigits((uint)offset.Hours, result.AppendSpan(2), 0); + Number.WriteTwoDigits((uint)offset.Hours, result.AppendSpan(2), 0); if (tokenLen >= 3) { result.Append(TChar.CreateTruncating(':')); - FormattingHelpers.WriteTwoDigits((uint)offset.Minutes, result.AppendSpan(2), 0); + Number.WriteTwoDigits((uint)offset.Minutes, result.AppendSpan(2), 0); } } } @@ -864,9 +866,9 @@ private static void FormatCustomizedRoundripTimeZone(DateTime dateTime, T } Span hoursMinutes = result.AppendSpan(5); - FormattingHelpers.WriteTwoDigits((uint)offset.Hours, hoursMinutes, 0); + Number.WriteTwoDigits((uint)offset.Hours, hoursMinutes, 0); hoursMinutes[2] = TChar.CreateTruncating(':'); - FormattingHelpers.WriteTwoDigits((uint)offset.Minutes, hoursMinutes, 3); + Number.WriteTwoDigits((uint)offset.Minutes, hoursMinutes, 3); } internal static string GetRealFormat(ReadOnlySpan format, DateTimeFormatInfo dtfi) @@ -1245,13 +1247,13 @@ internal static bool TryFormatTimeOnlyO(int hour, int minute, int second, return false; } - FormattingHelpers.WriteTwoDigits((uint)hour, destination, 0); + Number.WriteTwoDigits((uint)hour, destination, 0); destination[2] = TChar.CreateTruncating(':'); - FormattingHelpers.WriteTwoDigits((uint)minute, destination, 3); + Number.WriteTwoDigits((uint)minute, destination, 3); destination[5] = TChar.CreateTruncating(':'); - FormattingHelpers.WriteTwoDigits((uint)second, destination, 6); + Number.WriteTwoDigits((uint)second, destination, 6); destination[8] = TChar.CreateTruncating('.'); - FormattingHelpers.WriteDigits((uint)fraction, destination.Slice(9, 7)); + Number.WriteDigits((uint)fraction, destination.Slice(9, 7)); return true; } @@ -1266,11 +1268,11 @@ internal static bool TryFormatTimeOnlyR(int hour, int minute, int second, return false; } - FormattingHelpers.WriteTwoDigits((uint)hour, destination, 0); + Number.WriteTwoDigits((uint)hour, destination, 0); destination[2] = TChar.CreateTruncating(':'); - FormattingHelpers.WriteTwoDigits((uint)minute, destination, 3); + Number.WriteTwoDigits((uint)minute, destination, 3); destination[5] = TChar.CreateTruncating(':'); - FormattingHelpers.WriteTwoDigits((uint)second, destination, 6); + Number.WriteTwoDigits((uint)second, destination, 6); return true; } @@ -1286,11 +1288,11 @@ internal static bool TryFormatDateOnlyO(int year, int month, int day, Spa return false; } - FormattingHelpers.WriteFourDigits((uint)year, destination, 0); + Number.WriteFourDigits((uint)year, destination, 0); destination[4] = TChar.CreateTruncating('-'); - FormattingHelpers.WriteTwoDigits((uint)month, destination, 5); + Number.WriteTwoDigits((uint)month, destination, 5); destination[7] = TChar.CreateTruncating('-'); - FormattingHelpers.WriteTwoDigits((uint)day, destination, 8); + Number.WriteTwoDigits((uint)day, destination, 8); return true; } @@ -1311,24 +1313,24 @@ internal static bool TryFormatDateOnlyR(DayOfWeek dayOfWeek, int year, in { Span dest = MemoryMarshal.Cast(destination); - FormattingHelpers.CopyFour("Sun,Mon,Tue,Wed,Thu,Fri,Sat,".AsSpan(4 * (int)dayOfWeek), dest); + Number.CopyFour("Sun,Mon,Tue,Wed,Thu,Fri,Sat,".AsSpan(4 * (int)dayOfWeek), dest); dest[4] = ' '; - FormattingHelpers.WriteTwoDigits((uint)day, dest, 5); + Number.WriteTwoDigits((uint)day, dest, 5); dest[7] = ' '; - FormattingHelpers.CopyFour("Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec ".AsSpan(4 * (month - 1)), dest.Slice(8)); - FormattingHelpers.WriteFourDigits((uint)year, dest, 12); + Number.CopyFour("Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec ".AsSpan(4 * (month - 1)), dest.Slice(8)); + Number.WriteFourDigits((uint)year, dest, 12); } else { Debug.Assert(typeof(TChar) == typeof(byte)); Span dest = MemoryMarshal.Cast(destination); - FormattingHelpers.CopyFour("Sun,Mon,Tue,Wed,Thu,Fri,Sat,"u8.Slice(4 * (int)dayOfWeek), dest); + Number.CopyFour("Sun,Mon,Tue,Wed,Thu,Fri,Sat,"u8.Slice(4 * (int)dayOfWeek), dest); dest[4] = (byte)' '; - FormattingHelpers.WriteTwoDigits((uint)day, dest, 5); + Number.WriteTwoDigits((uint)day, dest, 5); dest[7] = (byte)' '; - FormattingHelpers.CopyFour("Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec "u8.Slice(4 * (month - 1)), dest.Slice(8)); - FormattingHelpers.WriteFourDigits((uint)year, dest, 12); + Number.CopyFour("Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec "u8.Slice(4 * (month - 1)), dest.Slice(8)); + Number.WriteFourDigits((uint)year, dest, 12); } return true; @@ -1378,19 +1380,19 @@ internal static bool TryFormatO(DateTime dateTime, TimeSpan offset, Span< dateTime.GetDate(out int year, out int month, out int day); dateTime.GetTimePrecise(out int hour, out int minute, out int second, out int tick); - FormattingHelpers.WriteFourDigits((uint)year, destination, 0); + Number.WriteFourDigits((uint)year, destination, 0); destination[4] = TChar.CreateTruncating('-'); - FormattingHelpers.WriteTwoDigits((uint)month, destination, 5); + Number.WriteTwoDigits((uint)month, destination, 5); destination[7] = TChar.CreateTruncating('-'); - FormattingHelpers.WriteTwoDigits((uint)day, destination, 8); + Number.WriteTwoDigits((uint)day, destination, 8); destination[10] = TChar.CreateTruncating('T'); - FormattingHelpers.WriteTwoDigits((uint)hour, destination, 11); + Number.WriteTwoDigits((uint)hour, destination, 11); destination[13] = TChar.CreateTruncating(':'); - FormattingHelpers.WriteTwoDigits((uint)minute, destination, 14); + Number.WriteTwoDigits((uint)minute, destination, 14); destination[16] = TChar.CreateTruncating(':'); - FormattingHelpers.WriteTwoDigits((uint)second, destination, 17); + Number.WriteTwoDigits((uint)second, destination, 17); destination[19] = TChar.CreateTruncating('.'); - FormattingHelpers.WriteDigits((uint)tick, destination.Slice(20, 7)); + Number.WriteDigits((uint)tick, destination.Slice(20, 7)); if (kind == DateTimeKind.Local) { @@ -1407,9 +1409,9 @@ internal static bool TryFormatO(DateTime dateTime, TimeSpan offset, Span< // Writing the value backward allows the JIT to optimize by // performing a single bounds check against buffer. - FormattingHelpers.WriteTwoDigits((uint)offsetMinutes, destination, 31); + Number.WriteTwoDigits((uint)offsetMinutes, destination, 31); destination[30] = TChar.CreateTruncating(':'); - FormattingHelpers.WriteTwoDigits((uint)offsetHours, destination, 28); + Number.WriteTwoDigits((uint)offsetHours, destination, 28); destination[27] = TChar.CreateTruncating(sign); } else if (kind == DateTimeKind.Utc) @@ -1445,38 +1447,38 @@ internal static bool TryFormatR(DateTime dateTime, TimeSpan offset, Span< { Span dest = MemoryMarshal.Cast(destination); - FormattingHelpers.CopyFour("Sun,Mon,Tue,Wed,Thu,Fri,Sat,".AsSpan(4 * (int)dateTime.DayOfWeek), dest); + Number.CopyFour("Sun,Mon,Tue,Wed,Thu,Fri,Sat,".AsSpan(4 * (int)dateTime.DayOfWeek), dest); dest[4] = ' '; - FormattingHelpers.WriteTwoDigits((uint)day, dest, 5); + Number.WriteTwoDigits((uint)day, dest, 5); dest[7] = ' '; - FormattingHelpers.CopyFour("Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec ".AsSpan(4 * (month - 1)), dest.Slice(8)); - FormattingHelpers.WriteFourDigits((uint)year, dest, 12); + Number.CopyFour("Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec ".AsSpan(4 * (month - 1)), dest.Slice(8)); + Number.WriteFourDigits((uint)year, dest, 12); dest[16] = ' '; - FormattingHelpers.WriteTwoDigits((uint)hour, dest, 17); + Number.WriteTwoDigits((uint)hour, dest, 17); dest[19] = ':'; - FormattingHelpers.WriteTwoDigits((uint)minute, dest, 20); + Number.WriteTwoDigits((uint)minute, dest, 20); dest[22] = ':'; - FormattingHelpers.WriteTwoDigits((uint)second, dest, 23); - FormattingHelpers.CopyFour(" GMT", dest.Slice(25)); + Number.WriteTwoDigits((uint)second, dest, 23); + Number.CopyFour(" GMT", dest.Slice(25)); } else { Debug.Assert(typeof(TChar) == typeof(byte)); Span dest = MemoryMarshal.Cast(destination); - FormattingHelpers.CopyFour("Sun,Mon,Tue,Wed,Thu,Fri,Sat,"u8.Slice(4 * (int)dateTime.DayOfWeek), dest); + Number.CopyFour("Sun,Mon,Tue,Wed,Thu,Fri,Sat,"u8.Slice(4 * (int)dateTime.DayOfWeek), dest); dest[4] = (byte)' '; - FormattingHelpers.WriteTwoDigits((uint)day, dest, 5); + Number.WriteTwoDigits((uint)day, dest, 5); dest[7] = (byte)' '; - FormattingHelpers.CopyFour("Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec "u8.Slice(4 * (month - 1)), dest.Slice(8)); - FormattingHelpers.WriteFourDigits((uint)year, dest, 12); + Number.CopyFour("Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec "u8.Slice(4 * (month - 1)), dest.Slice(8)); + Number.WriteFourDigits((uint)year, dest, 12); dest[16] = (byte)' '; - FormattingHelpers.WriteTwoDigits((uint)hour, dest, 17); + Number.WriteTwoDigits((uint)hour, dest, 17); dest[19] = (byte)':'; - FormattingHelpers.WriteTwoDigits((uint)minute, dest, 20); + Number.WriteTwoDigits((uint)minute, dest, 20); dest[22] = (byte)':'; - FormattingHelpers.WriteTwoDigits((uint)second, dest, 23); - FormattingHelpers.CopyFour(" GMT"u8, dest.Slice(25)); + Number.WriteTwoDigits((uint)second, dest, 23); + Number.CopyFour(" GMT"u8, dest.Slice(25)); } charsWritten = 29; diff --git a/src/libraries/System.Private.CoreLib/src/System/Globalization/NumberFormatInfo.cs b/src/libraries/System.Private.CoreLib/src/System/Globalization/NumberFormatInfo.cs index 3d2c60fd01550..84c84387d3e05 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Globalization/NumberFormatInfo.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Globalization/NumberFormatInfo.cs @@ -1,6 +1,12 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Diagnostics; +using System.Numerics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Text; + namespace System.Globalization { /// @@ -55,6 +61,21 @@ public sealed class NumberFormatInfo : IFormatProvider, ICloneable internal string _percentSymbol = "%"; internal string _perMilleSymbol = "\u2030"; + internal byte[]? _positiveSignUtf8; + internal byte[]? _negativeSignUtf8; + internal byte[]? _currencySymbolUtf8; + internal byte[]? _numberDecimalSeparatorUtf8; + internal byte[]? _currencyDecimalSeparatorUtf8; + internal byte[]? _currencyGroupSeparatorUtf8; + internal byte[]? _numberGroupSeparatorUtf8; + internal byte[]? _percentSymbolUtf8; + internal byte[]? _percentDecimalSeparatorUtf8; + internal byte[]? _percentGroupSeparatorUtf8; + internal byte[]? _perMilleSymbolUtf8; + internal byte[]? _nanSymbolUtf8; + internal byte[]? _positiveInfinitySymbolUtf8; + internal byte[]? _negativeInfinitySymbolUtf8; + internal string[] _nativeDigits = { "0", "1", "2", "3", "4", "5", "6", "7", "8", "9" }; internal int _numberDecimalDigits = 2; @@ -242,9 +263,19 @@ public string CurrencyDecimalSeparator VerifyWritable(); ArgumentException.ThrowIfNullOrEmpty(value); _currencyDecimalSeparator = value; + _currencyDecimalSeparatorUtf8 = null; } } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal ReadOnlySpan CurrencyDecimalSeparatorTChar() where TChar : unmanaged, IBinaryInteger + { + Debug.Assert(typeof(TChar) == typeof(char) || typeof(TChar) == typeof(byte)); + return typeof(TChar) == typeof(char) ? + MemoryMarshal.Cast(_currencyDecimalSeparator) : + MemoryMarshal.Cast(_currencyDecimalSeparatorUtf8 ??= Encoding.UTF8.GetBytes(_currencyDecimalSeparator)); + } + public bool IsReadOnly => _isReadOnly; /// @@ -324,9 +355,19 @@ public string CurrencyGroupSeparator VerifyWritable(); ArgumentNullException.ThrowIfNull(value); _currencyGroupSeparator = value; + _currencyGroupSeparatorUtf8 = null; } } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal ReadOnlySpan CurrencyGroupSeparatorTChar() where TChar : unmanaged, IBinaryInteger + { + Debug.Assert(typeof(TChar) == typeof(char) || typeof(TChar) == typeof(byte)); + return typeof(TChar) == typeof(char) ? + MemoryMarshal.Cast(_currencyGroupSeparator) : + MemoryMarshal.Cast(_currencyGroupSeparatorUtf8 ??= Encoding.UTF8.GetBytes(_currencyGroupSeparator)); + } + public string CurrencySymbol { get => _currencySymbol; @@ -336,9 +377,21 @@ public string CurrencySymbol VerifyWritable(); _currencySymbol = value; + _currencySymbolUtf8 = null; } } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal ReadOnlySpan CurrencySymbolTChar() where TChar : unmanaged, IBinaryInteger + { + Debug.Assert(typeof(TChar) == typeof(char) || typeof(TChar) == typeof(byte)); + return typeof(TChar) == typeof(char) ? + MemoryMarshal.Cast(_currencySymbol) : + MemoryMarshal.Cast(_currencySymbolUtf8 ??= Encoding.UTF8.GetBytes(_currencySymbol)); + } + + internal byte[]? CurrencySymbolUtf8 => _currencySymbolUtf8 ??= Encoding.UTF8.GetBytes(_currencySymbol); + /// /// Returns the current culture's NumberFormatInfo. Used by Parse methods. /// @@ -370,9 +423,19 @@ public string NaNSymbol VerifyWritable(); _nanSymbol = value; + _nanSymbolUtf8 = null; } } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal ReadOnlySpan NaNSymbolTChar() where TChar : unmanaged, IBinaryInteger + { + Debug.Assert(typeof(TChar) == typeof(char) || typeof(TChar) == typeof(byte)); + return typeof(TChar) == typeof(char) ? + MemoryMarshal.Cast(_nanSymbol) : + MemoryMarshal.Cast(_nanSymbolUtf8 ??= Encoding.UTF8.GetBytes(_nanSymbol)); + } + public int CurrencyNegativePattern { get => _currencyNegativePattern; @@ -457,9 +520,19 @@ public string NegativeInfinitySymbol VerifyWritable(); _negativeInfinitySymbol = value; + _negativeInfinitySymbolUtf8 = null; } } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal ReadOnlySpan NegativeInfinitySymbolTChar() where TChar : unmanaged, IBinaryInteger + { + Debug.Assert(typeof(TChar) == typeof(char) || typeof(TChar) == typeof(byte)); + return typeof(TChar) == typeof(char) ? + MemoryMarshal.Cast(_negativeInfinitySymbol) : + MemoryMarshal.Cast(_negativeInfinitySymbolUtf8 ??= Encoding.UTF8.GetBytes(_negativeInfinitySymbol)); + } + public string NegativeSign { get => _negativeSign; @@ -469,10 +542,20 @@ public string NegativeSign VerifyWritable(); _negativeSign = value; + _negativeSignUtf8 = null; InitializeInvariantAndNegativeSignFlags(); } } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal ReadOnlySpan NegativeSignTChar() where TChar : unmanaged, IBinaryInteger + { + Debug.Assert(typeof(TChar) == typeof(char) || typeof(TChar) == typeof(byte)); + return typeof(TChar) == typeof(char) ? + MemoryMarshal.Cast(_negativeSign) : + MemoryMarshal.Cast(_negativeSignUtf8 ??= Encoding.UTF8.GetBytes(_negativeSign)); + } + public int NumberDecimalDigits { get => _numberDecimalDigits; @@ -499,9 +582,19 @@ public string NumberDecimalSeparator VerifyWritable(); ArgumentException.ThrowIfNullOrEmpty(value); _numberDecimalSeparator = value; + _numberDecimalSeparatorUtf8 = null; } } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal ReadOnlySpan NumberDecimalSeparatorTChar() where TChar : unmanaged, IBinaryInteger + { + Debug.Assert(typeof(TChar) == typeof(char) || typeof(TChar) == typeof(byte)); + return typeof(TChar) == typeof(char) ? + MemoryMarshal.Cast(_numberDecimalSeparator) : + MemoryMarshal.Cast(_numberDecimalSeparatorUtf8 ??= Encoding.UTF8.GetBytes(_numberDecimalSeparator)); + } + public string NumberGroupSeparator { get => _numberGroupSeparator; @@ -510,9 +603,19 @@ public string NumberGroupSeparator VerifyWritable(); ArgumentNullException.ThrowIfNull(value); _numberGroupSeparator = value; + _numberGroupSeparatorUtf8 = null; } } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal ReadOnlySpan NumberGroupSeparatorTChar() where TChar : unmanaged, IBinaryInteger + { + Debug.Assert(typeof(TChar) == typeof(char) || typeof(TChar) == typeof(byte)); + return typeof(TChar) == typeof(char) ? + MemoryMarshal.Cast(_numberGroupSeparator) : + MemoryMarshal.Cast(_numberGroupSeparatorUtf8 ??= Encoding.UTF8.GetBytes(_numberGroupSeparator)); + } + public int CurrencyPositivePattern { get => _currencyPositivePattern; @@ -540,9 +643,19 @@ public string PositiveInfinitySymbol VerifyWritable(); _positiveInfinitySymbol = value; + _positiveInfinitySymbolUtf8 = null; } } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal ReadOnlySpan PositiveInfinitySymbolTChar() where TChar : unmanaged, IBinaryInteger + { + Debug.Assert(typeof(TChar) == typeof(char) || typeof(TChar) == typeof(byte)); + return typeof(TChar) == typeof(char) ? + MemoryMarshal.Cast(_positiveInfinitySymbol) : + MemoryMarshal.Cast(_positiveInfinitySymbolUtf8 ??= Encoding.UTF8.GetBytes(_positiveInfinitySymbol)); + } + public string PositiveSign { get => _positiveSign; @@ -552,10 +665,20 @@ public string PositiveSign VerifyWritable(); _positiveSign = value; + _positiveSignUtf8 = null; InitializeInvariantAndNegativeSignFlags(); } } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal ReadOnlySpan PositiveSignTChar() where TChar : unmanaged, IBinaryInteger + { + Debug.Assert(typeof(TChar) == typeof(char) || typeof(TChar) == typeof(byte)); + return typeof(TChar) == typeof(char) ? + MemoryMarshal.Cast(_positiveSign) : + MemoryMarshal.Cast(_positiveSignUtf8 ??= Encoding.UTF8.GetBytes(_positiveSign)); + } + public int PercentDecimalDigits { get => _percentDecimalDigits; @@ -582,9 +705,19 @@ public string PercentDecimalSeparator VerifyWritable(); ArgumentException.ThrowIfNullOrEmpty(value); _percentDecimalSeparator = value; + _percentDecimalSeparatorUtf8 = null; } } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal ReadOnlySpan PercentDecimalSeparatorTChar() where TChar : unmanaged, IBinaryInteger + { + Debug.Assert(typeof(TChar) == typeof(char) || typeof(TChar) == typeof(byte)); + return typeof(TChar) == typeof(char) ? + MemoryMarshal.Cast(_percentDecimalSeparator) : + MemoryMarshal.Cast(_percentDecimalSeparatorUtf8 ??= Encoding.UTF8.GetBytes(_percentDecimalSeparator)); + } + public string PercentGroupSeparator { get => _percentGroupSeparator; @@ -593,9 +726,19 @@ public string PercentGroupSeparator VerifyWritable(); ArgumentNullException.ThrowIfNull(value); _percentGroupSeparator = value; + _percentGroupSeparatorUtf8 = null; } } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal ReadOnlySpan PercentGroupSeparatorTChar() where TChar : unmanaged, IBinaryInteger + { + Debug.Assert(typeof(TChar) == typeof(char) || typeof(TChar) == typeof(byte)); + return typeof(TChar) == typeof(char) ? + MemoryMarshal.Cast(_percentGroupSeparator) : + MemoryMarshal.Cast(_percentGroupSeparatorUtf8 ??= Encoding.UTF8.GetBytes(_percentGroupSeparator)); + } + public string PercentSymbol { get => _percentSymbol; @@ -604,9 +747,19 @@ public string PercentSymbol ArgumentNullException.ThrowIfNull(value); VerifyWritable(); _percentSymbol = value; + _percentSymbolUtf8 = null; } } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal ReadOnlySpan PercentSymbolTChar() where TChar : unmanaged, IBinaryInteger + { + Debug.Assert(typeof(TChar) == typeof(char) || typeof(TChar) == typeof(byte)); + return typeof(TChar) == typeof(char) ? + MemoryMarshal.Cast(_percentSymbol) : + MemoryMarshal.Cast(_percentSymbolUtf8 ??= Encoding.UTF8.GetBytes(_percentSymbol)); + } + public string PerMilleSymbol { get => _perMilleSymbol; @@ -616,9 +769,19 @@ public string PerMilleSymbol VerifyWritable(); _perMilleSymbol = value; + _perMilleSymbolUtf8 = null; } } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal ReadOnlySpan PerMilleSymbolTChar() where TChar : unmanaged, IBinaryInteger + { + Debug.Assert(typeof(TChar) == typeof(char) || typeof(TChar) == typeof(byte)); + return typeof(TChar) == typeof(char) ? + MemoryMarshal.Cast(_perMilleSymbol) : + MemoryMarshal.Cast(_perMilleSymbolUtf8 ??= Encoding.UTF8.GetBytes(_perMilleSymbol)); + } + public string[] NativeDigits { get => (string[])_nativeDigits.Clone(); diff --git a/src/libraries/System.Private.CoreLib/src/System/Globalization/TimeSpanFormat.cs b/src/libraries/System.Private.CoreLib/src/System/Globalization/TimeSpanFormat.cs index aa891eb81846a..094c1a3dadfcb 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Globalization/TimeSpanFormat.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Globalization/TimeSpanFormat.cs @@ -240,7 +240,7 @@ internal static bool TryFormatStandard(TimeSpan value, StandardFormat for // Write day and separator, if necessary if (dayDigits != 0) { - FormattingHelpers.WriteDigits(days, destination.Slice(idx, dayDigits)); + Number.WriteDigits(days, destination.Slice(idx, dayDigits)); idx += dayDigits; destination[idx++] = TChar.CreateTruncating(format == StandardFormat.C ? '.' : ':'); } @@ -249,7 +249,7 @@ internal static bool TryFormatStandard(TimeSpan value, StandardFormat for Debug.Assert(hourDigits == 1 || hourDigits == 2); if (hourDigits == 2) { - FormattingHelpers.WriteTwoDigits(hours, destination, idx); + Number.WriteTwoDigits(hours, destination, idx); idx += 2; } else @@ -257,10 +257,10 @@ internal static bool TryFormatStandard(TimeSpan value, StandardFormat for destination[idx++] = TChar.CreateTruncating('0' + hours); } destination[idx++] = TChar.CreateTruncating(':'); - FormattingHelpers.WriteTwoDigits((uint)minutes, destination, idx); + Number.WriteTwoDigits((uint)minutes, destination, idx); idx += 2; destination[idx++] = TChar.CreateTruncating(':'); - FormattingHelpers.WriteTwoDigits((uint)seconds, destination, idx); + Number.WriteTwoDigits((uint)seconds, destination, idx); idx += 2; // Write fraction and separator, if necessary @@ -295,7 +295,7 @@ internal static bool TryFormatStandard(TimeSpan value, StandardFormat for idx += Encoding.UTF8.GetBytes(decimalSeparator, MemoryMarshal.Cast(destination).Slice(idx)); } } - FormattingHelpers.WriteDigits(fraction, destination.Slice(idx, fractionDigits)); + Number.WriteDigits(fraction, destination.Slice(idx, fractionDigits)); idx += fractionDigits; } diff --git a/src/libraries/System.Private.CoreLib/src/System/Half.cs b/src/libraries/System.Private.CoreLib/src/System/Half.cs index 34173dd54768e..3a7a2048e45a2 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Half.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Half.cs @@ -23,7 +23,8 @@ public readonly struct Half IComparable, IEquatable, IBinaryFloatingPointIeee754, - IMinMaxValue + IMinMaxValue, + IUtf8SpanFormattable { private const NumberStyles DefaultParseStyle = NumberStyles.Float | NumberStyles.AllowThousands; @@ -539,6 +540,12 @@ public bool TryFormat(Span destination, out int charsWritten, [StringSynta return Number.TryFormatHalf(this, format, NumberFormatInfo.GetInstance(provider), destination, out charsWritten); } + /// + bool IUtf8SpanFormattable.TryFormat(Span utf8Destination, out int bytesWritten, ReadOnlySpan format, IFormatProvider? provider) + { + return Number.TryFormatHalf(this, format, NumberFormatInfo.GetInstance(provider), utf8Destination, out bytesWritten); + } + // // Explicit Convert To Half // diff --git a/src/libraries/System.Private.CoreLib/src/System/Int128.cs b/src/libraries/System.Private.CoreLib/src/System/Int128.cs index cbfeeb9b8ba6e..8247f5a600eac 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Int128.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Int128.cs @@ -17,7 +17,8 @@ namespace System public readonly struct Int128 : IBinaryInteger, IMinMaxValue, - ISignedNumber + ISignedNumber, + IUtf8SpanFormattable { internal const int Size = 16; @@ -118,6 +119,12 @@ public bool TryFormat(Span destination, out int charsWritten, [StringSynta return Number.TryFormatInt128(this, format, provider, destination, out charsWritten); } + /// + bool IUtf8SpanFormattable.TryFormat(Span utf8Destination, out int bytesWritten, ReadOnlySpan format, IFormatProvider? provider) + { + return Number.TryFormatInt128(this, format, provider, utf8Destination, out bytesWritten); + } + public static Int128 Parse(string s) { ArgumentNullException.ThrowIfNull(s); diff --git a/src/libraries/System.Private.CoreLib/src/System/Int16.cs b/src/libraries/System.Private.CoreLib/src/System/Int16.cs index 67183c0b1b5f7..7286d104def07 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Int16.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Int16.cs @@ -23,7 +23,8 @@ public readonly struct Int16 IEquatable, IBinaryInteger, IMinMaxValue, - ISignedNumber + ISignedNumber, + IUtf8SpanFormattable { private readonly short m_value; // Do not rename (binary serialization) @@ -118,6 +119,12 @@ public bool TryFormat(Span destination, out int charsWritten, [StringSynta return Number.TryFormatInt32(m_value, 0x0000FFFF, format, provider, destination, out charsWritten); } + /// + bool IUtf8SpanFormattable.TryFormat(Span utf8Destination, out int bytesWritten, ReadOnlySpan format, IFormatProvider? provider) + { + return Number.TryFormatInt32(m_value, 0x0000FFFF, format, provider, utf8Destination, out bytesWritten); + } + public static short Parse(string s) { if (s == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.s); diff --git a/src/libraries/System.Private.CoreLib/src/System/Int32.cs b/src/libraries/System.Private.CoreLib/src/System/Int32.cs index 56dc9a7c8ab05..cff29460616af 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Int32.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Int32.cs @@ -23,7 +23,8 @@ public readonly struct Int32 IEquatable, IBinaryInteger, IMinMaxValue, - ISignedNumber + ISignedNumber, + IUtf8SpanFormattable { private readonly int m_value; // Do not rename (binary serialization) @@ -128,6 +129,12 @@ public bool TryFormat(Span destination, out int charsWritten, [StringSynta return Number.TryFormatInt32(m_value, ~0, format, provider, destination, out charsWritten); } + /// + bool IUtf8SpanFormattable.TryFormat(Span utf8Destination, out int bytesWritten, ReadOnlySpan format, IFormatProvider? provider) + { + return Number.TryFormatInt32(m_value, ~0, format, provider, utf8Destination, out bytesWritten); + } + public static int Parse(string s) { if (s == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.s); diff --git a/src/libraries/System.Private.CoreLib/src/System/Int64.cs b/src/libraries/System.Private.CoreLib/src/System/Int64.cs index de499239ebfd3..4d7143d4a8ebd 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Int64.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Int64.cs @@ -23,7 +23,8 @@ public readonly struct Int64 IEquatable, IBinaryInteger, IMinMaxValue, - ISignedNumber + ISignedNumber, + IUtf8SpanFormattable { private readonly long m_value; // Do not rename (binary serialization) @@ -125,6 +126,12 @@ public bool TryFormat(Span destination, out int charsWritten, [StringSynta return Number.TryFormatInt64(m_value, format, provider, destination, out charsWritten); } + /// + bool IUtf8SpanFormattable.TryFormat(Span utf8Destination, out int bytesWritten, ReadOnlySpan format, IFormatProvider? provider) + { + return Number.TryFormatInt64(m_value, format, provider, utf8Destination, out bytesWritten); + } + public static long Parse(string s) { if (s == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.s); diff --git a/src/libraries/System.Private.CoreLib/src/System/IntPtr.cs b/src/libraries/System.Private.CoreLib/src/System/IntPtr.cs index cac171836c749..f44bca5e46955 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IntPtr.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IntPtr.cs @@ -32,7 +32,8 @@ public readonly struct IntPtr ISerializable, IBinaryInteger, IMinMaxValue, - ISignedNumber + ISignedNumber, + IUtf8SpanFormattable { private readonly nint _value; @@ -210,6 +211,10 @@ public int CompareTo(nint value) public bool TryFormat(Span destination, out int charsWritten, [StringSyntax(StringSyntaxAttribute.NumericFormat)] ReadOnlySpan format = default, IFormatProvider? provider = null) => ((nint_t)_value).TryFormat(destination, out charsWritten, format, provider); + /// + bool IUtf8SpanFormattable.TryFormat(Span utf8Destination, out int bytesWritten, ReadOnlySpan format, IFormatProvider? provider) => + ((IUtf8SpanFormattable)(nint_t)_value).TryFormat(utf8Destination, out bytesWritten, format, provider); + public static nint Parse(string s) => (nint)nint_t.Parse(s); public static nint Parse(string s, NumberStyles style) => (nint)nint_t.Parse(s, style); public static nint Parse(string s, IFormatProvider? provider) => (nint)nint_t.Parse(s, provider); diff --git a/src/libraries/System.Private.CoreLib/src/System/Number.Formatting.cs b/src/libraries/System.Private.CoreLib/src/System/Number.Formatting.cs index 5b106f5063147..98ce459f26a32 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Number.Formatting.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Number.Formatting.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Buffers.Text; +using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.Numerics; @@ -271,8 +272,8 @@ internal static partial class Number /// /// This is a semi-arbitrary bound. For mono, which is often used for more size-constrained workloads, /// we keep the size really small, supporting only single digit values. For coreclr, we use a larger - /// value, still relatively small but large enough to accomodate common sources of numbers to strings, e.g. HTTP success status codes. - /// By being >= 255, it also accomodates all byte.ToString()s. If no small numbers are ever formatted, we incur + /// value, still relatively small but large enough to accommodate common sources of numbers to strings, e.g. HTTP success status codes. + /// By being >= 255, it also accommodates all byte.ToString()s. If no small numbers are ever formatted, we incur /// the ~2400 bytes on 64-bit for the array itself. If all small numbers are formatted, we incur ~11,500 bytes /// on 64-bit for the array and all the strings. /// @@ -353,22 +354,26 @@ public static unsafe string FormatDecimal(decimal value, ReadOnlySpan form DecimalToNumber(ref value, ref number); char* stackPtr = stackalloc char[CharStackBufferSize]; - ValueStringBuilder sb = new ValueStringBuilder(new Span(stackPtr, CharStackBufferSize)); + var vlb = new ValueListBuilder(new Span(stackPtr, CharStackBufferSize)); if (fmt != 0) { - NumberToString(ref sb, ref number, fmt, digits, info); + NumberToString(ref vlb, ref number, fmt, digits, info); } else { - NumberToStringFormat(ref sb, ref number, format, info); + NumberToStringFormat(ref vlb, ref number, format, info); } - return sb.ToString(); + string result = vlb.AsSpan().ToString(); + vlb.Dispose(); + return result; } - public static unsafe bool TryFormatDecimal(decimal value, ReadOnlySpan format, NumberFormatInfo info, Span destination, out int charsWritten) + public static unsafe bool TryFormatDecimal(decimal value, ReadOnlySpan format, NumberFormatInfo info, Span destination, out int charsWritten) where TChar : unmanaged, IBinaryInteger { + Debug.Assert(typeof(TChar) == typeof(char) || typeof(TChar) == typeof(byte)); + char fmt = ParseFormatSpecifier(format, out int digits); byte* pDigits = stackalloc byte[DecimalNumberBufferLength]; @@ -376,19 +381,21 @@ public static unsafe bool TryFormatDecimal(decimal value, ReadOnlySpan for DecimalToNumber(ref value, ref number); - char* stackPtr = stackalloc char[CharStackBufferSize]; - ValueStringBuilder sb = new ValueStringBuilder(new Span(stackPtr, CharStackBufferSize)); + TChar* stackPtr = stackalloc TChar[CharStackBufferSize]; + var vlb = new ValueListBuilder(new Span(stackPtr, CharStackBufferSize)); if (fmt != 0) { - NumberToString(ref sb, ref number, fmt, digits, info); + NumberToString(ref vlb, ref number, fmt, digits, info); } else { - NumberToStringFormat(ref sb, ref number, format, info); + NumberToStringFormat(ref vlb, ref number, format, info); } - return sb.TryCopyTo(destination, out charsWritten); + bool success = vlb.TryCopyTo(destination, out charsWritten); + vlb.Dispose(); + return success; } internal static unsafe void DecimalToNumber(scoped ref decimal d, ref NumberBuffer number) @@ -414,24 +421,31 @@ internal static unsafe void DecimalToNumber(scoped ref decimal d, ref NumberBuff { *dst++ = *p++; } - *dst = (byte)('\0'); + *dst = (byte)'\0'; number.CheckConsistency(); } public static string FormatDouble(double value, string? format, NumberFormatInfo info) { - var sb = new ValueStringBuilder(stackalloc char[CharStackBufferSize]); - return FormatDouble(ref sb, value, format, info) ?? sb.ToString(); + var vlb = new ValueListBuilder(stackalloc char[CharStackBufferSize]); + string result = FormatDouble(ref vlb, value, format, info) ?? vlb.AsSpan().ToString(); + vlb.Dispose(); + return result; } - public static bool TryFormatDouble(double value, ReadOnlySpan format, NumberFormatInfo info, Span destination, out int charsWritten) + public static bool TryFormatDouble(double value, ReadOnlySpan format, NumberFormatInfo info, Span destination, out int charsWritten) where TChar : unmanaged, IBinaryInteger { - var sb = new ValueStringBuilder(stackalloc char[CharStackBufferSize]); - string? s = FormatDouble(ref sb, value, format, info); - return s != null ? + var vlb = new ValueListBuilder(stackalloc TChar[CharStackBufferSize]); + string? s = FormatDouble(ref vlb, value, format, info); + + Debug.Assert(s is null || typeof(TChar) == typeof(char)); + bool success = s != null ? TryCopyTo(s, destination, out charsWritten) : - sb.TryCopyTo(destination, out charsWritten); + vlb.TryCopyTo(destination, out charsWritten); + + vlb.Dispose(); + return success; } private static int GetFloatingPointMaxDigitsAndPrecision(char fmt, ref int precision, NumberFormatInfo info, out bool isSignificantDigits) @@ -448,107 +462,108 @@ private static int GetFloatingPointMaxDigitsAndPrecision(char fmt, ref int preci { case 'C': case 'c': - { - // The currency format uses the precision specifier to indicate the number of - // decimal digits to format. This defaults to NumberFormatInfo.CurrencyDecimalDigits. - - if (precision == -1) { - precision = info.CurrencyDecimalDigits; - } - isSignificantDigits = false; + // The currency format uses the precision specifier to indicate the number of + // decimal digits to format. This defaults to NumberFormatInfo.CurrencyDecimalDigits. - break; - } + if (precision == -1) + { + precision = info.CurrencyDecimalDigits; + } + isSignificantDigits = false; + + break; + } case 'E': case 'e': - { - // The exponential format uses the precision specifier to indicate the number of - // decimal digits to format. This defaults to 6. However, the exponential format - // also always formats a single integral digit, so we need to increase the precision - // specifier and treat it as the number of significant digits to account for this. - - if (precision == -1) { - precision = DefaultPrecisionExponentialFormat; - } + // The exponential format uses the precision specifier to indicate the number of + // decimal digits to format. This defaults to 6. However, the exponential format + // also always formats a single integral digit, so we need to increase the precision + // specifier and treat it as the number of significant digits to account for this. - precision++; - isSignificantDigits = true; + if (precision == -1) + { + precision = DefaultPrecisionExponentialFormat; + } - break; - } + precision++; + isSignificantDigits = true; + + break; + } case 'F': case 'f': case 'N': case 'n': - { - // The fixed-point and number formats use the precision specifier to indicate the number - // of decimal digits to format. This defaults to NumberFormatInfo.NumberDecimalDigits. - - if (precision == -1) { - precision = info.NumberDecimalDigits; - } - isSignificantDigits = false; + // The fixed-point and number formats use the precision specifier to indicate the number + // of decimal digits to format. This defaults to NumberFormatInfo.NumberDecimalDigits. - break; - } + if (precision == -1) + { + precision = info.NumberDecimalDigits; + } + isSignificantDigits = false; + + break; + } case 'G': case 'g': - { - // The general format uses the precision specifier to indicate the number of significant - // digits to format. This defaults to the shortest roundtrippable string. Additionally, - // given that we can't return zero significant digits, we treat 0 as returning the shortest - // roundtrippable string as well. - - if (precision == 0) { - precision = -1; - } - isSignificantDigits = true; + // The general format uses the precision specifier to indicate the number of significant + // digits to format. This defaults to the shortest roundtrippable string. Additionally, + // given that we can't return zero significant digits, we treat 0 as returning the shortest + // roundtrippable string as well. - break; - } + if (precision == 0) + { + precision = -1; + } + isSignificantDigits = true; + + break; + } case 'P': case 'p': - { - // The percent format uses the precision specifier to indicate the number of - // decimal digits to format. This defaults to NumberFormatInfo.PercentDecimalDigits. - // However, the percent format also always multiplies the number by 100, so we need - // to increase the precision specifier to ensure we get the appropriate number of digits. - - if (precision == -1) { - precision = info.PercentDecimalDigits; - } + // The percent format uses the precision specifier to indicate the number of + // decimal digits to format. This defaults to NumberFormatInfo.PercentDecimalDigits. + // However, the percent format also always multiplies the number by 100, so we need + // to increase the precision specifier to ensure we get the appropriate number of digits. + + if (precision == -1) + { + precision = info.PercentDecimalDigits; + } - precision += 2; - isSignificantDigits = false; + precision += 2; + isSignificantDigits = false; - break; - } + break; + } case 'R': case 'r': - { - // The roundtrip format ignores the precision specifier and always returns the shortest - // roundtrippable string. + { + // The roundtrip format ignores the precision specifier and always returns the shortest + // roundtrippable string. - precision = -1; - isSignificantDigits = true; + precision = -1; + isSignificantDigits = true; - break; - } + break; + } default: - { - throw new FormatException(SR.Argument_BadFormatSpecifier); - } + { + ThrowHelper.ThrowFormatException_BadFormatSpecifier(); + goto case 'r'; // unreachable + } } return maxDigits; @@ -559,16 +574,32 @@ private static int GetFloatingPointMaxDigitsAndPrecision(char fmt, ref int preci /// Non-null if an existing string can be returned, in which case the builder will be unmodified. /// Null if no existing string was returned, in which case the formatted output is in the builder. /// - private static unsafe string? FormatDouble(ref ValueStringBuilder sb, double value, ReadOnlySpan format, NumberFormatInfo info) + private static unsafe string? FormatDouble(ref ValueListBuilder vlb, double value, ReadOnlySpan format, NumberFormatInfo info) where TChar : unmanaged, IBinaryInteger { if (!double.IsFinite(value)) { if (double.IsNaN(value)) { - return info.NaNSymbol; + if (typeof(TChar) == typeof(char)) + { + return info.NaNSymbol; + } + else + { + vlb.Append(info.NaNSymbolTChar()); + return null; + } } - return double.IsNegative(value) ? info.NegativeInfinitySymbol : info.PositiveInfinitySymbol; + if (typeof(TChar) == typeof(char)) + { + return double.IsNegative(value) ? info.NegativeInfinitySymbol : info.PositiveInfinitySymbol; + } + else + { + vlb.Append(double.IsNegative(value) ? info.NegativeInfinitySymbolTChar() : info.PositiveInfinitySymbolTChar()); + return null; + } } char fmt = ParseFormatSpecifier(format, out int precision); @@ -615,29 +646,36 @@ private static int GetFloatingPointMaxDigitsAndPrecision(char fmt, ref int preci nMaxDigits = Math.Max(number.DigitsCount, DoublePrecision); } - NumberToString(ref sb, ref number, fmt, nMaxDigits, info); + NumberToString(ref vlb, ref number, fmt, nMaxDigits, info); } else { Debug.Assert(precision == DoublePrecisionCustomFormat); - NumberToStringFormat(ref sb, ref number, format, info); + NumberToStringFormat(ref vlb, ref number, format, info); } return null; } public static string FormatSingle(float value, string? format, NumberFormatInfo info) { - var sb = new ValueStringBuilder(stackalloc char[CharStackBufferSize]); - return FormatSingle(ref sb, value, format, info) ?? sb.ToString(); + var vlb = new ValueListBuilder(stackalloc char[CharStackBufferSize]); + string result = FormatSingle(ref vlb, value, format, info) ?? vlb.AsSpan().ToString(); + vlb.Dispose(); + return result; } - public static bool TryFormatSingle(float value, ReadOnlySpan format, NumberFormatInfo info, Span destination, out int charsWritten) + public static bool TryFormatSingle(float value, ReadOnlySpan format, NumberFormatInfo info, Span destination, out int charsWritten) where TChar : unmanaged, IBinaryInteger { - var sb = new ValueStringBuilder(stackalloc char[CharStackBufferSize]); - string? s = FormatSingle(ref sb, value, format, info); - return s != null ? + var vlb = new ValueListBuilder(stackalloc TChar[CharStackBufferSize]); + string? s = FormatSingle(ref vlb, value, format, info); + + Debug.Assert(s is null || typeof(TChar) == typeof(char)); + bool success = s != null ? TryCopyTo(s, destination, out charsWritten) : - sb.TryCopyTo(destination, out charsWritten); + vlb.TryCopyTo(destination, out charsWritten); + + vlb.Dispose(); + return success; } /// Formats the specified value according to the specified format and info. @@ -645,16 +683,34 @@ public static bool TryFormatSingle(float value, ReadOnlySpan format, Numbe /// Non-null if an existing string can be returned, in which case the builder will be unmodified. /// Null if no existing string was returned, in which case the formatted output is in the builder. /// - private static unsafe string? FormatSingle(ref ValueStringBuilder sb, float value, ReadOnlySpan format, NumberFormatInfo info) + private static unsafe string? FormatSingle(ref ValueListBuilder vlb, float value, ReadOnlySpan format, NumberFormatInfo info) where TChar : unmanaged, IBinaryInteger { + Debug.Assert(typeof(TChar) == typeof(char) || typeof(TChar) == typeof(byte)); + if (!float.IsFinite(value)) { if (float.IsNaN(value)) { - return info.NaNSymbol; + if (typeof(TChar) == typeof(char)) + { + return info.NaNSymbol; + } + else + { + vlb.Append(info.NaNSymbolTChar()); + return null; + } } - return float.IsNegative(value) ? info.NegativeInfinitySymbol : info.PositiveInfinitySymbol; + if (typeof(TChar) == typeof(char)) + { + return float.IsNegative(value) ? info.NegativeInfinitySymbol : info.PositiveInfinitySymbol; + } + else + { + vlb.Append(float.IsNegative(value) ? info.NegativeInfinitySymbolTChar() : info.PositiveInfinitySymbolTChar()); + return null; + } } char fmt = ParseFormatSpecifier(format, out int precision); @@ -701,20 +757,22 @@ public static bool TryFormatSingle(float value, ReadOnlySpan format, Numbe nMaxDigits = Math.Max(number.DigitsCount, SinglePrecision); } - NumberToString(ref sb, ref number, fmt, nMaxDigits, info); + NumberToString(ref vlb, ref number, fmt, nMaxDigits, info); } else { Debug.Assert(precision == SinglePrecisionCustomFormat); - NumberToStringFormat(ref sb, ref number, format, info); + NumberToStringFormat(ref vlb, ref number, format, info); } return null; } public static string FormatHalf(Half value, string? format, NumberFormatInfo info) { - var sb = new ValueStringBuilder(stackalloc char[CharStackBufferSize]); - return FormatHalf(ref sb, value, format, info) ?? sb.ToString(); + var vlb = new ValueListBuilder(stackalloc char[CharStackBufferSize]); + string result = FormatHalf(ref vlb, value, format, info) ?? vlb.AsSpan().ToString(); + vlb.Dispose(); + return result; } /// Formats the specified value according to the specified format and info. @@ -722,16 +780,34 @@ public static string FormatHalf(Half value, string? format, NumberFormatInfo inf /// Non-null if an existing string can be returned, in which case the builder will be unmodified. /// Null if no existing string was returned, in which case the formatted output is in the builder. /// - private static unsafe string? FormatHalf(ref ValueStringBuilder sb, Half value, ReadOnlySpan format, NumberFormatInfo info) + private static unsafe string? FormatHalf(ref ValueListBuilder vlb, Half value, ReadOnlySpan format, NumberFormatInfo info) where TChar : unmanaged, IBinaryInteger { + Debug.Assert(typeof(TChar) == typeof(char) || typeof(TChar) == typeof(byte)); + if (!Half.IsFinite(value)) { if (Half.IsNaN(value)) { - return info.NaNSymbol; + if (typeof(TChar) == typeof(char)) + { + return info.NaNSymbol; + } + else + { + vlb.Append(info.NaNSymbolTChar()); + return null; + } } - return Half.IsNegative(value) ? info.NegativeInfinitySymbol : info.PositiveInfinitySymbol; + if (typeof(TChar) == typeof(char)) + { + return Half.IsNegative(value) ? info.NegativeInfinitySymbol : info.PositiveInfinitySymbol; + } + else + { + vlb.Append(Half.IsNegative(value) ? info.NegativeInfinitySymbolTChar() : info.PositiveInfinitySymbolTChar()); + return null; + } } char fmt = ParseFormatSpecifier(format, out int precision); @@ -776,38 +852,52 @@ public static string FormatHalf(Half value, string? format, NumberFormatInfo inf nMaxDigits = Math.Max(number.DigitsCount, HalfPrecision); } - NumberToString(ref sb, ref number, fmt, nMaxDigits, info); + NumberToString(ref vlb, ref number, fmt, nMaxDigits, info); } else { Debug.Assert(precision == HalfPrecisionCustomFormat); - NumberToStringFormat(ref sb, ref number, format, info); + NumberToStringFormat(ref vlb, ref number, format, info); } return null; } - public static bool TryFormatHalf(Half value, ReadOnlySpan format, NumberFormatInfo info, Span destination, out int charsWritten) + public static bool TryFormatHalf(Half value, ReadOnlySpan format, NumberFormatInfo info, Span destination, out int charsWritten) where TChar : unmanaged, IBinaryInteger { - var sb = new ValueStringBuilder(stackalloc char[CharStackBufferSize]); - string? s = FormatHalf(ref sb, value, format, info); - return s != null ? + Debug.Assert(typeof(TChar) == typeof(char) || typeof(TChar) == typeof(byte)); + + var vlb = new ValueListBuilder(stackalloc TChar[CharStackBufferSize]); + string? s = FormatHalf(ref vlb, value, format, info); + + Debug.Assert(s is null || typeof(TChar) == typeof(char)); + bool success = s != null ? TryCopyTo(s, destination, out charsWritten) : - sb.TryCopyTo(destination, out charsWritten); - } + vlb.TryCopyTo(destination, out charsWritten); + vlb.Dispose(); + return success; + } - private static bool TryCopyTo(string source, Span destination, out int charsWritten) + private static bool TryCopyTo(string source, Span destination, out int charsWritten) where TChar : unmanaged, IBinaryInteger { + Debug.Assert(typeof(TChar) == typeof(char) || typeof(TChar) == typeof(byte)); Debug.Assert(source != null); - if (source.TryCopyTo(destination)) + if (typeof(TChar) == typeof(char)) { - charsWritten = source.Length; - return true; - } + if (source.TryCopyTo(MemoryMarshal.Cast(destination))) + { + charsWritten = source.Length; + return true; + } - charsWritten = 0; - return false; + charsWritten = 0; + return false; + } + else + { + return Encoding.UTF8.TryGetBytes(source, MemoryMarshal.Cast(destination), out charsWritten); + } } private static char GetHexBase(char fmt) @@ -854,34 +944,38 @@ static unsafe string FormatInt32Slow(int value, int hexMask, string? format, IFo Int32ToNumber(value, ref number); char* stackPtr = stackalloc char[CharStackBufferSize]; - ValueStringBuilder sb = new ValueStringBuilder(new Span(stackPtr, CharStackBufferSize)); + var vlb = new ValueListBuilder(new Span(stackPtr, CharStackBufferSize)); if (fmt != 0) { - NumberToString(ref sb, ref number, fmt, digits, info); + NumberToString(ref vlb, ref number, fmt, digits, info); } else { - NumberToStringFormat(ref sb, ref number, formatSpan, info); + NumberToStringFormat(ref vlb, ref number, formatSpan, info); } - return sb.ToString(); + + string result = vlb.AsSpan().ToString(); + vlb.Dispose(); + return result; } } } - public static bool TryFormatInt32(int value, int hexMask, ReadOnlySpan format, IFormatProvider? provider, Span destination, out int charsWritten) + [MethodImpl(MethodImplOptions.AggressiveInlining)] // expose to caller's likely-const format to trim away slow path + public static bool TryFormatInt32(int value, int hexMask, ReadOnlySpan format, IFormatProvider? provider, Span destination, out int charsWritten) where TChar : unmanaged, IBinaryInteger { // Fast path for default format if (format.Length == 0) { return value >= 0 ? TryUInt32ToDecStr((uint)value, destination, out charsWritten) : - TryNegativeInt32ToDecStr(value, digits: -1, NumberFormatInfo.GetInstance(provider).NegativeSign, destination, out charsWritten); + TryNegativeInt32ToDecStr(value, digits: -1, NumberFormatInfo.GetInstance(provider).NegativeSignTChar(), destination, out charsWritten); } return TryFormatInt32Slow(value, hexMask, format, provider, destination, out charsWritten); - static unsafe bool TryFormatInt32Slow(int value, int hexMask, ReadOnlySpan format, IFormatProvider? provider, Span destination, out int charsWritten) + static unsafe bool TryFormatInt32Slow(int value, int hexMask, ReadOnlySpan format, IFormatProvider? provider, Span destination, out int charsWritten) { char fmt = ParseFormatSpecifier(format, out int digits); char fmtUpper = (char)(fmt & 0xFFDF); // ensure fmt is upper-cased for purposes of comparison @@ -889,7 +983,7 @@ static unsafe bool TryFormatInt32Slow(int value, int hexMask, ReadOnlySpan { return value >= 0 ? TryUInt32ToDecStr((uint)value, digits, destination, out charsWritten) : - TryNegativeInt32ToDecStr(value, digits, NumberFormatInfo.GetInstance(provider).NegativeSign, destination, out charsWritten); + TryNegativeInt32ToDecStr(value, digits, NumberFormatInfo.GetInstance(provider).NegativeSignTChar(), destination, out charsWritten); } else if (fmtUpper == 'X') { @@ -904,18 +998,21 @@ static unsafe bool TryFormatInt32Slow(int value, int hexMask, ReadOnlySpan Int32ToNumber(value, ref number); - char* stackPtr = stackalloc char[CharStackBufferSize]; - ValueStringBuilder sb = new ValueStringBuilder(new Span(stackPtr, CharStackBufferSize)); + TChar* stackPtr = stackalloc TChar[CharStackBufferSize]; + var vlb = new ValueListBuilder(new Span(stackPtr, CharStackBufferSize)); if (fmt != 0) { - NumberToString(ref sb, ref number, fmt, digits, info); + NumberToString(ref vlb, ref number, fmt, digits, info); } else { - NumberToStringFormat(ref sb, ref number, format, info); + NumberToStringFormat(ref vlb, ref number, format, info); } - return sb.TryCopyTo(destination, out charsWritten); + + bool success = vlb.TryCopyTo(destination, out charsWritten); + vlb.Dispose(); + return success; } } } @@ -953,23 +1050,29 @@ static unsafe string FormatUInt32Slow(uint value, string? format, IFormatProvide UInt32ToNumber(value, ref number); char* stackPtr = stackalloc char[CharStackBufferSize]; - ValueStringBuilder sb = new ValueStringBuilder(new Span(stackPtr, CharStackBufferSize)); + var vlb = new ValueListBuilder(new Span(stackPtr, CharStackBufferSize)); if (fmt != 0) { - NumberToString(ref sb, ref number, fmt, digits, info); + NumberToString(ref vlb, ref number, fmt, digits, info); } else { - NumberToStringFormat(ref sb, ref number, formatSpan, info); + NumberToStringFormat(ref vlb, ref number, formatSpan, info); } - return sb.ToString(); + + string result = vlb.AsSpan().ToString(); + vlb.Dispose(); + return result; } } } - public static bool TryFormatUInt32(uint value, ReadOnlySpan format, IFormatProvider? provider, Span destination, out int charsWritten) + [MethodImpl(MethodImplOptions.AggressiveInlining)] // expose to caller's likely-const format to trim away slow path + public static bool TryFormatUInt32(uint value, ReadOnlySpan format, IFormatProvider? provider, Span destination, out int charsWritten) where TChar : unmanaged, IBinaryInteger { + Debug.Assert(typeof(TChar) == typeof(char) || typeof(TChar) == typeof(byte)); + // Fast path for default format if (format.Length == 0) { @@ -978,7 +1081,7 @@ public static bool TryFormatUInt32(uint value, ReadOnlySpan format, IForma return TryFormatUInt32Slow(value, format, provider, destination, out charsWritten); - static unsafe bool TryFormatUInt32Slow(uint value, ReadOnlySpan format, IFormatProvider? provider, Span destination, out int charsWritten) + static unsafe bool TryFormatUInt32Slow(uint value, ReadOnlySpan format, IFormatProvider? provider, Span destination, out int charsWritten) { char fmt = ParseFormatSpecifier(format, out int digits); char fmtUpper = (char)(fmt & 0xFFDF); // ensure fmt is upper-cased for purposes of comparison @@ -999,18 +1102,21 @@ static unsafe bool TryFormatUInt32Slow(uint value, ReadOnlySpan format, IF UInt32ToNumber(value, ref number); - char* stackPtr = stackalloc char[CharStackBufferSize]; - ValueStringBuilder sb = new ValueStringBuilder(new Span(stackPtr, CharStackBufferSize)); + TChar* stackPtr = stackalloc TChar[CharStackBufferSize]; + var vlb = new ValueListBuilder(new Span(stackPtr, CharStackBufferSize)); if (fmt != 0) { - NumberToString(ref sb, ref number, fmt, digits, info); + NumberToString(ref vlb, ref number, fmt, digits, info); } else { - NumberToStringFormat(ref sb, ref number, format, info); + NumberToStringFormat(ref vlb, ref number, format, info); } - return sb.TryCopyTo(destination, out charsWritten); + + bool success = vlb.TryCopyTo(destination, out charsWritten); + vlb.Dispose(); + return success; } } } @@ -1052,34 +1158,40 @@ static unsafe string FormatInt64Slow(long value, string? format, IFormatProvider Int64ToNumber(value, ref number); char* stackPtr = stackalloc char[CharStackBufferSize]; - ValueStringBuilder sb = new ValueStringBuilder(new Span(stackPtr, CharStackBufferSize)); + var vlb = new ValueListBuilder(new Span(stackPtr, CharStackBufferSize)); if (fmt != 0) { - NumberToString(ref sb, ref number, fmt, digits, info); + NumberToString(ref vlb, ref number, fmt, digits, info); } else { - NumberToStringFormat(ref sb, ref number, formatSpan, info); + NumberToStringFormat(ref vlb, ref number, formatSpan, info); } - return sb.ToString(); + + string result = vlb.AsSpan().ToString(); + vlb.Dispose(); + return result; } } } - public static bool TryFormatInt64(long value, ReadOnlySpan format, IFormatProvider? provider, Span destination, out int charsWritten) + [MethodImpl(MethodImplOptions.AggressiveInlining)] // expose to caller's likely-const format to trim away slow path + public static bool TryFormatInt64(long value, ReadOnlySpan format, IFormatProvider? provider, Span destination, out int charsWritten) where TChar : unmanaged, IBinaryInteger { + Debug.Assert(typeof(TChar) == typeof(char) || typeof(TChar) == typeof(byte)); + // Fast path for default format if (format.Length == 0) { return value >= 0 ? TryUInt64ToDecStr((ulong)value, destination, out charsWritten) : - TryNegativeInt64ToDecStr(value, digits: -1, NumberFormatInfo.GetInstance(provider).NegativeSign, destination, out charsWritten); + TryNegativeInt64ToDecStr(value, digits: -1, NumberFormatInfo.GetInstance(provider).NegativeSignTChar(), destination, out charsWritten); } return TryFormatInt64Slow(value, format, provider, destination, out charsWritten); - static unsafe bool TryFormatInt64Slow(long value, ReadOnlySpan format, IFormatProvider? provider, Span destination, out int charsWritten) + static unsafe bool TryFormatInt64Slow(long value, ReadOnlySpan format, IFormatProvider? provider, Span destination, out int charsWritten) { char fmt = ParseFormatSpecifier(format, out int digits); char fmtUpper = (char)(fmt & 0xFFDF); // ensure fmt is upper-cased for purposes of comparison @@ -1087,7 +1199,7 @@ static unsafe bool TryFormatInt64Slow(long value, ReadOnlySpan format, IFo { return value >= 0 ? TryUInt64ToDecStr((ulong)value, digits, destination, out charsWritten) : - TryNegativeInt64ToDecStr(value, digits, NumberFormatInfo.GetInstance(provider).NegativeSign, destination, out charsWritten); + TryNegativeInt64ToDecStr(value, digits, NumberFormatInfo.GetInstance(provider).NegativeSignTChar(), destination, out charsWritten); } else if (fmtUpper == 'X') { @@ -1103,17 +1215,20 @@ static unsafe bool TryFormatInt64Slow(long value, ReadOnlySpan format, IFo Int64ToNumber(value, ref number); char* stackPtr = stackalloc char[CharStackBufferSize]; - ValueStringBuilder sb = new ValueStringBuilder(new Span(stackPtr, CharStackBufferSize)); + var vlb = new ValueListBuilder(new Span(stackPtr, CharStackBufferSize)); if (fmt != 0) { - NumberToString(ref sb, ref number, fmt, digits, info); + NumberToString(ref vlb, ref number, fmt, digits, info); } else { - NumberToStringFormat(ref sb, ref number, format, info); + NumberToStringFormat(ref vlb, ref number, format, info); } - return sb.TryCopyTo(destination, out charsWritten); + + bool success = vlb.TryCopyTo(destination, out charsWritten); + vlb.Dispose(); + return success; } } } @@ -1151,23 +1266,29 @@ static unsafe string FormatUInt64Slow(ulong value, string? format, IFormatProvid UInt64ToNumber(value, ref number); char* stackPtr = stackalloc char[CharStackBufferSize]; - ValueStringBuilder sb = new ValueStringBuilder(new Span(stackPtr, CharStackBufferSize)); + var vlb = new ValueListBuilder(new Span(stackPtr, CharStackBufferSize)); if (fmt != 0) { - NumberToString(ref sb, ref number, fmt, digits, info); + NumberToString(ref vlb, ref number, fmt, digits, info); } else { - NumberToStringFormat(ref sb, ref number, formatSpan, info); + NumberToStringFormat(ref vlb, ref number, formatSpan, info); } - return sb.ToString(); + + string result = vlb.AsSpan().ToString(); + vlb.Dispose(); + return result; } } } - public static bool TryFormatUInt64(ulong value, ReadOnlySpan format, IFormatProvider? provider, Span destination, out int charsWritten) + [MethodImpl(MethodImplOptions.AggressiveInlining)] // expose to caller's likely-const format to trim away slow path + public static bool TryFormatUInt64(ulong value, ReadOnlySpan format, IFormatProvider? provider, Span destination, out int charsWritten) where TChar : unmanaged, IBinaryInteger { + Debug.Assert(typeof(TChar) == typeof(char) || typeof(TChar) == typeof(byte)); + // Fast path for default format if (format.Length == 0) { @@ -1176,7 +1297,7 @@ public static bool TryFormatUInt64(ulong value, ReadOnlySpan format, IForm return TryFormatUInt64Slow(value, format, provider, destination, out charsWritten); - static unsafe bool TryFormatUInt64Slow(ulong value, ReadOnlySpan format, IFormatProvider? provider, Span destination, out int charsWritten) + static unsafe bool TryFormatUInt64Slow(ulong value, ReadOnlySpan format, IFormatProvider? provider, Span destination, out int charsWritten) { char fmt = ParseFormatSpecifier(format, out int digits); char fmtUpper = (char)(fmt & 0xFFDF); // ensure fmt is upper-cased for purposes of comparison @@ -1197,18 +1318,21 @@ static unsafe bool TryFormatUInt64Slow(ulong value, ReadOnlySpan format, I UInt64ToNumber(value, ref number); - char* stackPtr = stackalloc char[CharStackBufferSize]; - ValueStringBuilder sb = new ValueStringBuilder(new Span(stackPtr, CharStackBufferSize)); + TChar* stackPtr = stackalloc TChar[CharStackBufferSize]; + var vlb = new ValueListBuilder(new Span(stackPtr, CharStackBufferSize)); if (fmt != 0) { - NumberToString(ref sb, ref number, fmt, digits, info); + NumberToString(ref vlb, ref number, fmt, digits, info); } else { - NumberToStringFormat(ref sb, ref number, format, info); + NumberToStringFormat(ref vlb, ref number, format, info); } - return sb.TryCopyTo(destination, out charsWritten); + + bool success = vlb.TryCopyTo(destination, out charsWritten); + vlb.Dispose(); + return success; } } } @@ -1252,35 +1376,39 @@ static unsafe string FormatInt128Slow(Int128 value, string? format, IFormatProvi Int128ToNumber(value, ref number); char* stackPtr = stackalloc char[CharStackBufferSize]; - ValueStringBuilder sb = new ValueStringBuilder(new Span(stackPtr, CharStackBufferSize)); + var vlb = new ValueListBuilder(new Span(stackPtr, CharStackBufferSize)); if (fmt != 0) { - NumberToString(ref sb, ref number, fmt, digits, info); + NumberToString(ref vlb, ref number, fmt, digits, info); } else { - NumberToStringFormat(ref sb, ref number, formatSpan, info); + NumberToStringFormat(ref vlb, ref number, formatSpan, info); } - return sb.ToString(); + string result = vlb.AsSpan().ToString(); + vlb.Dispose(); + return result; } } } - public static bool TryFormatInt128(Int128 value, ReadOnlySpan format, IFormatProvider? provider, Span destination, out int charsWritten) + public static bool TryFormatInt128(Int128 value, ReadOnlySpan format, IFormatProvider? provider, Span destination, out int charsWritten) where TChar : unmanaged, IBinaryInteger { + Debug.Assert(typeof(TChar) == typeof(char) || typeof(TChar) == typeof(byte)); + // Fast path for default format if (format.Length == 0) { return Int128.IsPositive(value) ? TryUInt128ToDecStr((UInt128)value, digits: -1, destination, out charsWritten) - : TryNegativeInt128ToDecStr(value, digits: -1, NumberFormatInfo.GetInstance(provider).NegativeSign, destination, out charsWritten); + : TryNegativeInt128ToDecStr(value, digits: -1, NumberFormatInfo.GetInstance(provider).NegativeSignTChar(), destination, out charsWritten); } return TryFormatInt128Slow(value, format, provider, destination, out charsWritten); - static unsafe bool TryFormatInt128Slow(Int128 value, ReadOnlySpan format, IFormatProvider? provider, Span destination, out int charsWritten) + static unsafe bool TryFormatInt128Slow(Int128 value, ReadOnlySpan format, IFormatProvider? provider, Span destination, out int charsWritten) { char fmt = ParseFormatSpecifier(format, out int digits); char fmtUpper = (char)(fmt & 0xFFDF); // ensure fmt is upper-cased for purposes of comparison @@ -1289,7 +1417,7 @@ static unsafe bool TryFormatInt128Slow(Int128 value, ReadOnlySpan format, { return Int128.IsPositive(value) ? TryUInt128ToDecStr((UInt128)value, digits, destination, out charsWritten) - : TryNegativeInt128ToDecStr(value, digits, NumberFormatInfo.GetInstance(provider).NegativeSign, destination, out charsWritten); + : TryNegativeInt128ToDecStr(value, digits, NumberFormatInfo.GetInstance(provider).NegativeSignTChar(), destination, out charsWritten); } else if (fmtUpper == 'X') { @@ -1304,19 +1432,21 @@ static unsafe bool TryFormatInt128Slow(Int128 value, ReadOnlySpan format, Int128ToNumber(value, ref number); - char* stackPtr = stackalloc char[CharStackBufferSize]; - ValueStringBuilder sb = new ValueStringBuilder(new Span(stackPtr, CharStackBufferSize)); + TChar* stackPtr = stackalloc TChar[CharStackBufferSize]; + var vlb = new ValueListBuilder(new Span(stackPtr, CharStackBufferSize)); if (fmt != 0) { - NumberToString(ref sb, ref number, fmt, digits, info); + NumberToString(ref vlb, ref number, fmt, digits, info); } else { - NumberToStringFormat(ref sb, ref number, format, info); + NumberToStringFormat(ref vlb, ref number, format, info); } - return sb.TryCopyTo(destination, out charsWritten); + bool success = vlb.TryCopyTo(destination, out charsWritten); + vlb.Dispose(); + return success; } } } @@ -1356,24 +1486,28 @@ static unsafe string FormatUInt128Slow(UInt128 value, string? format, IFormatPro UInt128ToNumber(value, ref number); char* stackPtr = stackalloc char[CharStackBufferSize]; - ValueStringBuilder sb = new ValueStringBuilder(new Span(stackPtr, CharStackBufferSize)); + var vlb = new ValueListBuilder(new Span(stackPtr, CharStackBufferSize)); if (fmt != 0) { - NumberToString(ref sb, ref number, fmt, digits, info); + NumberToString(ref vlb, ref number, fmt, digits, info); } else { - NumberToStringFormat(ref sb, ref number, formatSpan, info); + NumberToStringFormat(ref vlb, ref number, formatSpan, info); } - return sb.ToString(); + string result = vlb.AsSpan().ToString(); + vlb.Dispose(); + return result; } } } - public static bool TryFormatUInt128(UInt128 value, ReadOnlySpan format, IFormatProvider? provider, Span destination, out int charsWritten) + public static bool TryFormatUInt128(UInt128 value, ReadOnlySpan format, IFormatProvider? provider, Span destination, out int charsWritten) where TChar : unmanaged, IBinaryInteger { + Debug.Assert(typeof(TChar) == typeof(char) || typeof(TChar) == typeof(byte)); + // Fast path for default format if (format.Length == 0) { @@ -1382,7 +1516,7 @@ public static bool TryFormatUInt128(UInt128 value, ReadOnlySpan format, IF return TryFormatUInt128Slow(value, format, provider, destination, out charsWritten); - static unsafe bool TryFormatUInt128Slow(UInt128 value, ReadOnlySpan format, IFormatProvider? provider, Span destination, out int charsWritten) + static unsafe bool TryFormatUInt128Slow(UInt128 value, ReadOnlySpan format, IFormatProvider? provider, Span destination, out int charsWritten) { char fmt = ParseFormatSpecifier(format, out int digits); char fmtUpper = (char)(fmt & 0xFFDF); // ensure fmt is upper-cased for purposes of comparison @@ -1404,19 +1538,21 @@ static unsafe bool TryFormatUInt128Slow(UInt128 value, ReadOnlySpan format UInt128ToNumber(value, ref number); - char* stackPtr = stackalloc char[CharStackBufferSize]; - ValueStringBuilder sb = new ValueStringBuilder(new Span(stackPtr, CharStackBufferSize)); + TChar* stackPtr = stackalloc TChar[CharStackBufferSize]; + var vlb = new ValueListBuilder(new Span(stackPtr, CharStackBufferSize)); if (fmt != 0) { - NumberToString(ref sb, ref number, fmt, digits, info); + NumberToString(ref vlb, ref number, fmt, digits, info); } else { - NumberToStringFormat(ref sb, ref number, format, info); + NumberToStringFormat(ref vlb, ref number, format, info); } - return sb.TryCopyTo(destination, out charsWritten); + bool success = vlb.TryCopyTo(destination, out charsWritten); + vlb.Dispose(); + return success; } } } @@ -1446,25 +1582,27 @@ private static unsafe void Int32ToNumber(int value, ref NumberBuffer number) byte* dst = number.GetDigitsPointer(); while (--i >= 0) + { *dst++ = *p++; - *dst = (byte)('\0'); + } + *dst = (byte)'\0'; number.CheckConsistency(); } - public static string Int32ToDecStr(int value) - { - return value >= 0 ? + public static string Int32ToDecStr(int value) => + value >= 0 ? UInt32ToDecStr((uint)value) : NegativeInt32ToDecStr(value, -1, NumberFormatInfo.CurrentInfo.NegativeSign); - } private static unsafe string NegativeInt32ToDecStr(int value, int digits, string sNegative) { Debug.Assert(value < 0); if (digits < 1) + { digits = 1; + } int bufferLength = Math.Max(digits, FormattingHelpers.CountDigits((uint)(-value))) + sNegative.Length; string result = string.FastAllocateString(bufferLength); @@ -1482,12 +1620,15 @@ private static unsafe string NegativeInt32ToDecStr(int value, int digits, string return result; } - private static unsafe bool TryNegativeInt32ToDecStr(int value, int digits, string sNegative, Span destination, out int charsWritten) + private static unsafe bool TryNegativeInt32ToDecStr(int value, int digits, ReadOnlySpan sNegative, Span destination, out int charsWritten) where TChar : unmanaged, IBinaryInteger { + Debug.Assert(typeof(TChar) == typeof(char) || typeof(TChar) == typeof(byte)); Debug.Assert(value < 0); if (digits < 1) + { digits = 1; + } int bufferLength = Math.Max(digits, FormattingHelpers.CountDigits((uint)(-value))) + sNegative.Length; if (bufferLength > destination.Length) @@ -1497,9 +1638,9 @@ private static unsafe bool TryNegativeInt32ToDecStr(int value, int digits, strin } charsWritten = bufferLength; - fixed (char* buffer = &MemoryMarshal.GetReference(destination)) + fixed (TChar* buffer = &MemoryMarshal.GetReference(destination)) { - char* p = UInt32ToDecChars(buffer + bufferLength, (uint)(-value), digits); + TChar* p = UInt32ToDecChars(buffer + bufferLength, (uint)(-value), digits); Debug.Assert(p == buffer + sNegative.Length); for (int i = sNegative.Length - 1; i >= 0; i--) @@ -1514,7 +1655,9 @@ private static unsafe bool TryNegativeInt32ToDecStr(int value, int digits, strin private static unsafe string Int32ToHexStr(int value, char hexBase, int digits) { if (digits < 1) + { digits = 1; + } int bufferLength = Math.Max(digits, FormattingHelpers.CountHexDigits((uint)value)); string result = string.FastAllocateString(bufferLength); @@ -1526,10 +1669,14 @@ private static unsafe string Int32ToHexStr(int value, char hexBase, int digits) return result; } - private static unsafe bool TryInt32ToHexStr(int value, char hexBase, int digits, Span destination, out int charsWritten) + private static unsafe bool TryInt32ToHexStr(int value, char hexBase, int digits, Span destination, out int charsWritten) where TChar : unmanaged, IBinaryInteger { + Debug.Assert(typeof(TChar) == typeof(char) || typeof(TChar) == typeof(byte)); + if (digits < 1) + { digits = 1; + } int bufferLength = Math.Max(digits, FormattingHelpers.CountHexDigits((uint)value)); if (bufferLength > destination.Length) @@ -1539,21 +1686,23 @@ private static unsafe bool TryInt32ToHexStr(int value, char hexBase, int digits, } charsWritten = bufferLength; - fixed (char* buffer = &MemoryMarshal.GetReference(destination)) + fixed (TChar* buffer = &MemoryMarshal.GetReference(destination)) { - char* p = Int32ToHexChars(buffer + bufferLength, (uint)value, hexBase, digits); + TChar* p = Int32ToHexChars(buffer + bufferLength, (uint)value, hexBase, digits); Debug.Assert(p == buffer); } return true; } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static unsafe char* Int32ToHexChars(char* buffer, uint value, int hexBase, int digits) + private static unsafe TChar* Int32ToHexChars(TChar* buffer, uint value, int hexBase, int digits) where TChar : unmanaged, IBinaryInteger { + Debug.Assert(typeof(TChar) == typeof(char) || typeof(TChar) == typeof(byte)); + while (--digits >= 0 || value != 0) { byte digit = (byte)(value & 0xF); - *(--buffer) = (char)(digit + (digit < 10 ? (byte)'0' : hexBase)); + *(--buffer) = TChar.CreateTruncating(digit + (digit < 10 ? (byte)'0' : hexBase)); value >>= 4; } return buffer; @@ -1575,12 +1724,31 @@ private static unsafe void UInt32ToNumber(uint value, ref NumberBuffer number) byte* dst = number.GetDigitsPointer(); while (--i >= 0) + { *dst++ = *p++; - *dst = (byte)('\0'); + } + *dst = (byte)'\0'; number.CheckConsistency(); } + /// + /// Writes a value [ 00 .. 99 ] to the buffer starting at the specified offset. + /// This method performs best when the starting index is a constant literal. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static unsafe void WriteTwoDigits(uint value, Span buffer, int startingIndex = 0) where TChar : unmanaged, IBinaryInteger + { + Debug.Assert(typeof(TChar) == typeof(char) || typeof(TChar) == typeof(byte)); + Debug.Assert(value <= 99); + Debug.Assert(startingIndex <= buffer.Length - 2); + + fixed (TChar* bufferPtr = &MemoryMarshal.GetReference(buffer)) + { + WriteTwoDigits(bufferPtr + startingIndex, value); + } + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] internal static unsafe void WriteTwoDigits(TChar* ptr, uint value) where TChar : unmanaged, IBinaryInteger { @@ -1592,68 +1760,72 @@ internal static unsafe void WriteTwoDigits(TChar* ptr, uint value) where Unsafe.WriteUnaligned(ptr, Unsafe.ReadUnaligned( ref Unsafe.As( - ref Unsafe.Add(ref TwoDigitsChars.GetRawStringData(), (int)value * 2)))); + ref Unsafe.Add(ref TwoDigitsChars.GetRawStringData(), (nuint)value * 2)))); } else { Unsafe.WriteUnaligned(ptr, Unsafe.ReadUnaligned( - ref Unsafe.Add(ref MemoryMarshal.GetReference(TwoDigitsBytes), (int)value * 2))); + ref Unsafe.Add(ref MemoryMarshal.GetReference(TwoDigitsBytes), (nuint)value * 2))); } } + /// + /// Writes a value [ 0000 .. 9999 ] to the buffer starting at the specified offset. + /// This method performs best when the starting index is a constant literal. + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static unsafe byte* UInt32ToDecChars(byte* bufferEnd, uint value) + internal static unsafe void WriteFourDigits(uint value, Span buffer, int startingIndex = 0) where TChar : unmanaged, IBinaryInteger { - if (value >= 10) - { - // Handle all values >= 100 two-digits at a time so as to avoid expensive integer division operations. - while (value >= 100) - { - bufferEnd -= 2; - (value, uint remainder) = Math.DivRem(value, 100); - WriteTwoDigits(bufferEnd, remainder); - } + Debug.Assert(typeof(TChar) == typeof(char) || typeof(TChar) == typeof(byte)); + Debug.Assert(value <= 9999); + Debug.Assert(startingIndex <= buffer.Length - 4); - // If there are two digits remaining, store them. - if (value >= 10) - { - bufferEnd -= 2; - WriteTwoDigits(bufferEnd, value); - return bufferEnd; - } + (value, uint remainder) = Math.DivRem(value, 100); + fixed (TChar* bufferPtr = &MemoryMarshal.GetReference(buffer)) + { + WriteTwoDigits(bufferPtr + startingIndex, value); + WriteTwoDigits(bufferPtr + startingIndex + 2, remainder); } - - // Otherwise, store the single digit remaining. - *(--bufferEnd) = (byte)(value + '0'); - return bufferEnd; } [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static unsafe byte* UInt32ToDecChars(byte* bufferEnd, uint value, int digits) + internal static void CopyFour(ReadOnlySpan source, Span destination) where TChar : unmanaged, IBinaryInteger { - uint remainder; - while (value >= 100) + if (typeof(TChar) == typeof(byte)) { - bufferEnd -= 2; - digits -= 2; - (value, remainder) = Math.DivRem(value, 100); - WriteTwoDigits(bufferEnd, remainder); + Unsafe.WriteUnaligned(ref Unsafe.As(ref MemoryMarshal.GetReference(destination)), + Unsafe.ReadUnaligned(ref Unsafe.As(ref MemoryMarshal.GetReference(source)))); + } + else + { + Debug.Assert(typeof(TChar) == typeof(char)); + Unsafe.WriteUnaligned(ref Unsafe.As(ref MemoryMarshal.GetReference(destination)), + Unsafe.ReadUnaligned(ref Unsafe.As(ref MemoryMarshal.GetReference(source)))); } + } - while (value != 0 || digits > 0) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static void WriteDigits(uint value, Span buffer) where TChar : unmanaged, IBinaryInteger + { + Debug.Assert(buffer.Length > 0); + + for (int i = buffer.Length - 1; i >= 1; i--) { - digits--; - (value, remainder) = Math.DivRem(value, 10); - *(--bufferEnd) = (byte)(remainder + '0'); + uint temp = '0' + value; + value /= 10; + buffer[i] = TChar.CreateTruncating(temp - (value * 10)); } - return bufferEnd; + Debug.Assert(value < 10); + buffer[0] = TChar.CreateTruncating('0' + value); } [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static unsafe char* UInt32ToDecChars(char* bufferEnd, uint value) + internal static unsafe TChar* UInt32ToDecChars(TChar* bufferEnd, uint value) where TChar : unmanaged, IBinaryInteger { + Debug.Assert(typeof(TChar) == typeof(char) || typeof(TChar) == typeof(byte)); + if (value >= 10) { // Handle all values >= 100 two-digits at a time so as to avoid expensive integer division operations. @@ -1674,14 +1846,15 @@ ref Unsafe.As( } // Otherwise, store the single digit remaining. - *(--bufferEnd) = (char)(value + '0'); + *(--bufferEnd) = TChar.CreateTruncating(value + '0'); return bufferEnd; } [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static unsafe char* UInt32ToDecChars(char* bufferEnd, uint value, int digits) + internal static unsafe TChar* UInt32ToDecChars(TChar* bufferEnd, uint value, int digits) where TChar : unmanaged, IBinaryInteger { - // Handle all values >= 100 two-digits at a time so as to avoid expensive integer division operations. + Debug.Assert(typeof(TChar) == typeof(char) || typeof(TChar) == typeof(byte)); + uint remainder; while (value >= 100) { @@ -1691,12 +1864,11 @@ ref Unsafe.As( WriteTwoDigits(bufferEnd, remainder); } - // Continue writing single digits until we've exhausted both the value and the requested number of digits. while (value != 0 || digits > 0) { digits--; (value, remainder) = Math.DivRem(value, 10); - *(--bufferEnd) = (char)(remainder + '0'); + *(--bufferEnd) = TChar.CreateTruncating(remainder + '0'); } return bufferEnd; @@ -1753,15 +1925,17 @@ private static unsafe string UInt32ToDecStr(uint value, int digits) return result; } - private static unsafe bool TryUInt32ToDecStr(uint value, Span destination, out int charsWritten) + private static unsafe bool TryUInt32ToDecStr(uint value, Span destination, out int charsWritten) where TChar : unmanaged, IBinaryInteger { + Debug.Assert(typeof(TChar) == typeof(char) || typeof(TChar) == typeof(byte)); + int bufferLength = FormattingHelpers.CountDigits(value); if (bufferLength <= destination.Length) { charsWritten = bufferLength; - fixed (char* buffer = &MemoryMarshal.GetReference(destination)) + fixed (TChar* buffer = &MemoryMarshal.GetReference(destination)) { - char* p = UInt32ToDecChars(buffer + bufferLength, value); + TChar* p = UInt32ToDecChars(buffer + bufferLength, value); Debug.Assert(p == buffer); } return true; @@ -1771,16 +1945,18 @@ private static unsafe bool TryUInt32ToDecStr(uint value, Span destination, return false; } - private static unsafe bool TryUInt32ToDecStr(uint value, int digits, Span destination, out int charsWritten) + private static unsafe bool TryUInt32ToDecStr(uint value, int digits, Span destination, out int charsWritten) where TChar : unmanaged, IBinaryInteger { + Debug.Assert(typeof(TChar) == typeof(char) || typeof(TChar) == typeof(byte)); + int countedDigits = FormattingHelpers.CountDigits(value); int bufferLength = Math.Max(digits, countedDigits); if (bufferLength <= destination.Length) { charsWritten = bufferLength; - fixed (char* buffer = &MemoryMarshal.GetReference(destination)) + fixed (TChar* buffer = &MemoryMarshal.GetReference(destination)) { - char* p = buffer + bufferLength; + TChar* p = buffer + bufferLength; p = digits > countedDigits ? UInt32ToDecChars(p, value, digits) : UInt32ToDecChars(p, value); @@ -1818,8 +1994,10 @@ private static unsafe void Int64ToNumber(long value, ref NumberBuffer number) byte* dst = number.GetDigitsPointer(); while (--i >= 0) + { *dst++ = *p++; - *dst = (byte)('\0'); + } + *dst = (byte)'\0'; number.CheckConsistency(); } @@ -1836,7 +2014,9 @@ private static unsafe string NegativeInt64ToDecStr(long value, int digits, strin Debug.Assert(value < 0); if (digits < 1) + { digits = 1; + } int bufferLength = Math.Max(digits, FormattingHelpers.CountDigits((ulong)(-value))) + sNegative.Length; string result = string.FastAllocateString(bufferLength); @@ -1854,12 +2034,15 @@ private static unsafe string NegativeInt64ToDecStr(long value, int digits, strin return result; } - private static unsafe bool TryNegativeInt64ToDecStr(long value, int digits, string sNegative, Span destination, out int charsWritten) + private static unsafe bool TryNegativeInt64ToDecStr(long value, int digits, ReadOnlySpan sNegative, Span destination, out int charsWritten) where TChar : unmanaged, IBinaryInteger { + Debug.Assert(typeof(TChar) == typeof(char) || typeof(TChar) == typeof(byte)); Debug.Assert(value < 0); if (digits < 1) + { digits = 1; + } int bufferLength = Math.Max(digits, FormattingHelpers.CountDigits((ulong)(-value))) + sNegative.Length; if (bufferLength > destination.Length) @@ -1869,9 +2052,9 @@ private static unsafe bool TryNegativeInt64ToDecStr(long value, int digits, stri } charsWritten = bufferLength; - fixed (char* buffer = &MemoryMarshal.GetReference(destination)) + fixed (TChar* buffer = &MemoryMarshal.GetReference(destination)) { - char* p = UInt64ToDecChars(buffer + bufferLength, (ulong)(-value), digits); + TChar* p = UInt64ToDecChars(buffer + bufferLength, (ulong)(-value), digits); Debug.Assert(p == buffer + sNegative.Length); for (int i = sNegative.Length - 1; i >= 0; i--) @@ -1886,7 +2069,9 @@ private static unsafe bool TryNegativeInt64ToDecStr(long value, int digits, stri private static unsafe string Int64ToHexStr(long value, char hexBase, int digits) { if (digits < 1) + { digits = 1; + } int bufferLength = Math.Max(digits, FormattingHelpers.CountHexDigits((ulong)value)); string result = string.FastAllocateString(bufferLength); @@ -1898,10 +2083,14 @@ private static unsafe string Int64ToHexStr(long value, char hexBase, int digits) return result; } - private static unsafe bool TryInt64ToHexStr(long value, char hexBase, int digits, Span destination, out int charsWritten) + private static unsafe bool TryInt64ToHexStr(long value, char hexBase, int digits, Span destination, out int charsWritten) where TChar : unmanaged, IBinaryInteger { + Debug.Assert(typeof(TChar) == typeof(char) || typeof(TChar) == typeof(byte)); + if (digits < 1) + { digits = 1; + } int bufferLength = Math.Max(digits, FormattingHelpers.CountHexDigits((ulong)value)); if (bufferLength > destination.Length) @@ -1911,9 +2100,9 @@ private static unsafe bool TryInt64ToHexStr(long value, char hexBase, int digits } charsWritten = bufferLength; - fixed (char* buffer = &MemoryMarshal.GetReference(destination)) + fixed (TChar* buffer = &MemoryMarshal.GetReference(destination)) { - char* p = Int64ToHexChars(buffer + bufferLength, (ulong)value, hexBase, digits); + TChar* p = Int64ToHexChars(buffer + bufferLength, (ulong)value, hexBase, digits); Debug.Assert(p == buffer); } return true; @@ -1922,8 +2111,9 @@ private static unsafe bool TryInt64ToHexStr(long value, char hexBase, int digits #if TARGET_64BIT [MethodImpl(MethodImplOptions.AggressiveInlining)] #endif - private static unsafe char* Int64ToHexChars(char* buffer, ulong value, int hexBase, int digits) + private static unsafe TChar* Int64ToHexChars(TChar* buffer, ulong value, int hexBase, int digits) where TChar : unmanaged, IBinaryInteger { + Debug.Assert(typeof(TChar) == typeof(char) || typeof(TChar) == typeof(byte)); #if TARGET_32BIT uint lower = (uint)value; uint upper = (uint)(value >> 32); @@ -1941,7 +2131,7 @@ private static unsafe bool TryInt64ToHexStr(long value, char hexBase, int digits while (--digits >= 0 || value != 0) { byte digit = (byte)(value & 0xF); - *(--buffer) = (char)(digit + (digit < 10 ? (byte)'0' : hexBase)); + *(--buffer) = TChar.CreateTruncating(digit + (digit < 10 ? (byte)'0' : hexBase)); value >>= 4; } return buffer; @@ -1964,8 +2154,10 @@ private static unsafe void UInt64ToNumber(ulong value, ref NumberBuffer number) byte* dst = number.GetDigitsPointer(); while (--i >= 0) + { *dst++ = *p++; - *dst = (byte)('\0'); + } + *dst = (byte)'\0'; number.CheckConsistency(); } @@ -1981,8 +2173,10 @@ private static uint Int64DivMod1E9(ref ulong value) #if TARGET_64BIT [MethodImpl(MethodImplOptions.AggressiveInlining)] #endif - internal static unsafe byte* UInt64ToDecChars(byte* bufferEnd, ulong value) + internal static unsafe TChar* UInt64ToDecChars(TChar* bufferEnd, ulong value) where TChar : unmanaged, IBinaryInteger { + Debug.Assert(typeof(TChar) == typeof(char) || typeof(TChar) == typeof(byte)); + #if TARGET_32BIT while ((uint)(value >> 32) != 0) { @@ -2010,7 +2204,7 @@ private static uint Int64DivMod1E9(ref ulong value) } // Otherwise, store the single digit remaining. - *(--bufferEnd) = (byte)(value + '0'); + *(--bufferEnd) = TChar.CreateTruncating(value + '0'); return bufferEnd; #endif } @@ -2018,8 +2212,10 @@ private static uint Int64DivMod1E9(ref ulong value) #if TARGET_64BIT [MethodImpl(MethodImplOptions.AggressiveInlining)] #endif - internal static unsafe byte* UInt64ToDecChars(byte* bufferEnd, ulong value, int digits) + internal static unsafe TChar* UInt64ToDecChars(TChar* bufferEnd, ulong value, int digits) where TChar : unmanaged, IBinaryInteger { + Debug.Assert(typeof(TChar) == typeof(char) || typeof(TChar) == typeof(byte)); + #if TARGET_32BIT while ((uint)(value >> 32) != 0) { @@ -2041,87 +2237,17 @@ private static uint Int64DivMod1E9(ref ulong value) { digits--; (value, remainder) = Math.DivRem(value, 10); - *(--bufferEnd) = (byte)(remainder + '0'); + *(--bufferEnd) = TChar.CreateTruncating(remainder + '0'); } return bufferEnd; #endif } -#if TARGET_64BIT - [MethodImpl(MethodImplOptions.AggressiveInlining)] -#endif - internal static unsafe char* UInt64ToDecChars(char* bufferEnd, ulong value) + internal static unsafe string UInt64ToDecStr(ulong value) { -#if TARGET_32BIT - while ((uint)(value >> 32) != 0) - { - bufferEnd = UInt32ToDecChars(bufferEnd, Int64DivMod1E9(ref value), 9); - } - return UInt32ToDecChars(bufferEnd, (uint)value); -#else - if (value >= 10) - { - // Handle all values >= 100 two-digits at a time so as to avoid expensive integer division operations. - while (value >= 100) - { - bufferEnd -= 2; - (value, ulong remainder) = Math.DivRem(value, 100); - WriteTwoDigits(bufferEnd, (uint)remainder); - } - - // If there are two digits remaining, store them. - if (value >= 10) - { - bufferEnd -= 2; - WriteTwoDigits(bufferEnd, (uint)value); - return bufferEnd; - } - } - - // Otherwise, store the single digit remaining. - *(--bufferEnd) = (char)(value + '0'); - return bufferEnd; -#endif - } - -#if TARGET_64BIT - [MethodImpl(MethodImplOptions.AggressiveInlining)] -#endif - internal static unsafe char* UInt64ToDecChars(char* bufferEnd, ulong value, int digits) - { -#if TARGET_32BIT - while ((uint)(value >> 32) != 0) - { - bufferEnd = UInt32ToDecChars(bufferEnd, Int64DivMod1E9(ref value), 9); - digits -= 9; - } - return UInt32ToDecChars(bufferEnd, (uint)value, digits); -#else - ulong remainder; - while (value >= 100) - { - bufferEnd -= 2; - digits -= 2; - (value, remainder) = Math.DivRem(value, 100); - WriteTwoDigits(bufferEnd, (uint)remainder); - } - - while (value != 0 || digits > 0) - { - digits--; - (value, remainder) = Math.DivRem(value, 10); - *(--bufferEnd) = (char)(remainder + '0'); - } - - return bufferEnd; -#endif - } - - internal static unsafe string UInt64ToDecStr(ulong value) - { - // For small numbers, consult a lazily-populated cache. - if (value < SmallNumberCacheLength) + // For small numbers, consult a lazily-populated cache. + if (value < SmallNumberCacheLength) { return UInt32ToDecStrForKnownSmallNumber((uint)value); } @@ -2141,7 +2267,9 @@ internal static unsafe string UInt64ToDecStr(ulong value) internal static unsafe string UInt64ToDecStr(ulong value, int digits) { if (digits <= 1) + { return UInt64ToDecStr(value); + } int bufferLength = Math.Max(digits, FormattingHelpers.CountDigits(value)); string result = string.FastAllocateString(bufferLength); @@ -2154,15 +2282,17 @@ internal static unsafe string UInt64ToDecStr(ulong value, int digits) return result; } - private static unsafe bool TryUInt64ToDecStr(ulong value, Span destination, out int charsWritten) + private static unsafe bool TryUInt64ToDecStr(ulong value, Span destination, out int charsWritten) where TChar : unmanaged, IBinaryInteger { + Debug.Assert(typeof(TChar) == typeof(char) || typeof(TChar) == typeof(byte)); + int bufferLength = FormattingHelpers.CountDigits(value); if (bufferLength <= destination.Length) { charsWritten = bufferLength; - fixed (char* buffer = &MemoryMarshal.GetReference(destination)) + fixed (TChar* buffer = &MemoryMarshal.GetReference(destination)) { - char* p = buffer + bufferLength; + TChar* p = buffer + bufferLength; p = UInt64ToDecChars(p, value); Debug.Assert(p == buffer); } @@ -2173,16 +2303,16 @@ private static unsafe bool TryUInt64ToDecStr(ulong value, Span destination return false; } - private static unsafe bool TryUInt64ToDecStr(ulong value, int digits, Span destination, out int charsWritten) + private static unsafe bool TryUInt64ToDecStr(ulong value, int digits, Span destination, out int charsWritten) where TChar : unmanaged, IBinaryInteger { int countedDigits = FormattingHelpers.CountDigits(value); int bufferLength = Math.Max(digits, countedDigits); if (bufferLength <= destination.Length) { charsWritten = bufferLength; - fixed (char* buffer = &MemoryMarshal.GetReference(destination)) + fixed (TChar* buffer = &MemoryMarshal.GetReference(destination)) { - char* p = buffer + bufferLength; + TChar* p = buffer + bufferLength; p = digits > countedDigits ? UInt64ToDecChars(p, value, digits) : UInt64ToDecChars(p, value); @@ -2219,8 +2349,10 @@ private static unsafe void Int128ToNumber(Int128 value, ref NumberBuffer number) byte* dst = number.GetDigitsPointer(); while (--i >= 0) + { *dst++ = *p++; - *dst = (byte)('\0'); + } + *dst = (byte)'\0'; number.CheckConsistency(); } @@ -2237,7 +2369,9 @@ private static unsafe string NegativeInt128ToDecStr(Int128 value, int digits, st Debug.Assert(Int128.IsNegative(value)); if (digits < 1) + { digits = 1; + } UInt128 absValue = (UInt128)(-value); @@ -2257,12 +2391,15 @@ private static unsafe string NegativeInt128ToDecStr(Int128 value, int digits, st return result; } - private static unsafe bool TryNegativeInt128ToDecStr(Int128 value, int digits, string sNegative, Span destination, out int charsWritten) + private static unsafe bool TryNegativeInt128ToDecStr(Int128 value, int digits, ReadOnlySpan sNegative, Span destination, out int charsWritten) where TChar : unmanaged, IBinaryInteger { + Debug.Assert(typeof(TChar) == typeof(char) || typeof(TChar) == typeof(byte)); Debug.Assert(Int128.IsNegative(value)); if (digits < 1) + { digits = 1; + } UInt128 absValue = (UInt128)(-value); @@ -2274,9 +2411,9 @@ private static unsafe bool TryNegativeInt128ToDecStr(Int128 value, int digits, s } charsWritten = bufferLength; - fixed (char* buffer = &MemoryMarshal.GetReference(destination)) + fixed (TChar* buffer = &MemoryMarshal.GetReference(destination)) { - char* p = UInt128ToDecChars(buffer + bufferLength, absValue, digits); + TChar* p = UInt128ToDecChars(buffer + bufferLength, absValue, digits); Debug.Assert(p == buffer + sNegative.Length); for (int i = sNegative.Length - 1; i >= 0; i--) @@ -2291,7 +2428,9 @@ private static unsafe bool TryNegativeInt128ToDecStr(Int128 value, int digits, s private static unsafe string Int128ToHexStr(Int128 value, char hexBase, int digits) { if (digits < 1) + { digits = 1; + } UInt128 uValue = (UInt128)value; @@ -2305,10 +2444,14 @@ private static unsafe string Int128ToHexStr(Int128 value, char hexBase, int digi return result; } - private static unsafe bool TryInt128ToHexStr(Int128 value, char hexBase, int digits, Span destination, out int charsWritten) + private static unsafe bool TryInt128ToHexStr(Int128 value, char hexBase, int digits, Span destination, out int charsWritten) where TChar : unmanaged, IBinaryInteger { + Debug.Assert(typeof(TChar) == typeof(char) || typeof(TChar) == typeof(byte)); + if (digits < 1) + { digits = 1; + } UInt128 uValue = (UInt128)value; @@ -2320,16 +2463,16 @@ private static unsafe bool TryInt128ToHexStr(Int128 value, char hexBase, int dig } charsWritten = bufferLength; - fixed (char* buffer = &MemoryMarshal.GetReference(destination)) + fixed (TChar* buffer = &MemoryMarshal.GetReference(destination)) { - char* p = Int128ToHexChars(buffer + bufferLength, uValue, hexBase, digits); + TChar* p = Int128ToHexChars(buffer + bufferLength, uValue, hexBase, digits); Debug.Assert(p == buffer); } return true; } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static unsafe char* Int128ToHexChars(char* buffer, UInt128 value, int hexBase, int digits) + private static unsafe TChar* Int128ToHexChars(TChar* buffer, UInt128 value, int hexBase, int digits) where TChar : unmanaged, IBinaryInteger { ulong lower = value.Lower; ulong upper = value.Upper; @@ -2360,8 +2503,10 @@ private static unsafe void UInt128ToNumber(UInt128 value, ref NumberBuffer numbe byte* dst = number.GetDigitsPointer(); while (--i >= 0) + { *dst++ = *p++; - *dst = (byte)('\0'); + } + *dst = (byte)'\0'; number.CheckConsistency(); } @@ -2375,19 +2520,10 @@ private static ulong Int128DivMod1E19(ref UInt128 value) } [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static unsafe byte* UInt128ToDecChars(byte* bufferEnd, UInt128 value, int digits) + internal static unsafe TChar* UInt128ToDecChars(TChar* bufferEnd, UInt128 value) where TChar : unmanaged, IBinaryInteger { - while (value.Upper != 0) - { - bufferEnd = UInt64ToDecChars(bufferEnd, Int128DivMod1E19(ref value), 19); - digits -= 19; - } - return UInt64ToDecChars(bufferEnd, value.Lower, digits); - } + Debug.Assert(typeof(TChar) == typeof(char) || typeof(TChar) == typeof(byte)); - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static unsafe char* UInt128ToDecChars(char* bufferEnd, UInt128 value) - { while (value.Upper != 0) { bufferEnd = UInt64ToDecChars(bufferEnd, Int128DivMod1E19(ref value), 19); @@ -2396,8 +2532,10 @@ private static ulong Int128DivMod1E19(ref UInt128 value) } [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static unsafe char* UInt128ToDecChars(char* bufferEnd, UInt128 value, int digits) + internal static unsafe TChar* UInt128ToDecChars(TChar* bufferEnd, UInt128 value, int digits) where TChar : unmanaged, IBinaryInteger { + Debug.Assert(typeof(TChar) == typeof(char) || typeof(TChar) == typeof(byte)); + while (value.Upper != 0) { bufferEnd = UInt64ToDecChars(bufferEnd, Int128DivMod1E19(ref value), 19); @@ -2428,7 +2566,9 @@ internal static unsafe string UInt128ToDecStr(UInt128 value) internal static unsafe string UInt128ToDecStr(UInt128 value, int digits) { if (digits <= 1) + { return UInt128ToDecStr(value); + } int bufferLength = Math.Max(digits, FormattingHelpers.CountDigits(value)); string result = string.FastAllocateString(bufferLength); @@ -2441,16 +2581,16 @@ internal static unsafe string UInt128ToDecStr(UInt128 value, int digits) return result; } - private static unsafe bool TryUInt128ToDecStr(UInt128 value, int digits, Span destination, out int charsWritten) + private static unsafe bool TryUInt128ToDecStr(UInt128 value, int digits, Span destination, out int charsWritten) where TChar : unmanaged, IBinaryInteger { int countedDigits = FormattingHelpers.CountDigits(value); int bufferLength = Math.Max(digits, countedDigits); if (bufferLength <= destination.Length) { charsWritten = bufferLength; - fixed (char* buffer = &MemoryMarshal.GetReference(destination)) + fixed (TChar* buffer = &MemoryMarshal.GetReference(destination)) { - char* p = buffer + bufferLength; + TChar* p = buffer + bufferLength; p = digits > countedDigits ? UInt128ToDecChars(p, value, digits) : UInt128ToDecChars(p, value); @@ -2511,9 +2651,9 @@ internal static unsafe char ParseFormatSpecifier(ReadOnlySpan format, out // Check if we are about to overflow past our limit of 9 digits if (n >= 100_000_000) { - throw new FormatException(SR.Argument_BadFormatSpecifier); + ThrowHelper.ThrowFormatException_BadFormatSpecifier(); } - n = ((n * 10) + format[i++] - '0'); + n = (n * 10) + format[i++] - '0'; } // If we're at the end of the digits rather than having stopped because we hit something @@ -2533,8 +2673,10 @@ internal static unsafe char ParseFormatSpecifier(ReadOnlySpan format, out '\0'; } - internal static unsafe void NumberToString(ref ValueStringBuilder sb, ref NumberBuffer number, char format, int nMaxDigits, NumberFormatInfo info) + internal static unsafe void NumberToString(ref ValueListBuilder vlb, ref NumberBuffer number, char format, int nMaxDigits, NumberFormatInfo info) where TChar : unmanaged, IBinaryInteger { + Debug.Assert(typeof(TChar) == typeof(char) || typeof(TChar) == typeof(byte)); + number.CheckConsistency(); bool isCorrectlyRounded = (number.Kind == NumberBufferKind.FloatingPoint); @@ -2542,129 +2684,148 @@ internal static unsafe void NumberToString(ref ValueStringBuilder sb, ref Number { case 'C': case 'c': - { - if (nMaxDigits < 0) - nMaxDigits = info.CurrencyDecimalDigits; + { + if (nMaxDigits < 0) + { + nMaxDigits = info.CurrencyDecimalDigits; + } - RoundNumber(ref number, number.Scale + nMaxDigits, isCorrectlyRounded); // Don't change this line to use digPos since digCount could have its sign changed. + RoundNumber(ref number, number.Scale + nMaxDigits, isCorrectlyRounded); // Don't change this line to use digPos since digCount could have its sign changed. - FormatCurrency(ref sb, ref number, nMaxDigits, info); + FormatCurrency(ref vlb, ref number, nMaxDigits, info); - break; - } + break; + } case 'F': case 'f': - { - if (nMaxDigits < 0) - nMaxDigits = info.NumberDecimalDigits; + { + if (nMaxDigits < 0) + { + nMaxDigits = info.NumberDecimalDigits; + } - RoundNumber(ref number, number.Scale + nMaxDigits, isCorrectlyRounded); + RoundNumber(ref number, number.Scale + nMaxDigits, isCorrectlyRounded); - if (number.IsNegative) - sb.Append(info.NegativeSign); + if (number.IsNegative) + { + vlb.Append(info.NegativeSignTChar()); + } - FormatFixed(ref sb, ref number, nMaxDigits, null, info.NumberDecimalSeparator, null); + FormatFixed(ref vlb, ref number, nMaxDigits, null, info.NumberDecimalSeparatorTChar(), null); - break; - } + break; + } case 'N': case 'n': - { - if (nMaxDigits < 0) - nMaxDigits = info.NumberDecimalDigits; // Since we are using digits in our calculation + { + if (nMaxDigits < 0) + { + nMaxDigits = info.NumberDecimalDigits; // Since we are using digits in our calculation + } - RoundNumber(ref number, number.Scale + nMaxDigits, isCorrectlyRounded); + RoundNumber(ref number, number.Scale + nMaxDigits, isCorrectlyRounded); - FormatNumber(ref sb, ref number, nMaxDigits, info); + FormatNumber(ref vlb, ref number, nMaxDigits, info); - break; - } + break; + } case 'E': case 'e': - { - if (nMaxDigits < 0) - nMaxDigits = DefaultPrecisionExponentialFormat; - nMaxDigits++; + { + if (nMaxDigits < 0) + { + nMaxDigits = DefaultPrecisionExponentialFormat; + } + nMaxDigits++; - RoundNumber(ref number, nMaxDigits, isCorrectlyRounded); + RoundNumber(ref number, nMaxDigits, isCorrectlyRounded); - if (number.IsNegative) - sb.Append(info.NegativeSign); + if (number.IsNegative) + { + vlb.Append(info.NegativeSignTChar()); + } - FormatScientific(ref sb, ref number, nMaxDigits, info, format); + FormatScientific(ref vlb, ref number, nMaxDigits, info, format); - break; - } + break; + } case 'G': case 'g': - { - bool noRounding = false; - if (nMaxDigits < 1) { - if ((number.Kind == NumberBufferKind.Decimal) && (nMaxDigits == -1)) + bool noRounding = false; + if (nMaxDigits < 1) { - noRounding = true; // Turn off rounding for ECMA compliance to output trailing 0's after decimal as significant + if ((number.Kind == NumberBufferKind.Decimal) && (nMaxDigits == -1)) + { + noRounding = true; // Turn off rounding for ECMA compliance to output trailing 0's after decimal as significant - if (number.Digits[0] == 0) + if (number.Digits[0] == 0) + { + // -0 should be formatted as 0 for decimal. This is normally handled by RoundNumber (which we are skipping) + goto SkipSign; + } + + goto SkipRounding; + } + else { - // -0 should be formatted as 0 for decimal. This is normally handled by RoundNumber (which we are skipping) - goto SkipSign; + // This ensures that the PAL code pads out to the correct place even when we use the default precision + nMaxDigits = number.DigitsCount; } - - goto SkipRounding; - } - else - { - // 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); + RoundNumber(ref number, nMaxDigits, isCorrectlyRounded); - SkipRounding: - if (number.IsNegative) - sb.Append(info.NegativeSign); + SkipRounding: + if (number.IsNegative) + { + vlb.Append(info.NegativeSignTChar()); + } - SkipSign: - FormatGeneral(ref sb, ref number, nMaxDigits, info, (char)(format - ('G' - 'E')), noRounding); + SkipSign: + FormatGeneral(ref vlb, ref number, nMaxDigits, info, (char)(format - ('G' - 'E')), noRounding); - break; - } + break; + } case 'P': case 'p': - { - if (nMaxDigits < 0) - nMaxDigits = info.PercentDecimalDigits; - number.Scale += 2; + { + if (nMaxDigits < 0) + { + nMaxDigits = info.PercentDecimalDigits; + } + number.Scale += 2; - RoundNumber(ref number, number.Scale + nMaxDigits, isCorrectlyRounded); + RoundNumber(ref number, number.Scale + nMaxDigits, isCorrectlyRounded); - FormatPercent(ref sb, ref number, nMaxDigits, info); + FormatPercent(ref vlb, ref number, nMaxDigits, info); - break; - } + break; + } case 'R': case 'r': - { - format = (char)(format - ('R' - 'G')); - Debug.Assert((format == 'G') || (format == 'g')); - goto case 'G'; - } + { + format = (char)(format - ('R' - 'G')); + Debug.Assert(format is 'G' or 'g'); + goto case 'G'; + } default: - throw new FormatException(SR.Argument_BadFormatSpecifier); + ThrowHelper.ThrowFormatException_BadFormatSpecifier(); + break; } } - internal static unsafe void NumberToStringFormat(ref ValueStringBuilder sb, ref NumberBuffer number, ReadOnlySpan format, NumberFormatInfo info) + internal static unsafe void NumberToStringFormat(ref ValueListBuilder vlb, ref NumberBuffer number, ReadOnlySpan format, NumberFormatInfo info) where TChar : unmanaged, IBinaryInteger { + Debug.Assert(typeof(TChar) == typeof(char) || typeof(TChar) == typeof(byte)); + number.CheckConsistency(); int digitCount; @@ -2707,16 +2868,23 @@ internal static unsafe void NumberToStringFormat(ref ValueStringBuilder sb, ref case '#': digitCount++; break; + case '0': if (firstDigit == 0x7FFFFFFF) + { firstDigit = digitCount; + } digitCount++; lastDigit = digitCount; break; + case '.': if (decimalPos < 0) + { decimalPos = digitCount; + } break; + case ',': if (digitCount > 0 && decimalPos < 0) { @@ -2733,28 +2901,33 @@ internal static unsafe void NumberToStringFormat(ref ValueStringBuilder sb, ref thousandCount = 1; } break; + case '%': scaleAdjust += 2; break; + case '\x2030': scaleAdjust += 3; break; + case '\'': case '"': - while (src < format.Length && pFormat[src] != 0 && pFormat[src++] != ch) - ; + while (src < format.Length && pFormat[src] != 0 && pFormat[src++] != ch); break; + case '\\': if (src < format.Length && pFormat[src] != 0) + { src++; + } break; + case 'E': case 'e': if ((src < format.Length && pFormat[src] == '0') || (src + 1 < format.Length && (pFormat[src] == '+' || pFormat[src] == '-') && pFormat[src + 1] == '0')) { - while (++src < format.Length && pFormat[src] == '0') - ; + while (++src < format.Length && pFormat[src] == '0'); scientific = true; } break; @@ -2763,14 +2936,20 @@ internal static unsafe void NumberToStringFormat(ref ValueStringBuilder sb, ref } if (decimalPos < 0) + { decimalPos = digitCount; + } if (thousandPos >= 0) { if (thousandPos == decimalPos) + { scaleAdjust -= thousandCount * 3; + } else + { thousandSeps = true; + } } if (dig[0] != 0) @@ -2839,7 +3018,9 @@ internal static unsafe void NumberToStringFormat(ref ValueStringBuilder sb, ref int groupTotalSizeCount = 0; int groupSizeLen = groupDigits.Length; // The length of groupDigits array. if (groupSizeLen != 0) + { groupTotalSizeCount = groupDigits[groupSizeIndex]; // The current running total of group size. + } int groupSize = groupTotalSizeCount; int totalDigits = digPos + ((adjust < 0) ? adjust : 0); // Actual number of digits in o/p @@ -2847,7 +3028,10 @@ internal static unsafe void NumberToStringFormat(ref ValueStringBuilder sb, ref while (numDigits > groupTotalSizeCount) { if (groupSize == 0) + { break; + } + ++thousandsSepCtr; if (thousandsSepCtr >= thousandsSepPos.Length) { @@ -2868,7 +3052,9 @@ internal static unsafe void NumberToStringFormat(ref ValueStringBuilder sb, ref } if (number.IsNegative && (section == 0) && (number.Scale != 0)) - sb.Append(info.NegativeSign); + { + vlb.Append(info.NegativeSignTChar()); + } bool decimalWritten = false; @@ -2889,12 +3075,12 @@ internal static unsafe void NumberToStringFormat(ref ValueStringBuilder sb, ref { // digPos will be one greater than thousandsSepPos[thousandsSepCtr] since we are at // the character after which the groupSeparator needs to be appended. - sb.Append(*cur != 0 ? (char)(*cur++) : '0'); + vlb.Append(TChar.CreateTruncating(*cur != 0 ? (char)(*cur++) : '0')); if (thousandSeps && digPos > 1 && thousandsSepCtr >= 0) { if (digPos == thousandsSepPos[thousandsSepCtr] + 1) { - sb.Append(info.NumberGroupSeparator); + vlb.Append(info.NumberGroupSeparatorTChar()); thousandsSepCtr--; } } @@ -2909,129 +3095,160 @@ internal static unsafe void NumberToStringFormat(ref ValueStringBuilder sb, ref { case '#': case '0': - { - if (adjust < 0) { - adjust++; - ch = digPos <= firstDigit ? '0' : '\0'; - } - else - { - ch = *cur != 0 ? (char)(*cur++) : digPos > lastDigit ? '0' : '\0'; - } - if (ch != 0) - { - sb.Append(ch); - if (thousandSeps && digPos > 1 && thousandsSepCtr >= 0) + if (adjust < 0) + { + adjust++; + ch = digPos <= firstDigit ? '0' : '\0'; + } + else { - if (digPos == thousandsSepPos[thousandsSepCtr] + 1) + ch = *cur != 0 ? (char)(*cur++) : digPos > lastDigit ? '0' : '\0'; + } + + if (ch != 0) + { + vlb.Append(TChar.CreateTruncating(ch)); + if (thousandSeps && digPos > 1 && thousandsSepCtr >= 0) { - sb.Append(info.NumberGroupSeparator); - thousandsSepCtr--; + if (digPos == thousandsSepPos[thousandsSepCtr] + 1) + { + vlb.Append(info.NumberGroupSeparatorTChar()); + thousandsSepCtr--; + } } } + + digPos--; + break; } - digPos--; - break; - } case '.': - { - if (digPos != 0 || decimalWritten) { - // For compatibility, don't echo repeated decimals + if (digPos != 0 || decimalWritten) + { + // For compatibility, don't echo repeated decimals + break; + } + + // If the format has trailing zeros or the format has a decimal and digits remain + if (lastDigit < 0 || (decimalPos < digitCount && *cur != 0)) + { + vlb.Append(info.NumberDecimalSeparatorTChar()); + decimalWritten = true; + } break; } - // If the format has trailing zeros or the format has a decimal and digits remain - if (lastDigit < 0 || (decimalPos < digitCount && *cur != 0)) - { - sb.Append(info.NumberDecimalSeparator); - decimalWritten = true; - } - break; - } + case '\x2030': - sb.Append(info.PerMilleSymbol); + vlb.Append(info.PerMilleSymbolTChar()); break; + case '%': - sb.Append(info.PercentSymbol); + vlb.Append(info.PercentSymbolTChar()); break; + case ',': break; + case '\'': case '"': while (src < format.Length && pFormat[src] != 0 && pFormat[src] != ch) - sb.Append(pFormat[src++]); + { + AppendUnknownChar(ref vlb, pFormat[src++]); + } + if (src < format.Length && pFormat[src] != 0) + { src++; + } break; + case '\\': if (src < format.Length && pFormat[src] != 0) - sb.Append(pFormat[src++]); + { + AppendUnknownChar(ref vlb, pFormat[src++]); + } break; + case 'E': case 'e': - { - bool positiveSign = false; - int i = 0; - if (scientific) { - if (src < format.Length && pFormat[src] == '0') - { - // Handles E0, which should format the same as E-0 - i++; - } - else if (src + 1 < format.Length && pFormat[src] == '+' && pFormat[src + 1] == '0') - { - // Handles E+0 - positiveSign = true; - } - else if (src + 1 < format.Length && pFormat[src] == '-' && pFormat[src + 1] == '0') + bool positiveSign = false; + int i = 0; + if (scientific) { - // Handles E-0 - // Do nothing, this is just a place holder s.t. we don't break out of the loop. + if (src < format.Length && pFormat[src] == '0') + { + // Handles E0, which should format the same as E-0 + i++; + } + else if (src + 1 < format.Length && pFormat[src] == '+' && pFormat[src + 1] == '0') + { + // Handles E+0 + positiveSign = true; + } + else if (src + 1 < format.Length && pFormat[src] == '-' && pFormat[src + 1] == '0') + { + // Handles E-0 + // Do nothing, this is just a place holder s.t. we don't break out of the loop. + } + else + { + vlb.Append(TChar.CreateTruncating(ch)); + break; + } + + while (++src < format.Length && pFormat[src] == '0') + { + i++; + } + + if (i > 10) + { + i = 10; + } + + int exp = dig[0] == 0 ? 0 : number.Scale - decimalPos; + FormatExponent(ref vlb, info, exp, ch, i, positiveSign); + scientific = false; } else { - sb.Append(ch); - break; - } - - while (++src < format.Length && pFormat[src] == '0') - i++; - if (i > 10) - i = 10; + vlb.Append(TChar.CreateTruncating(ch)); + if (src < format.Length) + { + if (pFormat[src] == '+' || pFormat[src] == '-') + { + AppendUnknownChar(ref vlb, pFormat[src++]); + } - int exp = dig[0] == 0 ? 0 : number.Scale - decimalPos; - FormatExponent(ref sb, info, exp, ch, i, positiveSign); - scientific = false; - } - else - { - sb.Append(ch); // Copy E or e to output - if (src < format.Length) - { - if (pFormat[src] == '+' || pFormat[src] == '-') - sb.Append(pFormat[src++]); - while (src < format.Length && pFormat[src] == '0') - sb.Append(pFormat[src++]); + while (src < format.Length && pFormat[src] == '0') + { + AppendUnknownChar(ref vlb, pFormat[src++]); + } + } } + break; } - break; - } + default: - sb.Append(ch); + AppendUnknownChar(ref vlb, ch); break; } } } - if (number.IsNegative && (section == 0) && (number.Scale == 0) && (sb.Length > 0)) - sb.Insert(0, info.NegativeSign); + if (number.IsNegative && (section == 0) && (number.Scale == 0) && (vlb.Length > 0)) + { + vlb.Insert(0, info.NegativeSignTChar()); + } } - private static void FormatCurrency(ref ValueStringBuilder sb, ref NumberBuffer number, int nMaxDigits, NumberFormatInfo info) + private static void FormatCurrency(ref ValueListBuilder vlb, ref NumberBuffer number, int nMaxDigits, NumberFormatInfo info) where TChar : unmanaged, IBinaryInteger { + Debug.Assert(typeof(TChar) == typeof(char) || typeof(TChar) == typeof(byte)); + string fmt = number.IsNegative ? s_negCurrencyFormats[info.CurrencyNegativePattern] : s_posCurrencyFormats[info.CurrencyPositivePattern]; @@ -3041,23 +3258,31 @@ private static void FormatCurrency(ref ValueStringBuilder sb, ref NumberBuffer n switch (ch) { case '#': - FormatFixed(ref sb, ref number, nMaxDigits, info._currencyGroupSizes, info.CurrencyDecimalSeparator, info.CurrencyGroupSeparator); + FormatFixed(ref vlb, ref number, nMaxDigits, info._currencyGroupSizes, info.CurrencyDecimalSeparatorTChar(), info.CurrencyGroupSeparatorTChar()); break; + case '-': - sb.Append(info.NegativeSign); + vlb.Append(info.NegativeSignTChar()); break; + case '$': - sb.Append(info.CurrencySymbol); + vlb.Append(info.CurrencySymbolTChar()); break; + default: - sb.Append(ch); + vlb.Append(TChar.CreateTruncating(ch)); break; } } } - private static unsafe void FormatFixed(ref ValueStringBuilder sb, ref NumberBuffer number, int nMaxDigits, int[]? groupDigits, string? sDecimal, string? sGroup) + private static unsafe void FormatFixed( + ref ValueListBuilder vlb, ref NumberBuffer number, + int nMaxDigits, int[]? groupDigits, + ReadOnlySpan sDecimal, ReadOnlySpan sGroup) where TChar : unmanaged, IBinaryInteger { + Debug.Assert(typeof(TChar) == typeof(char) || typeof(TChar) == typeof(byte)); + int digPos = number.Scale; byte* dig = number.GetDigitsPointer(); @@ -3079,15 +3304,21 @@ private static unsafe void FormatFixed(ref ValueStringBuilder sb, ref NumberBuff { groupSize = groupDigits[groupSizeIndex]; if (groupSize == 0) + { break; + } bufferSize += sGroup.Length; if (groupSizeIndex < groupDigits.Length - 1) + { groupSizeIndex++; + } groupSizeCount += groupDigits[groupSizeIndex]; - if (groupSizeCount < 0 || bufferSize < 0) - throw new ArgumentOutOfRangeException(); // If we overflow + if ((groupSizeCount | bufferSize) < 0) + { + ThrowHelper.ThrowArgumentOutOfRangeException(); // If we overflow + } } groupSize = groupSizeCount == 0 ? 0 : groupDigits[0]; // If you passed in an array with one entry as 0, groupSizeCount == 0 @@ -3097,12 +3328,12 @@ private static unsafe void FormatFixed(ref ValueStringBuilder sb, ref NumberBuff int digitCount = 0; int digLength = number.DigitsCount; int digStart = (digPos < digLength) ? digPos : digLength; - fixed (char* spanPtr = &MemoryMarshal.GetReference(sb.AppendSpan(bufferSize))) + fixed (TChar* spanPtr = &MemoryMarshal.GetReference(vlb.AppendSpan(bufferSize))) { - char* p = spanPtr + bufferSize - 1; + TChar* p = spanPtr + bufferSize - 1; for (int i = digPos - 1; i >= 0; i--) { - *(p--) = (i < digStart) ? (char)(dig[i]) : '0'; + *(p--) = TChar.CreateTruncating((i < digStart) ? (char)dig[i] : '0'); if (groupSize > 0) { @@ -3110,7 +3341,9 @@ private static unsafe void FormatFixed(ref ValueStringBuilder sb, ref NumberBuff if ((digitCount == groupSize) && (i != 0)) { for (int j = sGroup.Length - 1; j >= 0; j--) + { *(p--) = sGroup[j]; + } if (groupSizeIndex < groupDigits.Length - 1) { @@ -3130,38 +3363,67 @@ private static unsafe void FormatFixed(ref ValueStringBuilder sb, ref NumberBuff { do { - sb.Append(*dig != 0 ? (char)(*dig++) : '0'); + vlb.Append(TChar.CreateTruncating(*dig != 0 ? (char)(*dig++) : '0')); } while (--digPos > 0); } } else { - sb.Append('0'); + vlb.Append(TChar.CreateTruncating('0')); } if (nMaxDigits > 0) { Debug.Assert(sDecimal != null); - sb.Append(sDecimal); + vlb.Append(sDecimal); if ((digPos < 0) && (nMaxDigits > 0)) { int zeroes = Math.Min(-digPos, nMaxDigits); - sb.Append('0', zeroes); + for (int i = 0; i < zeroes; i++) + { + vlb.Append(TChar.CreateTruncating('0')); + } digPos += zeroes; nMaxDigits -= zeroes; } while (nMaxDigits > 0) { - sb.Append((*dig != 0) ? (char)(*dig++) : '0'); + vlb.Append(TChar.CreateTruncating((*dig != 0) ? (char)(*dig++) : '0')); nMaxDigits--; } } } - private static void FormatNumber(ref ValueStringBuilder sb, ref NumberBuffer number, int nMaxDigits, NumberFormatInfo info) + /// Appends a char to the builder when the char is not known to be ASCII. + /// This requires a helper as if the character isn't ASCII, for UTF8 encoding it will result in multiple bytes added. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void AppendUnknownChar(ref ValueListBuilder vlb, char ch) where TChar : unmanaged, IBinaryInteger + { + Debug.Assert(typeof(TChar) == typeof(char) || typeof(TChar) == typeof(byte)); + + if (typeof(TChar) == typeof(char) || char.IsAscii(ch)) + { + vlb.Append(TChar.CreateTruncating(ch)); + } + else + { + AppendNonAsciiBytes(ref vlb, ch); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + static void AppendNonAsciiBytes(ref ValueListBuilder vlb, char ch) + { + var r = new Rune(ch); + r.EncodeToUtf8(MemoryMarshal.AsBytes(vlb.AppendSpan(r.Utf8SequenceLength))); + } + } + + private static void FormatNumber(ref ValueListBuilder vlb, ref NumberBuffer number, int nMaxDigits, NumberFormatInfo info) where TChar : unmanaged, IBinaryInteger { + Debug.Assert(typeof(TChar) == typeof(char) || typeof(TChar) == typeof(byte)); + string fmt = number.IsNegative ? s_negNumberFormats[info.NumberNegativePattern] : PosNumberFormat; @@ -3171,60 +3433,74 @@ private static void FormatNumber(ref ValueStringBuilder sb, ref NumberBuffer num switch (ch) { case '#': - FormatFixed(ref sb, ref number, nMaxDigits, info._numberGroupSizes, info.NumberDecimalSeparator, info.NumberGroupSeparator); + FormatFixed(ref vlb, ref number, nMaxDigits, info._numberGroupSizes, info.NumberDecimalSeparatorTChar(), info.NumberGroupSeparatorTChar()); break; + case '-': - sb.Append(info.NegativeSign); + vlb.Append(info.NegativeSignTChar()); break; + default: - sb.Append(ch); + vlb.Append(TChar.CreateTruncating(ch)); break; } } } - private static unsafe void FormatScientific(ref ValueStringBuilder sb, ref NumberBuffer number, int nMaxDigits, NumberFormatInfo info, char expChar) + private static unsafe void FormatScientific(ref ValueListBuilder vlb, ref NumberBuffer number, int nMaxDigits, NumberFormatInfo info, char expChar) where TChar : unmanaged, IBinaryInteger { + Debug.Assert(typeof(TChar) == typeof(char) || typeof(TChar) == typeof(byte)); + byte* dig = number.GetDigitsPointer(); - sb.Append((*dig != 0) ? (char)(*dig++) : '0'); + vlb.Append(TChar.CreateTruncating((*dig != 0) ? (char)(*dig++) : '0')); if (nMaxDigits != 1) // For E0 we would like to suppress the decimal point - sb.Append(info.NumberDecimalSeparator); + { + vlb.Append(info.NumberDecimalSeparatorTChar()); + } while (--nMaxDigits > 0) - sb.Append((*dig != 0) ? (char)(*dig++) : '0'); + { + vlb.Append(TChar.CreateTruncating((*dig != 0) ? (char)(*dig++) : '0')); + } int e = number.Digits[0] == 0 ? 0 : number.Scale - 1; - FormatExponent(ref sb, info, e, expChar, 3, true); + FormatExponent(ref vlb, info, e, expChar, 3, true); } - private static unsafe void FormatExponent(ref ValueStringBuilder sb, NumberFormatInfo info, int value, char expChar, int minDigits, bool positiveSign) + private static unsafe void FormatExponent(ref ValueListBuilder vlb, NumberFormatInfo info, int value, char expChar, int minDigits, bool positiveSign) where TChar : unmanaged, IBinaryInteger { - sb.Append(expChar); + Debug.Assert(typeof(TChar) == typeof(char) || typeof(TChar) == typeof(byte)); + + vlb.Append(TChar.CreateTruncating(expChar)); if (value < 0) { - sb.Append(info.NegativeSign); + vlb.Append(info.NegativeSignTChar()); value = -value; } else { if (positiveSign) - sb.Append(info.PositiveSign); + { + vlb.Append(info.PositiveSignTChar()); + } } - char* digits = stackalloc char[MaxUInt32DecDigits]; - char* p = UInt32ToDecChars(digits + MaxUInt32DecDigits, (uint)value, minDigits); - sb.Append(p, (int)(digits + MaxUInt32DecDigits - p)); + TChar* digits = stackalloc TChar[MaxUInt32DecDigits]; + TChar* p = UInt32ToDecChars(digits + MaxUInt32DecDigits, (uint)value, minDigits); + vlb.Append(new ReadOnlySpan(p, (int)(digits + MaxUInt32DecDigits - p))); } - private static unsafe void FormatGeneral(ref ValueStringBuilder sb, ref NumberBuffer number, int nMaxDigits, NumberFormatInfo info, char expChar, bool bSuppressScientific) + private static unsafe void FormatGeneral(ref ValueListBuilder vlb, ref NumberBuffer number, int nMaxDigits, NumberFormatInfo info, char expChar, bool suppressScientific) where TChar : unmanaged, IBinaryInteger { + Debug.Assert(typeof(TChar) == typeof(char) || typeof(TChar) == typeof(byte)); + int digPos = number.Scale; bool scientific = false; - if (!bSuppressScientific) + if (!suppressScientific) { // Don't switch to scientific notation if (digPos > nMaxDigits || digPos < -3) @@ -3240,34 +3516,41 @@ private static unsafe void FormatGeneral(ref ValueStringBuilder sb, ref NumberBu { do { - sb.Append((*dig != 0) ? (char)(*dig++) : '0'); - } while (--digPos > 0); + vlb.Append(TChar.CreateTruncating((*dig != 0) ? (char)(*dig++) : '0')); + } + while (--digPos > 0); } else { - sb.Append('0'); + vlb.Append(TChar.CreateTruncating('0')); } if (*dig != 0 || digPos < 0) { - sb.Append(info.NumberDecimalSeparator); + vlb.Append(info.NumberDecimalSeparatorTChar()); while (digPos < 0) { - sb.Append('0'); + vlb.Append(TChar.CreateTruncating('0')); digPos++; } while (*dig != 0) - sb.Append((char)(*dig++)); + { + vlb.Append(TChar.CreateTruncating(*dig++)); + } } if (scientific) - FormatExponent(ref sb, info, number.Scale - 1, expChar, 2, true); + { + FormatExponent(ref vlb, info, number.Scale - 1, expChar, 2, true); + } } - private static void FormatPercent(ref ValueStringBuilder sb, ref NumberBuffer number, int nMaxDigits, NumberFormatInfo info) + private static void FormatPercent(ref ValueListBuilder vlb, ref NumberBuffer number, int nMaxDigits, NumberFormatInfo info) where TChar : unmanaged, IBinaryInteger { + Debug.Assert(typeof(TChar) == typeof(char) || typeof(TChar) == typeof(byte)); + string fmt = number.IsNegative ? s_negPercentFormats[info.PercentNegativePattern] : s_posPercentFormats[info.PercentPositivePattern]; @@ -3277,16 +3560,19 @@ private static void FormatPercent(ref ValueStringBuilder sb, ref NumberBuffer nu switch (ch) { case '#': - FormatFixed(ref sb, ref number, nMaxDigits, info._percentGroupSizes, info.PercentDecimalSeparator, info.PercentGroupSeparator); + FormatFixed(ref vlb, ref number, nMaxDigits, info._percentGroupSizes, info.PercentDecimalSeparatorTChar(), info.PercentGroupSeparatorTChar()); break; + case '-': - sb.Append(info.NegativeSign); + vlb.Append(info.NegativeSignTChar()); break; + case '%': - sb.Append(info.PercentSymbol); + vlb.Append(info.PercentSymbolTChar()); break; + default: - sb.Append(ch); + vlb.Append(TChar.CreateTruncating(ch)); break; } } @@ -3298,12 +3584,16 @@ internal static unsafe void RoundNumber(ref NumberBuffer number, int pos, bool i int i = 0; while (i < pos && dig[i] != '\0') + { i++; + } if ((i == pos) && ShouldRoundUp(dig, i, number.Kind, isCorrectlyRounded)) { while (i > 0 && dig[i - 1] == '9') + { i--; + } if (i > 0) { @@ -3319,7 +3609,9 @@ internal static unsafe void RoundNumber(ref NumberBuffer number, int pos, bool i else { while (i > 0 && dig[i - 1] == '0') + { i--; + } } if (i == 0) @@ -3375,7 +3667,9 @@ private static unsafe int FindSection(ReadOnlySpan format, int section) char ch; if (section == 0) + { return 0; + } fixed (char* pFormat = &MemoryMarshal.GetReference(format)) { @@ -3393,16 +3687,26 @@ private static unsafe int FindSection(ReadOnlySpan format, int section) case '"': while (src < format.Length && pFormat[src] != 0 && pFormat[src++] != ch) ; break; + case '\\': if (src < format.Length && pFormat[src] != 0) + { src++; + } break; + case ';': if (--section != 0) + { break; + } + if (src < format.Length && pFormat[src] != 0 && pFormat[src] != ';') + { return src; + } goto case '\0'; + case '\0': return 0; } diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/NFloat.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/NFloat.cs index fa60aa987cbbf..76d9473d7a490 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/NFloat.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/NFloat.cs @@ -27,7 +27,8 @@ namespace System.Runtime.InteropServices [NonVersionable] // This only applies to field layout public readonly struct NFloat : IBinaryFloatingPointIeee754, - IMinMaxValue + IMinMaxValue, + IUtf8SpanFormattable { private const NumberStyles DefaultNumberStyles = NumberStyles.Float | NumberStyles.AllowThousands; @@ -860,6 +861,9 @@ public int CompareTo(object? obj) /// true if the formatting was successful; otherwise, false. public bool TryFormat(Span destination, out int charsWritten, [StringSyntax(StringSyntaxAttribute.NumericFormat)] ReadOnlySpan format = default, IFormatProvider? provider = null) => _value.TryFormat(destination, out charsWritten, format, provider); + /// + bool IUtf8SpanFormattable.TryFormat(Span utf8Destination, out int bytesWritten, ReadOnlySpan format, IFormatProvider? provider) => ((IUtf8SpanFormattable)_value).TryFormat(utf8Destination, out bytesWritten, format, provider); + // // IAdditiveIdentity // diff --git a/src/libraries/System.Private.CoreLib/src/System/SByte.cs b/src/libraries/System.Private.CoreLib/src/System/SByte.cs index ee9df96053b4c..9a95eecef3de4 100644 --- a/src/libraries/System.Private.CoreLib/src/System/SByte.cs +++ b/src/libraries/System.Private.CoreLib/src/System/SByte.cs @@ -23,7 +23,8 @@ public readonly struct SByte IEquatable, IBinaryInteger, IMinMaxValue, - ISignedNumber + ISignedNumber, + IUtf8SpanFormattable { private readonly sbyte m_value; // Do not rename (binary serialization) @@ -121,6 +122,12 @@ public bool TryFormat(Span destination, out int charsWritten, [StringSynta return Number.TryFormatInt32(m_value, 0x000000FF, format, provider, destination, out charsWritten); } + /// + bool IUtf8SpanFormattable.TryFormat(Span utf8Destination, out int bytesWritten, ReadOnlySpan format, IFormatProvider? provider) + { + return Number.TryFormatInt32(m_value, 0x000000FF, format, provider, utf8Destination, out bytesWritten); + } + public static sbyte Parse(string s) { if (s == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.s); diff --git a/src/libraries/System.Private.CoreLib/src/System/Single.cs b/src/libraries/System.Private.CoreLib/src/System/Single.cs index 887b26c4854ad..7ca8590a3a36a 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Single.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Single.cs @@ -31,7 +31,8 @@ public readonly struct Single IComparable, IEquatable, IBinaryFloatingPointIeee754, - IMinMaxValue + IMinMaxValue, + IUtf8SpanFormattable { private readonly float m_value; // Do not rename (binary serialization) @@ -357,6 +358,12 @@ public bool TryFormat(Span destination, out int charsWritten, [StringSynta return Number.TryFormatSingle(m_value, format, NumberFormatInfo.GetInstance(provider), destination, out charsWritten); } + /// + bool IUtf8SpanFormattable.TryFormat(Span utf8Destination, out int bytesWritten, ReadOnlySpan format, IFormatProvider? provider) + { + return Number.TryFormatSingle(m_value, format, NumberFormatInfo.GetInstance(provider), utf8Destination, out bytesWritten); + } + // Parses a float from a String in the given style. If // a NumberFormatInfo isn't specified, the current culture's // NumberFormatInfo is assumed. diff --git a/src/libraries/System.Private.CoreLib/src/System/UInt128.cs b/src/libraries/System.Private.CoreLib/src/System/UInt128.cs index 0ae4d6bee3400..5a6d33150c3a3 100644 --- a/src/libraries/System.Private.CoreLib/src/System/UInt128.cs +++ b/src/libraries/System.Private.CoreLib/src/System/UInt128.cs @@ -18,7 +18,8 @@ namespace System public readonly struct UInt128 : IBinaryInteger, IMinMaxValue, - IUnsignedNumber + IUnsignedNumber, + IUtf8SpanFormattable { internal const int Size = 16; @@ -119,6 +120,12 @@ public bool TryFormat(Span destination, out int charsWritten, [StringSynta return Number.TryFormatUInt128(this, format, provider, destination, out charsWritten); } + /// + bool IUtf8SpanFormattable.TryFormat(Span utf8Destination, out int bytesWritten, ReadOnlySpan format, IFormatProvider? provider) + { + return Number.TryFormatUInt128(this, format, provider, utf8Destination, out bytesWritten); + } + public static UInt128 Parse(string s) { ArgumentNullException.ThrowIfNull(s); diff --git a/src/libraries/System.Private.CoreLib/src/System/UInt16.cs b/src/libraries/System.Private.CoreLib/src/System/UInt16.cs index 025a6adddea1c..17a211c51b5ea 100644 --- a/src/libraries/System.Private.CoreLib/src/System/UInt16.cs +++ b/src/libraries/System.Private.CoreLib/src/System/UInt16.cs @@ -23,7 +23,8 @@ public readonly struct UInt16 IEquatable, IBinaryInteger, IMinMaxValue, - IUnsignedNumber + IUnsignedNumber, + IUtf8SpanFormattable { private readonly ushort m_value; // Do not rename (binary serialization) @@ -113,6 +114,12 @@ public bool TryFormat(Span destination, out int charsWritten, [StringSynta return Number.TryFormatUInt32(m_value, format, provider, destination, out charsWritten); } + /// + bool IUtf8SpanFormattable.TryFormat(Span utf8Destination, out int bytesWritten, ReadOnlySpan format, IFormatProvider? provider) + { + return Number.TryFormatUInt32(m_value, format, provider, utf8Destination, out bytesWritten); + } + public static ushort Parse(string s) { if (s == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.s); diff --git a/src/libraries/System.Private.CoreLib/src/System/UInt32.cs b/src/libraries/System.Private.CoreLib/src/System/UInt32.cs index c84612fc8530a..9813330a72499 100644 --- a/src/libraries/System.Private.CoreLib/src/System/UInt32.cs +++ b/src/libraries/System.Private.CoreLib/src/System/UInt32.cs @@ -23,7 +23,8 @@ public readonly struct UInt32 IEquatable, IBinaryInteger, IMinMaxValue, - IUnsignedNumber + IUnsignedNumber, + IUtf8SpanFormattable { private readonly uint m_value; // Do not rename (binary serialization) @@ -123,6 +124,12 @@ public bool TryFormat(Span destination, out int charsWritten, [StringSynta return Number.TryFormatUInt32(m_value, format, provider, destination, out charsWritten); } + /// + bool IUtf8SpanFormattable.TryFormat(Span utf8Destination, out int bytesWritten, ReadOnlySpan format, IFormatProvider? provider) + { + return Number.TryFormatUInt32(m_value, format, provider, utf8Destination, out bytesWritten); + } + public static uint Parse(string s) { if (s == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.s); diff --git a/src/libraries/System.Private.CoreLib/src/System/UInt64.cs b/src/libraries/System.Private.CoreLib/src/System/UInt64.cs index 91b569a71cf76..da40613f88f7b 100644 --- a/src/libraries/System.Private.CoreLib/src/System/UInt64.cs +++ b/src/libraries/System.Private.CoreLib/src/System/UInt64.cs @@ -23,7 +23,8 @@ public readonly struct UInt64 IEquatable, IBinaryInteger, IMinMaxValue, - IUnsignedNumber + IUnsignedNumber, + IUtf8SpanFormattable { private readonly ulong m_value; // Do not rename (binary serialization) @@ -122,6 +123,12 @@ public bool TryFormat(Span destination, out int charsWritten, [StringSynta return Number.TryFormatUInt64(m_value, format, provider, destination, out charsWritten); } + /// + bool IUtf8SpanFormattable.TryFormat(Span utf8Destination, out int bytesWritten, ReadOnlySpan format, IFormatProvider? provider) + { + return Number.TryFormatUInt64(m_value, format, provider, utf8Destination, out bytesWritten); + } + public static ulong Parse(string s) { if (s == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.s); diff --git a/src/libraries/System.Private.CoreLib/src/System/UIntPtr.cs b/src/libraries/System.Private.CoreLib/src/System/UIntPtr.cs index 2c24851d43cc6..c91992cba0805 100644 --- a/src/libraries/System.Private.CoreLib/src/System/UIntPtr.cs +++ b/src/libraries/System.Private.CoreLib/src/System/UIntPtr.cs @@ -32,7 +32,8 @@ public readonly struct UIntPtr ISerializable, IBinaryInteger, IMinMaxValue, - IUnsignedNumber + IUnsignedNumber, + IUtf8SpanFormattable { private readonly nuint _value; @@ -206,6 +207,10 @@ public int CompareTo(nuint value) public bool TryFormat(Span destination, out int charsWritten, [StringSyntax(StringSyntaxAttribute.NumericFormat)] ReadOnlySpan format = default, IFormatProvider? provider = null) => ((nuint_t)_value).TryFormat(destination, out charsWritten, format, provider); + /// + bool IUtf8SpanFormattable.TryFormat(Span utf8Destination, out int bytesWritten, ReadOnlySpan format, IFormatProvider? provider) => + ((IUtf8SpanFormattable)(nuint_t)_value).TryFormat(utf8Destination, out bytesWritten, format, provider); + public static nuint Parse(string s) => (nuint)nuint_t.Parse(s); public static nuint Parse(string s, NumberStyles style) => (nuint)nuint_t.Parse(s, style); public static nuint Parse(string s, IFormatProvider? provider) => (nuint)nuint_t.Parse(s, provider); diff --git a/src/libraries/System.Private.CoreLib/src/System/Version.cs b/src/libraries/System.Private.CoreLib/src/System/Version.cs index c0af3c196944f..8a74873a2dfe8 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Version.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Version.cs @@ -235,7 +235,7 @@ static void ThrowArgumentException(string failureUpperBound) => int valueCharsWritten; bool formatted = typeof(TChar) == typeof(char) ? ((uint)value).TryFormat(MemoryMarshal.Cast(destination), out valueCharsWritten) : - Utf8Formatter.TryFormat((uint)value, MemoryMarshal.Cast(destination), out valueCharsWritten); // TODO https://github.com/dotnet/runtime/issues/84527: Use UInt32's IUtf8SpanFormattable when available + ((IUtf8SpanFormattable)(uint)value).TryFormat(MemoryMarshal.Cast(destination), out valueCharsWritten, default, CultureInfo.InvariantCulture); if (!formatted) { diff --git a/src/libraries/System.Runtime.InteropServices/ref/System.Runtime.InteropServices.cs b/src/libraries/System.Runtime.InteropServices/ref/System.Runtime.InteropServices.cs index 78a92c9dbdadb..e9e54ff73ac13 100644 --- a/src/libraries/System.Runtime.InteropServices/ref/System.Runtime.InteropServices.cs +++ b/src/libraries/System.Runtime.InteropServices/ref/System.Runtime.InteropServices.cs @@ -1107,7 +1107,7 @@ public static void Free(void* ptr) { } [System.CLSCompliantAttribute(false)] public static void Fill(void* ptr, nuint byteCount, byte value) { throw null; } } - public readonly partial struct NFloat : System.IComparable, System.IComparable, System.IEquatable, System.IFormattable, System.IParsable, System.ISpanFormattable, System.ISpanParsable, System.Numerics.IAdditionOperators, System.Numerics.IAdditiveIdentity, System.Numerics.IBinaryFloatingPointIeee754, System.Numerics.IBinaryNumber, System.Numerics.IBitwiseOperators, System.Numerics.IComparisonOperators, System.Numerics.IDecrementOperators, System.Numerics.IDivisionOperators, System.Numerics.IEqualityOperators, System.Numerics.IExponentialFunctions, System.Numerics.IFloatingPoint, System.Numerics.IFloatingPointConstants, System.Numerics.IFloatingPointIeee754, System.Numerics.IHyperbolicFunctions, System.Numerics.IIncrementOperators, System.Numerics.ILogarithmicFunctions, System.Numerics.IMinMaxValue, System.Numerics.IModulusOperators, System.Numerics.IMultiplicativeIdentity, System.Numerics.IMultiplyOperators, System.Numerics.INumber, System.Numerics.INumberBase, System.Numerics.IPowerFunctions, System.Numerics.IRootFunctions, System.Numerics.ISignedNumber, System.Numerics.ISubtractionOperators, System.Numerics.ITrigonometricFunctions, System.Numerics.IUnaryNegationOperators, System.Numerics.IUnaryPlusOperators + public readonly partial struct NFloat : System.IComparable, System.IComparable, System.IEquatable, System.IFormattable, System.IParsable, System.ISpanFormattable, System.ISpanParsable, System.Numerics.IAdditionOperators, System.Numerics.IAdditiveIdentity, System.Numerics.IBinaryFloatingPointIeee754, System.Numerics.IBinaryNumber, System.Numerics.IBitwiseOperators, System.Numerics.IComparisonOperators, System.Numerics.IDecrementOperators, System.Numerics.IDivisionOperators, System.Numerics.IEqualityOperators, System.Numerics.IExponentialFunctions, System.Numerics.IFloatingPoint, System.Numerics.IFloatingPointConstants, System.Numerics.IFloatingPointIeee754, System.Numerics.IHyperbolicFunctions, System.Numerics.IIncrementOperators, System.Numerics.ILogarithmicFunctions, System.Numerics.IMinMaxValue, System.Numerics.IModulusOperators, System.Numerics.IMultiplicativeIdentity, System.Numerics.IMultiplyOperators, System.Numerics.INumber, System.Numerics.INumberBase, System.Numerics.IPowerFunctions, System.Numerics.IRootFunctions, System.Numerics.ISignedNumber, System.Numerics.ISubtractionOperators, System.Numerics.ITrigonometricFunctions, System.Numerics.IUnaryNegationOperators, System.Numerics.IUnaryPlusOperators, System.IUtf8SpanFormattable { private readonly int _dummyPrimitive; public NFloat(double value) { throw null; } @@ -1341,6 +1341,7 @@ public static void Free(void* ptr) { } public string ToString([System.Diagnostics.CodeAnalysis.StringSyntaxAttribute("NumericFormat")] string? format, System.IFormatProvider? provider) { throw null; } public static System.Runtime.InteropServices.NFloat Truncate(System.Runtime.InteropServices.NFloat x) { throw null; } public bool TryFormat(System.Span destination, out int charsWritten, [System.Diagnostics.CodeAnalysis.StringSyntaxAttribute("NumericFormat")] System.ReadOnlySpan format = default(System.ReadOnlySpan), System.IFormatProvider? provider = null) { throw null; } + bool System.IUtf8SpanFormattable.TryFormat(System.Span utf8Destination, out int bytesWritten, [System.Diagnostics.CodeAnalysis.StringSyntaxAttribute("NumericFormat")] System.ReadOnlySpan format, System.IFormatProvider? provider) { throw null; } public static bool TryParse(System.ReadOnlySpan s, System.Globalization.NumberStyles style, System.IFormatProvider? provider, out System.Runtime.InteropServices.NFloat result) { throw null; } public static bool TryParse(System.ReadOnlySpan s, System.IFormatProvider? provider, out System.Runtime.InteropServices.NFloat result) { throw null; } public static bool TryParse(System.ReadOnlySpan s, out System.Runtime.InteropServices.NFloat result) { throw null; } diff --git a/src/libraries/System.Runtime/ref/System.Runtime.cs b/src/libraries/System.Runtime/ref/System.Runtime.cs index 8ce544b882a85..c2ce193ceaf4f 100644 --- a/src/libraries/System.Runtime/ref/System.Runtime.cs +++ b/src/libraries/System.Runtime/ref/System.Runtime.cs @@ -754,7 +754,7 @@ public unsafe static void MemoryCopy(void* source, void* destination, long desti public unsafe static void MemoryCopy(void* source, void* destination, ulong destinationSizeInBytes, ulong sourceBytesToCopy) { } public static void SetByte(System.Array array, int index, byte value) { } } - public readonly partial struct Byte : System.IComparable, System.IComparable, System.IConvertible, System.IEquatable, System.IFormattable, System.IParsable, System.ISpanFormattable, System.ISpanParsable, System.Numerics.IAdditionOperators, System.Numerics.IAdditiveIdentity, System.Numerics.IBinaryInteger, System.Numerics.IBinaryNumber, System.Numerics.IBitwiseOperators, System.Numerics.IComparisonOperators, System.Numerics.IDecrementOperators, System.Numerics.IDivisionOperators, System.Numerics.IEqualityOperators, System.Numerics.IIncrementOperators, System.Numerics.IMinMaxValue, System.Numerics.IModulusOperators, System.Numerics.IMultiplicativeIdentity, System.Numerics.IMultiplyOperators, System.Numerics.INumber, System.Numerics.INumberBase, System.Numerics.IShiftOperators, System.Numerics.ISubtractionOperators, System.Numerics.IUnaryNegationOperators, System.Numerics.IUnaryPlusOperators, System.Numerics.IUnsignedNumber + public readonly partial struct Byte : System.IComparable, System.IComparable, System.IConvertible, System.IEquatable, System.IFormattable, System.IParsable, System.ISpanFormattable, System.ISpanParsable, System.Numerics.IAdditionOperators, System.Numerics.IAdditiveIdentity, System.Numerics.IBinaryInteger, System.Numerics.IBinaryNumber, System.Numerics.IBitwiseOperators, System.Numerics.IComparisonOperators, System.Numerics.IDecrementOperators, System.Numerics.IDivisionOperators, System.Numerics.IEqualityOperators, System.Numerics.IIncrementOperators, System.Numerics.IMinMaxValue, System.Numerics.IModulusOperators, System.Numerics.IMultiplicativeIdentity, System.Numerics.IMultiplyOperators, System.Numerics.INumber, System.Numerics.INumberBase, System.Numerics.IShiftOperators, System.Numerics.ISubtractionOperators, System.Numerics.IUnaryNegationOperators, System.Numerics.IUnaryPlusOperators, System.Numerics.IUnsignedNumber, System.IUtf8SpanFormattable { private readonly byte _dummyPrimitive; public const byte MaxValue = (byte)255; @@ -879,6 +879,7 @@ public static void SetByte(System.Array array, int index, byte value) { } public string ToString([System.Diagnostics.CodeAnalysis.StringSyntaxAttribute("NumericFormat")] string? format, System.IFormatProvider? provider) { throw null; } public static byte TrailingZeroCount(byte value) { throw null; } public bool TryFormat(System.Span destination, out int charsWritten, [System.Diagnostics.CodeAnalysis.StringSyntaxAttribute("NumericFormat")] System.ReadOnlySpan format = default(System.ReadOnlySpan), System.IFormatProvider? provider = null) { throw null; } + bool System.IUtf8SpanFormattable.TryFormat(System.Span utf8Destination, out int bytesWritten, [System.Diagnostics.CodeAnalysis.StringSyntaxAttribute("NumericFormat")] System.ReadOnlySpan format, System.IFormatProvider? provider) { throw null; } public static bool TryParse(System.ReadOnlySpan s, out byte result) { throw null; } public static bool TryParse(System.ReadOnlySpan s, System.Globalization.NumberStyles style, System.IFormatProvider? provider, out byte result) { throw null; } public static bool TryParse(System.ReadOnlySpan s, System.IFormatProvider? provider, out byte result) { throw null; } @@ -1925,7 +1926,7 @@ public void GetObjectData(System.Runtime.Serialization.SerializationInfo info, S public override string ToString() { throw null; } public string ToString(System.IFormatProvider? provider) { throw null; } } - public readonly partial struct Decimal : System.IComparable, System.IComparable, System.IConvertible, System.IEquatable, System.IFormattable, System.IParsable, System.ISpanFormattable, System.ISpanParsable, System.Numerics.IAdditionOperators, System.Numerics.IAdditiveIdentity, System.Numerics.IComparisonOperators, System.Numerics.IDecrementOperators, System.Numerics.IDivisionOperators, System.Numerics.IEqualityOperators, System.Numerics.IFloatingPoint, System.Numerics.IFloatingPointConstants, System.Numerics.IIncrementOperators, System.Numerics.IMinMaxValue, System.Numerics.IModulusOperators, System.Numerics.IMultiplicativeIdentity, System.Numerics.IMultiplyOperators, System.Numerics.INumber, System.Numerics.INumberBase, System.Numerics.ISignedNumber, System.Numerics.ISubtractionOperators, System.Numerics.IUnaryNegationOperators, System.Numerics.IUnaryPlusOperators, System.Runtime.Serialization.IDeserializationCallback, System.Runtime.Serialization.ISerializable + public readonly partial struct Decimal : System.IComparable, System.IComparable, System.IConvertible, System.IEquatable, System.IFormattable, System.IParsable, System.ISpanFormattable, System.ISpanParsable, System.Numerics.IAdditionOperators, System.Numerics.IAdditiveIdentity, System.Numerics.IComparisonOperators, System.Numerics.IDecrementOperators, System.Numerics.IDivisionOperators, System.Numerics.IEqualityOperators, System.Numerics.IFloatingPoint, System.Numerics.IFloatingPointConstants, System.Numerics.IIncrementOperators, System.Numerics.IMinMaxValue, System.Numerics.IModulusOperators, System.Numerics.IMultiplicativeIdentity, System.Numerics.IMultiplyOperators, System.Numerics.INumber, System.Numerics.INumberBase, System.Numerics.ISignedNumber, System.Numerics.ISubtractionOperators, System.Numerics.IUnaryNegationOperators, System.Numerics.IUnaryPlusOperators, System.Runtime.Serialization.IDeserializationCallback, System.Runtime.Serialization.ISerializable, System.IUtf8SpanFormattable { private readonly int _dummyPrimitive; [System.Runtime.CompilerServices.DecimalConstantAttribute((byte)0, (byte)0, (uint)4294967295, (uint)4294967295, (uint)4294967295)] @@ -2119,6 +2120,7 @@ void System.Runtime.Serialization.ISerializable.GetObjectData(System.Runtime.Ser public static ulong ToUInt64(decimal d) { throw null; } public static decimal Truncate(decimal d) { throw null; } public bool TryFormat(System.Span destination, out int charsWritten, [System.Diagnostics.CodeAnalysis.StringSyntaxAttribute("NumericFormat")] System.ReadOnlySpan format = default(System.ReadOnlySpan), System.IFormatProvider? provider = null) { throw null; } + bool System.IUtf8SpanFormattable.TryFormat(System.Span utf8Destination, out int bytesWritten, [System.Diagnostics.CodeAnalysis.StringSyntaxAttribute("NumericFormat")] System.ReadOnlySpan format, System.IFormatProvider? provider) { throw null; } public static bool TryGetBits(decimal d, System.Span destination, out int valuesWritten) { throw null; } public static bool TryParse(System.ReadOnlySpan s, out decimal result) { throw null; } public static bool TryParse(System.ReadOnlySpan s, System.Globalization.NumberStyles style, System.IFormatProvider? provider, out decimal result) { throw null; } @@ -2177,7 +2179,7 @@ protected DivideByZeroException(System.Runtime.Serialization.SerializationInfo i public DivideByZeroException(string? message) { } public DivideByZeroException(string? message, System.Exception? innerException) { } } - public readonly partial struct Double : System.IComparable, System.IComparable, System.IConvertible, System.IEquatable, System.IFormattable, System.IParsable, System.ISpanFormattable, System.ISpanParsable, System.Numerics.IAdditionOperators, System.Numerics.IAdditiveIdentity, System.Numerics.IBinaryFloatingPointIeee754, System.Numerics.IBinaryNumber, System.Numerics.IBitwiseOperators, System.Numerics.IComparisonOperators, System.Numerics.IDecrementOperators, System.Numerics.IDivisionOperators, System.Numerics.IEqualityOperators, System.Numerics.IExponentialFunctions, System.Numerics.IFloatingPoint, System.Numerics.IFloatingPointConstants, System.Numerics.IFloatingPointIeee754, System.Numerics.IHyperbolicFunctions, System.Numerics.IIncrementOperators, System.Numerics.ILogarithmicFunctions, System.Numerics.IMinMaxValue, System.Numerics.IModulusOperators, System.Numerics.IMultiplicativeIdentity, System.Numerics.IMultiplyOperators, System.Numerics.INumber, System.Numerics.INumberBase, System.Numerics.IPowerFunctions, System.Numerics.IRootFunctions, System.Numerics.ISignedNumber, System.Numerics.ISubtractionOperators, System.Numerics.ITrigonometricFunctions, System.Numerics.IUnaryNegationOperators, System.Numerics.IUnaryPlusOperators + public readonly partial struct Double : System.IComparable, System.IComparable, System.IConvertible, System.IEquatable, System.IFormattable, System.IParsable, System.ISpanFormattable, System.ISpanParsable, System.Numerics.IAdditionOperators, System.Numerics.IAdditiveIdentity, System.Numerics.IBinaryFloatingPointIeee754, System.Numerics.IBinaryNumber, System.Numerics.IBitwiseOperators, System.Numerics.IComparisonOperators, System.Numerics.IDecrementOperators, System.Numerics.IDivisionOperators, System.Numerics.IEqualityOperators, System.Numerics.IExponentialFunctions, System.Numerics.IFloatingPoint, System.Numerics.IFloatingPointConstants, System.Numerics.IFloatingPointIeee754, System.Numerics.IHyperbolicFunctions, System.Numerics.IIncrementOperators, System.Numerics.ILogarithmicFunctions, System.Numerics.IMinMaxValue, System.Numerics.IModulusOperators, System.Numerics.IMultiplicativeIdentity, System.Numerics.IMultiplyOperators, System.Numerics.INumber, System.Numerics.INumberBase, System.Numerics.IPowerFunctions, System.Numerics.IRootFunctions, System.Numerics.ISignedNumber, System.Numerics.ISubtractionOperators, System.Numerics.ITrigonometricFunctions, System.Numerics.IUnaryNegationOperators, System.Numerics.IUnaryPlusOperators, System.IUtf8SpanFormattable { private readonly double _dummyPrimitive; public const double E = 2.718281828459045; @@ -2361,6 +2363,7 @@ public DivideByZeroException(string? message, System.Exception? innerException) public string ToString([System.Diagnostics.CodeAnalysis.StringSyntaxAttribute("NumericFormat")] string? format, System.IFormatProvider? provider) { throw null; } public static double Truncate(double x) { throw null; } public bool TryFormat(System.Span destination, out int charsWritten, [System.Diagnostics.CodeAnalysis.StringSyntaxAttribute("NumericFormat")] System.ReadOnlySpan format = default(System.ReadOnlySpan), System.IFormatProvider? provider = null) { throw null; } + bool System.IUtf8SpanFormattable.TryFormat(System.Span utf8Destination, out int bytesWritten, [System.Diagnostics.CodeAnalysis.StringSyntaxAttribute("NumericFormat")] System.ReadOnlySpan format, System.IFormatProvider? provider) { throw null; } public static bool TryParse(System.ReadOnlySpan s, out double result) { throw null; } public static bool TryParse(System.ReadOnlySpan s, System.Globalization.NumberStyles style, System.IFormatProvider? provider, out double result) { throw null; } public static bool TryParse(System.ReadOnlySpan s, System.IFormatProvider? provider, out double result) { throw null; } @@ -2798,7 +2801,7 @@ public enum GCNotificationStatus public static bool TryParseExact([System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] string? input, [System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true), System.Diagnostics.CodeAnalysis.StringSyntaxAttribute("GuidFormat")] string? format, out System.Guid result) { throw null; } public bool TryWriteBytes(System.Span destination) { throw null; } } - public readonly partial struct Half : System.IComparable, System.IComparable, System.IEquatable, System.IFormattable, System.IParsable, System.ISpanFormattable, System.ISpanParsable, System.Numerics.IAdditionOperators, System.Numerics.IAdditiveIdentity, System.Numerics.IBinaryFloatingPointIeee754, System.Numerics.IBinaryNumber, System.Numerics.IBitwiseOperators, System.Numerics.IComparisonOperators, System.Numerics.IDecrementOperators, System.Numerics.IDivisionOperators, System.Numerics.IEqualityOperators, System.Numerics.IExponentialFunctions, System.Numerics.IFloatingPoint, System.Numerics.IFloatingPointConstants, System.Numerics.IFloatingPointIeee754, System.Numerics.IHyperbolicFunctions, System.Numerics.IIncrementOperators, System.Numerics.ILogarithmicFunctions, System.Numerics.IMinMaxValue, System.Numerics.IModulusOperators, System.Numerics.IMultiplicativeIdentity, System.Numerics.IMultiplyOperators, System.Numerics.INumber, System.Numerics.INumberBase, System.Numerics.IPowerFunctions, System.Numerics.IRootFunctions, System.Numerics.ISignedNumber, System.Numerics.ISubtractionOperators, System.Numerics.ITrigonometricFunctions, System.Numerics.IUnaryNegationOperators, System.Numerics.IUnaryPlusOperators + public readonly partial struct Half : System.IComparable, System.IComparable, System.IEquatable, System.IFormattable, System.IParsable, System.ISpanFormattable, System.ISpanParsable, System.Numerics.IAdditionOperators, System.Numerics.IAdditiveIdentity, System.Numerics.IBinaryFloatingPointIeee754, System.Numerics.IBinaryNumber, System.Numerics.IBitwiseOperators, System.Numerics.IComparisonOperators, System.Numerics.IDecrementOperators, System.Numerics.IDivisionOperators, System.Numerics.IEqualityOperators, System.Numerics.IExponentialFunctions, System.Numerics.IFloatingPoint, System.Numerics.IFloatingPointConstants, System.Numerics.IFloatingPointIeee754, System.Numerics.IHyperbolicFunctions, System.Numerics.IIncrementOperators, System.Numerics.ILogarithmicFunctions, System.Numerics.IMinMaxValue, System.Numerics.IModulusOperators, System.Numerics.IMultiplicativeIdentity, System.Numerics.IMultiplyOperators, System.Numerics.INumber, System.Numerics.INumberBase, System.Numerics.IPowerFunctions, System.Numerics.IRootFunctions, System.Numerics.ISignedNumber, System.Numerics.ISubtractionOperators, System.Numerics.ITrigonometricFunctions, System.Numerics.IUnaryNegationOperators, System.Numerics.IUnaryPlusOperators, System.IUtf8SpanFormattable { private readonly int _dummyPrimitive; public static System.Half E { get { throw null; } } @@ -3016,6 +3019,7 @@ public enum GCNotificationStatus public string ToString([System.Diagnostics.CodeAnalysis.StringSyntaxAttribute("NumericFormat")] string? format, System.IFormatProvider? provider) { throw null; } public static System.Half Truncate(System.Half x) { throw null; } public bool TryFormat(System.Span destination, out int charsWritten, [System.Diagnostics.CodeAnalysis.StringSyntaxAttribute("NumericFormat")] System.ReadOnlySpan format = default(System.ReadOnlySpan), System.IFormatProvider? provider = null) { throw null; } + bool System.IUtf8SpanFormattable.TryFormat(System.Span utf8Destination, out int bytesWritten, [System.Diagnostics.CodeAnalysis.StringSyntaxAttribute("NumericFormat")] System.ReadOnlySpan format, System.IFormatProvider? provider) { throw null; } public static bool TryParse(System.ReadOnlySpan s, System.Globalization.NumberStyles style, System.IFormatProvider? provider, out System.Half result) { throw null; } public static bool TryParse(System.ReadOnlySpan s, out System.Half result) { throw null; } public static bool TryParse(System.ReadOnlySpan s, System.IFormatProvider? provider, out System.Half result) { throw null; } @@ -3144,7 +3148,7 @@ public InsufficientMemoryException() { } public InsufficientMemoryException(string? message) { } public InsufficientMemoryException(string? message, System.Exception? innerException) { } } - public readonly partial struct Int128 : System.IComparable, System.IComparable, System.IEquatable, System.IFormattable, System.IParsable, System.ISpanFormattable, System.ISpanParsable, System.Numerics.IAdditionOperators, System.Numerics.IAdditiveIdentity, System.Numerics.IBinaryInteger, System.Numerics.IBinaryNumber, System.Numerics.IBitwiseOperators, System.Numerics.IComparisonOperators, System.Numerics.IDecrementOperators, System.Numerics.IDivisionOperators, System.Numerics.IEqualityOperators, System.Numerics.IIncrementOperators, System.Numerics.IMinMaxValue, System.Numerics.IModulusOperators, System.Numerics.IMultiplicativeIdentity, System.Numerics.IMultiplyOperators, System.Numerics.INumber, System.Numerics.INumberBase, System.Numerics.IShiftOperators, System.Numerics.ISignedNumber, System.Numerics.ISubtractionOperators, System.Numerics.IUnaryNegationOperators, System.Numerics.IUnaryPlusOperators + public readonly partial struct Int128 : System.IComparable, System.IComparable, System.IEquatable, System.IFormattable, System.IParsable, System.ISpanFormattable, System.ISpanParsable, System.Numerics.IAdditionOperators, System.Numerics.IAdditiveIdentity, System.Numerics.IBinaryInteger, System.Numerics.IBinaryNumber, System.Numerics.IBitwiseOperators, System.Numerics.IComparisonOperators, System.Numerics.IDecrementOperators, System.Numerics.IDivisionOperators, System.Numerics.IEqualityOperators, System.Numerics.IIncrementOperators, System.Numerics.IMinMaxValue, System.Numerics.IModulusOperators, System.Numerics.IMultiplicativeIdentity, System.Numerics.IMultiplyOperators, System.Numerics.INumber, System.Numerics.INumberBase, System.Numerics.IShiftOperators, System.Numerics.ISignedNumber, System.Numerics.ISubtractionOperators, System.Numerics.IUnaryNegationOperators, System.Numerics.IUnaryPlusOperators, System.IUtf8SpanFormattable { private readonly int _dummyPrimitive; [System.CLSCompliantAttribute(false)] @@ -3316,6 +3320,7 @@ public InsufficientMemoryException(string? message, System.Exception? innerExcep public string ToString([System.Diagnostics.CodeAnalysis.StringSyntaxAttribute("NumericFormat")] string? format, System.IFormatProvider? provider) { throw null; } public static System.Int128 TrailingZeroCount(System.Int128 value) { throw null; } public bool TryFormat(System.Span destination, out int charsWritten, [System.Diagnostics.CodeAnalysis.StringSyntaxAttribute("NumericFormat")] System.ReadOnlySpan format = default(System.ReadOnlySpan), System.IFormatProvider? provider = null) { throw null; } + bool System.IUtf8SpanFormattable.TryFormat(System.Span utf8Destination, out int bytesWritten, [System.Diagnostics.CodeAnalysis.StringSyntaxAttribute("NumericFormat")] System.ReadOnlySpan format, System.IFormatProvider? provider) { throw null; } public static bool TryParse(System.ReadOnlySpan s, System.Globalization.NumberStyles style, System.IFormatProvider? provider, out System.Int128 result) { throw null; } public static bool TryParse(System.ReadOnlySpan s, System.IFormatProvider? provider, out System.Int128 result) { throw null; } public static bool TryParse(System.ReadOnlySpan s, out System.Int128 result) { throw null; } @@ -3323,7 +3328,7 @@ public InsufficientMemoryException(string? message, System.Exception? innerExcep public static bool TryParse([System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] string? s, System.IFormatProvider? provider, out System.Int128 result) { throw null; } public static bool TryParse([System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] string? s, out System.Int128 result) { throw null; } } - public readonly partial struct Int16 : System.IComparable, System.IComparable, System.IConvertible, System.IEquatable, System.IFormattable, System.IParsable, System.ISpanFormattable, System.ISpanParsable, System.Numerics.IAdditionOperators, System.Numerics.IAdditiveIdentity, System.Numerics.IBinaryInteger, System.Numerics.IBinaryNumber, System.Numerics.IBitwiseOperators, System.Numerics.IComparisonOperators, System.Numerics.IDecrementOperators, System.Numerics.IDivisionOperators, System.Numerics.IEqualityOperators, System.Numerics.IIncrementOperators, System.Numerics.IMinMaxValue, System.Numerics.IModulusOperators, System.Numerics.IMultiplicativeIdentity, System.Numerics.IMultiplyOperators, System.Numerics.INumber, System.Numerics.INumberBase, System.Numerics.IShiftOperators, System.Numerics.ISignedNumber, System.Numerics.ISubtractionOperators, System.Numerics.IUnaryNegationOperators, System.Numerics.IUnaryPlusOperators + public readonly partial struct Int16 : System.IComparable, System.IComparable, System.IConvertible, System.IEquatable, System.IFormattable, System.IParsable, System.ISpanFormattable, System.ISpanParsable, System.Numerics.IAdditionOperators, System.Numerics.IAdditiveIdentity, System.Numerics.IBinaryInteger, System.Numerics.IBinaryNumber, System.Numerics.IBitwiseOperators, System.Numerics.IComparisonOperators, System.Numerics.IDecrementOperators, System.Numerics.IDivisionOperators, System.Numerics.IEqualityOperators, System.Numerics.IIncrementOperators, System.Numerics.IMinMaxValue, System.Numerics.IModulusOperators, System.Numerics.IMultiplicativeIdentity, System.Numerics.IMultiplyOperators, System.Numerics.INumber, System.Numerics.INumberBase, System.Numerics.IShiftOperators, System.Numerics.ISignedNumber, System.Numerics.ISubtractionOperators, System.Numerics.IUnaryNegationOperators, System.Numerics.IUnaryPlusOperators, System.IUtf8SpanFormattable { private readonly short _dummyPrimitive; public const short MaxValue = (short)32767; @@ -3449,6 +3454,7 @@ public InsufficientMemoryException(string? message, System.Exception? innerExcep public string ToString([System.Diagnostics.CodeAnalysis.StringSyntaxAttribute("NumericFormat")] string? format, System.IFormatProvider? provider) { throw null; } public static short TrailingZeroCount(short value) { throw null; } public bool TryFormat(System.Span destination, out int charsWritten, [System.Diagnostics.CodeAnalysis.StringSyntaxAttribute("NumericFormat")] System.ReadOnlySpan format = default(System.ReadOnlySpan), System.IFormatProvider? provider = null) { throw null; } + bool System.IUtf8SpanFormattable.TryFormat(System.Span utf8Destination, out int bytesWritten, [System.Diagnostics.CodeAnalysis.StringSyntaxAttribute("NumericFormat")] System.ReadOnlySpan format, System.IFormatProvider? provider) { throw null; } public static bool TryParse(System.ReadOnlySpan s, System.Globalization.NumberStyles style, System.IFormatProvider? provider, out short result) { throw null; } public static bool TryParse(System.ReadOnlySpan s, System.IFormatProvider? provider, out short result) { throw null; } public static bool TryParse(System.ReadOnlySpan s, out short result) { throw null; } @@ -3456,7 +3462,7 @@ public InsufficientMemoryException(string? message, System.Exception? innerExcep public static bool TryParse([System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] string? s, System.IFormatProvider? provider, out short result) { throw null; } public static bool TryParse([System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] string? s, out short result) { throw null; } } - public readonly partial struct Int32 : System.IComparable, System.IComparable, System.IConvertible, System.IEquatable, System.IFormattable, System.IParsable, System.ISpanFormattable, System.ISpanParsable, System.Numerics.IAdditionOperators, System.Numerics.IAdditiveIdentity, System.Numerics.IBinaryInteger, System.Numerics.IBinaryNumber, System.Numerics.IBitwiseOperators, System.Numerics.IComparisonOperators, System.Numerics.IDecrementOperators, System.Numerics.IDivisionOperators, System.Numerics.IEqualityOperators, System.Numerics.IIncrementOperators, System.Numerics.IMinMaxValue, System.Numerics.IModulusOperators, System.Numerics.IMultiplicativeIdentity, System.Numerics.IMultiplyOperators, System.Numerics.INumber, System.Numerics.INumberBase, System.Numerics.IShiftOperators, System.Numerics.ISignedNumber, System.Numerics.ISubtractionOperators, System.Numerics.IUnaryNegationOperators, System.Numerics.IUnaryPlusOperators + public readonly partial struct Int32 : System.IComparable, System.IComparable, System.IConvertible, System.IEquatable, System.IFormattable, System.IParsable, System.ISpanFormattable, System.ISpanParsable, System.Numerics.IAdditionOperators, System.Numerics.IAdditiveIdentity, System.Numerics.IBinaryInteger, System.Numerics.IBinaryNumber, System.Numerics.IBitwiseOperators, System.Numerics.IComparisonOperators, System.Numerics.IDecrementOperators, System.Numerics.IDivisionOperators, System.Numerics.IEqualityOperators, System.Numerics.IIncrementOperators, System.Numerics.IMinMaxValue, System.Numerics.IModulusOperators, System.Numerics.IMultiplicativeIdentity, System.Numerics.IMultiplyOperators, System.Numerics.INumber, System.Numerics.INumberBase, System.Numerics.IShiftOperators, System.Numerics.ISignedNumber, System.Numerics.ISubtractionOperators, System.Numerics.IUnaryNegationOperators, System.Numerics.IUnaryPlusOperators, System.IUtf8SpanFormattable { private readonly int _dummyPrimitive; public const int MaxValue = 2147483647; @@ -3582,6 +3588,7 @@ public InsufficientMemoryException(string? message, System.Exception? innerExcep public string ToString([System.Diagnostics.CodeAnalysis.StringSyntaxAttribute("NumericFormat")] string? format, System.IFormatProvider? provider) { throw null; } public static int TrailingZeroCount(int value) { throw null; } public bool TryFormat(System.Span destination, out int charsWritten, [System.Diagnostics.CodeAnalysis.StringSyntaxAttribute("NumericFormat")] System.ReadOnlySpan format = default(System.ReadOnlySpan), System.IFormatProvider? provider = null) { throw null; } + bool System.IUtf8SpanFormattable.TryFormat(System.Span utf8Destination, out int bytesWritten, [System.Diagnostics.CodeAnalysis.StringSyntaxAttribute("NumericFormat")] System.ReadOnlySpan format, System.IFormatProvider? provider) { throw null; } public static bool TryParse(System.ReadOnlySpan s, System.Globalization.NumberStyles style, System.IFormatProvider? provider, out int result) { throw null; } public static bool TryParse(System.ReadOnlySpan s, System.IFormatProvider? provider, out int result) { throw null; } public static bool TryParse(System.ReadOnlySpan s, out int result) { throw null; } @@ -3589,7 +3596,7 @@ public InsufficientMemoryException(string? message, System.Exception? innerExcep public static bool TryParse([System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] string? s, System.IFormatProvider? provider, out int result) { throw null; } public static bool TryParse([System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] string? s, out int result) { throw null; } } - public readonly partial struct Int64 : System.IComparable, System.IComparable, System.IConvertible, System.IEquatable, System.IFormattable, System.IParsable, System.ISpanFormattable, System.ISpanParsable, System.Numerics.IAdditionOperators, System.Numerics.IAdditiveIdentity, System.Numerics.IBinaryInteger, System.Numerics.IBinaryNumber, System.Numerics.IBitwiseOperators, System.Numerics.IComparisonOperators, System.Numerics.IDecrementOperators, System.Numerics.IDivisionOperators, System.Numerics.IEqualityOperators, System.Numerics.IIncrementOperators, System.Numerics.IMinMaxValue, System.Numerics.IModulusOperators, System.Numerics.IMultiplicativeIdentity, System.Numerics.IMultiplyOperators, System.Numerics.INumber, System.Numerics.INumberBase, System.Numerics.IShiftOperators, System.Numerics.ISignedNumber, System.Numerics.ISubtractionOperators, System.Numerics.IUnaryNegationOperators, System.Numerics.IUnaryPlusOperators + public readonly partial struct Int64 : System.IComparable, System.IComparable, System.IConvertible, System.IEquatable, System.IFormattable, System.IParsable, System.ISpanFormattable, System.ISpanParsable, System.Numerics.IAdditionOperators, System.Numerics.IAdditiveIdentity, System.Numerics.IBinaryInteger, System.Numerics.IBinaryNumber, System.Numerics.IBitwiseOperators, System.Numerics.IComparisonOperators, System.Numerics.IDecrementOperators, System.Numerics.IDivisionOperators, System.Numerics.IEqualityOperators, System.Numerics.IIncrementOperators, System.Numerics.IMinMaxValue, System.Numerics.IModulusOperators, System.Numerics.IMultiplicativeIdentity, System.Numerics.IMultiplyOperators, System.Numerics.INumber, System.Numerics.INumberBase, System.Numerics.IShiftOperators, System.Numerics.ISignedNumber, System.Numerics.ISubtractionOperators, System.Numerics.IUnaryNegationOperators, System.Numerics.IUnaryPlusOperators, System.IUtf8SpanFormattable { private readonly long _dummyPrimitive; public const long MaxValue = (long)9223372036854775807; @@ -3715,6 +3722,7 @@ public InsufficientMemoryException(string? message, System.Exception? innerExcep public string ToString([System.Diagnostics.CodeAnalysis.StringSyntaxAttribute("NumericFormat")] string? format, System.IFormatProvider? provider) { throw null; } public static long TrailingZeroCount(long value) { throw null; } public bool TryFormat(System.Span destination, out int charsWritten, [System.Diagnostics.CodeAnalysis.StringSyntaxAttribute("NumericFormat")] System.ReadOnlySpan format = default(System.ReadOnlySpan), System.IFormatProvider? provider = null) { throw null; } + bool System.IUtf8SpanFormattable.TryFormat(System.Span utf8Destination, out int bytesWritten, [System.Diagnostics.CodeAnalysis.StringSyntaxAttribute("NumericFormat")] System.ReadOnlySpan format, System.IFormatProvider? provider) { throw null; } public static bool TryParse(System.ReadOnlySpan s, System.Globalization.NumberStyles style, System.IFormatProvider? provider, out long result) { throw null; } public static bool TryParse(System.ReadOnlySpan s, System.IFormatProvider? provider, out long result) { throw null; } public static bool TryParse(System.ReadOnlySpan s, out long result) { throw null; } @@ -3722,7 +3730,7 @@ public InsufficientMemoryException(string? message, System.Exception? innerExcep public static bool TryParse([System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] string? s, System.IFormatProvider? provider, out long result) { throw null; } public static bool TryParse([System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] string? s, out long result) { throw null; } } - public readonly partial struct IntPtr : System.IComparable, System.IComparable, System.IEquatable, System.IFormattable, System.IParsable, System.ISpanFormattable, System.ISpanParsable, System.Numerics.IAdditionOperators, System.Numerics.IAdditiveIdentity, System.Numerics.IBinaryInteger, System.Numerics.IBinaryNumber, System.Numerics.IBitwiseOperators, System.Numerics.IComparisonOperators, System.Numerics.IDecrementOperators, System.Numerics.IDivisionOperators, System.Numerics.IEqualityOperators, System.Numerics.IIncrementOperators, System.Numerics.IMinMaxValue, System.Numerics.IModulusOperators, System.Numerics.IMultiplicativeIdentity, System.Numerics.IMultiplyOperators, System.Numerics.INumber, System.Numerics.INumberBase, System.Numerics.IShiftOperators, System.Numerics.ISignedNumber, System.Numerics.ISubtractionOperators, System.Numerics.IUnaryNegationOperators, System.Numerics.IUnaryPlusOperators, System.Runtime.Serialization.ISerializable + public readonly partial struct IntPtr : System.IComparable, System.IComparable, System.IEquatable, System.IFormattable, System.IParsable, System.ISpanFormattable, System.ISpanParsable, System.Numerics.IAdditionOperators, System.Numerics.IAdditiveIdentity, System.Numerics.IBinaryInteger, System.Numerics.IBinaryNumber, System.Numerics.IBitwiseOperators, System.Numerics.IComparisonOperators, System.Numerics.IDecrementOperators, System.Numerics.IDivisionOperators, System.Numerics.IEqualityOperators, System.Numerics.IIncrementOperators, System.Numerics.IMinMaxValue, System.Numerics.IModulusOperators, System.Numerics.IMultiplicativeIdentity, System.Numerics.IMultiplyOperators, System.Numerics.INumber, System.Numerics.INumberBase, System.Numerics.IShiftOperators, System.Numerics.ISignedNumber, System.Numerics.ISubtractionOperators, System.Numerics.IUnaryNegationOperators, System.Numerics.IUnaryPlusOperators, System.Runtime.Serialization.ISerializable, System.IUtf8SpanFormattable { private readonly unsafe void* _dummyPrimitive; public static readonly nint Zero; @@ -3855,6 +3863,7 @@ void System.Runtime.Serialization.ISerializable.GetObjectData(System.Runtime.Ser public string ToString([System.Diagnostics.CodeAnalysis.StringSyntaxAttribute("NumericFormat")] string? format, System.IFormatProvider? provider) { throw null; } public static nint TrailingZeroCount(nint value) { throw null; } public bool TryFormat(System.Span destination, out int charsWritten, [System.Diagnostics.CodeAnalysis.StringSyntaxAttribute("NumericFormat")] System.ReadOnlySpan format = default(System.ReadOnlySpan), System.IFormatProvider? provider = null) { throw null; } + bool System.IUtf8SpanFormattable.TryFormat(System.Span utf8Destination, out int bytesWritten, [System.Diagnostics.CodeAnalysis.StringSyntaxAttribute("NumericFormat")] System.ReadOnlySpan format, System.IFormatProvider? provider) { throw null; } public static bool TryParse(System.ReadOnlySpan s, System.Globalization.NumberStyles style, System.IFormatProvider? provider, out nint result) { throw null; } public static bool TryParse(System.ReadOnlySpan s, System.IFormatProvider? provider, out nint result) { throw null; } public static bool TryParse(System.ReadOnlySpan s, out nint result) { throw null; } @@ -4716,7 +4725,7 @@ public void GetObjectData(System.Runtime.Serialization.SerializationInfo info, S public static bool operator !=(System.RuntimeTypeHandle left, object? right) { throw null; } } [System.CLSCompliantAttribute(false)] - public readonly partial struct SByte : System.IComparable, System.IComparable, System.IConvertible, System.IEquatable, System.IFormattable, System.IParsable, System.ISpanFormattable, System.ISpanParsable, System.Numerics.IAdditionOperators, System.Numerics.IAdditiveIdentity, System.Numerics.IBinaryInteger, System.Numerics.IBinaryNumber, System.Numerics.IBitwiseOperators, System.Numerics.IComparisonOperators, System.Numerics.IDecrementOperators, System.Numerics.IDivisionOperators, System.Numerics.IEqualityOperators, System.Numerics.IIncrementOperators, System.Numerics.IMinMaxValue, System.Numerics.IModulusOperators, System.Numerics.IMultiplicativeIdentity, System.Numerics.IMultiplyOperators, System.Numerics.INumber, System.Numerics.INumberBase, System.Numerics.IShiftOperators, System.Numerics.ISignedNumber, System.Numerics.ISubtractionOperators, System.Numerics.IUnaryNegationOperators, System.Numerics.IUnaryPlusOperators + public readonly partial struct SByte : System.IComparable, System.IComparable, System.IConvertible, System.IEquatable, System.IFormattable, System.IParsable, System.ISpanFormattable, System.ISpanParsable, System.Numerics.IAdditionOperators, System.Numerics.IAdditiveIdentity, System.Numerics.IBinaryInteger, System.Numerics.IBinaryNumber, System.Numerics.IBitwiseOperators, System.Numerics.IComparisonOperators, System.Numerics.IDecrementOperators, System.Numerics.IDivisionOperators, System.Numerics.IEqualityOperators, System.Numerics.IIncrementOperators, System.Numerics.IMinMaxValue, System.Numerics.IModulusOperators, System.Numerics.IMultiplicativeIdentity, System.Numerics.IMultiplyOperators, System.Numerics.INumber, System.Numerics.INumberBase, System.Numerics.IShiftOperators, System.Numerics.ISignedNumber, System.Numerics.ISubtractionOperators, System.Numerics.IUnaryNegationOperators, System.Numerics.IUnaryPlusOperators, System.IUtf8SpanFormattable { private readonly sbyte _dummyPrimitive; public const sbyte MaxValue = (sbyte)127; @@ -4842,6 +4851,7 @@ public void GetObjectData(System.Runtime.Serialization.SerializationInfo info, S public string ToString([System.Diagnostics.CodeAnalysis.StringSyntaxAttribute("NumericFormat")] string? format, System.IFormatProvider? provider) { throw null; } public static sbyte TrailingZeroCount(sbyte value) { throw null; } public bool TryFormat(System.Span destination, out int charsWritten, [System.Diagnostics.CodeAnalysis.StringSyntaxAttribute("NumericFormat")] System.ReadOnlySpan format = default(System.ReadOnlySpan), System.IFormatProvider? provider = null) { throw null; } + bool System.IUtf8SpanFormattable.TryFormat(System.Span utf8Destination, out int bytesWritten, [System.Diagnostics.CodeAnalysis.StringSyntaxAttribute("NumericFormat")] System.ReadOnlySpan format, System.IFormatProvider? provider) { throw null; } public static bool TryParse(System.ReadOnlySpan s, System.Globalization.NumberStyles style, System.IFormatProvider? provider, out sbyte result) { throw null; } public static bool TryParse(System.ReadOnlySpan s, System.IFormatProvider? provider, out sbyte result) { throw null; } public static bool TryParse(System.ReadOnlySpan s, out sbyte result) { throw null; } @@ -4855,7 +4865,7 @@ public sealed partial class SerializableAttribute : System.Attribute { public SerializableAttribute() { } } - public readonly partial struct Single : System.IComparable, System.IComparable, System.IConvertible, System.IEquatable, System.IFormattable, System.IParsable, System.ISpanFormattable, System.ISpanParsable, System.Numerics.IAdditionOperators, System.Numerics.IAdditiveIdentity, System.Numerics.IBinaryFloatingPointIeee754, System.Numerics.IBinaryNumber, System.Numerics.IBitwiseOperators, System.Numerics.IComparisonOperators, System.Numerics.IDecrementOperators, System.Numerics.IDivisionOperators, System.Numerics.IEqualityOperators, System.Numerics.IExponentialFunctions, System.Numerics.IFloatingPoint, System.Numerics.IFloatingPointConstants, System.Numerics.IFloatingPointIeee754, System.Numerics.IHyperbolicFunctions, System.Numerics.IIncrementOperators, System.Numerics.ILogarithmicFunctions, System.Numerics.IMinMaxValue, System.Numerics.IModulusOperators, System.Numerics.IMultiplicativeIdentity, System.Numerics.IMultiplyOperators, System.Numerics.INumber, System.Numerics.INumberBase, System.Numerics.IPowerFunctions, System.Numerics.IRootFunctions, System.Numerics.ISignedNumber, System.Numerics.ISubtractionOperators, System.Numerics.ITrigonometricFunctions, System.Numerics.IUnaryNegationOperators, System.Numerics.IUnaryPlusOperators + public readonly partial struct Single : System.IComparable, System.IComparable, System.IConvertible, System.IEquatable, System.IFormattable, System.IParsable, System.ISpanFormattable, System.ISpanParsable, System.Numerics.IAdditionOperators, System.Numerics.IAdditiveIdentity, System.Numerics.IBinaryFloatingPointIeee754, System.Numerics.IBinaryNumber, System.Numerics.IBitwiseOperators, System.Numerics.IComparisonOperators, System.Numerics.IDecrementOperators, System.Numerics.IDivisionOperators, System.Numerics.IEqualityOperators, System.Numerics.IExponentialFunctions, System.Numerics.IFloatingPoint, System.Numerics.IFloatingPointConstants, System.Numerics.IFloatingPointIeee754, System.Numerics.IHyperbolicFunctions, System.Numerics.IIncrementOperators, System.Numerics.ILogarithmicFunctions, System.Numerics.IMinMaxValue, System.Numerics.IModulusOperators, System.Numerics.IMultiplicativeIdentity, System.Numerics.IMultiplyOperators, System.Numerics.INumber, System.Numerics.INumberBase, System.Numerics.IPowerFunctions, System.Numerics.IRootFunctions, System.Numerics.ISignedNumber, System.Numerics.ISubtractionOperators, System.Numerics.ITrigonometricFunctions, System.Numerics.IUnaryNegationOperators, System.Numerics.IUnaryPlusOperators, System.IUtf8SpanFormattable { private readonly float _dummyPrimitive; public const float E = 2.7182817f; @@ -5039,6 +5049,7 @@ public SerializableAttribute() { } public string ToString([System.Diagnostics.CodeAnalysis.StringSyntaxAttribute("NumericFormat")] string? format, System.IFormatProvider? provider) { throw null; } public static float Truncate(float x) { throw null; } public bool TryFormat(System.Span destination, out int charsWritten, [System.Diagnostics.CodeAnalysis.StringSyntaxAttribute("NumericFormat")] System.ReadOnlySpan format = default(System.ReadOnlySpan), System.IFormatProvider? provider = null) { throw null; } + bool System.IUtf8SpanFormattable.TryFormat(System.Span utf8Destination, out int bytesWritten, [System.Diagnostics.CodeAnalysis.StringSyntaxAttribute("NumericFormat")] System.ReadOnlySpan format, System.IFormatProvider? provider) { throw null; } public static bool TryParse(System.ReadOnlySpan s, System.Globalization.NumberStyles style, System.IFormatProvider? provider, out float result) { throw null; } public static bool TryParse(System.ReadOnlySpan s, System.IFormatProvider? provider, out float result) { throw null; } public static bool TryParse(System.ReadOnlySpan s, out float result) { throw null; } @@ -6260,7 +6271,7 @@ public TypeUnloadedException(string? message) { } public TypeUnloadedException(string? message, System.Exception? innerException) { } } [System.CLSCompliantAttribute(false)] - public readonly partial struct UInt128 : System.IComparable, System.IComparable, System.IEquatable, System.IFormattable, System.IParsable, System.ISpanFormattable, System.ISpanParsable, System.Numerics.IAdditionOperators, System.Numerics.IAdditiveIdentity, System.Numerics.IBinaryInteger, System.Numerics.IBinaryNumber, System.Numerics.IBitwiseOperators, System.Numerics.IComparisonOperators, System.Numerics.IDecrementOperators, System.Numerics.IDivisionOperators, System.Numerics.IEqualityOperators, System.Numerics.IIncrementOperators, System.Numerics.IMinMaxValue, System.Numerics.IModulusOperators, System.Numerics.IMultiplicativeIdentity, System.Numerics.IMultiplyOperators, System.Numerics.INumber, System.Numerics.INumberBase, System.Numerics.IShiftOperators, System.Numerics.ISubtractionOperators, System.Numerics.IUnaryNegationOperators, System.Numerics.IUnaryPlusOperators, System.Numerics.IUnsignedNumber + public readonly partial struct UInt128 : System.IComparable, System.IComparable, System.IEquatable, System.IFormattable, System.IParsable, System.ISpanFormattable, System.ISpanParsable, System.Numerics.IAdditionOperators, System.Numerics.IAdditiveIdentity, System.Numerics.IBinaryInteger, System.Numerics.IBinaryNumber, System.Numerics.IBitwiseOperators, System.Numerics.IComparisonOperators, System.Numerics.IDecrementOperators, System.Numerics.IDivisionOperators, System.Numerics.IEqualityOperators, System.Numerics.IIncrementOperators, System.Numerics.IMinMaxValue, System.Numerics.IModulusOperators, System.Numerics.IMultiplicativeIdentity, System.Numerics.IMultiplyOperators, System.Numerics.INumber, System.Numerics.INumberBase, System.Numerics.IShiftOperators, System.Numerics.ISubtractionOperators, System.Numerics.IUnaryNegationOperators, System.Numerics.IUnaryPlusOperators, System.Numerics.IUnsignedNumber, System.IUtf8SpanFormattable { private readonly int _dummyPrimitive; [System.CLSCompliantAttribute(false)] @@ -6437,6 +6448,7 @@ public TypeUnloadedException(string? message, System.Exception? innerException) public string ToString([System.Diagnostics.CodeAnalysis.StringSyntaxAttribute("NumericFormat")] string? format, System.IFormatProvider? provider) { throw null; } public static System.UInt128 TrailingZeroCount(System.UInt128 value) { throw null; } public bool TryFormat(System.Span destination, out int charsWritten, [System.Diagnostics.CodeAnalysis.StringSyntaxAttribute("NumericFormat")] System.ReadOnlySpan format = default(System.ReadOnlySpan), System.IFormatProvider? provider = null) { throw null; } + bool System.IUtf8SpanFormattable.TryFormat(System.Span utf8Destination, out int bytesWritten, [System.Diagnostics.CodeAnalysis.StringSyntaxAttribute("NumericFormat")] System.ReadOnlySpan format, System.IFormatProvider? provider) { throw null; } public static bool TryParse(System.ReadOnlySpan s, System.Globalization.NumberStyles style, System.IFormatProvider? provider, out System.UInt128 result) { throw null; } public static bool TryParse(System.ReadOnlySpan s, System.IFormatProvider? provider, out System.UInt128 result) { throw null; } public static bool TryParse(System.ReadOnlySpan s, out System.UInt128 result) { throw null; } @@ -6445,7 +6457,7 @@ public TypeUnloadedException(string? message, System.Exception? innerException) public static bool TryParse([System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] string? s, out System.UInt128 result) { throw null; } } [System.CLSCompliantAttribute(false)] - public readonly partial struct UInt16 : System.IComparable, System.IComparable, System.IConvertible, System.IEquatable, System.IFormattable, System.IParsable, System.ISpanFormattable, System.ISpanParsable, System.Numerics.IAdditionOperators, System.Numerics.IAdditiveIdentity, System.Numerics.IBinaryInteger, System.Numerics.IBinaryNumber, System.Numerics.IBitwiseOperators, System.Numerics.IComparisonOperators, System.Numerics.IDecrementOperators, System.Numerics.IDivisionOperators, System.Numerics.IEqualityOperators, System.Numerics.IIncrementOperators, System.Numerics.IMinMaxValue, System.Numerics.IModulusOperators, System.Numerics.IMultiplicativeIdentity, System.Numerics.IMultiplyOperators, System.Numerics.INumber, System.Numerics.INumberBase, System.Numerics.IShiftOperators, System.Numerics.ISubtractionOperators, System.Numerics.IUnaryNegationOperators, System.Numerics.IUnaryPlusOperators, System.Numerics.IUnsignedNumber + public readonly partial struct UInt16 : System.IComparable, System.IComparable, System.IConvertible, System.IEquatable, System.IFormattable, System.IParsable, System.ISpanFormattable, System.ISpanParsable, System.Numerics.IAdditionOperators, System.Numerics.IAdditiveIdentity, System.Numerics.IBinaryInteger, System.Numerics.IBinaryNumber, System.Numerics.IBitwiseOperators, System.Numerics.IComparisonOperators, System.Numerics.IDecrementOperators, System.Numerics.IDivisionOperators, System.Numerics.IEqualityOperators, System.Numerics.IIncrementOperators, System.Numerics.IMinMaxValue, System.Numerics.IModulusOperators, System.Numerics.IMultiplicativeIdentity, System.Numerics.IMultiplyOperators, System.Numerics.INumber, System.Numerics.INumberBase, System.Numerics.IShiftOperators, System.Numerics.ISubtractionOperators, System.Numerics.IUnaryNegationOperators, System.Numerics.IUnaryPlusOperators, System.Numerics.IUnsignedNumber, System.IUtf8SpanFormattable { private readonly ushort _dummyPrimitive; public const ushort MaxValue = (ushort)65535; @@ -6570,6 +6582,7 @@ public TypeUnloadedException(string? message, System.Exception? innerException) public string ToString([System.Diagnostics.CodeAnalysis.StringSyntaxAttribute("NumericFormat")] string? format, System.IFormatProvider? provider) { throw null; } public static ushort TrailingZeroCount(ushort value) { throw null; } public bool TryFormat(System.Span destination, out int charsWritten, [System.Diagnostics.CodeAnalysis.StringSyntaxAttribute("NumericFormat")] System.ReadOnlySpan format = default(System.ReadOnlySpan), System.IFormatProvider? provider = null) { throw null; } + bool System.IUtf8SpanFormattable.TryFormat(System.Span utf8Destination, out int bytesWritten, [System.Diagnostics.CodeAnalysis.StringSyntaxAttribute("NumericFormat")] System.ReadOnlySpan format, System.IFormatProvider? provider) { throw null; } public static bool TryParse(System.ReadOnlySpan s, System.Globalization.NumberStyles style, System.IFormatProvider? provider, out ushort result) { throw null; } public static bool TryParse(System.ReadOnlySpan s, System.IFormatProvider? provider, out ushort result) { throw null; } public static bool TryParse(System.ReadOnlySpan s, out ushort result) { throw null; } @@ -6578,7 +6591,7 @@ public TypeUnloadedException(string? message, System.Exception? innerException) public static bool TryParse([System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] string? s, out ushort result) { throw null; } } [System.CLSCompliantAttribute(false)] - public readonly partial struct UInt32 : System.IComparable, System.IComparable, System.IConvertible, System.IEquatable, System.IFormattable, System.IParsable, System.ISpanFormattable, System.ISpanParsable, System.Numerics.IAdditionOperators, System.Numerics.IAdditiveIdentity, System.Numerics.IBinaryInteger, System.Numerics.IBinaryNumber, System.Numerics.IBitwiseOperators, System.Numerics.IComparisonOperators, System.Numerics.IDecrementOperators, System.Numerics.IDivisionOperators, System.Numerics.IEqualityOperators, System.Numerics.IIncrementOperators, System.Numerics.IMinMaxValue, System.Numerics.IModulusOperators, System.Numerics.IMultiplicativeIdentity, System.Numerics.IMultiplyOperators, System.Numerics.INumber, System.Numerics.INumberBase, System.Numerics.IShiftOperators, System.Numerics.ISubtractionOperators, System.Numerics.IUnaryNegationOperators, System.Numerics.IUnaryPlusOperators, System.Numerics.IUnsignedNumber + public readonly partial struct UInt32 : System.IComparable, System.IComparable, System.IConvertible, System.IEquatable, System.IFormattable, System.IParsable, System.ISpanFormattable, System.ISpanParsable, System.Numerics.IAdditionOperators, System.Numerics.IAdditiveIdentity, System.Numerics.IBinaryInteger, System.Numerics.IBinaryNumber, System.Numerics.IBitwiseOperators, System.Numerics.IComparisonOperators, System.Numerics.IDecrementOperators, System.Numerics.IDivisionOperators, System.Numerics.IEqualityOperators, System.Numerics.IIncrementOperators, System.Numerics.IMinMaxValue, System.Numerics.IModulusOperators, System.Numerics.IMultiplicativeIdentity, System.Numerics.IMultiplyOperators, System.Numerics.INumber, System.Numerics.INumberBase, System.Numerics.IShiftOperators, System.Numerics.ISubtractionOperators, System.Numerics.IUnaryNegationOperators, System.Numerics.IUnaryPlusOperators, System.Numerics.IUnsignedNumber, System.IUtf8SpanFormattable { private readonly uint _dummyPrimitive; public const uint MaxValue = (uint)4294967295; @@ -6703,6 +6716,7 @@ public TypeUnloadedException(string? message, System.Exception? innerException) public string ToString([System.Diagnostics.CodeAnalysis.StringSyntaxAttribute("NumericFormat")] string? format, System.IFormatProvider? provider) { throw null; } public static uint TrailingZeroCount(uint value) { throw null; } public bool TryFormat(System.Span destination, out int charsWritten, [System.Diagnostics.CodeAnalysis.StringSyntaxAttribute("NumericFormat")] System.ReadOnlySpan format = default(System.ReadOnlySpan), System.IFormatProvider? provider = null) { throw null; } + bool System.IUtf8SpanFormattable.TryFormat(System.Span utf8Destination, out int bytesWritten, [System.Diagnostics.CodeAnalysis.StringSyntaxAttribute("NumericFormat")] System.ReadOnlySpan format, System.IFormatProvider? provider) { throw null; } public static bool TryParse(System.ReadOnlySpan s, System.Globalization.NumberStyles style, System.IFormatProvider? provider, out uint result) { throw null; } public static bool TryParse(System.ReadOnlySpan s, System.IFormatProvider? provider, out uint result) { throw null; } public static bool TryParse(System.ReadOnlySpan s, out uint result) { throw null; } @@ -6711,7 +6725,7 @@ public TypeUnloadedException(string? message, System.Exception? innerException) public static bool TryParse([System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] string? s, out uint result) { throw null; } } [System.CLSCompliantAttribute(false)] - public readonly partial struct UInt64 : System.IComparable, System.IComparable, System.IConvertible, System.IEquatable, System.IFormattable, System.IParsable, System.ISpanFormattable, System.ISpanParsable, System.Numerics.IAdditionOperators, System.Numerics.IAdditiveIdentity, System.Numerics.IBinaryInteger, System.Numerics.IBinaryNumber, System.Numerics.IBitwiseOperators, System.Numerics.IComparisonOperators, System.Numerics.IDecrementOperators, System.Numerics.IDivisionOperators, System.Numerics.IEqualityOperators, System.Numerics.IIncrementOperators, System.Numerics.IMinMaxValue, System.Numerics.IModulusOperators, System.Numerics.IMultiplicativeIdentity, System.Numerics.IMultiplyOperators, System.Numerics.INumber, System.Numerics.INumberBase, System.Numerics.IShiftOperators, System.Numerics.ISubtractionOperators, System.Numerics.IUnaryNegationOperators, System.Numerics.IUnaryPlusOperators, System.Numerics.IUnsignedNumber + public readonly partial struct UInt64 : System.IComparable, System.IComparable, System.IConvertible, System.IEquatable, System.IFormattable, System.IParsable, System.ISpanFormattable, System.ISpanParsable, System.Numerics.IAdditionOperators, System.Numerics.IAdditiveIdentity, System.Numerics.IBinaryInteger, System.Numerics.IBinaryNumber, System.Numerics.IBitwiseOperators, System.Numerics.IComparisonOperators, System.Numerics.IDecrementOperators, System.Numerics.IDivisionOperators, System.Numerics.IEqualityOperators, System.Numerics.IIncrementOperators, System.Numerics.IMinMaxValue, System.Numerics.IModulusOperators, System.Numerics.IMultiplicativeIdentity, System.Numerics.IMultiplyOperators, System.Numerics.INumber, System.Numerics.INumberBase, System.Numerics.IShiftOperators, System.Numerics.ISubtractionOperators, System.Numerics.IUnaryNegationOperators, System.Numerics.IUnaryPlusOperators, System.Numerics.IUnsignedNumber, System.IUtf8SpanFormattable { private readonly ulong _dummyPrimitive; public const ulong MaxValue = (ulong)18446744073709551615; @@ -6836,6 +6850,7 @@ public TypeUnloadedException(string? message, System.Exception? innerException) public string ToString([System.Diagnostics.CodeAnalysis.StringSyntaxAttribute("NumericFormat")] string? format, System.IFormatProvider? provider) { throw null; } public static ulong TrailingZeroCount(ulong value) { throw null; } public bool TryFormat(System.Span destination, out int charsWritten, [System.Diagnostics.CodeAnalysis.StringSyntaxAttribute("NumericFormat")] System.ReadOnlySpan format = default(System.ReadOnlySpan), System.IFormatProvider? provider = null) { throw null; } + bool System.IUtf8SpanFormattable.TryFormat(System.Span utf8Destination, out int bytesWritten, [System.Diagnostics.CodeAnalysis.StringSyntaxAttribute("NumericFormat")] System.ReadOnlySpan format, System.IFormatProvider? provider) { throw null; } public static bool TryParse(System.ReadOnlySpan s, System.Globalization.NumberStyles style, System.IFormatProvider? provider, out ulong result) { throw null; } public static bool TryParse(System.ReadOnlySpan s, System.IFormatProvider? provider, out ulong result) { throw null; } public static bool TryParse(System.ReadOnlySpan s, out ulong result) { throw null; } @@ -6844,7 +6859,7 @@ public TypeUnloadedException(string? message, System.Exception? innerException) public static bool TryParse([System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] string? s, out ulong result) { throw null; } } [System.CLSCompliantAttribute(false)] - public readonly partial struct UIntPtr : System.IComparable, System.IComparable, System.IEquatable, System.IFormattable, System.IParsable, System.ISpanFormattable, System.ISpanParsable, System.Numerics.IAdditionOperators, System.Numerics.IAdditiveIdentity, System.Numerics.IBinaryInteger, System.Numerics.IBinaryNumber, System.Numerics.IBitwiseOperators, System.Numerics.IComparisonOperators, System.Numerics.IDecrementOperators, System.Numerics.IDivisionOperators, System.Numerics.IEqualityOperators, System.Numerics.IIncrementOperators, System.Numerics.IMinMaxValue, System.Numerics.IModulusOperators, System.Numerics.IMultiplicativeIdentity, System.Numerics.IMultiplyOperators, System.Numerics.INumber, System.Numerics.INumberBase, System.Numerics.IShiftOperators, System.Numerics.ISubtractionOperators, System.Numerics.IUnaryNegationOperators, System.Numerics.IUnaryPlusOperators, System.Numerics.IUnsignedNumber, System.Runtime.Serialization.ISerializable + public readonly partial struct UIntPtr : System.IComparable, System.IComparable, System.IEquatable, System.IFormattable, System.IParsable, System.ISpanFormattable, System.ISpanParsable, System.Numerics.IAdditionOperators, System.Numerics.IAdditiveIdentity, System.Numerics.IBinaryInteger, System.Numerics.IBinaryNumber, System.Numerics.IBitwiseOperators, System.Numerics.IComparisonOperators, System.Numerics.IDecrementOperators, System.Numerics.IDivisionOperators, System.Numerics.IEqualityOperators, System.Numerics.IIncrementOperators, System.Numerics.IMinMaxValue, System.Numerics.IModulusOperators, System.Numerics.IMultiplicativeIdentity, System.Numerics.IMultiplyOperators, System.Numerics.INumber, System.Numerics.INumberBase, System.Numerics.IShiftOperators, System.Numerics.ISubtractionOperators, System.Numerics.IUnaryNegationOperators, System.Numerics.IUnaryPlusOperators, System.Numerics.IUnsignedNumber, System.Runtime.Serialization.ISerializable, System.IUtf8SpanFormattable { private readonly unsafe void* _dummyPrimitive; public static readonly nuint Zero; @@ -6972,6 +6987,7 @@ void System.Runtime.Serialization.ISerializable.GetObjectData(System.Runtime.Ser public ulong ToUInt64() { throw null; } public static nuint TrailingZeroCount(nuint value) { throw null; } public bool TryFormat(System.Span destination, out int charsWritten, [System.Diagnostics.CodeAnalysis.StringSyntaxAttribute("NumericFormat")] System.ReadOnlySpan format = default(System.ReadOnlySpan), System.IFormatProvider? provider = null) { throw null; } + bool System.IUtf8SpanFormattable.TryFormat(System.Span utf8Destination, out int bytesWritten, [System.Diagnostics.CodeAnalysis.StringSyntaxAttribute("NumericFormat")] System.ReadOnlySpan format, System.IFormatProvider? provider) { throw null; } public static bool TryParse(System.ReadOnlySpan s, System.Globalization.NumberStyles style, System.IFormatProvider? provider, out nuint result) { throw null; } public static bool TryParse(System.ReadOnlySpan s, System.IFormatProvider? provider, out nuint result) { throw null; } public static bool TryParse(System.ReadOnlySpan s, out nuint result) { throw null; } diff --git a/src/libraries/System.Runtime/tests/System.Runtime.Tests.csproj b/src/libraries/System.Runtime/tests/System.Runtime.Tests.csproj index 994ce15426fef..b4e389485006a 100644 --- a/src/libraries/System.Runtime/tests/System.Runtime.Tests.csproj +++ b/src/libraries/System.Runtime/tests/System.Runtime.Tests.csproj @@ -68,6 +68,7 @@ + diff --git a/src/libraries/System.Runtime/tests/System/BooleanTests.cs b/src/libraries/System.Runtime/tests/System/BooleanTests.cs index 18ef70e5e22f0..835f038afd74d 100644 --- a/src/libraries/System.Runtime/tests/System/BooleanTests.cs +++ b/src/libraries/System.Runtime/tests/System/BooleanTests.cs @@ -185,26 +185,20 @@ public void GetTypeCode_Invoke_ReturnsBoolean() [InlineData(false, "False")] public static void TryFormat(bool i, string expected) { - char[] actual; - int charsWritten; - - // Just right - actual = new char[expected.Length]; - Assert.True(i.TryFormat(actual.AsSpan(), out charsWritten)); - Assert.Equal(expected.Length, charsWritten); - Assert.Equal(expected, new string(actual)); - - // Longer than needed - actual = new char[expected.Length + 1]; - Assert.True(i.TryFormat(actual.AsSpan(), out charsWritten)); - Assert.Equal(expected.Length, charsWritten); - Assert.Equal(expected, new string(actual, 0, charsWritten)); + // Just right and longer than needed + for (int additional = 0; additional < 2; additional++) + { + var actual = new char[expected.Length + additional]; + Assert.True(i.TryFormat(actual.AsSpan(), out int charsWritten)); + Assert.Equal(expected.Length, charsWritten); + Assert.Equal(expected, new string(actual.AsSpan(0, charsWritten))); + } // Too short if (expected.Length > 0) { - actual = new char[expected.Length - 1]; - Assert.False(i.TryFormat(actual.AsSpan(), out charsWritten)); + var actual = new char[expected.Length - 1]; + Assert.False(i.TryFormat(actual.AsSpan(), out int charsWritten)); Assert.Equal(0, charsWritten); } } diff --git a/src/libraries/System.Runtime/tests/System/ByteTests.cs b/src/libraries/System.Runtime/tests/System/ByteTests.cs index f243c4ac06400..94c41f34aa95d 100644 --- a/src/libraries/System.Runtime/tests/System/ByteTests.cs +++ b/src/libraries/System.Runtime/tests/System/ByteTests.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Globalization; +using System.Text; using Xunit; namespace System.Tests @@ -357,45 +358,7 @@ public static void Parse_Span_Invalid(string value, NumberStyles style, IFormatP [Theory] [MemberData(nameof(ToString_TestData))] - public static void TryFormat(byte i, string format, IFormatProvider provider, string expected) - { - char[] actual; - int charsWritten; - - // Just right - actual = new char[expected.Length]; - Assert.True(i.TryFormat(actual.AsSpan(), out charsWritten, format, provider)); - Assert.Equal(expected.Length, charsWritten); - Assert.Equal(expected, new string(actual)); - - // Longer than needed - actual = new char[expected.Length + 1]; - Assert.True(i.TryFormat(actual.AsSpan(), out charsWritten, format, provider)); - Assert.Equal(expected.Length, charsWritten); - Assert.Equal(expected, new string(actual, 0, charsWritten)); - - // Too short - if (expected.Length > 0) - { - actual = new char[expected.Length - 1]; - Assert.False(i.TryFormat(actual.AsSpan(), out charsWritten, format, provider)); - Assert.Equal(0, charsWritten); - } - - if (format != null) - { - // Upper format - actual = new char[expected.Length]; - Assert.True(i.TryFormat(actual.AsSpan(), out charsWritten, format.ToUpperInvariant(), provider)); - Assert.Equal(expected.Length, charsWritten); - Assert.Equal(expected.ToUpperInvariant(), new string(actual)); - - // Lower format - actual = new char[expected.Length]; - Assert.True(i.TryFormat(actual.AsSpan(), out charsWritten, format.ToLowerInvariant(), provider)); - Assert.Equal(expected.Length, charsWritten); - Assert.Equal(expected.ToLowerInvariant(), new string(actual)); - } - } + public static void TryFormat(byte i, string format, IFormatProvider provider, string expected) => + NumberFormatTestHelper.TryFormatNumberTest(i, format, provider, expected); } } diff --git a/src/libraries/System.Runtime/tests/System/DecimalTests.cs b/src/libraries/System.Runtime/tests/System/DecimalTests.cs index a40c21787bfb7..e4ef2ace97a34 100644 --- a/src/libraries/System.Runtime/tests/System/DecimalTests.cs +++ b/src/libraries/System.Runtime/tests/System/DecimalTests.cs @@ -2368,43 +2368,7 @@ public static void TryFormat() try { - char[] actual; - int charsWritten; - - // Just right - actual = new char[localExpected.Length]; - Assert.True(localI.TryFormat(actual.AsSpan(), out charsWritten, localFormat, localProvider)); - Assert.Equal(localExpected.Length, charsWritten); - Assert.Equal(localExpected, new string(actual)); - - // Longer than needed - actual = new char[localExpected.Length + 1]; - Assert.True(localI.TryFormat(actual.AsSpan(), out charsWritten, localFormat, localProvider)); - Assert.Equal(localExpected.Length, charsWritten); - Assert.Equal(localExpected, new string(actual, 0, charsWritten)); - - // Too short - if (localExpected.Length > 0) - { - actual = new char[localExpected.Length - 1]; - Assert.False(localI.TryFormat(actual.AsSpan(), out charsWritten, localFormat, localProvider)); - Assert.Equal(0, charsWritten); - } - - if (localFormat != null) - { - // Upper localFormat - actual = new char[localExpected.Length]; - Assert.True(localI.TryFormat(actual.AsSpan(), out charsWritten, localFormat.ToUpperInvariant(), localProvider)); - Assert.Equal(localExpected.Length, charsWritten); - Assert.Equal(localExpected.ToUpperInvariant(), new string(actual)); - - // Lower format - actual = new char[localExpected.Length]; - Assert.True(localI.TryFormat(actual.AsSpan(), out charsWritten, localFormat.ToLowerInvariant(), localProvider)); - Assert.Equal(localExpected.Length, charsWritten); - Assert.Equal(localExpected.ToLowerInvariant(), new string(actual)); - } + NumberFormatTestHelper.TryFormatNumberTest(localI, localFormat, localProvider, localExpected); } catch (Exception exc) { diff --git a/src/libraries/System.Runtime/tests/System/DoubleTests.cs b/src/libraries/System.Runtime/tests/System/DoubleTests.cs index cff858f4e357d..b2a93df11b480 100644 --- a/src/libraries/System.Runtime/tests/System/DoubleTests.cs +++ b/src/libraries/System.Runtime/tests/System/DoubleTests.cs @@ -941,28 +941,7 @@ public static void TryFormat() try { - char[] actual; - int charsWritten; - - // Just right - actual = new char[localExpected.Length]; - Assert.True(localI.TryFormat(actual.AsSpan(), out charsWritten, localFormat, localProvider)); - Assert.Equal(localExpected.Length, charsWritten); - Assert.Equal(localExpected, new string(actual)); - - // Longer than needed - actual = new char[localExpected.Length + 1]; - Assert.True(localI.TryFormat(actual.AsSpan(), out charsWritten, localFormat, localProvider)); - Assert.Equal(localExpected.Length, charsWritten); - Assert.Equal(localExpected, new string(actual, 0, charsWritten)); - - // Too short - if (localExpected.Length > 0) - { - actual = new char[localExpected.Length - 1]; - Assert.False(localI.TryFormat(actual.AsSpan(), out charsWritten, localFormat, localProvider)); - Assert.Equal(0, charsWritten); - } + NumberFormatTestHelper.TryFormatNumberTest(localI, localFormat, localProvider, localExpected, formatCasingMatchesOutput: false); } catch (Exception exc) { diff --git a/src/libraries/System.Runtime/tests/System/HalfTests.cs b/src/libraries/System.Runtime/tests/System/HalfTests.cs index 19f1fc0793d13..cae21d055f4d4 100644 --- a/src/libraries/System.Runtime/tests/System/HalfTests.cs +++ b/src/libraries/System.Runtime/tests/System/HalfTests.cs @@ -1001,28 +1001,7 @@ public static void TryFormat() try { - char[] actual; - int charsWritten; - - // Just right - actual = new char[localExpected.Length]; - Assert.True(localI.TryFormat(actual.AsSpan(), out charsWritten, localFormat, localProvider)); - Assert.Equal(localExpected.Length, charsWritten); - Assert.Equal(localExpected, new string(actual)); - - // Longer than needed - actual = new char[localExpected.Length + 1]; - Assert.True(localI.TryFormat(actual.AsSpan(), out charsWritten, localFormat, localProvider)); - Assert.Equal(localExpected.Length, charsWritten); - Assert.Equal(localExpected, new string(actual, 0, charsWritten)); - - // Too short - if (localExpected.Length > 0) - { - actual = new char[localExpected.Length - 1]; - Assert.False(localI.TryFormat(actual.AsSpan(), out charsWritten, localFormat, localProvider)); - Assert.Equal(0, charsWritten); - } + NumberFormatTestHelper.TryFormatNumberTest(localI, localFormat, localProvider, localExpected, formatCasingMatchesOutput: false); } catch (Exception exc) { diff --git a/src/libraries/System.Runtime/tests/System/Int128Tests.cs b/src/libraries/System.Runtime/tests/System/Int128Tests.cs index 3be0daacfaf3d..1161a6d763a79 100644 --- a/src/libraries/System.Runtime/tests/System/Int128Tests.cs +++ b/src/libraries/System.Runtime/tests/System/Int128Tests.cs @@ -410,46 +410,8 @@ public static void Parse_Span_Invalid(string value, NumberStyles style, IFormatP [Theory] [MemberData(nameof(ToString_TestData))] - public static void TryFormat(Int128 i, string format, IFormatProvider provider, string expected) - { - char[] actual; - int charsWritten; - - // Just right - actual = new char[expected.Length]; - Assert.True(i.TryFormat(actual.AsSpan(), out charsWritten, format, provider)); - Assert.Equal(expected.Length, charsWritten); - Assert.Equal(expected, new string(actual)); - - // Longer than needed - actual = new char[expected.Length + 1]; - Assert.True(i.TryFormat(actual.AsSpan(), out charsWritten, format, provider)); - Assert.Equal(expected.Length, charsWritten); - Assert.Equal(expected, new string(actual, 0, charsWritten)); - - // Too short - if (expected.Length > 0) - { - actual = new char[expected.Length - 1]; - Assert.False(i.TryFormat(actual.AsSpan(), out charsWritten, format, provider)); - Assert.Equal(0, charsWritten); - } - - if (format is not null) - { - // Upper format - actual = new char[expected.Length]; - Assert.True(i.TryFormat(actual.AsSpan(), out charsWritten, format.ToUpperInvariant(), provider)); - Assert.Equal(expected.Length, charsWritten); - Assert.Equal(expected.ToUpperInvariant(), new string(actual)); - - // Lower format - actual = new char[expected.Length]; - Assert.True(i.TryFormat(actual.AsSpan(), out charsWritten, format.ToLowerInvariant(), provider)); - Assert.Equal(expected.Length, charsWritten); - Assert.Equal(expected.ToLowerInvariant(), new string(actual)); - } - } + public static void TryFormat(Int128 i, string format, IFormatProvider provider, string expected) => + NumberFormatTestHelper.TryFormatNumberTest(i, format, provider, expected); [Fact] public static void TestNegativeNumberParsingWithHyphen() diff --git a/src/libraries/System.Runtime/tests/System/Int16Tests.cs b/src/libraries/System.Runtime/tests/System/Int16Tests.cs index 053b99c82b8b9..60ffdcac3158a 100644 --- a/src/libraries/System.Runtime/tests/System/Int16Tests.cs +++ b/src/libraries/System.Runtime/tests/System/Int16Tests.cs @@ -381,45 +381,7 @@ public static void Parse_Span_Invalid(string value, NumberStyles style, IFormatP [Theory] [MemberData(nameof(ToString_TestData))] - public static void TryFormat(short i, string format, IFormatProvider provider, string expected) - { - char[] actual; - int charsWritten; - - // Just right - actual = new char[expected.Length]; - Assert.True(i.TryFormat(actual.AsSpan(), out charsWritten, format, provider)); - Assert.Equal(expected.Length, charsWritten); - Assert.Equal(expected, new string(actual)); - - // Longer than needed - actual = new char[expected.Length + 1]; - Assert.True(i.TryFormat(actual.AsSpan(), out charsWritten, format, provider)); - Assert.Equal(expected.Length, charsWritten); - Assert.Equal(expected, new string(actual, 0, charsWritten)); - - // Too short - if (expected.Length > 0) - { - actual = new char[expected.Length - 1]; - Assert.False(i.TryFormat(actual.AsSpan(), out charsWritten, format, provider)); - Assert.Equal(0, charsWritten); - } - - if (format != null) - { - // Upper format - actual = new char[expected.Length]; - Assert.True(i.TryFormat(actual.AsSpan(), out charsWritten, format.ToUpperInvariant(), provider)); - Assert.Equal(expected.Length, charsWritten); - Assert.Equal(expected.ToUpperInvariant(), new string(actual)); - - // Lower format - actual = new char[expected.Length]; - Assert.True(i.TryFormat(actual.AsSpan(), out charsWritten, format.ToLowerInvariant(), provider)); - Assert.Equal(expected.Length, charsWritten); - Assert.Equal(expected.ToLowerInvariant(), new string(actual)); - } - } + public static void TryFormat(short i, string format, IFormatProvider provider, string expected) => + NumberFormatTestHelper.TryFormatNumberTest(i, format, provider, expected); } } diff --git a/src/libraries/System.Runtime/tests/System/Int32Tests.cs b/src/libraries/System.Runtime/tests/System/Int32Tests.cs index 6666c00727d8a..e619fff5ea5dc 100644 --- a/src/libraries/System.Runtime/tests/System/Int32Tests.cs +++ b/src/libraries/System.Runtime/tests/System/Int32Tests.cs @@ -796,46 +796,8 @@ public static void ToString_C_EmptyPercentGroup_Success() [Theory] [MemberData(nameof(ToString_TestData))] - public static void TryFormat(int i, string format, IFormatProvider provider, string expected) - { - char[] actual; - int charsWritten; - - // Just right - actual = new char[expected.Length]; - Assert.True(i.TryFormat(actual.AsSpan(), out charsWritten, format, provider)); - Assert.Equal(expected.Length, charsWritten); - Assert.Equal(expected, new string(actual)); - - // Longer than needed - actual = new char[expected.Length + 1]; - Assert.True(i.TryFormat(actual.AsSpan(), out charsWritten, format, provider)); - Assert.Equal(expected.Length, charsWritten); - Assert.Equal(expected, new string(actual, 0, charsWritten)); - - // Too short - if (expected.Length > 0) - { - actual = new char[expected.Length - 1]; - Assert.False(i.TryFormat(actual.AsSpan(), out charsWritten, format, provider)); - Assert.Equal(0, charsWritten); - } - - if (format != null) - { - // Upper format - actual = new char[expected.Length]; - Assert.True(i.TryFormat(actual.AsSpan(), out charsWritten, format.ToUpperInvariant(), provider)); - Assert.Equal(expected.Length, charsWritten); - Assert.Equal(expected.ToUpperInvariant(), new string(actual)); - - // Lower format - actual = new char[expected.Length]; - Assert.True(i.TryFormat(actual.AsSpan(), out charsWritten, format.ToLowerInvariant(), provider)); - Assert.Equal(expected.Length, charsWritten); - Assert.Equal(expected.ToLowerInvariant(), new string(actual)); - } - } + public static void TryFormat(int i, string format, IFormatProvider provider, string expected) => + NumberFormatTestHelper.TryFormatNumberTest(i, format, provider, expected); [Fact] public static void TestNegativeNumberParsingWithHyphen() diff --git a/src/libraries/System.Runtime/tests/System/Int64Tests.cs b/src/libraries/System.Runtime/tests/System/Int64Tests.cs index 1687f16f606b4..052ae58bdd818 100644 --- a/src/libraries/System.Runtime/tests/System/Int64Tests.cs +++ b/src/libraries/System.Runtime/tests/System/Int64Tests.cs @@ -395,46 +395,8 @@ public static void Parse_Span_Invalid(string value, NumberStyles style, IFormatP [Theory] [MemberData(nameof(ToString_TestData))] - public static void TryFormat(long i, string format, IFormatProvider provider, string expected) - { - char[] actual; - int charsWritten; - - // Just right - actual = new char[expected.Length]; - Assert.True(i.TryFormat(actual.AsSpan(), out charsWritten, format, provider)); - Assert.Equal(expected.Length, charsWritten); - Assert.Equal(expected, new string(actual)); - - // Longer than needed - actual = new char[expected.Length + 1]; - Assert.True(i.TryFormat(actual.AsSpan(), out charsWritten, format, provider)); - Assert.Equal(expected.Length, charsWritten); - Assert.Equal(expected, new string(actual, 0, charsWritten)); - - // Too short - if (expected.Length > 0) - { - actual = new char[expected.Length - 1]; - Assert.False(i.TryFormat(actual.AsSpan(), out charsWritten, format, provider)); - Assert.Equal(0, charsWritten); - } - - if (format != null) - { - // Upper format - actual = new char[expected.Length]; - Assert.True(i.TryFormat(actual.AsSpan(), out charsWritten, format.ToUpperInvariant(), provider)); - Assert.Equal(expected.Length, charsWritten); - Assert.Equal(expected.ToUpperInvariant(), new string(actual)); - - // Lower format - actual = new char[expected.Length]; - Assert.True(i.TryFormat(actual.AsSpan(), out charsWritten, format.ToLowerInvariant(), provider)); - Assert.Equal(expected.Length, charsWritten); - Assert.Equal(expected.ToLowerInvariant(), new string(actual)); - } - } + public static void TryFormat(long i, string format, IFormatProvider provider, string expected) => + NumberFormatTestHelper.TryFormatNumberTest(i, format, provider, expected); [Fact] public static void TestNegativeNumberParsingWithHyphen() diff --git a/src/libraries/System.Runtime/tests/System/IntPtrTests.cs b/src/libraries/System.Runtime/tests/System/IntPtrTests.cs index 79f6bc1ca7400..24cc1d3d85856 100644 --- a/src/libraries/System.Runtime/tests/System/IntPtrTests.cs +++ b/src/libraries/System.Runtime/tests/System/IntPtrTests.cs @@ -950,45 +950,7 @@ public static void ToString_C_EmptyPercentGroup_Success() [Theory] [MemberData(nameof(ToString_TestData))] - public static void TryFormat(nint i, string format, IFormatProvider provider, string expected) - { - char[] actual; - int charsWritten; - - // Just right - actual = new char[expected.Length]; - Assert.True(i.TryFormat(actual.AsSpan(), out charsWritten, format, provider)); - Assert.Equal(expected.Length, charsWritten); - Assert.Equal(expected, new string(actual)); - - // Longer than needed - actual = new char[expected.Length + 1]; - Assert.True(i.TryFormat(actual.AsSpan(), out charsWritten, format, provider)); - Assert.Equal(expected.Length, charsWritten); - Assert.Equal(expected, new string(actual, 0, charsWritten)); - - // Too short - if (expected.Length > 0) - { - actual = new char[expected.Length - 1]; - Assert.False(i.TryFormat(actual.AsSpan(), out charsWritten, format, provider)); - Assert.Equal(0, charsWritten); - } - - if (format != null) - { - // Upper format - actual = new char[expected.Length]; - Assert.True(i.TryFormat(actual.AsSpan(), out charsWritten, format.ToUpperInvariant(), provider)); - Assert.Equal(expected.Length, charsWritten); - Assert.Equal(expected.ToUpperInvariant(), new string(actual)); - - // Lower format - actual = new char[expected.Length]; - Assert.True(i.TryFormat(actual.AsSpan(), out charsWritten, format.ToLowerInvariant(), provider)); - Assert.Equal(expected.Length, charsWritten); - Assert.Equal(expected.ToLowerInvariant(), new string(actual)); - } - } + public static void TryFormat(nint i, string format, IFormatProvider provider, string expected) => + NumberFormatTestHelper.TryFormatNumberTest(i, format, provider, expected); } } diff --git a/src/libraries/System.Runtime/tests/System/NumberFormatTestHelper.cs b/src/libraries/System.Runtime/tests/System/NumberFormatTestHelper.cs new file mode 100644 index 0000000000000..6cfb12928cd08 --- /dev/null +++ b/src/libraries/System.Runtime/tests/System/NumberFormatTestHelper.cs @@ -0,0 +1,91 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Text; +using Xunit; + +namespace System.Tests +{ + internal static class NumberFormatTestHelper + { + internal static void TryFormatNumberTest(T i, string format, IFormatProvider provider, string expected, bool formatCasingMatchesOutput = true) where T : ISpanFormattable, IUtf8SpanFormattable + { + // UTF16 + { + char[] actual; + int charsWritten; + + // Just right and longer than needed + for (int additional = 0; additional < 2; additional++) + { + actual = new char[expected.Length + additional]; + Assert.True(i.TryFormat(actual.AsSpan(), out charsWritten, format, provider)); + Assert.Equal(expected.Length, charsWritten); + Assert.Equal(expected, new string(actual.AsSpan(0, charsWritten))); + } + + // Too short + if (expected.Length > 0) + { + actual = new char[expected.Length - 1]; + Assert.False(i.TryFormat(actual.AsSpan(), out charsWritten, format, provider)); + Assert.Equal(0, charsWritten); + } + + if (formatCasingMatchesOutput && format != null) + { + // Upper format + actual = new char[expected.Length]; + Assert.True(i.TryFormat(actual.AsSpan(), out charsWritten, format.ToUpperInvariant(), provider)); + Assert.Equal(expected.Length, charsWritten); + Assert.Equal(expected.ToUpperInvariant(), new string(actual)); + + // Lower format + actual = new char[expected.Length]; + Assert.True(i.TryFormat(actual.AsSpan(), out charsWritten, format.ToLowerInvariant(), provider)); + Assert.Equal(expected.Length, charsWritten); + Assert.Equal(expected.ToLowerInvariant(), new string(actual)); + } + } + + // UTF8 + { + byte[] actual; + int charsWritten; + int expectedLength = Encoding.UTF8.GetByteCount(expected); + + // Just right and longer than needed + for (int additional = 0; additional < 2; additional++) + { + actual = new byte[expectedLength + additional]; + Assert.True(i.TryFormat(actual.AsSpan(), out charsWritten, format, provider)); + Assert.Equal(expectedLength, charsWritten); + Assert.Equal(expected, Encoding.UTF8.GetString(actual.AsSpan(0, charsWritten))); + } + + // Too short + if (expectedLength > 0) + { + actual = new byte[expectedLength - 1]; + Assert.False(i.TryFormat(actual.AsSpan(), out charsWritten, format, provider)); + Assert.Equal(0, charsWritten); + } + + if (formatCasingMatchesOutput && format != null) + { + // Upper format + actual = new byte[expectedLength]; + Assert.True(i.TryFormat(actual.AsSpan(), out charsWritten, format.ToUpperInvariant(), provider)); + Assert.Equal(expectedLength, charsWritten); + Assert.Equal(expected.ToUpperInvariant(), Encoding.UTF8.GetString(actual.AsSpan(0, charsWritten))); + + // Lower format + actual = new byte[expectedLength]; + Assert.True(i.TryFormat(actual.AsSpan(), out charsWritten, format.ToLowerInvariant(), provider)); + Assert.Equal(expectedLength, charsWritten); + Assert.Equal(expected.ToLowerInvariant(), Encoding.UTF8.GetString(actual.AsSpan(0, charsWritten))); + } + } + } + } +} diff --git a/src/libraries/System.Runtime/tests/System/SByteTests.cs b/src/libraries/System.Runtime/tests/System/SByteTests.cs index 415b5662a1b74..7a2b9b946f4b7 100644 --- a/src/libraries/System.Runtime/tests/System/SByteTests.cs +++ b/src/libraries/System.Runtime/tests/System/SByteTests.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Globalization; +using System.Text; using Xunit; namespace System.Tests @@ -374,45 +375,7 @@ public static void Parse_Span_Invalid(string value, NumberStyles style, IFormatP [Theory] [MemberData(nameof(ToString_TestData))] - public static void TryFormat(sbyte i, string format, IFormatProvider provider, string expected) - { - char[] actual; - int charsWritten; - - // Just right - actual = new char[expected.Length]; - Assert.True(i.TryFormat(actual.AsSpan(), out charsWritten, format, provider)); - Assert.Equal(expected.Length, charsWritten); - Assert.Equal(expected, new string(actual)); - - // Longer than needed - actual = new char[expected.Length + 1]; - Assert.True(i.TryFormat(actual.AsSpan(), out charsWritten, format, provider)); - Assert.Equal(expected.Length, charsWritten); - Assert.Equal(expected, new string(actual, 0, charsWritten)); - - // Too short - if (expected.Length > 0) - { - actual = new char[expected.Length - 1]; - Assert.False(i.TryFormat(actual.AsSpan(), out charsWritten, format, provider)); - Assert.Equal(0, charsWritten); - } - - if (format != null) - { - // Upper format - actual = new char[expected.Length]; - Assert.True(i.TryFormat(actual.AsSpan(), out charsWritten, format.ToUpperInvariant(), provider)); - Assert.Equal(expected.Length, charsWritten); - Assert.Equal(expected.ToUpperInvariant(), new string(actual)); - - // Lower format - actual = new char[expected.Length]; - Assert.True(i.TryFormat(actual.AsSpan(), out charsWritten, format.ToLowerInvariant(), provider)); - Assert.Equal(expected.Length, charsWritten); - Assert.Equal(expected.ToLowerInvariant(), new string(actual)); - } - } + public static void TryFormat(sbyte i, string format, IFormatProvider provider, string expected) => + NumberFormatTestHelper.TryFormatNumberTest(i, format, provider, expected); } } diff --git a/src/libraries/System.Runtime/tests/System/SingleTests.cs b/src/libraries/System.Runtime/tests/System/SingleTests.cs index 54db87a2cb28f..07414d8759668 100644 --- a/src/libraries/System.Runtime/tests/System/SingleTests.cs +++ b/src/libraries/System.Runtime/tests/System/SingleTests.cs @@ -863,28 +863,7 @@ public static void TryFormat() try { - char[] actual; - int charsWritten; - - // Just right - actual = new char[localExpected.Length]; - Assert.True(localI.TryFormat(actual.AsSpan(), out charsWritten, localFormat, localProvider)); - Assert.Equal(localExpected.Length, charsWritten); - Assert.Equal(localExpected, new string(actual)); - - // Longer than needed - actual = new char[localExpected.Length + 1]; - Assert.True(localI.TryFormat(actual.AsSpan(), out charsWritten, localFormat, localProvider)); - Assert.Equal(localExpected.Length, charsWritten); - Assert.Equal(localExpected, new string(actual, 0, charsWritten)); - - // Too short - if (localExpected.Length > 0) - { - actual = new char[localExpected.Length - 1]; - Assert.False(localI.TryFormat(actual.AsSpan(), out charsWritten, localFormat, localProvider)); - Assert.Equal(0, charsWritten); - } + NumberFormatTestHelper.TryFormatNumberTest(localI, localFormat, localProvider, localExpected, formatCasingMatchesOutput: false); } catch (Exception exc) { diff --git a/src/libraries/System.Runtime/tests/System/UInt128Tests.cs b/src/libraries/System.Runtime/tests/System/UInt128Tests.cs index 799cbd39c3534..ba67e15edb155 100644 --- a/src/libraries/System.Runtime/tests/System/UInt128Tests.cs +++ b/src/libraries/System.Runtime/tests/System/UInt128Tests.cs @@ -401,46 +401,8 @@ public static void Parse_Span_Invalid(string value, NumberStyles style, IFormatP [Theory] [MemberData(nameof(ToString_TestData))] - public static void TryFormat(UInt128 i, string format, IFormatProvider provider, string expected) - { - char[] actual; - int charsWritten; - - // Just right - actual = new char[expected.Length]; - Assert.True(i.TryFormat(actual.AsSpan(), out charsWritten, format, provider)); - Assert.Equal(expected.Length, charsWritten); - Assert.Equal(expected, new string(actual)); - - // Longer than needed - actual = new char[expected.Length + 1]; - Assert.True(i.TryFormat(actual.AsSpan(), out charsWritten, format, provider)); - Assert.Equal(expected.Length, charsWritten); - Assert.Equal(expected, new string(actual, 0, charsWritten)); - - // Too short - if (expected.Length > 0) - { - actual = new char[expected.Length - 1]; - Assert.False(i.TryFormat(actual.AsSpan(), out charsWritten, format, provider)); - Assert.Equal(0, charsWritten); - } - - if (format != null) - { - // Upper format - actual = new char[expected.Length]; - Assert.True(i.TryFormat(actual.AsSpan(), out charsWritten, format.ToUpperInvariant(), provider)); - Assert.Equal(expected.Length, charsWritten); - Assert.Equal(expected.ToUpperInvariant(), new string(actual)); - - // Lower format - actual = new char[expected.Length]; - Assert.True(i.TryFormat(actual.AsSpan(), out charsWritten, format.ToLowerInvariant(), provider)); - Assert.Equal(expected.Length, charsWritten); - Assert.Equal(expected.ToLowerInvariant(), new string(actual)); - } - } + public static void TryFormat(UInt128 i, string format, IFormatProvider provider, string expected) => + NumberFormatTestHelper.TryFormatNumberTest(i, format, provider, expected); [Fact] public static void Runtime75416() diff --git a/src/libraries/System.Runtime/tests/System/UInt16Tests.cs b/src/libraries/System.Runtime/tests/System/UInt16Tests.cs index 5482dc96081be..d43738f0e64a5 100644 --- a/src/libraries/System.Runtime/tests/System/UInt16Tests.cs +++ b/src/libraries/System.Runtime/tests/System/UInt16Tests.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Globalization; +using System.Text; using Xunit; namespace System.Tests @@ -354,45 +355,7 @@ public static void Parse_Span_Invalid(string value, NumberStyles style, IFormatP [Theory] [MemberData(nameof(ToString_TestData))] - public static void TryFormat(ushort i, string format, IFormatProvider provider, string expected) - { - char[] actual; - int charsWritten; - - // Just right - actual = new char[expected.Length]; - Assert.True(i.TryFormat(actual.AsSpan(), out charsWritten, format, provider)); - Assert.Equal(expected.Length, charsWritten); - Assert.Equal(expected, new string(actual)); - - // Longer than needed - actual = new char[expected.Length + 1]; - Assert.True(i.TryFormat(actual.AsSpan(), out charsWritten, format, provider)); - Assert.Equal(expected.Length, charsWritten); - Assert.Equal(expected, new string(actual, 0, charsWritten)); - - // Too short - if (expected.Length > 0) - { - actual = new char[expected.Length - 1]; - Assert.False(i.TryFormat(actual.AsSpan(), out charsWritten, format, provider)); - Assert.Equal(0, charsWritten); - } - - if (format != null) - { - // Upper format - actual = new char[expected.Length]; - Assert.True(i.TryFormat(actual.AsSpan(), out charsWritten, format.ToUpperInvariant(), provider)); - Assert.Equal(expected.Length, charsWritten); - Assert.Equal(expected.ToUpperInvariant(), new string(actual)); - - // Lower format - actual = new char[expected.Length]; - Assert.True(i.TryFormat(actual.AsSpan(), out charsWritten, format.ToLowerInvariant(), provider)); - Assert.Equal(expected.Length, charsWritten); - Assert.Equal(expected.ToLowerInvariant(), new string(actual)); - } - } + public static void TryFormat(ushort i, string format, IFormatProvider provider, string expected) => + NumberFormatTestHelper.TryFormatNumberTest(i, format, provider, expected); } } diff --git a/src/libraries/System.Runtime/tests/System/UInt32Tests.cs b/src/libraries/System.Runtime/tests/System/UInt32Tests.cs index 89a0bd8823142..d85edb2f401b3 100644 --- a/src/libraries/System.Runtime/tests/System/UInt32Tests.cs +++ b/src/libraries/System.Runtime/tests/System/UInt32Tests.cs @@ -373,45 +373,7 @@ public static void Parse_Span_Invalid(string value, NumberStyles style, IFormatP [Theory] [MemberData(nameof(ToString_TestData))] - public static void TryFormat(uint i, string format, IFormatProvider provider, string expected) - { - char[] actual; - int charsWritten; - - // Just right - actual = new char[expected.Length]; - Assert.True(i.TryFormat(actual.AsSpan(), out charsWritten, format, provider)); - Assert.Equal(expected.Length, charsWritten); - Assert.Equal(expected, new string(actual)); - - // Longer than needed - actual = new char[expected.Length + 1]; - Assert.True(i.TryFormat(actual.AsSpan(), out charsWritten, format, provider)); - Assert.Equal(expected.Length, charsWritten); - Assert.Equal(expected, new string(actual, 0, charsWritten)); - - // Too short - if (expected.Length > 0) - { - actual = new char[expected.Length - 1]; - Assert.False(i.TryFormat(actual.AsSpan(), out charsWritten, format, provider)); - Assert.Equal(0, charsWritten); - } - - if (format != null) - { - // Upper format - actual = new char[expected.Length]; - Assert.True(i.TryFormat(actual.AsSpan(), out charsWritten, format.ToUpperInvariant(), provider)); - Assert.Equal(expected.Length, charsWritten); - Assert.Equal(expected.ToUpperInvariant(), new string(actual)); - - // Lower format - actual = new char[expected.Length]; - Assert.True(i.TryFormat(actual.AsSpan(), out charsWritten, format.ToLowerInvariant(), provider)); - Assert.Equal(expected.Length, charsWritten); - Assert.Equal(expected.ToLowerInvariant(), new string(actual)); - } - } + public static void TryFormat(uint i, string format, IFormatProvider provider, string expected) => + NumberFormatTestHelper.TryFormatNumberTest(i, format, provider, expected); } } diff --git a/src/libraries/System.Runtime/tests/System/UInt64Tests.cs b/src/libraries/System.Runtime/tests/System/UInt64Tests.cs index dd8ee1a0f8a48..c8fae97320698 100644 --- a/src/libraries/System.Runtime/tests/System/UInt64Tests.cs +++ b/src/libraries/System.Runtime/tests/System/UInt64Tests.cs @@ -385,45 +385,7 @@ public static void Parse_Span_Invalid(string value, NumberStyles style, IFormatP [Theory] [MemberData(nameof(ToString_TestData))] - public static void TryFormat(ulong i, string format, IFormatProvider provider, string expected) - { - char[] actual; - int charsWritten; - - // Just right - actual = new char[expected.Length]; - Assert.True(i.TryFormat(actual.AsSpan(), out charsWritten, format, provider)); - Assert.Equal(expected.Length, charsWritten); - Assert.Equal(expected, new string(actual)); - - // Longer than needed - actual = new char[expected.Length + 1]; - Assert.True(i.TryFormat(actual.AsSpan(), out charsWritten, format, provider)); - Assert.Equal(expected.Length, charsWritten); - Assert.Equal(expected, new string(actual, 0, charsWritten)); - - // Too short - if (expected.Length > 0) - { - actual = new char[expected.Length - 1]; - Assert.False(i.TryFormat(actual.AsSpan(), out charsWritten, format, provider)); - Assert.Equal(0, charsWritten); - } - - if (format != null) - { - // Upper format - actual = new char[expected.Length]; - Assert.True(i.TryFormat(actual.AsSpan(), out charsWritten, format.ToUpperInvariant(), provider)); - Assert.Equal(expected.Length, charsWritten); - Assert.Equal(expected.ToUpperInvariant(), new string(actual)); - - // Lower format - actual = new char[expected.Length]; - Assert.True(i.TryFormat(actual.AsSpan(), out charsWritten, format.ToLowerInvariant(), provider)); - Assert.Equal(expected.Length, charsWritten); - Assert.Equal(expected.ToLowerInvariant(), new string(actual)); - } - } + public static void TryFormat(ulong i, string format, IFormatProvider provider, string expected) => + NumberFormatTestHelper.TryFormatNumberTest(i, format, provider, expected); } } diff --git a/src/libraries/System.Runtime/tests/System/UIntPtrTests.cs b/src/libraries/System.Runtime/tests/System/UIntPtrTests.cs index 08051f2f6b74c..ff8a857b9cda8 100644 --- a/src/libraries/System.Runtime/tests/System/UIntPtrTests.cs +++ b/src/libraries/System.Runtime/tests/System/UIntPtrTests.cs @@ -1,7 +1,6 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System; using System.Collections.Generic; using System.Globalization; using System.Linq; @@ -526,45 +525,7 @@ public static void Parse_Span_Invalid(string value, NumberStyles style, IFormatP [Theory] [MemberData(nameof(ToString_TestData))] - public static void TryFormat(nuint i, string format, IFormatProvider provider, string expected) - { - char[] actual; - int charsWritten; - - // Just right - actual = new char[expected.Length]; - Assert.True(i.TryFormat(actual.AsSpan(), out charsWritten, format, provider)); - Assert.Equal(expected.Length, charsWritten); - Assert.Equal(expected, new string(actual)); - - // Longer than needed - actual = new char[expected.Length + 1]; - Assert.True(i.TryFormat(actual.AsSpan(), out charsWritten, format, provider)); - Assert.Equal(expected.Length, charsWritten); - Assert.Equal(expected, new string(actual, 0, charsWritten)); - - // Too short - if (expected.Length > 0) - { - actual = new char[expected.Length - 1]; - Assert.False(i.TryFormat(actual.AsSpan(), out charsWritten, format, provider)); - Assert.Equal(0, charsWritten); - } - - if (format != null) - { - // Upper format - actual = new char[expected.Length]; - Assert.True(i.TryFormat(actual.AsSpan(), out charsWritten, format.ToUpperInvariant(), provider)); - Assert.Equal(expected.Length, charsWritten); - Assert.Equal(expected.ToUpperInvariant(), new string(actual)); - - // Lower format - actual = new char[expected.Length]; - Assert.True(i.TryFormat(actual.AsSpan(), out charsWritten, format.ToLowerInvariant(), provider)); - Assert.Equal(expected.Length, charsWritten); - Assert.Equal(expected.ToLowerInvariant(), new string(actual)); - } - } + public static void TryFormat(nuint i, string format, IFormatProvider provider, string expected) => + NumberFormatTestHelper.TryFormatNumberTest(i, format, provider, expected); } } From b593d867984ea44e7a05d14cccd85c3315b939a7 Mon Sep 17 00:00:00 2001 From: Stephen Toub Date: Wed, 12 Apr 2023 13:16:09 -0400 Subject: [PATCH 2/3] Address regressions from previous formatting changes - Use an internal interface implemented by char and byte to have dedicated CastFrom methods that are always inlineable due to very small size. - Use pointers in some core formatting routines to avoid needing bulky IL for manipulating refs with spans, making various members more inlineable. - Avoid Encoding.UTF8.GetBytes in various code paths by caching more UTF8 sequences on DateTimeFormatInfo and NumberFormatInfo - Change FormatCustomizedTimeZone to special-case 2 vs 3+ tokens in order to avoid extra AppendSpan calls - Fix growth logic in ValueListBuilder to not forcibly grow more than is needed - Inline ValueListBuilder.AppendSpan and remove some bounds checks (at least on 64-bit) - Change FormatDigits to special-case lengths of 1/2/4 and to use existing formatting routines rather than a custom one - Remove the FormatDigits wrapper overload and just have all calls go to the main workhorse method. - Remove the use of "..."u8 in R/O formatting that leads to needing to use additional span-based helpers. The minimal gain on coreclr isn't worth the extra complication - Changed some switches to include half the cases based on lowercasing the ASCII input char - Moved Date/TimeOnly charsWritten into Try method to be closer to the source of truth rather than having the value far aware (this isn't for perf and could possibly even be a microregression, so I included it here to ensure it's not measurable). --- .../System.Private.CoreLib.Shared.projitems | 1 + .../Utf8Formatter/Utf8Formatter.Date.G.cs | 75 ++- .../Utf8Formatter/Utf8Formatter.TimeSpan.cs | 4 +- .../System.Private.CoreLib/src/System/Byte.cs | 14 +- .../System.Private.CoreLib/src/System/Char.cs | 13 +- .../Collections/Generic/ValueListBuilder.cs | 19 +- .../src/System/DateOnly.cs | 52 +-- .../System/Globalization/DateTimeFormat.cs | 442 ++++++++++-------- .../Globalization/DateTimeFormatInfo.cs | 52 +++ .../src/System/Globalization/HebrewNumber.cs | 6 +- .../System/Globalization/NumberFormatInfo.cs | 28 +- .../System/Globalization/TimeSpanFormat.cs | 121 +++-- .../src/System/IUtfChar.cs | 32 ++ .../src/System/Number.Formatting.cs | 303 ++++++------ .../src/System/TimeOnly.cs | 47 +- .../src/System/Version.cs | 4 +- 16 files changed, 644 insertions(+), 569 deletions(-) create mode 100644 src/libraries/System.Private.CoreLib/src/System/IUtfChar.cs diff --git a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems index 8e40a73ad2f0c..16ba6ff21026e 100644 --- a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems +++ b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems @@ -504,6 +504,7 @@ + diff --git a/src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Utf8Formatter/Utf8Formatter.Date.G.cs b/src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Utf8Formatter/Utf8Formatter.Date.G.cs index cd45fda493c07..2a5d463e05316 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Utf8Formatter/Utf8Formatter.Date.G.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Utf8Formatter/Utf8Formatter.Date.G.cs @@ -1,6 +1,8 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Runtime.InteropServices; + namespace System.Buffers.Text { public static partial class Utf8Formatter @@ -18,7 +20,7 @@ public static partial class Utf8Formatter // -------------------------- // 05/25/2017 10:30:15 -08:00 // - private static bool TryFormatDateTimeG(DateTime value, TimeSpan offset, Span destination, out int bytesWritten) + private static unsafe bool TryFormatDateTimeG(DateTime value, TimeSpan offset, Span destination, out int bytesWritten) { const int MinimumBytesNeeded = 19; @@ -37,54 +39,51 @@ private static bool TryFormatDateTimeG(DateTime value, TimeSpan offset, Span destination, out int bytesWritten, StandardFormat format = default) { TimeSpanFormat.StandardFormat sf = TimeSpanFormat.StandardFormat.C; - string? decimalSeparator = null; + ReadOnlySpan decimalSeparator = default; char symbol = FormattingHelpers.GetSymbolOrDefault(format, 'c'); if (symbol != 'c' && (symbol | 0x20) != 't') { - decimalSeparator = DateTimeFormatInfo.InvariantInfo.DecimalSeparator; + decimalSeparator = DateTimeFormatInfo.InvariantInfo.DecimalSeparatorTChar(); if (symbol == 'g') { sf = TimeSpanFormat.StandardFormat.g; diff --git a/src/libraries/System.Private.CoreLib/src/System/Byte.cs b/src/libraries/System.Private.CoreLib/src/System/Byte.cs index d70797f876e56..ab924f1e4b55a 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Byte.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Byte.cs @@ -1,7 +1,6 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System.Buffers.Binary; using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.Numerics; @@ -23,7 +22,8 @@ public readonly struct Byte IBinaryInteger, IMinMaxValue, IUnsignedNumber, - IUtf8SpanFormattable + IUtf8SpanFormattable, + IUtfChar { private readonly byte m_value; // Do not rename (binary serialization) @@ -1203,5 +1203,15 @@ static bool INumberBase.TryConvertToTruncating(byte value, [MaybeN /// static byte IUnaryPlusOperators.operator +(byte value) => (byte)(+value); + + // + // IUtfChar + // + + static byte IUtfChar.CastFrom(byte value) => value; + static byte IUtfChar.CastFrom(char value) => (byte)value; + static byte IUtfChar.CastFrom(int value) => (byte)value; + static byte IUtfChar.CastFrom(uint value) => (byte)value; + static byte IUtfChar.CastFrom(ulong value) => (byte)value; } } diff --git a/src/libraries/System.Private.CoreLib/src/System/Char.cs b/src/libraries/System.Private.CoreLib/src/System/Char.cs index 5678c119e0b96..643574b8fbfb1 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Char.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Char.cs @@ -35,7 +35,8 @@ public readonly struct Char IBinaryInteger, IMinMaxValue, IUnsignedNumber, - IUtf8SpanFormattable + IUtf8SpanFormattable, + IUtfChar { // // Member Variables @@ -2010,5 +2011,15 @@ static bool ISpanParsable.TryParse(ReadOnlySpan s, IFormatProvider? /// static char IUnaryPlusOperators.operator +(char value) => (char)(+value); + + // + // IUtfChar + // + + static char IUtfChar.CastFrom(byte value) => (char)value; + static char IUtfChar.CastFrom(char value) => value; + static char IUtfChar.CastFrom(int value) => (char)value; + static char IUtfChar.CastFrom(uint value) => (char)value; + static char IUtfChar.CastFrom(ulong value) => (char)value; } } diff --git a/src/libraries/System.Private.CoreLib/src/System/Collections/Generic/ValueListBuilder.cs b/src/libraries/System.Private.CoreLib/src/System/Collections/Generic/ValueListBuilder.cs index 85cb993fc0bb3..85c86d3dcd29c 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Collections/Generic/ValueListBuilder.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Collections/Generic/ValueListBuilder.cs @@ -79,7 +79,7 @@ private void AppendMultiChar(scoped ReadOnlySpan source) { if ((uint)(_pos + source.Length) > (uint)_span.Length) { - Grow(source.Length); + Grow(_span.Length - _pos + source.Length); } source.CopyTo(_span.Slice(_pos)); @@ -100,16 +100,29 @@ public void Insert(int index, scoped ReadOnlySpan source) _pos += source.Length; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public Span AppendSpan(int length) { Debug.Assert(length >= 0); int pos = _pos; - if ((uint)(pos + length) > (uint)_span.Length) + Span span = _span; + if ((ulong)(uint)pos + (ulong)(uint)length <= (ulong)(uint)span.Length) // same guard condition as in Span.Slice on 64-bit + { + _pos = pos + length; + return span.Slice(pos, length); + } + else { - Grow(length); + return AppendSpanWithGrow(length); } + } + [MethodImpl(MethodImplOptions.NoInlining)] + private Span AppendSpanWithGrow(int length) + { + int pos = _pos; + Grow(_span.Length - pos + length); _pos += length; return _span.Slice(pos, length); } diff --git a/src/libraries/System.Private.CoreLib/src/System/DateOnly.cs b/src/libraries/System.Private.CoreLib/src/System/DateOnly.cs index 13985c540633b..a515a66f8ae97 100644 --- a/src/libraries/System.Private.CoreLib/src/System/DateOnly.cs +++ b/src/libraries/System.Private.CoreLib/src/System/DateOnly.cs @@ -491,16 +491,14 @@ private static ParseFailureKind TryParseExactInternal(ReadOnlySpan s, Read if (format.Length == 1) { - switch (format[0]) + switch (format[0] | 0x20) { case 'o': - case 'O': format = OFormat; provider = CultureInfo.InvariantCulture.DateTimeFormat; break; case 'r': - case 'R': format = RFormat; provider = CultureInfo.InvariantCulture.DateTimeFormat; break; @@ -570,16 +568,14 @@ private static ParseFailureKind TryParseExactInternal(ReadOnlySpan s, stri if (format.Length == 1) { - switch (format[0]) + switch (format[0] | 0x20) { case 'o': - case 'O': format = OFormat; dtfiToUse = CultureInfo.InvariantCulture.DateTimeFormat; break; case 'r': - case 'R': format = RFormat; dtfiToUse = CultureInfo.InvariantCulture.DateTimeFormat; break; @@ -750,30 +746,25 @@ public string ToString([StringSyntax(StringSyntaxAttribute.DateOnlyFormat)] stri if (format.Length == 1) { - switch (format[0]) + switch (format[0] | 0x20) { case 'o': - case 'O': return string.Create(10, this, (destination, value) => { - bool b = DateTimeFormat.TryFormatDateOnlyO(value.Year, value.Month, value.Day, destination); - Debug.Assert(b); + DateTimeFormat.TryFormatDateOnlyO(value.Year, value.Month, value.Day, destination, out int charsWritten); + Debug.Assert(charsWritten == destination.Length); }); case 'r': - case 'R': return string.Create(16, this, (destination, value) => { - bool b = DateTimeFormat.TryFormatDateOnlyR(value.DayOfWeek, value.Year, value.Month, value.Day, destination); - Debug.Assert(b); + DateTimeFormat.TryFormatDateOnlyR(value.DayOfWeek, value.Year, value.Month, value.Day, destination, out int charsWritten); + Debug.Assert(charsWritten == destination.Length); }); case 'm': - case 'M': case 'd': - case 'D': case 'y': - case 'Y': return DateTimeFormat.Format(GetEquivalentDateTime(), format, provider); default: @@ -800,7 +791,7 @@ bool IUtf8SpanFormattable.TryFormat(Span utf8Destination, out int bytesWri TryFormatCore(utf8Destination, out bytesWritten, format, provider); private bool TryFormatCore(Span destination, out int charsWritten, [StringSyntax(StringSyntaxAttribute.DateOnlyFormat)] ReadOnlySpan format, IFormatProvider? provider = null) - where TChar : unmanaged, IBinaryInteger + where TChar : unmanaged, IUtfChar { if (format.Length == 0) { @@ -809,39 +800,22 @@ private bool TryFormatCore(Span destination, out int charsWritten, if (format.Length == 1) { - switch (format[0]) + switch (format[0] | 0x20) { case 'o': - case 'O': - if (!DateTimeFormat.TryFormatDateOnlyO(Year, Month, Day, destination)) - { - charsWritten = 0; - return false; - } - charsWritten = 10; - return true; + return DateTimeFormat.TryFormatDateOnlyO(Year, Month, Day, destination, out charsWritten); case 'r': - case 'R': - - if (!DateTimeFormat.TryFormatDateOnlyR(DayOfWeek, Year, Month, Day, destination)) - { - charsWritten = 0; - return false; - } - charsWritten = 16; - return true; + return DateTimeFormat.TryFormatDateOnlyR(DayOfWeek, Year, Month, Day, destination, out charsWritten); case 'm': - case 'M': case 'd': - case 'D': case 'y': - case 'Y': return DateTimeFormat.TryFormat(GetEquivalentDateTime(), destination, out charsWritten, format, provider); default: - throw new FormatException(SR.Argument_BadFormatSpecifier); + ThrowHelper.ThrowFormatException_BadFormatSpecifier(); + break; } } diff --git a/src/libraries/System.Private.CoreLib/src/System/Globalization/DateTimeFormat.cs b/src/libraries/System.Private.CoreLib/src/System/Globalization/DateTimeFormat.cs index f376088560c3a..b38edce0cf21a 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Globalization/DateTimeFormat.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Globalization/DateTimeFormat.cs @@ -135,6 +135,8 @@ internal static class DateTimeFormat private const int DEFAULT_ALL_DATETIMES_SIZE = 132; internal static readonly DateTimeFormatInfo InvariantFormatInfo = CultureInfo.InvariantCulture.DateTimeFormat; + private static readonly string[] s_invariantAbbreviatedMonthNames = InvariantFormatInfo.AbbreviatedMonthNames; + private static readonly string[] s_invariantAbbreviatedDayNames = InvariantFormatInfo.AbbreviatedDayNames; internal static string[] fixedNumberFormats = new string[] { "0", @@ -146,57 +148,44 @@ internal static class DateTimeFormat "0000000", }; - //////////////////////////////////////////////////////////////////////////// - // - // Format the positive integer value to a string and prefix with assigned - // length of leading zero. - // - // Parameters: - // value: The value to format - // len: The maximum length for leading zero. - // If the digits of the value is greater than len, no leading zero is added. - // - // Notes: - // The function can format to int.MaxValue. - // - //////////////////////////////////////////////////////////////////////////// - internal static void FormatDigits(ref ValueListBuilder outputBuffer, int value, int len) where TChar : unmanaged, IBinaryInteger - { - Debug.Assert(value >= 0, "DateTimeFormat.FormatDigits(): value >= 0"); - FormatDigits(ref outputBuffer, value, len, false); - } - - internal static unsafe void FormatDigits(ref ValueListBuilder outputBuffer, int value, int len, bool overrideLengthLimit) where TChar : unmanaged, IBinaryInteger + /// Format the positive integer value to a string and prefix with assigned length of leading zero. + /// The type of the character. + /// The buffer into which to write the digits. + /// The value to format + /// + /// The minimum length for formatted number. If the number of digits in the value is less than this length, it will be padded with leading zeros. + /// + internal static unsafe void FormatDigits(ref ValueListBuilder outputBuffer, int value, int minimumLength) where TChar : unmanaged, IUtfChar { Debug.Assert(value >= 0, "DateTimeFormat.FormatDigits(): value >= 0"); + Debug.Assert(minimumLength <= 16); - // Limit the use of this function to be two-digits, so that we have the same behavior - // as RTM bits. - if (!overrideLengthLimit && len > 2) + switch (minimumLength) { - len = 2; - } + case 1 when value < 10: + outputBuffer.Append(TChar.CreateTruncating(value + '0')); + break; - TChar* buffer = stackalloc TChar[16]; - TChar* p = buffer + 16; - int n = value; - do - { - *--p = TChar.CreateTruncating(n % 10 + '0'); - n /= 10; - } while ((n != 0) && (p > buffer)); + case 2 when value < 100: + fixed (TChar* ptr = &MemoryMarshal.GetReference(outputBuffer.AppendSpan(2))) + { + Number.WriteTwoDigits((uint)value, ptr); + } + break; - int digits = (int)(buffer + 16 - p); + case 4 when value < 10000: + fixed (TChar* ptr = &MemoryMarshal.GetReference(outputBuffer.AppendSpan(4))) + { + Number.WriteFourDigits((uint)value, ptr); + } + break; - // If the repeat count is greater than 0, we're trying - // to emulate the "00" format, so we have to prepend - // a zero if the string only has one character. - while ((digits < len) && (p > buffer)) - { - *--p = TChar.CreateTruncating('0'); - digits++; + default: + TChar* buffer = stackalloc TChar[16]; + TChar* p = Number.UInt32ToDecChars(buffer + 16, (uint)value, minimumLength); + outputBuffer.Append(new ReadOnlySpan(p, (int)(buffer + 16 - p))); + break; } - new ReadOnlySpan(p, digits).CopyTo(outputBuffer.AppendSpan(digits)); } internal static int ParseRepeatPattern(ReadOnlySpan format, int pos, char patternChar) @@ -287,7 +276,7 @@ private static string FormatHebrewMonthName(DateTime time, int month, int repeat // The pos should point to a quote character. This method will // append to the result StringBuilder the string enclosed by the quote character. // - internal static int ParseQuoteString(scoped ReadOnlySpan format, int pos, ref ValueListBuilder result) where TChar : unmanaged, IBinaryInteger + internal static int ParseQuoteString(scoped ReadOnlySpan format, int pos, ref ValueListBuilder result) where TChar : unmanaged, IUtfChar { // // NOTE : pos will be the index of the quote character in the 'format' string. @@ -314,7 +303,7 @@ internal static int ParseQuoteString(scoped ReadOnlySpan format, in // because the second double quote is escaped. if (pos < formatLen) { - result.Append(TChar.CreateTruncating(format[pos++])); + result.Append(TChar.CastFrom(format[pos++])); } else { @@ -326,7 +315,7 @@ internal static int ParseQuoteString(scoped ReadOnlySpan format, in } else { - result.Append(TChar.CreateTruncating(ch)); + result.Append(TChar.CastFrom(ch)); } } @@ -432,7 +421,7 @@ private static bool IsUseGenitiveForm(ReadOnlySpan format, int index, int // Actions: Format the DateTime instance using the specified format. // private static void FormatCustomized( - DateTime dateTime, scoped ReadOnlySpan format, DateTimeFormatInfo dtfi, TimeSpan offset, ref ValueListBuilder result) where TChar : unmanaged, IBinaryInteger + DateTime dateTime, scoped ReadOnlySpan format, DateTimeFormatInfo dtfi, TimeSpan offset, ref ValueListBuilder result) where TChar : unmanaged, IUtfChar { Calendar cal = dtfi.Calendar; @@ -455,6 +444,7 @@ private static void FormatCustomized( tokenLen = ParseRepeatPattern(format, i, ch); AppendString(ref result, dtfi.GetEraName(cal.GetEra(dateTime))); break; + case 'h': tokenLen = ParseRepeatPattern(format, i, ch); hour12 = dateTime.Hour % 12; @@ -462,20 +452,24 @@ private static void FormatCustomized( { hour12 = 12; } - FormatDigits(ref result, hour12, tokenLen); + FormatDigits(ref result, hour12, Math.Min(tokenLen, 2)); break; + case 'H': tokenLen = ParseRepeatPattern(format, i, ch); - FormatDigits(ref result, dateTime.Hour, tokenLen); + FormatDigits(ref result, dateTime.Hour, Math.Min(tokenLen, 2)); break; + case 'm': tokenLen = ParseRepeatPattern(format, i, ch); - FormatDigits(ref result, dateTime.Minute, tokenLen); + FormatDigits(ref result, dateTime.Minute, Math.Min(tokenLen, 2)); break; + case 's': tokenLen = ParseRepeatPattern(format, i, ch); - FormatDigits(ref result, dateTime.Second, tokenLen); + FormatDigits(ref result, dateTime.Second, Math.Min(tokenLen, 2)); break; + case 'f': case 'F': tokenLen = ParseRepeatPattern(format, i, ch); @@ -509,7 +503,7 @@ private static void FormatCustomized( else { // No fraction to emit, so see if we should remove decimal also. - if (result.Length > 0 && result[result.Length - 1] == TChar.CreateTruncating('.')) + if (result.Length > 0 && result[result.Length - 1] == TChar.CastFrom('.')) { result.Length--; } @@ -521,6 +515,7 @@ private static void FormatCustomized( throw new FormatException(SR.Format_InvalidString); } break; + case 't': tokenLen = ParseRepeatPattern(format, i, ch); if (tokenLen == 1) @@ -533,9 +528,10 @@ private static void FormatCustomized( } else { - AppendString(ref result, dateTime.Hour < 12 ? dtfi.AMDesignator : dtfi.PMDesignator); + result.Append(dateTime.Hour < 12 ? dtfi.AMDesignatorTChar() : dtfi.PMDesignatorTChar()); } break; + case 'd': // // tokenLen == 1 : Day of month as digits with no leading zero. @@ -564,13 +560,12 @@ private static void FormatCustomized( } bTimeOnly = false; break; + case 'M': - // // tokenLen == 1 : Month as digits with no leading zero. // tokenLen == 2 : Month as digits with leading zero for single-digit months. // tokenLen == 3 : Month as a three-letter abbreviation. // tokenLen >= 4 : Month as its full name. - // tokenLen = ParseRepeatPattern(format, i, ch); int month = cal.GetMonth(dateTime); if (tokenLen <= 2) @@ -609,6 +604,7 @@ private static void FormatCustomized( } bTimeOnly = false; break; + case 'y': // Notes about OS behavior: // y: Always print (year % 100). No leading zero. @@ -630,7 +626,7 @@ private static void FormatCustomized( } else if (dtfi.HasForceTwoDigitYears) { - FormatDigits(ref result, year, tokenLen <= 2 ? tokenLen : 2); + FormatDigits(ref result, year, Math.Min(tokenLen, 2)); } else if (cal.ID == CalendarId.HEBREW) { @@ -644,7 +640,7 @@ private static void FormatCustomized( } else if (tokenLen <= 16) // FormatDigits has an implicit 16-digit limit { - FormatDigits(ref result, year, tokenLen, overrideLengthLimit: true); + FormatDigits(ref result, year, tokenLen); } else { @@ -653,26 +649,32 @@ private static void FormatCustomized( } bTimeOnly = false; break; + case 'z': tokenLen = ParseRepeatPattern(format, i, ch); FormatCustomizedTimeZone(dateTime, offset, tokenLen, bTimeOnly, ref result); break; + case 'K': tokenLen = 1; FormatCustomizedRoundripTimeZone(dateTime, offset, ref result); break; + case ':': - AppendString(ref result, dtfi.TimeSeparator); + result.Append(dtfi.TimeSeparatorTChar()); tokenLen = 1; break; + case '/': - AppendString(ref result, dtfi.DateSeparator); + result.Append(dtfi.DateSeparatorTChar()); tokenLen = 1; break; + case '\'': case '\"': tokenLen = ParseQuoteString(format, i, ref result); break; + case '%': // Optional format character. // For example, format string "%d" will print day of month @@ -695,6 +697,7 @@ private static void FormatCustomized( throw new FormatException(SR.Format_InvalidString); } break; + case '\\': // Escaped character. Can be used to insert a character into the format string. // For example, "\d" will insert the character 'd' into the string. @@ -707,7 +710,7 @@ private static void FormatCustomized( nextChar = ParseNextChar(format, i); if (nextChar >= 0) { - result.Append(TChar.CreateTruncating(nextChar)); + result.Append(TChar.CastFrom(nextChar)); tokenLen = 2; } else @@ -718,12 +721,13 @@ private static void FormatCustomized( throw new FormatException(SR.Format_InvalidString); } break; + default: // NOTENOTE : we can remove this rule if we enforce the enforced quote // character rule. // That is, if we ask everyone to use single quote or double quote to insert characters, // then we can remove this default block. - result.Append(TChar.CreateTruncating(ch)); + result.Append(TChar.CastFrom(ch)); tokenLen = 1; break; } @@ -732,11 +736,11 @@ private static void FormatCustomized( } [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static void AppendChar(ref ValueListBuilder result, char ch) where TChar : unmanaged, IBinaryInteger + internal static void AppendChar(ref ValueListBuilder result, char ch) where TChar : unmanaged, IUtfChar { if (typeof(TChar) == typeof(char) || char.IsAscii(ch)) { - result.Append(TChar.CreateTruncating(ch)); + result.Append(TChar.CastFrom(ch)); } else { @@ -747,7 +751,7 @@ internal static void AppendChar(ref ValueListBuilder result, char } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void AppendString(ref ValueListBuilder result, scoped ReadOnlySpan s) where TChar : unmanaged, IBinaryInteger + private static void AppendString(ref ValueListBuilder result, scoped ReadOnlySpan s) where TChar : unmanaged, IUtfChar { if (typeof(TChar) == typeof(char)) { @@ -760,7 +764,7 @@ private static void AppendString(ref ValueListBuilder result, scop } } - internal static void FormatFraction(ref ValueListBuilder result, int fraction, ReadOnlySpan fractionFormat) where TChar : unmanaged, IBinaryInteger + internal static void FormatFraction(ref ValueListBuilder result, int fraction, ReadOnlySpan fractionFormat) where TChar : unmanaged, IUtfChar { Span chars = stackalloc TChar[11]; int charCount; @@ -772,10 +776,10 @@ internal static void FormatFraction(ref ValueListBuilder result, i } // output the 'z' family of formats, which output a the offset from UTC, e.g. "-07:30" - private static void FormatCustomizedTimeZone(DateTime dateTime, TimeSpan offset, int tokenLen, bool timeOnly, ref ValueListBuilder result) where TChar : unmanaged, IBinaryInteger + private static unsafe void FormatCustomizedTimeZone(DateTime dateTime, TimeSpan offset, int tokenLen, bool timeOnly, ref ValueListBuilder result) where TChar : unmanaged, IUtfChar { // See if the instance already has an offset - bool dateTimeFormat = (offset.Ticks == NullOffset); + bool dateTimeFormat = offset.Ticks == NullOffset; if (dateTimeFormat) { // No offset. The instance is a DateTime and the output should be the local time zone @@ -795,15 +799,15 @@ private static void FormatCustomizedTimeZone(DateTime dateTime, TimeSpan offset = TimeZoneInfo.GetLocalUtcOffset(dateTime, TimeZoneInfoOptions.NoThrowOnInvalidTime); } } + if (offset.Ticks >= 0) { - result.Append(TChar.CreateTruncating('+')); + result.Append(TChar.CastFrom('+')); } else { - result.Append(TChar.CreateTruncating('-')); - // get a positive offset, so that you don't need a separate code path for the negative numbers. - offset = offset.Negate(); + result.Append(TChar.CastFrom('-')); + offset = offset.Negate(); // get a positive offset, so that you don't need a separate code path for the negative numbers. } if (tokenLen <= 1) @@ -812,24 +816,32 @@ private static void FormatCustomizedTimeZone(DateTime dateTime, TimeSpan (int tens, int ones) = Math.DivRem(offset.Hours, 10); if (tens != 0) { - result.Append(TChar.CreateTruncating('0' + tens)); + result.Append(TChar.CastFrom('0' + tens)); + } + result.Append(TChar.CastFrom('0' + ones)); + } + else if (tokenLen == 2) + { + // 'zz' format e.g "-07" + fixed (TChar* p = &MemoryMarshal.GetReference(result.AppendSpan(2))) + { + Number.WriteTwoDigits((uint)offset.Hours, p); } - result.Append(TChar.CreateTruncating('0' + ones)); } else { - // 'zz' or longer format e.g "-07" - Number.WriteTwoDigits((uint)offset.Hours, result.AppendSpan(2), 0); - if (tokenLen >= 3) + Debug.Assert(tokenLen >= 3); + fixed (TChar* p = &MemoryMarshal.GetReference(result.AppendSpan(5))) { - result.Append(TChar.CreateTruncating(':')); - Number.WriteTwoDigits((uint)offset.Minutes, result.AppendSpan(2), 0); + Number.WriteTwoDigits((uint)offset.Hours, p); + p[2] = TChar.CastFrom(':'); + Number.WriteTwoDigits((uint)offset.Minutes, p + 3); } } } // output the 'K' format, which is for round-tripping the data - private static void FormatCustomizedRoundripTimeZone(DateTime dateTime, TimeSpan offset, ref ValueListBuilder result) where TChar : unmanaged, IBinaryInteger + private static unsafe void FormatCustomizedRoundripTimeZone(DateTime dateTime, TimeSpan offset, ref ValueListBuilder result) where TChar : unmanaged, IUtfChar { // The objective of this format is to round trip the data in the type // For DateTime it should round-trip the Kind value and preserve the time zone. @@ -847,7 +859,7 @@ private static void FormatCustomizedRoundripTimeZone(DateTime dateTime, T break; case DateTimeKind.Utc: // The 'Z' constant is a marker for a UTC date - result.Append(TChar.CreateTruncating('Z')); + result.Append(TChar.CastFrom('Z')); return; default: // If the kind is unspecified, we output nothing here @@ -856,19 +868,21 @@ private static void FormatCustomizedRoundripTimeZone(DateTime dateTime, T } if (offset.Ticks >= 0) { - result.Append(TChar.CreateTruncating('+')); + result.Append(TChar.CastFrom('+')); } else { - result.Append(TChar.CreateTruncating('-')); + result.Append(TChar.CastFrom('-')); // get a positive offset, so that you don't need a separate code path for the negative numbers. offset = offset.Negate(); } - Span hoursMinutes = result.AppendSpan(5); - Number.WriteTwoDigits((uint)offset.Hours, hoursMinutes, 0); - hoursMinutes[2] = TChar.CreateTruncating(':'); - Number.WriteTwoDigits((uint)offset.Minutes, hoursMinutes, 3); + fixed (TChar* hoursMinutes = &MemoryMarshal.GetReference(result.AppendSpan(5))) + { + Number.WriteTwoDigits((uint)offset.Hours, hoursMinutes); + hoursMinutes[2] = TChar.CastFrom(':'); + Number.WriteTwoDigits((uint)offset.Minutes, hoursMinutes + 3); + } } internal static string GetRealFormat(ReadOnlySpan format, DateTimeFormatInfo dtfi) @@ -1016,10 +1030,10 @@ internal static string Format(DateTime dateTime, string? format, IFormatProvider return resultString; } - internal static bool TryFormat(DateTime dateTime, Span destination, out int written, ReadOnlySpan format, IFormatProvider? provider) where TChar : unmanaged, IBinaryInteger => + internal static bool TryFormat(DateTime dateTime, Span destination, out int written, ReadOnlySpan format, IFormatProvider? provider) where TChar : unmanaged, IUtfChar => TryFormat(dateTime, destination, out written, format, provider, new TimeSpan(NullOffset)); - internal static bool TryFormat(DateTime dateTime, Span destination, out int written, ReadOnlySpan format, IFormatProvider? provider, TimeSpan offset) where TChar : unmanaged, IBinaryInteger + internal static bool TryFormat(DateTime dateTime, Span destination, out int written, ReadOnlySpan format, IFormatProvider? provider, TimeSpan offset) where TChar : unmanaged, IUtfChar { Debug.Assert(typeof(TChar) == typeof(char) || typeof(TChar) == typeof(byte)); @@ -1045,7 +1059,7 @@ internal static bool TryFormat(DateTime dateTime, Span destination return copied; } - private static void FormatIntoBuilder(DateTime dateTime, ReadOnlySpan format, DateTimeFormatInfo dtfi, TimeSpan offset, ref ValueListBuilder result) where TChar : unmanaged, IBinaryInteger + private static void FormatIntoBuilder(DateTime dateTime, ReadOnlySpan format, DateTimeFormatInfo dtfi, TimeSpan offset, ref ValueListBuilder result) where TChar : unmanaged, IUtfChar { Debug.Assert(dtfi != null); if (format.Length == 0) @@ -1240,20 +1254,26 @@ internal static bool IsValidCustomTimeFormat(ReadOnlySpan format, bool thr // 012345678901234567890123456789012 // --------------------------------- // 05:30:45.7680000 - internal static bool TryFormatTimeOnlyO(int hour, int minute, int second, long fraction, Span destination) where TChar : unmanaged, IBinaryInteger + internal static unsafe bool TryFormatTimeOnlyO(int hour, int minute, int second, long fraction, Span destination, out int charsWritten) where TChar : unmanaged, IUtfChar { if (destination.Length < 16) { + charsWritten = 0; return false; } - Number.WriteTwoDigits((uint)hour, destination, 0); - destination[2] = TChar.CreateTruncating(':'); - Number.WriteTwoDigits((uint)minute, destination, 3); - destination[5] = TChar.CreateTruncating(':'); - Number.WriteTwoDigits((uint)second, destination, 6); - destination[8] = TChar.CreateTruncating('.'); - Number.WriteDigits((uint)fraction, destination.Slice(9, 7)); + charsWritten = 16; + + fixed (TChar* dest = &MemoryMarshal.GetReference(destination)) + { + Number.WriteTwoDigits((uint)hour, dest); + dest[2] = TChar.CastFrom(':'); + Number.WriteTwoDigits((uint)minute, dest + 3); + dest[5] = TChar.CastFrom(':'); + Number.WriteTwoDigits((uint)second, dest + 6); + dest[8] = TChar.CastFrom('.'); + Number.WriteDigits((uint)fraction, dest + 9, 7); + } return true; } @@ -1261,18 +1281,24 @@ internal static bool TryFormatTimeOnlyO(int hour, int minute, int second, // 012345678901234567890123456789012 // --------------------------------- // 05:30:45 - internal static bool TryFormatTimeOnlyR(int hour, int minute, int second, Span destination) where TChar : unmanaged, IBinaryInteger + internal static unsafe bool TryFormatTimeOnlyR(int hour, int minute, int second, Span destination, out int charsWritten) where TChar : unmanaged, IUtfChar { if (destination.Length < 8) { + charsWritten = 0; return false; } - Number.WriteTwoDigits((uint)hour, destination, 0); - destination[2] = TChar.CreateTruncating(':'); - Number.WriteTwoDigits((uint)minute, destination, 3); - destination[5] = TChar.CreateTruncating(':'); - Number.WriteTwoDigits((uint)second, destination, 6); + charsWritten = 8; + + fixed (TChar* dest = &MemoryMarshal.GetReference(destination)) + { + Number.WriteTwoDigits((uint)hour, dest); + dest[2] = TChar.CastFrom(':'); + Number.WriteTwoDigits((uint)minute, dest + 3); + dest[5] = TChar.CastFrom(':'); + Number.WriteTwoDigits((uint)second, dest +6); + } return true; } @@ -1281,18 +1307,25 @@ internal static bool TryFormatTimeOnlyR(int hour, int minute, int second, // 012345678901234567890123456789012 // --------------------------------- // 2017-06-12 - internal static bool TryFormatDateOnlyO(int year, int month, int day, Span destination) where TChar : unmanaged, IBinaryInteger + internal static unsafe bool TryFormatDateOnlyO(int year, int month, int day, Span destination, out int charsWritten) where TChar : unmanaged, IUtfChar { if (destination.Length < 10) { + charsWritten = 0; return false; } - Number.WriteFourDigits((uint)year, destination, 0); - destination[4] = TChar.CreateTruncating('-'); - Number.WriteTwoDigits((uint)month, destination, 5); - destination[7] = TChar.CreateTruncating('-'); - Number.WriteTwoDigits((uint)day, destination, 8); + charsWritten = 10; + + fixed (TChar* dest = &MemoryMarshal.GetReference(destination)) + { + Number.WriteFourDigits((uint)year, dest); + dest[4] = TChar.CastFrom('-'); + Number.WriteTwoDigits((uint)month, dest + 5); + dest[7] = TChar.CastFrom('-'); + Number.WriteTwoDigits((uint)day, dest + 8); + } + return true; } @@ -1300,37 +1333,39 @@ internal static bool TryFormatDateOnlyO(int year, int month, int day, Spa // 01234567890123456789012345678 // ----------------------------- // Tue, 03 Jan 2017 - internal static bool TryFormatDateOnlyR(DayOfWeek dayOfWeek, int year, int month, int day, Span destination) where TChar : unmanaged, IBinaryInteger + internal static unsafe bool TryFormatDateOnlyR(DayOfWeek dayOfWeek, int year, int month, int day, Span destination, out int charsWritten) where TChar : unmanaged, IUtfChar { - Debug.Assert((uint)dayOfWeek < 7); - if (destination.Length < 16) { + charsWritten = 0; return false; } - if (typeof(TChar) == typeof(char)) + charsWritten = 16; + + Debug.Assert((uint)dayOfWeek < 7); + string dayAbbrev = s_invariantAbbreviatedDayNames[(int)dayOfWeek]; + Debug.Assert(dayAbbrev.Length == 3); + + string monthAbbrev = s_invariantAbbreviatedMonthNames[month - 1]; + Debug.Assert(monthAbbrev.Length == 3); + + fixed (TChar* dest = &MemoryMarshal.GetReference(destination)) { - Span dest = MemoryMarshal.Cast(destination); - - Number.CopyFour("Sun,Mon,Tue,Wed,Thu,Fri,Sat,".AsSpan(4 * (int)dayOfWeek), dest); - dest[4] = ' '; - Number.WriteTwoDigits((uint)day, dest, 5); - dest[7] = ' '; - Number.CopyFour("Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec ".AsSpan(4 * (month - 1)), dest.Slice(8)); - Number.WriteFourDigits((uint)year, dest, 12); - } - else - { - Debug.Assert(typeof(TChar) == typeof(byte)); - Span dest = MemoryMarshal.Cast(destination); - - Number.CopyFour("Sun,Mon,Tue,Wed,Thu,Fri,Sat,"u8.Slice(4 * (int)dayOfWeek), dest); - dest[4] = (byte)' '; - Number.WriteTwoDigits((uint)day, dest, 5); - dest[7] = (byte)' '; - Number.CopyFour("Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec "u8.Slice(4 * (month - 1)), dest.Slice(8)); - Number.WriteFourDigits((uint)year, dest, 12); + char c = dayAbbrev[2]; // remove bounds checks on remaining dayAbbrev accesses + dest[0] = TChar.CastFrom(dayAbbrev[0]); + dest[1] = TChar.CastFrom(dayAbbrev[1]); + dest[2] = TChar.CastFrom(c); + dest[3] = TChar.CastFrom(','); + dest[4] = TChar.CastFrom(' '); + Number.WriteTwoDigits((uint)day, dest + 5); + dest[7] = TChar.CastFrom(' '); + c = monthAbbrev[2]; // remove bounds checks on remaining monthAbbrev accesses + dest[8] = TChar.CastFrom(monthAbbrev[0]); + dest[9] = TChar.CastFrom(monthAbbrev[1]); + dest[10] = TChar.CastFrom(c); + dest[11] = TChar.CastFrom(' '); + Number.WriteFourDigits((uint)year, dest + 12); } return true; @@ -1342,7 +1377,7 @@ internal static bool TryFormatDateOnlyR(DayOfWeek dayOfWeek, int year, in // 2017-06-12T05:30:45.7680000-07:00 // 2017-06-12T05:30:45.7680000Z (Z is short for "+00:00" but also distinguishes DateTimeKind.Utc from DateTimeKind.Local) // 2017-06-12T05:30:45.7680000 (interpreted as local time wrt to current time zone) - internal static bool TryFormatO(DateTime dateTime, TimeSpan offset, Span destination, out int charsWritten) where TChar : unmanaged, IBinaryInteger + internal static unsafe bool TryFormatO(DateTime dateTime, TimeSpan offset, Span destination, out int charsWritten) where TChar : unmanaged, IUtfChar { const int MinimumBytesNeeded = 27; @@ -1374,49 +1409,47 @@ internal static bool TryFormatO(DateTime dateTime, TimeSpan offset, Span< } charsWritten = charsRequired; - // Hoist most of the bounds checks on destination. - { _ = destination[MinimumBytesNeeded - 1]; } - dateTime.GetDate(out int year, out int month, out int day); dateTime.GetTimePrecise(out int hour, out int minute, out int second, out int tick); - Number.WriteFourDigits((uint)year, destination, 0); - destination[4] = TChar.CreateTruncating('-'); - Number.WriteTwoDigits((uint)month, destination, 5); - destination[7] = TChar.CreateTruncating('-'); - Number.WriteTwoDigits((uint)day, destination, 8); - destination[10] = TChar.CreateTruncating('T'); - Number.WriteTwoDigits((uint)hour, destination, 11); - destination[13] = TChar.CreateTruncating(':'); - Number.WriteTwoDigits((uint)minute, destination, 14); - destination[16] = TChar.CreateTruncating(':'); - Number.WriteTwoDigits((uint)second, destination, 17); - destination[19] = TChar.CreateTruncating('.'); - Number.WriteDigits((uint)tick, destination.Slice(20, 7)); - - if (kind == DateTimeKind.Local) + fixed (TChar* dest = &MemoryMarshal.GetReference(destination)) { - int offsetTotalMinutes = (int)(offset.Ticks / TimeSpan.TicksPerMinute); + Number.WriteFourDigits((uint)year, dest); + dest[4] = TChar.CastFrom('-'); + Number.WriteTwoDigits((uint)month, dest + 5); + dest[7] = TChar.CastFrom('-'); + Number.WriteTwoDigits((uint)day, dest + 8); + dest[10] = TChar.CastFrom('T'); + Number.WriteTwoDigits((uint)hour, dest + 11); + dest[13] = TChar.CastFrom(':'); + Number.WriteTwoDigits((uint)minute, dest + 14); + dest[16] = TChar.CastFrom(':'); + Number.WriteTwoDigits((uint)second, dest + 17); + dest[19] = TChar.CastFrom('.'); + Number.WriteDigits((uint)tick, dest + 20, 7); - char sign = '+'; - if (offsetTotalMinutes < 0) + if (kind == DateTimeKind.Local) { - sign = '-'; - offsetTotalMinutes = -offsetTotalMinutes; - } + int offsetTotalMinutes = (int)(offset.Ticks / TimeSpan.TicksPerMinute); + + char sign = '+'; + if (offsetTotalMinutes < 0) + { + sign = '-'; + offsetTotalMinutes = -offsetTotalMinutes; + } - int offsetHours = Math.DivRem(offsetTotalMinutes, 60, out int offsetMinutes); + (int offsetHours, int offsetMinutes) = Math.DivRem(offsetTotalMinutes, 60); - // Writing the value backward allows the JIT to optimize by - // performing a single bounds check against buffer. - Number.WriteTwoDigits((uint)offsetMinutes, destination, 31); - destination[30] = TChar.CreateTruncating(':'); - Number.WriteTwoDigits((uint)offsetHours, destination, 28); - destination[27] = TChar.CreateTruncating(sign); - } - else if (kind == DateTimeKind.Utc) - { - destination[27] = TChar.CreateTruncating('Z'); + dest[27] = TChar.CastFrom(sign); + Number.WriteTwoDigits((uint)offsetHours, dest + 28); + dest[30] = TChar.CastFrom(':'); + Number.WriteTwoDigits((uint)offsetMinutes, dest + 31); + } + else if (kind == DateTimeKind.Utc) + { + dest[27] = TChar.CastFrom('Z'); + } } return true; @@ -1426,7 +1459,7 @@ internal static bool TryFormatO(DateTime dateTime, TimeSpan offset, Span< // 01234567890123456789012345678 // ----------------------------- // Tue, 03 Jan 2017 08:08:05 GMT - internal static bool TryFormatR(DateTime dateTime, TimeSpan offset, Span destination, out int charsWritten) where TChar : unmanaged, IBinaryInteger + internal static unsafe bool TryFormatR(DateTime dateTime, TimeSpan offset, Span destination, out int charsWritten) where TChar : unmanaged, IUtfChar { if (destination.Length <= 28) { @@ -1434,6 +1467,8 @@ internal static bool TryFormatR(DateTime dateTime, TimeSpan offset, Span< return false; } + charsWritten = 29; + if (offset.Ticks != NullOffset) { // Convert to UTC invariants. @@ -1443,45 +1478,40 @@ internal static bool TryFormatR(DateTime dateTime, TimeSpan offset, Span< dateTime.GetDate(out int year, out int month, out int day); dateTime.GetTime(out int hour, out int minute, out int second); - if (typeof(TChar) == typeof(char)) - { - Span dest = MemoryMarshal.Cast(destination); - - Number.CopyFour("Sun,Mon,Tue,Wed,Thu,Fri,Sat,".AsSpan(4 * (int)dateTime.DayOfWeek), dest); - dest[4] = ' '; - Number.WriteTwoDigits((uint)day, dest, 5); - dest[7] = ' '; - Number.CopyFour("Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec ".AsSpan(4 * (month - 1)), dest.Slice(8)); - Number.WriteFourDigits((uint)year, dest, 12); - dest[16] = ' '; - Number.WriteTwoDigits((uint)hour, dest, 17); - dest[19] = ':'; - Number.WriteTwoDigits((uint)minute, dest, 20); - dest[22] = ':'; - Number.WriteTwoDigits((uint)second, dest, 23); - Number.CopyFour(" GMT", dest.Slice(25)); - } - else + string dayAbbrev = s_invariantAbbreviatedDayNames[(int)dateTime.DayOfWeek]; + Debug.Assert(dayAbbrev.Length == 3); + + string monthAbbrev = s_invariantAbbreviatedMonthNames[month - 1]; + Debug.Assert(monthAbbrev.Length == 3); + + fixed (TChar* dest = &MemoryMarshal.GetReference(destination)) { - Debug.Assert(typeof(TChar) == typeof(byte)); - Span dest = MemoryMarshal.Cast(destination); - - Number.CopyFour("Sun,Mon,Tue,Wed,Thu,Fri,Sat,"u8.Slice(4 * (int)dateTime.DayOfWeek), dest); - dest[4] = (byte)' '; - Number.WriteTwoDigits((uint)day, dest, 5); - dest[7] = (byte)' '; - Number.CopyFour("Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec "u8.Slice(4 * (month - 1)), dest.Slice(8)); - Number.WriteFourDigits((uint)year, dest, 12); - dest[16] = (byte)' '; - Number.WriteTwoDigits((uint)hour, dest, 17); - dest[19] = (byte)':'; - Number.WriteTwoDigits((uint)minute, dest, 20); - dest[22] = (byte)':'; - Number.WriteTwoDigits((uint)second, dest, 23); - Number.CopyFour(" GMT"u8, dest.Slice(25)); + char c = dayAbbrev[2]; // remove bounds checks on remaining dayAbbrev accesses + dest[0] = TChar.CastFrom(dayAbbrev[0]); + dest[1] = TChar.CastFrom(dayAbbrev[1]); + dest[2] = TChar.CastFrom(c); + dest[3] = TChar.CastFrom(','); + dest[4] = TChar.CastFrom(' '); + Number.WriteTwoDigits((uint)day, dest + 5); + dest[7] = TChar.CastFrom(' '); + c = monthAbbrev[2]; // remove bounds checks on remaining monthAbbrev accesses + dest[8] = TChar.CastFrom(monthAbbrev[0]); + dest[9] = TChar.CastFrom(monthAbbrev[1]); + dest[10] = TChar.CastFrom(c); + dest[11] = TChar.CastFrom(' '); + Number.WriteFourDigits((uint)year, dest + 12); + dest[16] = TChar.CastFrom(' '); + Number.WriteTwoDigits((uint)hour, dest + 17); + dest[19] = TChar.CastFrom(':'); + Number.WriteTwoDigits((uint)minute, dest + 20); + dest[22] = TChar.CastFrom(':'); + Number.WriteTwoDigits((uint)second, dest + 23); + dest[25] = TChar.CastFrom(' '); + dest[26] = TChar.CastFrom('G'); + dest[27] = TChar.CastFrom('M'); + dest[28] = TChar.CastFrom('T'); } - charsWritten = 29; return true; } diff --git a/src/libraries/System.Private.CoreLib/src/System/Globalization/DateTimeFormatInfo.cs b/src/libraries/System.Private.CoreLib/src/System/Globalization/DateTimeFormatInfo.cs index 26a182843ac8b..6d07fa3ff9cb1 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Globalization/DateTimeFormatInfo.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Globalization/DateTimeFormatInfo.cs @@ -5,6 +5,8 @@ using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Text; namespace System.Globalization { @@ -81,6 +83,11 @@ public sealed class DateTimeFormatInfo : IFormatProvider, ICloneable private string? monthDayPattern; private string? dateTimeOffsetPattern; + private byte[]? amDesignatorUtf8; + private byte[]? pmDesignatorUtf8; + private byte[]? timeSeparatorUtf8; + private byte[]? dateSeparatorUtf8; + private const string rfc1123Pattern = "ddd, dd MMM yyyy HH':'mm':'ss 'GMT'"; // The sortable pattern is based on ISO 8601. @@ -360,9 +367,18 @@ public string AMDesignator ClearTokenHashTable(); amDesignator = value; + amDesignatorUtf8 = null; } } + internal ReadOnlySpan AMDesignatorTChar() where TChar : unmanaged, IUtfChar + { + Debug.Assert(typeof(TChar) == typeof(char) || typeof(TChar) == typeof(byte)); + return typeof(TChar) == typeof(char) ? + MemoryMarshal.Cast(AMDesignator) : + MemoryMarshal.Cast(amDesignatorUtf8 ??= Encoding.UTF8.GetBytes(AMDesignator)); + } + public Calendar Calendar { get @@ -597,9 +613,18 @@ public string DateSeparator ClearTokenHashTable(); dateSeparator = value; + dateSeparatorUtf8 = null; } } + internal ReadOnlySpan DateSeparatorTChar() where TChar : unmanaged, IUtfChar + { + Debug.Assert(typeof(TChar) == typeof(char) || typeof(TChar) == typeof(byte)); + return typeof(TChar) == typeof(char) ? + MemoryMarshal.Cast(DateSeparator) : + MemoryMarshal.Cast(dateSeparatorUtf8 ??= Encoding.UTF8.GetBytes(DateSeparator)); + } + public DayOfWeek FirstDayOfWeek { get @@ -791,9 +816,18 @@ public string PMDesignator ClearTokenHashTable(); pmDesignator = value; + pmDesignatorUtf8 = null; } } + internal ReadOnlySpan PMDesignatorTChar() where TChar : unmanaged, IUtfChar + { + Debug.Assert(typeof(TChar) == typeof(char) || typeof(TChar) == typeof(byte)); + return typeof(TChar) == typeof(char) ? + MemoryMarshal.Cast(PMDesignator) : + MemoryMarshal.Cast(pmDesignatorUtf8 ??= Encoding.UTF8.GetBytes(PMDesignator)); + } + public string RFC1123Pattern => rfc1123Pattern; /// @@ -966,9 +1000,18 @@ public string TimeSeparator ClearTokenHashTable(); timeSeparator = value; + timeSeparatorUtf8 = null; } } + internal ReadOnlySpan TimeSeparatorTChar() where TChar : unmanaged, IUtfChar + { + Debug.Assert(typeof(TChar) == typeof(char) || typeof(TChar) == typeof(byte)); + return typeof(TChar) == typeof(char) ? + MemoryMarshal.Cast(TimeSeparator) : + MemoryMarshal.Cast(timeSeparatorUtf8 ??= Encoding.UTF8.GetBytes(TimeSeparator)); + } + public string UniversalSortableDateTimePattern => universalSortableDateTimePattern; /// @@ -1713,6 +1756,15 @@ public string[] MonthGenitiveNames _decimalSeparator ??= new NumberFormatInfo(_cultureData.UseUserOverride ? CultureData.GetCultureData(_cultureData.CultureName, false) : _cultureData).NumberDecimalSeparator; + private byte[]? _decimalSeparatorUtf8; + internal ReadOnlySpan DecimalSeparatorTChar() where TChar : unmanaged, IUtfChar + { + Debug.Assert(typeof(TChar) == typeof(char) || typeof(TChar) == typeof(byte)); + return typeof(TChar) == typeof(char) ? + MemoryMarshal.Cast(DecimalSeparator) : + MemoryMarshal.Cast(_decimalSeparatorUtf8 ??= Encoding.UTF8.GetBytes(DecimalSeparator)); + } + // Positive TimeSpan Pattern private string? _fullTimeSpanPositivePattern; internal string FullTimeSpanPositivePattern => diff --git a/src/libraries/System.Private.CoreLib/src/System/Globalization/HebrewNumber.cs b/src/libraries/System.Private.CoreLib/src/System/Globalization/HebrewNumber.cs index dddbaba644777..94652898a521c 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Globalization/HebrewNumber.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Globalization/HebrewNumber.cs @@ -83,7 +83,7 @@ internal static class HebrewNumber // //////////////////////////////////////////////////////////////////////////// - internal static void Append(ref ValueListBuilder outputBuffer, int Number) where TChar : unmanaged, IBinaryInteger + internal static void Append(ref ValueListBuilder outputBuffer, int Number) where TChar : unmanaged, IUtfChar { int outputBufferStartingLength = outputBuffer.Length; @@ -205,7 +205,7 @@ internal static void Append(ref ValueListBuilder outputBuffer, int { TChar last = outputBuffer[outputBuffer.Length - 1]; outputBuffer.Length--; - outputBuffer.Append(TChar.CreateTruncating('"')); + outputBuffer.Append(TChar.CastFrom('"')); outputBuffer.Append(last); } else @@ -213,7 +213,7 @@ internal static void Append(ref ValueListBuilder outputBuffer, int Debug.Assert(typeof(TChar) == typeof(byte)); Rune.DecodeLastFromUtf8(MemoryMarshal.AsBytes(outputBuffer.AsSpan()), out Rune value, out int bytesConsumed); outputBuffer.Length -= bytesConsumed; - outputBuffer.Append(TChar.CreateTruncating('"')); + outputBuffer.Append(TChar.CastFrom('"')); DateTimeFormat.AppendChar(ref outputBuffer, (char)value.Value); } } diff --git a/src/libraries/System.Private.CoreLib/src/System/Globalization/NumberFormatInfo.cs b/src/libraries/System.Private.CoreLib/src/System/Globalization/NumberFormatInfo.cs index 84c84387d3e05..5af77c998154a 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Globalization/NumberFormatInfo.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Globalization/NumberFormatInfo.cs @@ -268,7 +268,7 @@ public string CurrencyDecimalSeparator } [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal ReadOnlySpan CurrencyDecimalSeparatorTChar() where TChar : unmanaged, IBinaryInteger + internal ReadOnlySpan CurrencyDecimalSeparatorTChar() where TChar : unmanaged, IUtfChar { Debug.Assert(typeof(TChar) == typeof(char) || typeof(TChar) == typeof(byte)); return typeof(TChar) == typeof(char) ? @@ -360,7 +360,7 @@ public string CurrencyGroupSeparator } [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal ReadOnlySpan CurrencyGroupSeparatorTChar() where TChar : unmanaged, IBinaryInteger + internal ReadOnlySpan CurrencyGroupSeparatorTChar() where TChar : unmanaged, IUtfChar { Debug.Assert(typeof(TChar) == typeof(char) || typeof(TChar) == typeof(byte)); return typeof(TChar) == typeof(char) ? @@ -382,7 +382,7 @@ public string CurrencySymbol } [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal ReadOnlySpan CurrencySymbolTChar() where TChar : unmanaged, IBinaryInteger + internal ReadOnlySpan CurrencySymbolTChar() where TChar : unmanaged, IUtfChar { Debug.Assert(typeof(TChar) == typeof(char) || typeof(TChar) == typeof(byte)); return typeof(TChar) == typeof(char) ? @@ -428,7 +428,7 @@ public string NaNSymbol } [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal ReadOnlySpan NaNSymbolTChar() where TChar : unmanaged, IBinaryInteger + internal ReadOnlySpan NaNSymbolTChar() where TChar : unmanaged, IUtfChar { Debug.Assert(typeof(TChar) == typeof(char) || typeof(TChar) == typeof(byte)); return typeof(TChar) == typeof(char) ? @@ -525,7 +525,7 @@ public string NegativeInfinitySymbol } [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal ReadOnlySpan NegativeInfinitySymbolTChar() where TChar : unmanaged, IBinaryInteger + internal ReadOnlySpan NegativeInfinitySymbolTChar() where TChar : unmanaged, IUtfChar { Debug.Assert(typeof(TChar) == typeof(char) || typeof(TChar) == typeof(byte)); return typeof(TChar) == typeof(char) ? @@ -548,7 +548,7 @@ public string NegativeSign } [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal ReadOnlySpan NegativeSignTChar() where TChar : unmanaged, IBinaryInteger + internal ReadOnlySpan NegativeSignTChar() where TChar : unmanaged, IUtfChar { Debug.Assert(typeof(TChar) == typeof(char) || typeof(TChar) == typeof(byte)); return typeof(TChar) == typeof(char) ? @@ -587,7 +587,7 @@ public string NumberDecimalSeparator } [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal ReadOnlySpan NumberDecimalSeparatorTChar() where TChar : unmanaged, IBinaryInteger + internal ReadOnlySpan NumberDecimalSeparatorTChar() where TChar : unmanaged, IUtfChar { Debug.Assert(typeof(TChar) == typeof(char) || typeof(TChar) == typeof(byte)); return typeof(TChar) == typeof(char) ? @@ -608,7 +608,7 @@ public string NumberGroupSeparator } [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal ReadOnlySpan NumberGroupSeparatorTChar() where TChar : unmanaged, IBinaryInteger + internal ReadOnlySpan NumberGroupSeparatorTChar() where TChar : unmanaged, IUtfChar { Debug.Assert(typeof(TChar) == typeof(char) || typeof(TChar) == typeof(byte)); return typeof(TChar) == typeof(char) ? @@ -648,7 +648,7 @@ public string PositiveInfinitySymbol } [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal ReadOnlySpan PositiveInfinitySymbolTChar() where TChar : unmanaged, IBinaryInteger + internal ReadOnlySpan PositiveInfinitySymbolTChar() where TChar : unmanaged, IUtfChar { Debug.Assert(typeof(TChar) == typeof(char) || typeof(TChar) == typeof(byte)); return typeof(TChar) == typeof(char) ? @@ -671,7 +671,7 @@ public string PositiveSign } [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal ReadOnlySpan PositiveSignTChar() where TChar : unmanaged, IBinaryInteger + internal ReadOnlySpan PositiveSignTChar() where TChar : unmanaged, IUtfChar { Debug.Assert(typeof(TChar) == typeof(char) || typeof(TChar) == typeof(byte)); return typeof(TChar) == typeof(char) ? @@ -710,7 +710,7 @@ public string PercentDecimalSeparator } [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal ReadOnlySpan PercentDecimalSeparatorTChar() where TChar : unmanaged, IBinaryInteger + internal ReadOnlySpan PercentDecimalSeparatorTChar() where TChar : unmanaged, IUtfChar { Debug.Assert(typeof(TChar) == typeof(char) || typeof(TChar) == typeof(byte)); return typeof(TChar) == typeof(char) ? @@ -731,7 +731,7 @@ public string PercentGroupSeparator } [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal ReadOnlySpan PercentGroupSeparatorTChar() where TChar : unmanaged, IBinaryInteger + internal ReadOnlySpan PercentGroupSeparatorTChar() where TChar : unmanaged, IUtfChar { Debug.Assert(typeof(TChar) == typeof(char) || typeof(TChar) == typeof(byte)); return typeof(TChar) == typeof(char) ? @@ -752,7 +752,7 @@ public string PercentSymbol } [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal ReadOnlySpan PercentSymbolTChar() where TChar : unmanaged, IBinaryInteger + internal ReadOnlySpan PercentSymbolTChar() where TChar : unmanaged, IUtfChar { Debug.Assert(typeof(TChar) == typeof(char) || typeof(TChar) == typeof(byte)); return typeof(TChar) == typeof(char) ? @@ -774,7 +774,7 @@ public string PerMilleSymbol } [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal ReadOnlySpan PerMilleSymbolTChar() where TChar : unmanaged, IBinaryInteger + internal ReadOnlySpan PerMilleSymbolTChar() where TChar : unmanaged, IUtfChar { Debug.Assert(typeof(TChar) == typeof(char) || typeof(TChar) == typeof(byte)); return typeof(TChar) == typeof(char) ? diff --git a/src/libraries/System.Private.CoreLib/src/System/Globalization/TimeSpanFormat.cs b/src/libraries/System.Private.CoreLib/src/System/Globalization/TimeSpanFormat.cs index 094c1a3dadfcb..5b35c0d6ede5c 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Globalization/TimeSpanFormat.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Globalization/TimeSpanFormat.cs @@ -48,7 +48,7 @@ internal static string Format(TimeSpan value, string? format, IFormatProvider? f } /// Main method called from TimeSpan.TryFormat. - internal static bool TryFormat(TimeSpan value, Span destination, out int charsWritten, ReadOnlySpan format, IFormatProvider? formatProvider) where TChar : unmanaged, IBinaryInteger + internal static bool TryFormat(TimeSpan value, Span destination, out int charsWritten, ReadOnlySpan format, IFormatProvider? formatProvider) where TChar : unmanaged, IUtfChar { Debug.Assert(typeof(TChar) == typeof(char) || typeof(TChar) == typeof(byte)); @@ -70,7 +70,7 @@ internal static bool TryFormat(TimeSpan value, Span destination, o c == 'g' ? StandardFormat.g : c == 'G' ? StandardFormat.G : throw new FormatException(SR.Format_InvalidString); - return TryFormatStandard(value, sf, DateTimeFormatInfo.GetInstance(formatProvider).DecimalSeparator, destination, out charsWritten); + return TryFormatStandard(value, sf, DateTimeFormatInfo.GetInstance(formatProvider).DecimalSeparatorTChar(), destination, out charsWritten); } } @@ -101,7 +101,7 @@ private static string FormatG(TimeSpan value, DateTimeFormatInfo dtfi, StandardF internal enum StandardFormat { C, G, g } - internal static bool TryFormatStandard(TimeSpan value, StandardFormat format, string? decimalSeparator, Span destination, out int written) where TChar : unmanaged, IBinaryInteger + internal static unsafe bool TryFormatStandard(TimeSpan value, StandardFormat format, ReadOnlySpan decimalSeparator, Span destination, out int written) where TChar : unmanaged, IUtfChar { Debug.Assert(format == StandardFormat.C || format == StandardFormat.G || format == StandardFormat.g); @@ -144,7 +144,7 @@ internal static bool TryFormatStandard(TimeSpan value, StandardFormat for // "c": Write out a fraction only if it's non-zero, and write out all 7 digits of it. if (fraction != 0) { - Debug.Assert(decimalSeparator is null); + Debug.Assert(decimalSeparator.IsEmpty); fractionDigits = DateTimeFormat.MaxSecondsFractionDigits; requiredOutputLength += fractionDigits + 1; // digits plus leading decimal separator } @@ -152,25 +152,19 @@ internal static bool TryFormatStandard(TimeSpan value, StandardFormat for case StandardFormat.G: // "G": Write out a fraction regardless of whether it's 0, and write out all 7 digits of it. - Debug.Assert(decimalSeparator is not null); fractionDigits = DateTimeFormat.MaxSecondsFractionDigits; requiredOutputLength += fractionDigits; - requiredOutputLength += typeof(TChar) == typeof(char) || (decimalSeparator.Length == 1 && char.IsAscii(decimalSeparator[0])) ? - decimalSeparator.Length : - Encoding.UTF8.GetByteCount(decimalSeparator); + requiredOutputLength += decimalSeparator.Length; break; default: // "g": Write out a fraction only if it's non-zero, and write out only the most significant digits. Debug.Assert(format == StandardFormat.g); - Debug.Assert(decimalSeparator is not null); if (fraction != 0) { fractionDigits = DateTimeFormat.MaxSecondsFractionDigits - FormattingHelpers.CountDecimalTrailingZeros(fraction, out fraction); requiredOutputLength += fractionDigits; - requiredOutputLength += typeof(TChar) == typeof(char) || (decimalSeparator.Length == 1 && char.IsAscii(decimalSeparator[0])) ? - decimalSeparator.Length : - Encoding.UTF8.GetByteCount(decimalSeparator); + requiredOutputLength += decimalSeparator.Length; } break; } @@ -230,82 +224,73 @@ internal static bool TryFormatStandard(TimeSpan value, StandardFormat for return false; } - // Write leading '-' if necessary - int idx = 0; - if (value.Ticks < 0) + fixed (TChar* dest = &MemoryMarshal.GetReference(destination)) { - destination[idx++] = TChar.CreateTruncating('-'); - } + TChar* p = dest; - // Write day and separator, if necessary - if (dayDigits != 0) - { - Number.WriteDigits(days, destination.Slice(idx, dayDigits)); - idx += dayDigits; - destination[idx++] = TChar.CreateTruncating(format == StandardFormat.C ? '.' : ':'); - } + // Write leading '-' if necessary + if (value.Ticks < 0) + { + *p++ = TChar.CastFrom('-'); + } - // Write "[h]h:mm:ss - Debug.Assert(hourDigits == 1 || hourDigits == 2); - if (hourDigits == 2) - { - Number.WriteTwoDigits(hours, destination, idx); - idx += 2; - } - else - { - destination[idx++] = TChar.CreateTruncating('0' + hours); - } - destination[idx++] = TChar.CreateTruncating(':'); - Number.WriteTwoDigits((uint)minutes, destination, idx); - idx += 2; - destination[idx++] = TChar.CreateTruncating(':'); - Number.WriteTwoDigits((uint)seconds, destination, idx); - idx += 2; - - // Write fraction and separator, if necessary - if (fractionDigits != 0) - { - Debug.Assert(format == StandardFormat.C || decimalSeparator != null); - if (format == StandardFormat.C) + // Write day and separator, if necessary + if (dayDigits != 0) { - destination[idx++] = TChar.CreateTruncating('.'); + Number.WriteDigits(days, p, dayDigits); + p += dayDigits; + *p++ = TChar.CastFrom(format == StandardFormat.C ? '.' : ':'); } - else if (typeof(TChar) == typeof(char)) + + // Write "[h]h:mm:ss + Debug.Assert(hourDigits == 1 || hourDigits == 2); + if (hourDigits == 2) { - if (decimalSeparator!.Length == 1) - { - destination[idx++] = TChar.CreateTruncating(decimalSeparator[0]); - } - else - { - decimalSeparator.CopyTo(MemoryMarshal.Cast(destination).Slice(idx)); - idx += decimalSeparator.Length; - } + Number.WriteTwoDigits(hours, p); + p += 2; } else { - Debug.Assert(typeof(TChar) == typeof(byte)); - if (decimalSeparator!.Length == 1 && char.IsAscii(decimalSeparator[0])) + *p++ = TChar.CastFrom('0' + hours); + } + *p++ = TChar.CastFrom(':'); + Number.WriteTwoDigits((uint)minutes, p); + p += 2; + *p++ = TChar.CastFrom(':'); + Number.WriteTwoDigits((uint)seconds, p); + p += 2; + + // Write fraction and separator, if necessary + if (fractionDigits != 0) + { + Debug.Assert(format == StandardFormat.C || decimalSeparator != null); + if (format == StandardFormat.C) + { + *p++ = TChar.CastFrom('.'); + } + else if (decimalSeparator!.Length == 1) { - destination[idx++] = TChar.CreateTruncating(decimalSeparator[0]); + *p++ = decimalSeparator[0]; } else { - idx += Encoding.UTF8.GetBytes(decimalSeparator, MemoryMarshal.Cast(destination).Slice(idx)); + decimalSeparator.CopyTo(new Span(p, decimalSeparator.Length)); + p += decimalSeparator.Length; } + + Number.WriteDigits(fraction, p, fractionDigits); + p += fractionDigits; } - Number.WriteDigits(fraction, destination.Slice(idx, fractionDigits)); - idx += fractionDigits; + + Debug.Assert(p - dest == requiredOutputLength); } - Debug.Assert(idx == requiredOutputLength); written = requiredOutputLength; return true; } /// Format the TimeSpan instance using the specified format. - private static void FormatCustomized(TimeSpan value, scoped ReadOnlySpan format, DateTimeFormatInfo dtfi, ref ValueListBuilder result) where TChar : unmanaged, IBinaryInteger + private static void FormatCustomized(TimeSpan value, scoped ReadOnlySpan format, DateTimeFormatInfo dtfi, ref ValueListBuilder result) where TChar : unmanaged, IUtfChar { Debug.Assert(dtfi != null); @@ -411,7 +396,7 @@ private static void FormatCustomized(TimeSpan value, scoped ReadOnlySpan< goto default; // to release the builder and throw } - DateTimeFormat.FormatDigits(ref result, day, tokenLen, true); + DateTimeFormat.FormatDigits(ref result, day, tokenLen); break; case '\'': case '\"': @@ -446,7 +431,7 @@ private static void FormatCustomized(TimeSpan value, scoped ReadOnlySpan< nextChar = DateTimeFormat.ParseNextChar(format, i); if (nextChar >= 0) { - result.Append(TChar.CreateTruncating(nextChar)); + result.Append(TChar.CastFrom(nextChar)); tokenLen = 2; } else diff --git a/src/libraries/System.Private.CoreLib/src/System/IUtfChar.cs b/src/libraries/System.Private.CoreLib/src/System/IUtfChar.cs new file mode 100644 index 0000000000000..09d8ba184436f --- /dev/null +++ b/src/libraries/System.Private.CoreLib/src/System/IUtfChar.cs @@ -0,0 +1,32 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Numerics; + +namespace System +{ + // NOTE: This is a workaround for current inlining limitations of some backend code generators. + // We would prefer to not have this interface at all and instead just use TChar.CreateTruncuating. + // Once inlining is improved on these hot code paths in formatting, we can remove this interface. + + /// Internal interface used to unify char and byte in formatting operations. + internal interface IUtfChar : + IBinaryInteger + where TSelf : unmanaged, IUtfChar + { + /// Casts the specified value to this type. + public static abstract TSelf CastFrom(byte value); + + /// Casts the specified value to this type. + public static abstract TSelf CastFrom(char value); + + /// Casts the specified value to this type. + public static abstract TSelf CastFrom(int value); + + /// Casts the specified value to this type. + public static abstract TSelf CastFrom(uint value); + + /// Casts the specified value to this type. + public static abstract TSelf CastFrom(ulong value); + } +} diff --git a/src/libraries/System.Private.CoreLib/src/System/Number.Formatting.cs b/src/libraries/System.Private.CoreLib/src/System/Number.Formatting.cs index 98ce459f26a32..9dbfc0501bbde 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Number.Formatting.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Number.Formatting.cs @@ -319,30 +319,32 @@ internal static partial class Number "(#)", "-#", "- #", "#-", "# -", }; +#if !MONO // Optimizations using "TwoDigits" inspired by: // https://engineering.fb.com/2013/03/15/developer-tools/three-optimization-tips-for-c/ - private const string TwoDigitsChars = - "00010203040506070809" + - "10111213141516171819" + - "20212223242526272829" + - "30313233343536373839" + - "40414243444546474849" + - "50515253545556575859" + - "60616263646566676869" + - "70717273747576777879" + - "80818283848586878889" + - "90919293949596979899"; - private static ReadOnlySpan TwoDigitsBytes => - "00010203040506070809"u8 + - "10111213141516171819"u8 + - "20212223242526272829"u8 + - "30313233343536373839"u8 + - "40414243444546474849"u8 + - "50515253545556575859"u8 + - "60616263646566676869"u8 + - "70717273747576777879"u8 + - "80818283848586878889"u8 + - "90919293949596979899"u8; + private static readonly byte[] TwoDigitsCharsAsBytes = + MemoryMarshal.AsBytes("00010203040506070809" + + "10111213141516171819" + + "20212223242526272829" + + "30313233343536373839" + + "40414243444546474849" + + "50515253545556575859" + + "60616263646566676869" + + "70717273747576777879" + + "80818283848586878889" + + "90919293949596979899").ToArray(); + private static readonly byte[] TwoDigitsBytes = + ("00010203040506070809"u8 + + "10111213141516171819"u8 + + "20212223242526272829"u8 + + "30313233343536373839"u8 + + "40414243444546474849"u8 + + "50515253545556575859"u8 + + "60616263646566676869"u8 + + "70717273747576777879"u8 + + "80818283848586878889"u8 + + "90919293949596979899"u8).ToArray(); +#endif public static unsafe string FormatDecimal(decimal value, ReadOnlySpan format, NumberFormatInfo info) { @@ -370,7 +372,7 @@ public static unsafe string FormatDecimal(decimal value, ReadOnlySpan form return result; } - public static unsafe bool TryFormatDecimal(decimal value, ReadOnlySpan format, NumberFormatInfo info, Span destination, out int charsWritten) where TChar : unmanaged, IBinaryInteger + public static unsafe bool TryFormatDecimal(decimal value, ReadOnlySpan format, NumberFormatInfo info, Span destination, out int charsWritten) where TChar : unmanaged, IUtfChar { Debug.Assert(typeof(TChar) == typeof(char) || typeof(TChar) == typeof(byte)); @@ -434,7 +436,7 @@ public static string FormatDouble(double value, string? format, NumberFormatInfo return result; } - public static bool TryFormatDouble(double value, ReadOnlySpan format, NumberFormatInfo info, Span destination, out int charsWritten) where TChar : unmanaged, IBinaryInteger + public static bool TryFormatDouble(double value, ReadOnlySpan format, NumberFormatInfo info, Span destination, out int charsWritten) where TChar : unmanaged, IUtfChar { var vlb = new ValueListBuilder(stackalloc TChar[CharStackBufferSize]); string? s = FormatDouble(ref vlb, value, format, info); @@ -574,7 +576,7 @@ private static int GetFloatingPointMaxDigitsAndPrecision(char fmt, ref int preci /// Non-null if an existing string can be returned, in which case the builder will be unmodified. /// Null if no existing string was returned, in which case the formatted output is in the builder. /// - private static unsafe string? FormatDouble(ref ValueListBuilder vlb, double value, ReadOnlySpan format, NumberFormatInfo info) where TChar : unmanaged, IBinaryInteger + private static unsafe string? FormatDouble(ref ValueListBuilder vlb, double value, ReadOnlySpan format, NumberFormatInfo info) where TChar : unmanaged, IUtfChar { if (!double.IsFinite(value)) { @@ -664,7 +666,7 @@ public static string FormatSingle(float value, string? format, NumberFormatInfo return result; } - public static bool TryFormatSingle(float value, ReadOnlySpan format, NumberFormatInfo info, Span destination, out int charsWritten) where TChar : unmanaged, IBinaryInteger + public static bool TryFormatSingle(float value, ReadOnlySpan format, NumberFormatInfo info, Span destination, out int charsWritten) where TChar : unmanaged, IUtfChar { var vlb = new ValueListBuilder(stackalloc TChar[CharStackBufferSize]); string? s = FormatSingle(ref vlb, value, format, info); @@ -683,7 +685,7 @@ public static bool TryFormatSingle(float value, ReadOnlySpan format /// Non-null if an existing string can be returned, in which case the builder will be unmodified. /// Null if no existing string was returned, in which case the formatted output is in the builder. /// - private static unsafe string? FormatSingle(ref ValueListBuilder vlb, float value, ReadOnlySpan format, NumberFormatInfo info) where TChar : unmanaged, IBinaryInteger + private static unsafe string? FormatSingle(ref ValueListBuilder vlb, float value, ReadOnlySpan format, NumberFormatInfo info) where TChar : unmanaged, IUtfChar { Debug.Assert(typeof(TChar) == typeof(char) || typeof(TChar) == typeof(byte)); @@ -780,7 +782,7 @@ public static string FormatHalf(Half value, string? format, NumberFormatInfo inf /// Non-null if an existing string can be returned, in which case the builder will be unmodified. /// Null if no existing string was returned, in which case the formatted output is in the builder. /// - private static unsafe string? FormatHalf(ref ValueListBuilder vlb, Half value, ReadOnlySpan format, NumberFormatInfo info) where TChar : unmanaged, IBinaryInteger + private static unsafe string? FormatHalf(ref ValueListBuilder vlb, Half value, ReadOnlySpan format, NumberFormatInfo info) where TChar : unmanaged, IUtfChar { Debug.Assert(typeof(TChar) == typeof(char) || typeof(TChar) == typeof(byte)); @@ -862,7 +864,7 @@ public static string FormatHalf(Half value, string? format, NumberFormatInfo inf return null; } - public static bool TryFormatHalf(Half value, ReadOnlySpan format, NumberFormatInfo info, Span destination, out int charsWritten) where TChar : unmanaged, IBinaryInteger + public static bool TryFormatHalf(Half value, ReadOnlySpan format, NumberFormatInfo info, Span destination, out int charsWritten) where TChar : unmanaged, IUtfChar { Debug.Assert(typeof(TChar) == typeof(char) || typeof(TChar) == typeof(byte)); @@ -878,7 +880,7 @@ public static bool TryFormatHalf(Half value, ReadOnlySpan format, N return success; } - private static bool TryCopyTo(string source, Span destination, out int charsWritten) where TChar : unmanaged, IBinaryInteger + private static bool TryCopyTo(string source, Span destination, out int charsWritten) where TChar : unmanaged, IUtfChar { Debug.Assert(typeof(TChar) == typeof(char) || typeof(TChar) == typeof(byte)); Debug.Assert(source != null); @@ -963,7 +965,7 @@ static unsafe string FormatInt32Slow(int value, int hexMask, string? format, IFo } [MethodImpl(MethodImplOptions.AggressiveInlining)] // expose to caller's likely-const format to trim away slow path - public static bool TryFormatInt32(int value, int hexMask, ReadOnlySpan format, IFormatProvider? provider, Span destination, out int charsWritten) where TChar : unmanaged, IBinaryInteger + public static bool TryFormatInt32(int value, int hexMask, ReadOnlySpan format, IFormatProvider? provider, Span destination, out int charsWritten) where TChar : unmanaged, IUtfChar { // Fast path for default format if (format.Length == 0) @@ -1069,7 +1071,7 @@ static unsafe string FormatUInt32Slow(uint value, string? format, IFormatProvide } [MethodImpl(MethodImplOptions.AggressiveInlining)] // expose to caller's likely-const format to trim away slow path - public static bool TryFormatUInt32(uint value, ReadOnlySpan format, IFormatProvider? provider, Span destination, out int charsWritten) where TChar : unmanaged, IBinaryInteger + public static bool TryFormatUInt32(uint value, ReadOnlySpan format, IFormatProvider? provider, Span destination, out int charsWritten) where TChar : unmanaged, IUtfChar { Debug.Assert(typeof(TChar) == typeof(char) || typeof(TChar) == typeof(byte)); @@ -1177,7 +1179,7 @@ static unsafe string FormatInt64Slow(long value, string? format, IFormatProvider } [MethodImpl(MethodImplOptions.AggressiveInlining)] // expose to caller's likely-const format to trim away slow path - public static bool TryFormatInt64(long value, ReadOnlySpan format, IFormatProvider? provider, Span destination, out int charsWritten) where TChar : unmanaged, IBinaryInteger + public static bool TryFormatInt64(long value, ReadOnlySpan format, IFormatProvider? provider, Span destination, out int charsWritten) where TChar : unmanaged, IUtfChar { Debug.Assert(typeof(TChar) == typeof(char) || typeof(TChar) == typeof(byte)); @@ -1285,7 +1287,7 @@ static unsafe string FormatUInt64Slow(ulong value, string? format, IFormatProvid } [MethodImpl(MethodImplOptions.AggressiveInlining)] // expose to caller's likely-const format to trim away slow path - public static bool TryFormatUInt64(ulong value, ReadOnlySpan format, IFormatProvider? provider, Span destination, out int charsWritten) where TChar : unmanaged, IBinaryInteger + public static bool TryFormatUInt64(ulong value, ReadOnlySpan format, IFormatProvider? provider, Span destination, out int charsWritten) where TChar : unmanaged, IUtfChar { Debug.Assert(typeof(TChar) == typeof(char) || typeof(TChar) == typeof(byte)); @@ -1394,7 +1396,7 @@ static unsafe string FormatInt128Slow(Int128 value, string? format, IFormatProvi } } - public static bool TryFormatInt128(Int128 value, ReadOnlySpan format, IFormatProvider? provider, Span destination, out int charsWritten) where TChar : unmanaged, IBinaryInteger + public static bool TryFormatInt128(Int128 value, ReadOnlySpan format, IFormatProvider? provider, Span destination, out int charsWritten) where TChar : unmanaged, IUtfChar { Debug.Assert(typeof(TChar) == typeof(char) || typeof(TChar) == typeof(byte)); @@ -1504,7 +1506,7 @@ static unsafe string FormatUInt128Slow(UInt128 value, string? format, IFormatPro } } - public static bool TryFormatUInt128(UInt128 value, ReadOnlySpan format, IFormatProvider? provider, Span destination, out int charsWritten) where TChar : unmanaged, IBinaryInteger + public static bool TryFormatUInt128(UInt128 value, ReadOnlySpan format, IFormatProvider? provider, Span destination, out int charsWritten) where TChar : unmanaged, IUtfChar { Debug.Assert(typeof(TChar) == typeof(char) || typeof(TChar) == typeof(byte)); @@ -1620,7 +1622,7 @@ private static unsafe string NegativeInt32ToDecStr(int value, int digits, string return result; } - private static unsafe bool TryNegativeInt32ToDecStr(int value, int digits, ReadOnlySpan sNegative, Span destination, out int charsWritten) where TChar : unmanaged, IBinaryInteger + private static unsafe bool TryNegativeInt32ToDecStr(int value, int digits, ReadOnlySpan sNegative, Span destination, out int charsWritten) where TChar : unmanaged, IUtfChar { Debug.Assert(typeof(TChar) == typeof(char) || typeof(TChar) == typeof(byte)); Debug.Assert(value < 0); @@ -1669,7 +1671,7 @@ private static unsafe string Int32ToHexStr(int value, char hexBase, int digits) return result; } - private static unsafe bool TryInt32ToHexStr(int value, char hexBase, int digits, Span destination, out int charsWritten) where TChar : unmanaged, IBinaryInteger + private static unsafe bool TryInt32ToHexStr(int value, char hexBase, int digits, Span destination, out int charsWritten) where TChar : unmanaged, IUtfChar { Debug.Assert(typeof(TChar) == typeof(char) || typeof(TChar) == typeof(byte)); @@ -1695,14 +1697,14 @@ private static unsafe bool TryInt32ToHexStr(int value, char hexBase, int } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static unsafe TChar* Int32ToHexChars(TChar* buffer, uint value, int hexBase, int digits) where TChar : unmanaged, IBinaryInteger + private static unsafe TChar* Int32ToHexChars(TChar* buffer, uint value, int hexBase, int digits) where TChar : unmanaged, IUtfChar { Debug.Assert(typeof(TChar) == typeof(char) || typeof(TChar) == typeof(byte)); while (--digits >= 0 || value != 0) { byte digit = (byte)(value & 0xF); - *(--buffer) = TChar.CreateTruncating(digit + (digit < 10 ? (byte)'0' : hexBase)); + *(--buffer) = TChar.CastFrom(digit + (digit < 10 ? (byte)'0' : hexBase)); value >>= 4; } return buffer; @@ -1732,42 +1734,23 @@ private static unsafe void UInt32ToNumber(uint value, ref NumberBuffer number) number.CheckConsistency(); } - /// - /// Writes a value [ 00 .. 99 ] to the buffer starting at the specified offset. - /// This method performs best when the starting index is a constant literal. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static unsafe void WriteTwoDigits(uint value, Span buffer, int startingIndex = 0) where TChar : unmanaged, IBinaryInteger - { - Debug.Assert(typeof(TChar) == typeof(char) || typeof(TChar) == typeof(byte)); - Debug.Assert(value <= 99); - Debug.Assert(startingIndex <= buffer.Length - 2); - - fixed (TChar* bufferPtr = &MemoryMarshal.GetReference(buffer)) - { - WriteTwoDigits(bufferPtr + startingIndex, value); - } - } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static unsafe void WriteTwoDigits(TChar* ptr, uint value) where TChar : unmanaged, IBinaryInteger + internal static unsafe void WriteTwoDigits(uint value, TChar* ptr) where TChar : unmanaged, IUtfChar { Debug.Assert(typeof(TChar) == typeof(char) || typeof(TChar) == typeof(byte)); Debug.Assert(value <= 99); - if (typeof(TChar) == typeof(char)) - { - Unsafe.WriteUnaligned(ptr, - Unsafe.ReadUnaligned( - ref Unsafe.As( - ref Unsafe.Add(ref TwoDigitsChars.GetRawStringData(), (nuint)value * 2)))); - } - else - { - Unsafe.WriteUnaligned(ptr, - Unsafe.ReadUnaligned( - ref Unsafe.Add(ref MemoryMarshal.GetReference(TwoDigitsBytes), (nuint)value * 2))); - } +#if MONO // mono currently regresses in performance taking the CopyBlockUnaligned path + uint temp = '0' + value; + value /= 10; + ptr[1] = TChar.CastFrom(temp - (value * 10)); + ptr[0] = TChar.CastFrom('0' + value); +#else + Unsafe.CopyBlockUnaligned( + ref *(byte*)ptr, + ref Unsafe.Add(ref MemoryMarshal.GetArrayDataReference(typeof(TChar) == typeof(char) ? TwoDigitsCharsAsBytes : TwoDigitsBytes), (uint)sizeof(TChar) * 2 * value), + (uint)sizeof(TChar) * 2); +#endif } /// @@ -1775,54 +1758,60 @@ ref Unsafe.As( /// This method performs best when the starting index is a constant literal. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static unsafe void WriteFourDigits(uint value, Span buffer, int startingIndex = 0) where TChar : unmanaged, IBinaryInteger + internal static unsafe void WriteFourDigits(uint value, TChar* ptr) where TChar : unmanaged, IUtfChar { Debug.Assert(typeof(TChar) == typeof(char) || typeof(TChar) == typeof(byte)); Debug.Assert(value <= 9999); - Debug.Assert(startingIndex <= buffer.Length - 4); +#if MONO // mono currently regresses in performance taking the CopyBlockUnaligned path + uint temp = '0' + value; + value /= 10; + ptr[3] = TChar.CastFrom(temp - (value * 10)); + + temp = '0' + value; + value /= 10; + ptr[2] = TChar.CastFrom(temp - (value * 10)); + + temp = '0' + value; + value /= 10; + ptr[1] = TChar.CastFrom(temp - (value * 10)); + + ptr[0] = TChar.CastFrom('0' + value); +#else (value, uint remainder) = Math.DivRem(value, 100); - fixed (TChar* bufferPtr = &MemoryMarshal.GetReference(buffer)) - { - WriteTwoDigits(bufferPtr + startingIndex, value); - WriteTwoDigits(bufferPtr + startingIndex + 2, remainder); - } - } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static void CopyFour(ReadOnlySpan source, Span destination) where TChar : unmanaged, IBinaryInteger - { - if (typeof(TChar) == typeof(byte)) - { - Unsafe.WriteUnaligned(ref Unsafe.As(ref MemoryMarshal.GetReference(destination)), - Unsafe.ReadUnaligned(ref Unsafe.As(ref MemoryMarshal.GetReference(source)))); - } - else - { - Debug.Assert(typeof(TChar) == typeof(char)); - Unsafe.WriteUnaligned(ref Unsafe.As(ref MemoryMarshal.GetReference(destination)), - Unsafe.ReadUnaligned(ref Unsafe.As(ref MemoryMarshal.GetReference(source)))); - } + ref byte charsArray = ref MemoryMarshal.GetArrayDataReference(typeof(TChar) == typeof(char) ? TwoDigitsCharsAsBytes : TwoDigitsBytes); + + Unsafe.CopyBlockUnaligned( + ref *(byte*)ptr, + ref Unsafe.Add(ref charsArray, (uint)sizeof(TChar) * 2 * value), + (uint)sizeof(TChar) * 2); + + Unsafe.CopyBlockUnaligned( + ref *(byte*)(ptr + 2), + ref Unsafe.Add(ref charsArray, (uint)sizeof(TChar) * 2 * remainder), + (uint)sizeof(TChar) * 2); +#endif } [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static void WriteDigits(uint value, Span buffer) where TChar : unmanaged, IBinaryInteger + internal static unsafe void WriteDigits(uint value, TChar* ptr, int count) where TChar : unmanaged, IUtfChar { - Debug.Assert(buffer.Length > 0); - - for (int i = buffer.Length - 1; i >= 1; i--) + TChar* cur; + for (cur = ptr + count - 1; cur > ptr; cur--) { uint temp = '0' + value; value /= 10; - buffer[i] = TChar.CreateTruncating(temp - (value * 10)); + *cur = TChar.CastFrom(temp - (value * 10)); } Debug.Assert(value < 10); - buffer[0] = TChar.CreateTruncating('0' + value); + Debug.Assert(cur == ptr); + *cur = TChar.CastFrom('0' + value); } [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static unsafe TChar* UInt32ToDecChars(TChar* bufferEnd, uint value) where TChar : unmanaged, IBinaryInteger + internal static unsafe TChar* UInt32ToDecChars(TChar* bufferEnd, uint value) where TChar : unmanaged, IUtfChar { Debug.Assert(typeof(TChar) == typeof(char) || typeof(TChar) == typeof(byte)); @@ -1833,25 +1822,25 @@ internal static void WriteDigits(uint value, Span buffer) where TC { bufferEnd -= 2; (value, uint remainder) = Math.DivRem(value, 100); - WriteTwoDigits(bufferEnd, remainder); + WriteTwoDigits(remainder, bufferEnd); } // If there are two digits remaining, store them. if (value >= 10) { bufferEnd -= 2; - WriteTwoDigits(bufferEnd, value); + WriteTwoDigits(value, bufferEnd); return bufferEnd; } } // Otherwise, store the single digit remaining. - *(--bufferEnd) = TChar.CreateTruncating(value + '0'); + *(--bufferEnd) = TChar.CastFrom(value + '0'); return bufferEnd; } [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static unsafe TChar* UInt32ToDecChars(TChar* bufferEnd, uint value, int digits) where TChar : unmanaged, IBinaryInteger + internal static unsafe TChar* UInt32ToDecChars(TChar* bufferEnd, uint value, int digits) where TChar : unmanaged, IUtfChar { Debug.Assert(typeof(TChar) == typeof(char) || typeof(TChar) == typeof(byte)); @@ -1861,14 +1850,14 @@ internal static void WriteDigits(uint value, Span buffer) where TC bufferEnd -= 2; digits -= 2; (value, remainder) = Math.DivRem(value, 100); - WriteTwoDigits(bufferEnd, remainder); + WriteTwoDigits(remainder, bufferEnd); } while (value != 0 || digits > 0) { digits--; (value, remainder) = Math.DivRem(value, 10); - *(--bufferEnd) = TChar.CreateTruncating(remainder + '0'); + *(--bufferEnd) = TChar.CastFrom(remainder + '0'); } return bufferEnd; @@ -1925,7 +1914,7 @@ private static unsafe string UInt32ToDecStr(uint value, int digits) return result; } - private static unsafe bool TryUInt32ToDecStr(uint value, Span destination, out int charsWritten) where TChar : unmanaged, IBinaryInteger + private static unsafe bool TryUInt32ToDecStr(uint value, Span destination, out int charsWritten) where TChar : unmanaged, IUtfChar { Debug.Assert(typeof(TChar) == typeof(char) || typeof(TChar) == typeof(byte)); @@ -1945,7 +1934,7 @@ private static unsafe bool TryUInt32ToDecStr(uint value, Span dest return false; } - private static unsafe bool TryUInt32ToDecStr(uint value, int digits, Span destination, out int charsWritten) where TChar : unmanaged, IBinaryInteger + private static unsafe bool TryUInt32ToDecStr(uint value, int digits, Span destination, out int charsWritten) where TChar : unmanaged, IUtfChar { Debug.Assert(typeof(TChar) == typeof(char) || typeof(TChar) == typeof(byte)); @@ -2034,7 +2023,7 @@ private static unsafe string NegativeInt64ToDecStr(long value, int digits, strin return result; } - private static unsafe bool TryNegativeInt64ToDecStr(long value, int digits, ReadOnlySpan sNegative, Span destination, out int charsWritten) where TChar : unmanaged, IBinaryInteger + private static unsafe bool TryNegativeInt64ToDecStr(long value, int digits, ReadOnlySpan sNegative, Span destination, out int charsWritten) where TChar : unmanaged, IUtfChar { Debug.Assert(typeof(TChar) == typeof(char) || typeof(TChar) == typeof(byte)); Debug.Assert(value < 0); @@ -2083,7 +2072,7 @@ private static unsafe string Int64ToHexStr(long value, char hexBase, int digits) return result; } - private static unsafe bool TryInt64ToHexStr(long value, char hexBase, int digits, Span destination, out int charsWritten) where TChar : unmanaged, IBinaryInteger + private static unsafe bool TryInt64ToHexStr(long value, char hexBase, int digits, Span destination, out int charsWritten) where TChar : unmanaged, IUtfChar { Debug.Assert(typeof(TChar) == typeof(char) || typeof(TChar) == typeof(byte)); @@ -2111,7 +2100,7 @@ private static unsafe bool TryInt64ToHexStr(long value, char hexBase, int #if TARGET_64BIT [MethodImpl(MethodImplOptions.AggressiveInlining)] #endif - private static unsafe TChar* Int64ToHexChars(TChar* buffer, ulong value, int hexBase, int digits) where TChar : unmanaged, IBinaryInteger + private static unsafe TChar* Int64ToHexChars(TChar* buffer, ulong value, int hexBase, int digits) where TChar : unmanaged, IUtfChar { Debug.Assert(typeof(TChar) == typeof(char) || typeof(TChar) == typeof(byte)); #if TARGET_32BIT @@ -2131,7 +2120,7 @@ private static unsafe bool TryInt64ToHexStr(long value, char hexBase, int while (--digits >= 0 || value != 0) { byte digit = (byte)(value & 0xF); - *(--buffer) = TChar.CreateTruncating(digit + (digit < 10 ? (byte)'0' : hexBase)); + *(--buffer) = TChar.CastFrom(digit + (digit < 10 ? (byte)'0' : hexBase)); value >>= 4; } return buffer; @@ -2173,7 +2162,7 @@ private static uint Int64DivMod1E9(ref ulong value) #if TARGET_64BIT [MethodImpl(MethodImplOptions.AggressiveInlining)] #endif - internal static unsafe TChar* UInt64ToDecChars(TChar* bufferEnd, ulong value) where TChar : unmanaged, IBinaryInteger + internal static unsafe TChar* UInt64ToDecChars(TChar* bufferEnd, ulong value) where TChar : unmanaged, IUtfChar { Debug.Assert(typeof(TChar) == typeof(char) || typeof(TChar) == typeof(byte)); @@ -2191,20 +2180,20 @@ private static uint Int64DivMod1E9(ref ulong value) { bufferEnd -= 2; (value, ulong remainder) = Math.DivRem(value, 100); - WriteTwoDigits(bufferEnd, (uint)remainder); + WriteTwoDigits((uint)remainder, bufferEnd); } // If there are two digits remaining, store them. if (value >= 10) { bufferEnd -= 2; - WriteTwoDigits(bufferEnd, (uint)value); + WriteTwoDigits((uint)value, bufferEnd); return bufferEnd; } } // Otherwise, store the single digit remaining. - *(--bufferEnd) = TChar.CreateTruncating(value + '0'); + *(--bufferEnd) = TChar.CastFrom(value + '0'); return bufferEnd; #endif } @@ -2212,7 +2201,7 @@ private static uint Int64DivMod1E9(ref ulong value) #if TARGET_64BIT [MethodImpl(MethodImplOptions.AggressiveInlining)] #endif - internal static unsafe TChar* UInt64ToDecChars(TChar* bufferEnd, ulong value, int digits) where TChar : unmanaged, IBinaryInteger + internal static unsafe TChar* UInt64ToDecChars(TChar* bufferEnd, ulong value, int digits) where TChar : unmanaged, IUtfChar { Debug.Assert(typeof(TChar) == typeof(char) || typeof(TChar) == typeof(byte)); @@ -2230,14 +2219,14 @@ private static uint Int64DivMod1E9(ref ulong value) bufferEnd -= 2; digits -= 2; (value, remainder) = Math.DivRem(value, 100); - WriteTwoDigits(bufferEnd, (uint)remainder); + WriteTwoDigits((uint)remainder, bufferEnd); } while (value != 0 || digits > 0) { digits--; (value, remainder) = Math.DivRem(value, 10); - *(--bufferEnd) = TChar.CreateTruncating(remainder + '0'); + *(--bufferEnd) = TChar.CastFrom(remainder + '0'); } return bufferEnd; @@ -2282,7 +2271,7 @@ internal static unsafe string UInt64ToDecStr(ulong value, int digits) return result; } - private static unsafe bool TryUInt64ToDecStr(ulong value, Span destination, out int charsWritten) where TChar : unmanaged, IBinaryInteger + private static unsafe bool TryUInt64ToDecStr(ulong value, Span destination, out int charsWritten) where TChar : unmanaged, IUtfChar { Debug.Assert(typeof(TChar) == typeof(char) || typeof(TChar) == typeof(byte)); @@ -2303,7 +2292,7 @@ private static unsafe bool TryUInt64ToDecStr(ulong value, Span des return false; } - private static unsafe bool TryUInt64ToDecStr(ulong value, int digits, Span destination, out int charsWritten) where TChar : unmanaged, IBinaryInteger + private static unsafe bool TryUInt64ToDecStr(ulong value, int digits, Span destination, out int charsWritten) where TChar : unmanaged, IUtfChar { int countedDigits = FormattingHelpers.CountDigits(value); int bufferLength = Math.Max(digits, countedDigits); @@ -2391,7 +2380,7 @@ private static unsafe string NegativeInt128ToDecStr(Int128 value, int digits, st return result; } - private static unsafe bool TryNegativeInt128ToDecStr(Int128 value, int digits, ReadOnlySpan sNegative, Span destination, out int charsWritten) where TChar : unmanaged, IBinaryInteger + private static unsafe bool TryNegativeInt128ToDecStr(Int128 value, int digits, ReadOnlySpan sNegative, Span destination, out int charsWritten) where TChar : unmanaged, IUtfChar { Debug.Assert(typeof(TChar) == typeof(char) || typeof(TChar) == typeof(byte)); Debug.Assert(Int128.IsNegative(value)); @@ -2444,7 +2433,7 @@ private static unsafe string Int128ToHexStr(Int128 value, char hexBase, int digi return result; } - private static unsafe bool TryInt128ToHexStr(Int128 value, char hexBase, int digits, Span destination, out int charsWritten) where TChar : unmanaged, IBinaryInteger + private static unsafe bool TryInt128ToHexStr(Int128 value, char hexBase, int digits, Span destination, out int charsWritten) where TChar : unmanaged, IUtfChar { Debug.Assert(typeof(TChar) == typeof(char) || typeof(TChar) == typeof(byte)); @@ -2472,7 +2461,7 @@ private static unsafe bool TryInt128ToHexStr(Int128 value, char hexBase, } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static unsafe TChar* Int128ToHexChars(TChar* buffer, UInt128 value, int hexBase, int digits) where TChar : unmanaged, IBinaryInteger + private static unsafe TChar* Int128ToHexChars(TChar* buffer, UInt128 value, int hexBase, int digits) where TChar : unmanaged, IUtfChar { ulong lower = value.Lower; ulong upper = value.Upper; @@ -2520,7 +2509,7 @@ private static ulong Int128DivMod1E19(ref UInt128 value) } [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static unsafe TChar* UInt128ToDecChars(TChar* bufferEnd, UInt128 value) where TChar : unmanaged, IBinaryInteger + internal static unsafe TChar* UInt128ToDecChars(TChar* bufferEnd, UInt128 value) where TChar : unmanaged, IUtfChar { Debug.Assert(typeof(TChar) == typeof(char) || typeof(TChar) == typeof(byte)); @@ -2532,7 +2521,7 @@ private static ulong Int128DivMod1E19(ref UInt128 value) } [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static unsafe TChar* UInt128ToDecChars(TChar* bufferEnd, UInt128 value, int digits) where TChar : unmanaged, IBinaryInteger + internal static unsafe TChar* UInt128ToDecChars(TChar* bufferEnd, UInt128 value, int digits) where TChar : unmanaged, IUtfChar { Debug.Assert(typeof(TChar) == typeof(char) || typeof(TChar) == typeof(byte)); @@ -2581,7 +2570,7 @@ internal static unsafe string UInt128ToDecStr(UInt128 value, int digits) return result; } - private static unsafe bool TryUInt128ToDecStr(UInt128 value, int digits, Span destination, out int charsWritten) where TChar : unmanaged, IBinaryInteger + private static unsafe bool TryUInt128ToDecStr(UInt128 value, int digits, Span destination, out int charsWritten) where TChar : unmanaged, IUtfChar { int countedDigits = FormattingHelpers.CountDigits(value); int bufferLength = Math.Max(digits, countedDigits); @@ -2673,7 +2662,7 @@ internal static unsafe char ParseFormatSpecifier(ReadOnlySpan format, out '\0'; } - internal static unsafe void NumberToString(ref ValueListBuilder vlb, ref NumberBuffer number, char format, int nMaxDigits, NumberFormatInfo info) where TChar : unmanaged, IBinaryInteger + internal static unsafe void NumberToString(ref ValueListBuilder vlb, ref NumberBuffer number, char format, int nMaxDigits, NumberFormatInfo info) where TChar : unmanaged, IUtfChar { Debug.Assert(typeof(TChar) == typeof(char) || typeof(TChar) == typeof(byte)); @@ -2822,7 +2811,7 @@ internal static unsafe void NumberToString(ref ValueListBuilder vl } } - internal static unsafe void NumberToStringFormat(ref ValueListBuilder vlb, ref NumberBuffer number, ReadOnlySpan format, NumberFormatInfo info) where TChar : unmanaged, IBinaryInteger + internal static unsafe void NumberToStringFormat(ref ValueListBuilder vlb, ref NumberBuffer number, ReadOnlySpan format, NumberFormatInfo info) where TChar : unmanaged, IUtfChar { Debug.Assert(typeof(TChar) == typeof(char) || typeof(TChar) == typeof(byte)); @@ -3075,7 +3064,7 @@ internal static unsafe void NumberToStringFormat(ref ValueListBuilder 1 && thousandsSepCtr >= 0) { if (digPos == thousandsSepPos[thousandsSepCtr] + 1) @@ -3108,7 +3097,7 @@ internal static unsafe void NumberToStringFormat(ref ValueListBuilder 1 && thousandsSepCtr >= 0) { if (digPos == thousandsSepPos[thousandsSepCtr] + 1) @@ -3195,7 +3184,7 @@ internal static unsafe void NumberToStringFormat(ref ValueListBuilder(ref ValueListBuilder(ref ValueListBuilder(ref ValueListBuilder vlb, ref NumberBuffer number, int nMaxDigits, NumberFormatInfo info) where TChar : unmanaged, IBinaryInteger + private static void FormatCurrency(ref ValueListBuilder vlb, ref NumberBuffer number, int nMaxDigits, NumberFormatInfo info) where TChar : unmanaged, IUtfChar { Debug.Assert(typeof(TChar) == typeof(char) || typeof(TChar) == typeof(byte)); @@ -3270,7 +3259,7 @@ private static void FormatCurrency(ref ValueListBuilder vlb, ref N break; default: - vlb.Append(TChar.CreateTruncating(ch)); + vlb.Append(TChar.CastFrom(ch)); break; } } @@ -3279,7 +3268,7 @@ private static void FormatCurrency(ref ValueListBuilder vlb, ref N private static unsafe void FormatFixed( ref ValueListBuilder vlb, ref NumberBuffer number, int nMaxDigits, int[]? groupDigits, - ReadOnlySpan sDecimal, ReadOnlySpan sGroup) where TChar : unmanaged, IBinaryInteger + ReadOnlySpan sDecimal, ReadOnlySpan sGroup) where TChar : unmanaged, IUtfChar { Debug.Assert(typeof(TChar) == typeof(char) || typeof(TChar) == typeof(byte)); @@ -3333,7 +3322,7 @@ private static unsafe void FormatFixed( TChar* p = spanPtr + bufferSize - 1; for (int i = digPos - 1; i >= 0; i--) { - *(p--) = TChar.CreateTruncating((i < digStart) ? (char)dig[i] : '0'); + *(p--) = TChar.CastFrom((i < digStart) ? (char)dig[i] : '0'); if (groupSize > 0) { @@ -3363,14 +3352,14 @@ private static unsafe void FormatFixed( { do { - vlb.Append(TChar.CreateTruncating(*dig != 0 ? (char)(*dig++) : '0')); + vlb.Append(TChar.CastFrom(*dig != 0 ? (char)(*dig++) : '0')); } while (--digPos > 0); } } else { - vlb.Append(TChar.CreateTruncating('0')); + vlb.Append(TChar.CastFrom('0')); } if (nMaxDigits > 0) @@ -3382,7 +3371,7 @@ private static unsafe void FormatFixed( int zeroes = Math.Min(-digPos, nMaxDigits); for (int i = 0; i < zeroes; i++) { - vlb.Append(TChar.CreateTruncating('0')); + vlb.Append(TChar.CastFrom('0')); } digPos += zeroes; nMaxDigits -= zeroes; @@ -3390,7 +3379,7 @@ private static unsafe void FormatFixed( while (nMaxDigits > 0) { - vlb.Append(TChar.CreateTruncating((*dig != 0) ? (char)(*dig++) : '0')); + vlb.Append(TChar.CastFrom((*dig != 0) ? (char)(*dig++) : '0')); nMaxDigits--; } } @@ -3399,13 +3388,13 @@ private static unsafe void FormatFixed( /// Appends a char to the builder when the char is not known to be ASCII. /// This requires a helper as if the character isn't ASCII, for UTF8 encoding it will result in multiple bytes added. [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void AppendUnknownChar(ref ValueListBuilder vlb, char ch) where TChar : unmanaged, IBinaryInteger + private static void AppendUnknownChar(ref ValueListBuilder vlb, char ch) where TChar : unmanaged, IUtfChar { Debug.Assert(typeof(TChar) == typeof(char) || typeof(TChar) == typeof(byte)); if (typeof(TChar) == typeof(char) || char.IsAscii(ch)) { - vlb.Append(TChar.CreateTruncating(ch)); + vlb.Append(TChar.CastFrom(ch)); } else { @@ -3420,7 +3409,7 @@ static void AppendNonAsciiBytes(ref ValueListBuilder vlb, char ch) } } - private static void FormatNumber(ref ValueListBuilder vlb, ref NumberBuffer number, int nMaxDigits, NumberFormatInfo info) where TChar : unmanaged, IBinaryInteger + private static void FormatNumber(ref ValueListBuilder vlb, ref NumberBuffer number, int nMaxDigits, NumberFormatInfo info) where TChar : unmanaged, IUtfChar { Debug.Assert(typeof(TChar) == typeof(char) || typeof(TChar) == typeof(byte)); @@ -3441,19 +3430,19 @@ private static void FormatNumber(ref ValueListBuilder vlb, ref Num break; default: - vlb.Append(TChar.CreateTruncating(ch)); + vlb.Append(TChar.CastFrom(ch)); break; } } } - private static unsafe void FormatScientific(ref ValueListBuilder vlb, ref NumberBuffer number, int nMaxDigits, NumberFormatInfo info, char expChar) where TChar : unmanaged, IBinaryInteger + private static unsafe void FormatScientific(ref ValueListBuilder vlb, ref NumberBuffer number, int nMaxDigits, NumberFormatInfo info, char expChar) where TChar : unmanaged, IUtfChar { Debug.Assert(typeof(TChar) == typeof(char) || typeof(TChar) == typeof(byte)); byte* dig = number.GetDigitsPointer(); - vlb.Append(TChar.CreateTruncating((*dig != 0) ? (char)(*dig++) : '0')); + vlb.Append(TChar.CastFrom((*dig != 0) ? (char)(*dig++) : '0')); if (nMaxDigits != 1) // For E0 we would like to suppress the decimal point { @@ -3462,18 +3451,18 @@ private static unsafe void FormatScientific(ref ValueListBuilder v while (--nMaxDigits > 0) { - vlb.Append(TChar.CreateTruncating((*dig != 0) ? (char)(*dig++) : '0')); + vlb.Append(TChar.CastFrom((*dig != 0) ? (char)(*dig++) : '0')); } int e = number.Digits[0] == 0 ? 0 : number.Scale - 1; FormatExponent(ref vlb, info, e, expChar, 3, true); } - private static unsafe void FormatExponent(ref ValueListBuilder vlb, NumberFormatInfo info, int value, char expChar, int minDigits, bool positiveSign) where TChar : unmanaged, IBinaryInteger + private static unsafe void FormatExponent(ref ValueListBuilder vlb, NumberFormatInfo info, int value, char expChar, int minDigits, bool positiveSign) where TChar : unmanaged, IUtfChar { Debug.Assert(typeof(TChar) == typeof(char) || typeof(TChar) == typeof(byte)); - vlb.Append(TChar.CreateTruncating(expChar)); + vlb.Append(TChar.CastFrom(expChar)); if (value < 0) { @@ -3493,7 +3482,7 @@ private static unsafe void FormatExponent(ref ValueListBuilder vlb vlb.Append(new ReadOnlySpan(p, (int)(digits + MaxUInt32DecDigits - p))); } - private static unsafe void FormatGeneral(ref ValueListBuilder vlb, ref NumberBuffer number, int nMaxDigits, NumberFormatInfo info, char expChar, bool suppressScientific) where TChar : unmanaged, IBinaryInteger + private static unsafe void FormatGeneral(ref ValueListBuilder vlb, ref NumberBuffer number, int nMaxDigits, NumberFormatInfo info, char expChar, bool suppressScientific) where TChar : unmanaged, IUtfChar { Debug.Assert(typeof(TChar) == typeof(char) || typeof(TChar) == typeof(byte)); @@ -3516,13 +3505,13 @@ private static unsafe void FormatGeneral(ref ValueListBuilder vlb, { do { - vlb.Append(TChar.CreateTruncating((*dig != 0) ? (char)(*dig++) : '0')); + vlb.Append(TChar.CastFrom((*dig != 0) ? (char)(*dig++) : '0')); } while (--digPos > 0); } else { - vlb.Append(TChar.CreateTruncating('0')); + vlb.Append(TChar.CastFrom('0')); } if (*dig != 0 || digPos < 0) @@ -3531,13 +3520,13 @@ private static unsafe void FormatGeneral(ref ValueListBuilder vlb, while (digPos < 0) { - vlb.Append(TChar.CreateTruncating('0')); + vlb.Append(TChar.CastFrom('0')); digPos++; } while (*dig != 0) { - vlb.Append(TChar.CreateTruncating(*dig++)); + vlb.Append(TChar.CastFrom(*dig++)); } } @@ -3547,7 +3536,7 @@ private static unsafe void FormatGeneral(ref ValueListBuilder vlb, } } - private static void FormatPercent(ref ValueListBuilder vlb, ref NumberBuffer number, int nMaxDigits, NumberFormatInfo info) where TChar : unmanaged, IBinaryInteger + private static void FormatPercent(ref ValueListBuilder vlb, ref NumberBuffer number, int nMaxDigits, NumberFormatInfo info) where TChar : unmanaged, IUtfChar { Debug.Assert(typeof(TChar) == typeof(char) || typeof(TChar) == typeof(byte)); @@ -3572,7 +3561,7 @@ private static void FormatPercent(ref ValueListBuilder vlb, ref Nu break; default: - vlb.Append(TChar.CreateTruncating(ch)); + vlb.Append(TChar.CastFrom(ch)); break; } } diff --git a/src/libraries/System.Private.CoreLib/src/System/TimeOnly.cs b/src/libraries/System.Private.CoreLib/src/System/TimeOnly.cs index 5a6eeb8d1c0c7..a004175374dc5 100644 --- a/src/libraries/System.Private.CoreLib/src/System/TimeOnly.cs +++ b/src/libraries/System.Private.CoreLib/src/System/TimeOnly.cs @@ -664,16 +664,14 @@ private static ParseFailureKind TryParseExactInternal(ReadOnlySpan s, Read if (format.Length == 1) { - switch (format[0]) + switch (format[0] | 0x20) { case 'o': - case 'O': format = OFormat; provider = CultureInfo.InvariantCulture.DateTimeFormat; break; case 'r': - case 'R': format = RFormat; provider = CultureInfo.InvariantCulture.DateTimeFormat; break; @@ -743,16 +741,14 @@ private static ParseFailureKind TryParseExactInternal(ReadOnlySpan s, stri if (format.Length == 1) { - switch (format[0]) + switch (format[0] | 0x20) { case 'o': - case 'O': format = OFormat; dtfiToUse = CultureInfo.InvariantCulture.DateTimeFormat; break; case 'r': - case 'R': format = RFormat; dtfiToUse = CultureInfo.InvariantCulture.DateTimeFormat; break; @@ -925,26 +921,23 @@ public string ToString([StringSyntax(StringSyntaxAttribute.TimeOnlyFormat)] stri if (format.Length == 1) { - switch (format[0]) + switch (format[0] | 0x20) { case 'o': - case 'O': return string.Create(16, this, (destination, value) => { - bool b = DateTimeFormat.TryFormatTimeOnlyO(value.Hour, value.Minute, value.Second, value._ticks % TimeSpan.TicksPerSecond, destination); - Debug.Assert(b); + DateTimeFormat.TryFormatTimeOnlyO(value.Hour, value.Minute, value.Second, value._ticks % TimeSpan.TicksPerSecond, destination, out int charsWritten); + Debug.Assert(charsWritten == destination.Length); }); case 'r': - case 'R': return string.Create(8, this, (destination, value) => { - bool b = DateTimeFormat.TryFormatTimeOnlyR(value.Hour, value.Minute, value.Second, destination); - Debug.Assert(b); + DateTimeFormat.TryFormatTimeOnlyR(value.Hour, value.Minute, value.Second, destination, out int charsWritten); + Debug.Assert(charsWritten == destination.Length); }); case 't': - case 'T': return DateTimeFormat.Format(ToDateTime(), format, provider); default: @@ -971,7 +964,7 @@ public string ToString([StringSyntax(StringSyntaxAttribute.TimeOnlyFormat)] stri bool IUtf8SpanFormattable.TryFormat(Span utf8Destination, out int bytesWritten, [StringSyntax(StringSyntaxAttribute.TimeOnlyFormat)] ReadOnlySpan format, IFormatProvider? provider) => TryFormatCore(utf8Destination, out bytesWritten, format, provider); - private bool TryFormatCore(Span destination, out int written, [StringSyntax(StringSyntaxAttribute.TimeOnlyFormat)] ReadOnlySpan format, IFormatProvider? provider) where TChar : unmanaged, IBinaryInteger + private bool TryFormatCore(Span destination, out int written, [StringSyntax(StringSyntaxAttribute.TimeOnlyFormat)] ReadOnlySpan format, IFormatProvider? provider) where TChar : unmanaged, IUtfChar { if (format.Length == 0) { @@ -980,34 +973,20 @@ private bool TryFormatCore(Span destination, out int written, [Str if (format.Length == 1) { - switch (format[0]) + switch (format[0] | 0x20) { case 'o': - case 'O': - if (!DateTimeFormat.TryFormatTimeOnlyO(Hour, Minute, Second, _ticks % TimeSpan.TicksPerSecond, destination)) - { - written = 0; - return false; - } - written = 16; - return true; + return DateTimeFormat.TryFormatTimeOnlyO(Hour, Minute, Second, _ticks % TimeSpan.TicksPerSecond, destination, out written); case 'r': - case 'R': - if (!DateTimeFormat.TryFormatTimeOnlyR(Hour, Minute, Second, destination)) - { - written = 0; - return false; - } - written = 8; - return true; + return DateTimeFormat.TryFormatTimeOnlyR(Hour, Minute, Second, destination, out written); case 't': - case 'T': return DateTimeFormat.TryFormat(ToDateTime(), destination, out written, format, provider); default: - throw new FormatException(SR.Argument_BadFormatSpecifier); + ThrowHelper.ThrowFormatException_BadFormatSpecifier(); + break; } } diff --git a/src/libraries/System.Private.CoreLib/src/System/Version.cs b/src/libraries/System.Private.CoreLib/src/System/Version.cs index 8a74873a2dfe8..611b209b1eaf9 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Version.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Version.cs @@ -185,7 +185,7 @@ public bool TryFormat(Span destination, out int charsWritten) => public bool TryFormat(Span destination, int fieldCount, out int charsWritten) => TryFormatCore(destination, fieldCount, out charsWritten); - private bool TryFormatCore(Span destination, int fieldCount, out int charsWritten) where TChar : unmanaged, IBinaryInteger + private bool TryFormatCore(Span destination, int fieldCount, out int charsWritten) where TChar : unmanaged, IUtfChar { Debug.Assert(typeof(TChar) == typeof(char) || typeof(TChar) == typeof(byte)); @@ -219,7 +219,7 @@ static void ThrowArgumentException(string failureUpperBound) => return false; } - destination[0] = TChar.CreateTruncating('.'); + destination[0] = TChar.CastFrom('.'); destination = destination.Slice(1); totalCharsWritten++; } From 72601d3c30b2c5ab71c57b2a5d6a4899c94093ce Mon Sep 17 00:00:00 2001 From: Stephen Toub Date: Thu, 13 Apr 2023 20:27:30 -0400 Subject: [PATCH 3/3] Remove mono ifdef in WriteTwo/FourDigits --- .../src/System/Number.Formatting.cs | 25 ------------------- 1 file changed, 25 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/Number.Formatting.cs b/src/libraries/System.Private.CoreLib/src/System/Number.Formatting.cs index 9dbfc0501bbde..41db3a79f707c 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Number.Formatting.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Number.Formatting.cs @@ -319,7 +319,6 @@ internal static partial class Number "(#)", "-#", "- #", "#-", "# -", }; -#if !MONO // Optimizations using "TwoDigits" inspired by: // https://engineering.fb.com/2013/03/15/developer-tools/three-optimization-tips-for-c/ private static readonly byte[] TwoDigitsCharsAsBytes = @@ -344,7 +343,6 @@ internal static partial class Number "70717273747576777879"u8 + "80818283848586878889"u8 + "90919293949596979899"u8).ToArray(); -#endif public static unsafe string FormatDecimal(decimal value, ReadOnlySpan format, NumberFormatInfo info) { @@ -1740,17 +1738,10 @@ internal static unsafe void WriteTwoDigits(uint value, TChar* ptr) where Debug.Assert(typeof(TChar) == typeof(char) || typeof(TChar) == typeof(byte)); Debug.Assert(value <= 99); -#if MONO // mono currently regresses in performance taking the CopyBlockUnaligned path - uint temp = '0' + value; - value /= 10; - ptr[1] = TChar.CastFrom(temp - (value * 10)); - ptr[0] = TChar.CastFrom('0' + value); -#else Unsafe.CopyBlockUnaligned( ref *(byte*)ptr, ref Unsafe.Add(ref MemoryMarshal.GetArrayDataReference(typeof(TChar) == typeof(char) ? TwoDigitsCharsAsBytes : TwoDigitsBytes), (uint)sizeof(TChar) * 2 * value), (uint)sizeof(TChar) * 2); -#endif } /// @@ -1763,21 +1754,6 @@ internal static unsafe void WriteFourDigits(uint value, TChar* ptr) where Debug.Assert(typeof(TChar) == typeof(char) || typeof(TChar) == typeof(byte)); Debug.Assert(value <= 9999); -#if MONO // mono currently regresses in performance taking the CopyBlockUnaligned path - uint temp = '0' + value; - value /= 10; - ptr[3] = TChar.CastFrom(temp - (value * 10)); - - temp = '0' + value; - value /= 10; - ptr[2] = TChar.CastFrom(temp - (value * 10)); - - temp = '0' + value; - value /= 10; - ptr[1] = TChar.CastFrom(temp - (value * 10)); - - ptr[0] = TChar.CastFrom('0' + value); -#else (value, uint remainder) = Math.DivRem(value, 100); ref byte charsArray = ref MemoryMarshal.GetArrayDataReference(typeof(TChar) == typeof(char) ? TwoDigitsCharsAsBytes : TwoDigitsBytes); @@ -1791,7 +1767,6 @@ ref Unsafe.Add(ref charsArray, (uint)sizeof(TChar) * 2 * value), ref *(byte*)(ptr + 2), ref Unsafe.Add(ref charsArray, (uint)sizeof(TChar) * 2 * remainder), (uint)sizeof(TChar) * 2); -#endif } [MethodImpl(MethodImplOptions.AggressiveInlining)]