Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement IUtf8SpanFormattable on all the numeric types in corelib #84587

Merged
merged 4 commits into from
Apr 14, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -248,7 +248,6 @@
<ItemGroup>
<Compile Include="ParsersAndFormatters\Formatter\FormatterTestData.cs" />
<Compile Include="ParsersAndFormatters\Formatter\FormatterTests.cs" />
<Compile Include="ParsersAndFormatters\Formatter\FormatterTests.Negative.cs" />
<Compile Include="ParsersAndFormatters\Formatter\TestData.Formatter.cs" />
<Compile Include="ParsersAndFormatters\Formatter\ValidateFormatter.cs" />
<Compile Include="ParsersAndFormatters\Parser\ParserTestData.cs" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -131,21 +131,9 @@
<Compile Include="$(MSBuildThisFileDirectory)System\Buffers\Text\Utf8Formatter\Utf8Formatter.Date.G.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Buffers\Text\Utf8Formatter\Utf8Formatter.Date.L.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Buffers\Text\Utf8Formatter\Utf8Formatter.Decimal.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Buffers\Text\Utf8Formatter\Utf8Formatter.Decimal.E.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Buffers\Text\Utf8Formatter\Utf8Formatter.Decimal.F.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Buffers\Text\Utf8Formatter\Utf8Formatter.Decimal.G.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Buffers\Text\Utf8Formatter\Utf8Formatter.Float.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Buffers\Text\Utf8Formatter\Utf8Formatter.Guid.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Buffers\Text\Utf8Formatter\Utf8Formatter.Integer.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Buffers\Text\Utf8Formatter\Utf8Formatter.Integer.Signed.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Buffers\Text\Utf8Formatter\Utf8Formatter.Integer.Signed.D.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Buffers\Text\Utf8Formatter\Utf8Formatter.Integer.Signed.Default.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Buffers\Text\Utf8Formatter\Utf8Formatter.Integer.Signed.N.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Buffers\Text\Utf8Formatter\Utf8Formatter.Integer.Unsigned.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Buffers\Text\Utf8Formatter\Utf8Formatter.Integer.Unsigned.D.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Buffers\Text\Utf8Formatter\Utf8Formatter.Integer.Unsigned.Default.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Buffers\Text\Utf8Formatter\Utf8Formatter.Integer.Unsigned.N.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Buffers\Text\Utf8Formatter\Utf8Formatter.Integer.Unsigned.X.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Buffers\Text\Utf8Formatter\Utf8Formatter.TimeSpan.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Buffers\Text\Utf8Parser\ParserHelpers.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Buffers\Text\Utf8Parser\Utf8Parser.Boolean.cs" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ namespace System.Buffers
/// <summary>
/// true if the StandardFormat == default(StandardFormat)
/// </summary>
public bool IsDefault => _format == 0 && _precision == 0;
public bool IsDefault => (_format | _precision) == 0;

/// <summary>
/// Create a StandardFormat.
Expand Down Expand Up @@ -144,30 +144,23 @@ private static bool ParseHelper(ReadOnlySpan<char> format, out StandardFormat st
/// <summary>
/// Returns the format in classic .NET format.
/// </summary>
public override string ToString()
{
Span<char> 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]));

/// <summary>The exact buffer length required by <see cref="Format"/>.</summary>
internal const int FormatStringLength = 3;

/// <summary>
/// Formats the format in classic .NET format.
/// </summary>
internal int Format(Span<char> destination)
internal Span<char> Format(Span<char> 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)
Expand All @@ -185,15 +178,18 @@ internal int Format(Span<char> 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;
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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}
}
}
Original file line number Diff line number Diff line change
@@ -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>(T value, Span<byte> utf8Destination, out int bytesWritten, StandardFormat format) where T : IUtf8SpanFormattable
{
scoped Span<char> formatText = default;
if (!format.IsDefault)
{
formatText = format.Format(stackalloc char[StandardFormat.FormatStringLength]);
}

return value.TryFormat(utf8Destination, out bytesWritten, formatText, CultureInfo.InvariantCulture);
}

/// <summary>
/// Returns the symbol contained within the standard format. If the standard format
/// has not been initialized, returns the provided fallback symbol.
Expand All @@ -32,138 +38,5 @@ public static char GetSymbolOrDefault(in StandardFormat format, char defaultSymb
}
return symbol;
}

/// <summary>
/// Fills a buffer with the ASCII character '0' (0x30).
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void FillWithAsciiZeros(Span<byte> buffer)
{
// This is a faster implementation of Span<T>.Fill() for very short buffers.
for (int i = 0; i < buffer.Length; i++)
{
buffer[i] = (byte)'0';
}
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void WriteDigits<TChar>(ulong value, Span<TChar> buffer) where TChar : unmanaged, IBinaryInteger<TChar>
{
// 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<byte> 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<TChar>(uint value, Span<TChar> buffer) where TChar : unmanaged, IBinaryInteger<TChar>
{
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);
}

/// <summary>
/// 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.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe void WriteTwoDigits<TChar>(uint value, Span<TChar> buffer, int startingIndex = 0) where TChar : unmanaged, IBinaryInteger<TChar>
{
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);
}
}

/// <summary>
/// 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.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe void WriteFourDigits<TChar>(uint value, Span<TChar> buffer, int startingIndex = 0) where TChar : unmanaged, IBinaryInteger<TChar>
{
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<TChar>(ReadOnlySpan<TChar> source, Span<TChar> destination) where TChar : unmanaged, IBinaryInteger<TChar>
{
if (typeof(TChar) == typeof(byte))
{
Unsafe.WriteUnaligned(ref Unsafe.As<TChar, byte>(ref MemoryMarshal.GetReference(destination)),
Unsafe.ReadUnaligned<uint>(ref Unsafe.As<TChar, byte>(ref MemoryMarshal.GetReference(source))));
}
else
{
Debug.Assert(typeof(TChar) == typeof(char));
Unsafe.WriteUnaligned(ref Unsafe.As<TChar, byte>(ref MemoryMarshal.GetReference(destination)),
Unsafe.ReadUnaligned<ulong>(ref Unsafe.As<TChar, byte>(ref MemoryMarshal.GetReference(source))));
}
}

/// <summary>Enable use of ThrowHelper from TryFormat() routines without introducing dozens of non-code-coveraged "bytesWritten = 0; return false" boilerplate.</summary>
public static bool TryFormatThrowFormatException(out int bytesWritten)
{
bytesWritten = 0;
ThrowHelper.ThrowFormatException_BadFormatSpecifier();
return false;
}
}
}
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -34,19 +32,14 @@ public static bool TryFormat(bool value, Span<byte> 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;
}
Expand All @@ -63,42 +56,33 @@ public static bool TryFormat(bool value, Span<byte> 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);
}
}
}
Loading