From f79f0ff118ab80c82583a1676eac6c1a644a3937 Mon Sep 17 00:00:00 2001 From: Tanner Gooding Date: Wed, 16 Jul 2025 20:11:35 -0700 Subject: [PATCH] Update BigInteger and Complex to support UTF8 parsing and formatting --- .../src/System/Text/ValueStringBuilder_1.cs | 314 ++++++++++++++++++ .../ref/System.Runtime.Numerics.cs | 22 +- .../src/System.Runtime.Numerics.csproj | 4 +- .../src/System/Number.BigInteger.cs | 177 ++++++---- .../src/System/Number.Polyfill.cs | 107 +++--- .../src/System/Numerics/BigInteger.cs | 36 +- .../src/System/Numerics/Complex.cs | 120 ++++--- .../src/System/ThrowHelper.cs | 6 + .../BigInteger/BigIntegerToStringTests.cs | 37 ++- .../tests/BigInteger/parse.cs | 182 ++++++++-- .../tests/ComplexTests.cs | 17 + 11 files changed, 824 insertions(+), 198 deletions(-) create mode 100644 src/libraries/Common/src/System/Text/ValueStringBuilder_1.cs diff --git a/src/libraries/Common/src/System/Text/ValueStringBuilder_1.cs b/src/libraries/Common/src/System/Text/ValueStringBuilder_1.cs new file mode 100644 index 00000000000000..8bf3e3a3347be3 --- /dev/null +++ b/src/libraries/Common/src/System/Text/ValueStringBuilder_1.cs @@ -0,0 +1,314 @@ +// 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; +using System.Diagnostics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +#nullable enable + +namespace System.Text +{ + internal ref partial struct ValueStringBuilder + where TChar : unmanaged + { + private TChar[]? _arrayToReturnToPool; + private Span _chars; + private int _pos; + + public ValueStringBuilder(Span initialBuffer) + { + Debug.Assert((typeof(TChar) == typeof(Utf8Char)) || (typeof(TChar) == typeof(Utf16Char))); + + _arrayToReturnToPool = null; + _chars = initialBuffer; + _pos = 0; + } + + public ValueStringBuilder(int initialCapacity) + { + Debug.Assert((typeof(TChar) == typeof(Utf8Char)) || (typeof(TChar) == typeof(Utf16Char))); + + _arrayToReturnToPool = ArrayPool.Shared.Rent(initialCapacity); + _chars = _arrayToReturnToPool; + _pos = 0; + } + + public int Length + { + readonly get => _pos; + set + { + Debug.Assert(value >= 0); + Debug.Assert(value <= _chars.Length); + _pos = value; + } + } + + public readonly int Capacity => _chars.Length; + + public void EnsureCapacity(int capacity) + { + // This is not expected to be called this with negative capacity + Debug.Assert(capacity >= 0); + + // If the caller has a bug and calls this with negative capacity, make sure to call Grow to throw an exception. + if ((uint)capacity > (uint)_chars.Length) + Grow(capacity - _pos); + } + + /// + /// Get a pinnable reference to the builder. + /// Does not ensure there is a null TChar after + /// This overload is pattern matched in the C# 7.3+ compiler so you can omit + /// the explicit method call, and write eg "fixed (TChar* c = builder)" + /// + public readonly ref TChar GetPinnableReference() + { + return ref MemoryMarshal.GetReference(_chars); + } + + /// + /// Get a pinnable reference to the builder. + /// + /// Ensures that the builder has a null TChar after + public ref TChar GetPinnableReference(bool terminate) + { + if (terminate) + { + EnsureCapacity(Length + 1); + _chars[Length] = default; + } + return ref MemoryMarshal.GetReference(_chars); + } + + public readonly ref TChar this[int index] + { + get + { + Debug.Assert(index < _pos); + return ref _chars[index]; + } + } + + public override string ToString() + { + string result; + Span slice = _chars.Slice(0, _pos); + + if (typeof(TChar) == typeof(Utf8Char)) + { + result = Encoding.UTF8.GetString(Unsafe.BitCast, ReadOnlySpan>(slice)); + } + else + { + Debug.Assert(typeof(TChar) == typeof(Utf16Char)); + result = Unsafe.BitCast, ReadOnlySpan>(slice).ToString(); + } + + Dispose(); + return result; + } + + /// Returns the underlying storage of the builder. + public readonly Span RawChars => _chars; + + /// + /// Returns a span around the contents of the builder. + /// + /// Ensures that the builder has a null TChar after + public ReadOnlySpan AsSpan(bool terminate) + { + if (terminate) + { + EnsureCapacity(Length + 1); + _chars[Length] = default; + } + return _chars.Slice(0, _pos); + } + + public readonly ReadOnlySpan AsSpan() => _chars.Slice(0, _pos); + public readonly ReadOnlySpan AsSpan(int start) => _chars.Slice(start, _pos - start); + public readonly ReadOnlySpan AsSpan(int start, int length) => _chars.Slice(start, length); + + public bool TryCopyTo(Span destination, out int charsWritten) + { + if (_chars.Slice(0, _pos).TryCopyTo(destination)) + { + charsWritten = _pos; + Dispose(); + return true; + } + else + { + charsWritten = 0; + Dispose(); + return false; + } + } + + public void Insert(int index, TChar value, int count) + { + if (_pos > _chars.Length - count) + { + Grow(count); + } + + int remaining = _pos - index; + _chars.Slice(index, remaining).CopyTo(_chars.Slice(index + count)); + _chars.Slice(index, count).Fill(value); + _pos += count; + } + + public void Insert(int index, ReadOnlySpan text) + { + if (text.IsEmpty) + { + return; + } + + int count = text.Length; + + if (_pos > (_chars.Length - count)) + { + Grow(count); + } + + int remaining = _pos - index; + _chars.Slice(index, remaining).CopyTo(_chars.Slice(index + count)); + text.CopyTo(_chars.Slice(index)); + _pos += count; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Append(TChar c) + { + int pos = _pos; + Span chars = _chars; + if ((uint)pos < (uint)chars.Length) + { + chars[pos] = c; + _pos = pos + 1; + } + else + { + GrowAndAppend(c); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Append(ReadOnlySpan text) + { + if (text.IsEmpty) + { + return; + } + + int pos = _pos; + if (text.Length == 1 && (uint)pos < (uint)_chars.Length) // very common case, e.g. appending strings from NumberFormatInfo like separators, percent symbols, etc. + { + _chars[pos] = text[0]; + _pos = pos + 1; + } + else + { + AppendSlow(text); + } + } + + private void AppendSlow(ReadOnlySpan text) + { + int pos = _pos; + if (pos > _chars.Length - text.Length) + { + Grow(text.Length); + } + + text.CopyTo(_chars.Slice(pos)); + _pos += text.Length; + } + + public void Append(TChar c, int count) + { + if (_pos > _chars.Length - count) + { + Grow(count); + } + + Span dst = _chars.Slice(_pos, count); + for (int i = 0; i < dst.Length; i++) + { + dst[i] = c; + } + _pos += count; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Span AppendSpan(int length) + { + int origPos = _pos; + if (origPos > _chars.Length - length) + { + Grow(length); + } + + _pos = origPos + length; + return _chars.Slice(origPos, length); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private void GrowAndAppend(TChar c) + { + Grow(1); + Append(c); + } + + /// + /// Resize the internal buffer either by doubling current buffer size or + /// by adding to + /// whichever is greater. + /// + /// + /// Number of chars requested beyond current position. + /// + [MethodImpl(MethodImplOptions.NoInlining)] + private void Grow(int additionalCapacityBeyondPos) + { + Debug.Assert(additionalCapacityBeyondPos > 0); + Debug.Assert(_pos > _chars.Length - additionalCapacityBeyondPos, "Grow called incorrectly, no resize is needed."); + + const uint ArrayMaxLength = 0x7FFFFFC7; // same as Array.MaxLength + + // Increase to at least the required size (_pos + additionalCapacityBeyondPos), but try + // to double the size if possible, bounding the doubling to not go beyond the max array length. + int newCapacity = (int)Math.Max( + (uint)(_pos + additionalCapacityBeyondPos), + Math.Min((uint)_chars.Length * 2, ArrayMaxLength)); + + // Make sure to let Rent throw an exception if the caller has a bug and the desired capacity is negative. + // This could also go negative if the actual required length wraps around. + TChar[] poolArray = ArrayPool.Shared.Rent(newCapacity); + + _chars.Slice(0, _pos).CopyTo(poolArray); + + TChar[]? toReturn = _arrayToReturnToPool; + _chars = _arrayToReturnToPool = poolArray; + if (toReturn != null) + { + ArrayPool.Shared.Return(toReturn); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Dispose() + { + TChar[]? toReturn = _arrayToReturnToPool; + this = default; // for safety, to avoid using pooled array if this instance is erroneously appended to again + if (toReturn != null) + { + ArrayPool.Shared.Return(toReturn); + } + } + } +} diff --git a/src/libraries/System.Runtime.Numerics/ref/System.Runtime.Numerics.cs b/src/libraries/System.Runtime.Numerics/ref/System.Runtime.Numerics.cs index 5ac9e6237bea3e..022410d106016d 100644 --- a/src/libraries/System.Runtime.Numerics/ref/System.Runtime.Numerics.cs +++ b/src/libraries/System.Runtime.Numerics/ref/System.Runtime.Numerics.cs @@ -6,7 +6,7 @@ namespace System.Numerics { - public readonly partial struct BigInteger : 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.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 BigInteger : System.IComparable, System.IComparable, System.IEquatable, System.IFormattable, System.IParsable, System.ISpanFormattable, System.ISpanParsable, System.IUtf8SpanFormattable, System.IUtf8SpanParsable, 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.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 { private readonly object _dummy; private readonly int _dummyPrimitive; @@ -182,6 +182,8 @@ namespace System.Numerics public static System.Numerics.BigInteger operator -(System.Numerics.BigInteger value) { throw null; } public static System.Numerics.BigInteger operator +(System.Numerics.BigInteger value) { throw null; } public static System.Numerics.BigInteger operator >>>(System.Numerics.BigInteger value, int shiftAmount) { throw null; } + public static System.Numerics.BigInteger Parse(System.ReadOnlySpan utf8Text, System.Globalization.NumberStyles style = System.Globalization.NumberStyles.Integer, System.IFormatProvider? provider = null) { throw null; } + public static System.Numerics.BigInteger Parse(System.ReadOnlySpan utf8Text, System.IFormatProvider? provider) { throw null; } public static System.Numerics.BigInteger Parse(System.ReadOnlySpan value, System.Globalization.NumberStyles style = System.Globalization.NumberStyles.Integer, System.IFormatProvider? provider = null) { throw null; } public static System.Numerics.BigInteger Parse(System.ReadOnlySpan s, System.IFormatProvider? provider) { throw null; } public static System.Numerics.BigInteger Parse(string value) { throw null; } @@ -194,10 +196,10 @@ namespace System.Numerics public static System.Numerics.BigInteger RotateLeft(System.Numerics.BigInteger value, int rotateAmount) { throw null; } public static System.Numerics.BigInteger RotateRight(System.Numerics.BigInteger value, int rotateAmount) { throw null; } public static System.Numerics.BigInteger Subtract(System.Numerics.BigInteger left, System.Numerics.BigInteger right) { throw null; } - static bool System.Numerics.IBinaryInteger.TryReadBigEndian(System.ReadOnlySpan source, bool isUnsigned, out System.Numerics.BigInteger value) { throw null; } - static bool System.Numerics.IBinaryInteger.TryReadLittleEndian(System.ReadOnlySpan source, bool isUnsigned, out System.Numerics.BigInteger value) { throw null; } int System.Numerics.IBinaryInteger.GetByteCount() { throw null; } int System.Numerics.IBinaryInteger.GetShortestBitLength() { throw null; } + static bool System.Numerics.IBinaryInteger.TryReadBigEndian(System.ReadOnlySpan source, bool isUnsigned, out System.Numerics.BigInteger value) { throw null; } + static bool System.Numerics.IBinaryInteger.TryReadLittleEndian(System.ReadOnlySpan source, bool isUnsigned, out System.Numerics.BigInteger value) { throw null; } bool System.Numerics.IBinaryInteger.TryWriteBigEndian(System.Span destination, out int bytesWritten) { throw null; } bool System.Numerics.IBinaryInteger.TryWriteLittleEndian(System.Span destination, out int bytesWritten) { throw null; } static bool System.Numerics.INumberBase.IsCanonical(System.Numerics.BigInteger value) { throw null; } @@ -232,7 +234,11 @@ namespace System.Numerics public string ToString([System.Diagnostics.CodeAnalysis.StringSyntaxAttribute("NumericFormat")] string? format) { throw null; } public string ToString([System.Diagnostics.CodeAnalysis.StringSyntaxAttribute("NumericFormat")] string? format, System.IFormatProvider? provider) { throw null; } public static System.Numerics.BigInteger TrailingZeroCount(System.Numerics.BigInteger value) { throw null; } + public bool TryFormat(System.Span utf8Destination, out int bytesWritten, [System.Diagnostics.CodeAnalysis.StringSyntaxAttribute("NumericFormat")] System.ReadOnlySpan format = default(System.ReadOnlySpan), System.IFormatProvider? provider = null) { 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; } + public static bool TryParse(System.ReadOnlySpan utf8Text, System.Globalization.NumberStyles style, System.IFormatProvider? provider, out System.Numerics.BigInteger result) { throw null; } + public static bool TryParse(System.ReadOnlySpan utf8Text, System.IFormatProvider? provider, out System.Numerics.BigInteger result) { throw null; } + public static bool TryParse(System.ReadOnlySpan utf8Text, out System.Numerics.BigInteger result) { throw null; } public static bool TryParse(System.ReadOnlySpan value, System.Globalization.NumberStyles style, System.IFormatProvider? provider, out System.Numerics.BigInteger result) { throw null; } public static bool TryParse(System.ReadOnlySpan s, System.IFormatProvider? provider, out System.Numerics.BigInteger result) { throw null; } public static bool TryParse(System.ReadOnlySpan value, out System.Numerics.BigInteger result) { throw null; } @@ -241,7 +247,7 @@ namespace System.Numerics public static bool TryParse([System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] string? value, out System.Numerics.BigInteger result) { throw null; } public bool TryWriteBytes(System.Span destination, out int bytesWritten, bool isUnsigned = false, bool isBigEndian = false) { throw null; } } - public readonly partial struct Complex : System.IEquatable, System.IFormattable, System.IParsable, System.ISpanFormattable, System.ISpanParsable, System.Numerics.IAdditionOperators, System.Numerics.IAdditiveIdentity, System.Numerics.IDecrementOperators, System.Numerics.IDivisionOperators, System.Numerics.IEqualityOperators, System.Numerics.IIncrementOperators, System.Numerics.IMultiplicativeIdentity, System.Numerics.IMultiplyOperators, System.Numerics.INumberBase, System.Numerics.ISignedNumber, System.Numerics.ISubtractionOperators, System.Numerics.IUnaryNegationOperators, System.Numerics.IUnaryPlusOperators, System.IUtf8SpanFormattable + public readonly partial struct Complex : System.IEquatable, System.IFormattable, System.IParsable, System.ISpanFormattable, System.ISpanParsable, System.IUtf8SpanFormattable, System.IUtf8SpanParsable, System.Numerics.IAdditionOperators, System.Numerics.IAdditiveIdentity, System.Numerics.IDecrementOperators, System.Numerics.IDivisionOperators, System.Numerics.IEqualityOperators, System.Numerics.IIncrementOperators, System.Numerics.IMultiplicativeIdentity, System.Numerics.IMultiplyOperators, System.Numerics.INumberBase, System.Numerics.ISignedNumber, System.Numerics.ISubtractionOperators, System.Numerics.IUnaryNegationOperators, System.Numerics.IUnaryPlusOperators { private readonly int _dummyPrimitive; public static readonly System.Numerics.Complex ImaginaryOne; @@ -347,6 +353,8 @@ namespace System.Numerics public static System.Numerics.Complex operator -(System.Numerics.Complex left, System.Numerics.Complex right) { throw null; } public static System.Numerics.Complex operator -(System.Numerics.Complex value) { throw null; } public static System.Numerics.Complex operator +(System.Numerics.Complex value) { throw null; } + public static System.Numerics.Complex Parse(System.ReadOnlySpan utf8Text, System.Globalization.NumberStyles style, System.IFormatProvider? provider) { throw null; } + public static System.Numerics.Complex Parse(System.ReadOnlySpan utf8Text, System.IFormatProvider? provider) { throw null; } public static System.Numerics.Complex Parse(System.ReadOnlySpan s, System.Globalization.NumberStyles style, System.IFormatProvider? provider) { throw null; } public static System.Numerics.Complex Parse(System.ReadOnlySpan s, System.IFormatProvider? provider) { throw null; } public static System.Numerics.Complex Parse(string s, System.Globalization.NumberStyles style, System.IFormatProvider? provider) { throw null; } @@ -378,8 +386,10 @@ namespace System.Numerics public string ToString(System.IFormatProvider? provider) { throw null; } public string ToString([System.Diagnostics.CodeAnalysis.StringSyntaxAttribute("NumericFormat")] string? format) { throw null; } public string ToString([System.Diagnostics.CodeAnalysis.StringSyntaxAttribute("NumericFormat")] string? format, System.IFormatProvider? provider) { throw null; } - public bool TryFormat(System.Span destination, out int charsWritten, [System.Diagnostics.CodeAnalysis.StringSyntaxAttribute("NumericFormat")] System.ReadOnlySpan format = default, System.IFormatProvider? provider = null) { throw null; } - public bool TryFormat(System.Span utf8Destination, out int bytesWritten, [System.Diagnostics.CodeAnalysis.StringSyntaxAttribute("NumericFormat")] System.ReadOnlySpan format = default, System.IFormatProvider? provider = null) { throw null; } + public bool TryFormat(System.Span utf8Destination, out int bytesWritten, [System.Diagnostics.CodeAnalysis.StringSyntaxAttribute("NumericFormat")] System.ReadOnlySpan format = default(System.ReadOnlySpan), System.IFormatProvider? provider = null) { 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; } + public static bool TryParse(System.ReadOnlySpan utf8Text, System.Globalization.NumberStyles style, System.IFormatProvider? provider, out System.Numerics.Complex result) { throw null; } + public static bool TryParse(System.ReadOnlySpan utf8Text, System.IFormatProvider? provider, out System.Numerics.Complex result) { throw null; } public static bool TryParse(System.ReadOnlySpan s, System.Globalization.NumberStyles style, System.IFormatProvider? provider, out System.Numerics.Complex result) { throw null; } public static bool TryParse(System.ReadOnlySpan s, System.IFormatProvider? provider, out System.Numerics.Complex result) { throw null; } public static bool TryParse([System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] string? s, System.Globalization.NumberStyles style, System.IFormatProvider? provider, out System.Numerics.Complex result) { throw null; } diff --git a/src/libraries/System.Runtime.Numerics/src/System.Runtime.Numerics.csproj b/src/libraries/System.Runtime.Numerics/src/System.Runtime.Numerics.csproj index 437b55291c8a29..2c7a1e491a5ab0 100644 --- a/src/libraries/System.Runtime.Numerics/src/System.Runtime.Numerics.csproj +++ b/src/libraries/System.Runtime.Numerics/src/System.Runtime.Numerics.csproj @@ -30,8 +30,8 @@ Link="CoreLib\System\Buffers\Text\FormattingHelpers.CountDigits.cs" /> - + value, NumberStyles style, NumberFormatInfo info, out BigInteger result) + internal static ParsingStatus TryParseBigInteger(ReadOnlySpan value, NumberStyles style, NumberFormatInfo info, out BigInteger result) + where TChar : unmanaged, IUtfChar { if (!TryValidateParseStyleInteger(style, out ArgumentException? e)) { @@ -66,18 +65,19 @@ internal static ParsingStatus TryParseBigInteger(ReadOnlySpan value, Numbe if ((style & NumberStyles.AllowHexSpecifier) != 0) { - return TryParseBigIntegerHexOrBinaryNumberStyle, char>(value, style, out result); + return TryParseBigIntegerHexOrBinaryNumberStyle, TChar>(value, style, out result); } if ((style & NumberStyles.AllowBinarySpecifier) != 0) { - return TryParseBigIntegerHexOrBinaryNumberStyle, char>(value, style, out result); + return TryParseBigIntegerHexOrBinaryNumberStyle, TChar>(value, style, out result); } return TryParseBigIntegerNumber(value, style, info, out result); } - internal static unsafe ParsingStatus TryParseBigIntegerNumber(ReadOnlySpan value, NumberStyles style, NumberFormatInfo info, out BigInteger result) + internal static unsafe ParsingStatus TryParseBigIntegerNumber(ReadOnlySpan value, NumberStyles style, NumberFormatInfo info, out BigInteger result) + where TChar : unmanaged, IUtfChar { scoped Span buffer; byte[]? arrayFromPool = null; @@ -87,6 +87,7 @@ internal static unsafe ParsingStatus TryParseBigIntegerNumber(ReadOnlySpan result = default; return ParsingStatus.Failed; } + if (value.Length < 255) { buffer = stackalloc byte[value.Length + 1 + 1]; @@ -102,7 +103,7 @@ internal static unsafe ParsingStatus TryParseBigIntegerNumber(ReadOnlySpan { NumberBuffer number = new NumberBuffer(NumberBufferKind.Integer, buffer); - if (!TryStringToNumber(MemoryMarshal.Cast(value), style, ref number, info)) + if (!TryStringToNumber(value, style, ref number, info)) { result = default; ret = ParsingStatus.Failed; @@ -121,7 +122,8 @@ internal static unsafe ParsingStatus TryParseBigIntegerNumber(ReadOnlySpan return ret; } - internal static BigInteger ParseBigInteger(ReadOnlySpan value, NumberStyles style, NumberFormatInfo info) + internal static BigInteger ParseBigInteger(ReadOnlySpan value, NumberStyles style, NumberFormatInfo info) + where TChar : unmanaged, IUtfChar { if (!TryValidateParseStyleInteger(style, out ArgumentException? e)) { @@ -139,7 +141,7 @@ internal static BigInteger ParseBigInteger(ReadOnlySpan value, NumberStyle internal static ParsingStatus TryParseBigIntegerHexOrBinaryNumberStyle(ReadOnlySpan value, NumberStyles style, out BigInteger result) where TParser : struct, IBigIntegerHexOrBinaryParser - where TChar : unmanaged, IBinaryInteger + where TChar : unmanaged, IUtfChar { int whiteIndex; @@ -148,7 +150,7 @@ internal static ParsingStatus TryParseBigIntegerHexOrBinaryNumberStyle= 0; whiteIndex--) { - if (!IsWhite(uint.CreateTruncating(value[whiteIndex]))) + if (!IsWhite(TChar.CastToUInt32(value[whiteIndex]))) break; } @@ -174,7 +176,7 @@ internal static ParsingStatus TryParseBigIntegerHexOrBinaryNumberStyle bits, uint multiplier, uint addValue) } } - private static string? FormatBigIntegerToHex(bool targetSpan, BigInteger value, char format, int digits, NumberFormatInfo info, Span destination, out int charsWritten, out bool spanSuccess) + private static string? FormatBigIntegerToHex(bool targetSpan, BigInteger value, char format, int digits, NumberFormatInfo info, Span destination, out int charsWritten, out bool spanSuccess) + where TChar : unmanaged, IUtfChar { Debug.Assert(format == 'x' || format == 'X'); @@ -569,7 +572,7 @@ static uint MultiplyAdd(Span bits, uint multiplier, uint addValue) } bits = bits.Slice(0, bytesWrittenOrNeeded); - var sb = new ValueStringBuilder(stackalloc char[128]); // each byte is typically two chars + var sb = new ValueStringBuilder(stackalloc TChar[128]); // each byte is typically two chars int cur = bits.Length - 1; if (cur > -1) @@ -591,22 +594,22 @@ static uint MultiplyAdd(Span bits, uint multiplier, uint addValue) // {0xF8-0xFF} print as {8-F} // {0x00-0x07} print as {0-7} sb.Append(head < 10 ? - (char)(head + '0') : - format == 'X' ? (char)((head & 0xF) - 10 + 'A') : (char)((head & 0xF) - 10 + 'a')); + TChar.CastFrom(head + '0') : + TChar.CastFrom(format == 'X' ? ((head & 0xF) - 10 + 'A') : ((head & 0xF) - 10 + 'a'))); cur--; } } if (cur > -1) { - Span chars = sb.AppendSpan((cur + 1) * 2); + Span chars = sb.AppendSpan((cur + 1) * 2); int charsPos = 0; string hexValues = format == 'x' ? "0123456789abcdef" : "0123456789ABCDEF"; while (cur > -1) { byte b = bits[cur--]; - chars[charsPos++] = hexValues[b >> 4]; - chars[charsPos++] = hexValues[b & 0xF]; + chars[charsPos++] = TChar.CastFrom(hexValues[b >> 4]); + chars[charsPos++] = TChar.CastFrom(hexValues[b & 0xF]); } } @@ -615,7 +618,7 @@ static uint MultiplyAdd(Span bits, uint multiplier, uint addValue) // Insert leading zeros, e.g. user specified "X5" so we create "0ABCD" instead of "ABCD" sb.Insert( 0, - value._sign >= 0 ? '0' : (format == 'x') ? 'f' : 'F', + TChar.CastFrom(value._sign >= 0 ? '0' : (format == 'x') ? 'f' : 'F'), digits - sb.Length); } @@ -638,7 +641,8 @@ static uint MultiplyAdd(Span bits, uint multiplier, uint addValue) } } - private static string? FormatBigIntegerToBinary(bool targetSpan, BigInteger value, int digits, Span destination, out int charsWritten, out bool spanSuccess) + private static string? FormatBigIntegerToBinary(bool targetSpan, BigInteger value, int digits, Span destination, out int charsWritten, out bool spanSuccess) + where TChar : unmanaged, IUtfChar { // Get the bytes that make up the BigInteger. byte[]? arrayToReturnToPool = null; @@ -673,7 +677,7 @@ static uint MultiplyAdd(Span bits, uint multiplier, uint addValue) try { - scoped ValueStringBuilder sb; + scoped ValueStringBuilder sb; if (targetSpan) { if (charsIncludeDigits > destination.Length) @@ -683,21 +687,21 @@ static uint MultiplyAdd(Span bits, uint multiplier, uint addValue) return null; } - // Because we have ensured destination can take actual char length, so now just use ValueStringBuilder as wrapper so that subsequent logic can be reused by 2 flows (targetSpan and non-targetSpan); + // Because we have ensured destination can take actual TChar length, so now just use ValueStringBuilder as wrapper so that subsequent logic can be reused by 2 flows (targetSpan and non-targetSpan); // meanwhile there is no need to copy to destination again after format data for targetSpan flow. - sb = new ValueStringBuilder(destination); + sb = new ValueStringBuilder(destination); } else { // each byte is typically eight chars sb = charsIncludeDigits > 512 - ? new ValueStringBuilder(charsIncludeDigits) - : new ValueStringBuilder(stackalloc char[512]); + ? new ValueStringBuilder(charsIncludeDigits) + : new ValueStringBuilder(stackalloc TChar[512]); } if (digits > charsForBits) { - sb.Append(value._sign >= 0 ? '0' : '1', digits - charsForBits); + sb.Append(TChar.CastFrom(value._sign >= 0 ? '0' : '1'), digits - charsForBits); } AppendByte(ref sb, highByte, charsInHighByte - 1); @@ -728,30 +732,29 @@ static uint MultiplyAdd(Span bits, uint multiplier, uint addValue) } } - static void AppendByte(ref ValueStringBuilder sb, byte b, int startHighBit = 7) + static void AppendByte(ref ValueStringBuilder sb, byte b, int startHighBit = 7) { for (int i = startHighBit; i >= 0; i--) { - sb.Append((char)('0' + ((b >> i) & 0x1))); + sb.Append(TChar.CastFrom('0' + ((b >> i) & 0x1))); } } } internal static string FormatBigInteger(BigInteger value, string? format, NumberFormatInfo info) { - return FormatBigInteger(targetSpan: false, value, format, format, info, default, out _, out _)!; + return FormatBigInteger(targetSpan: false, value, format, format, info, default, out _, out _)!; } - internal static bool TryFormatBigInteger(BigInteger value, ReadOnlySpan format, NumberFormatInfo info, Span destination, out int charsWritten) + internal static bool TryFormatBigInteger(BigInteger value, ReadOnlySpan format, NumberFormatInfo info, Span destination, out int charsWritten) + where TChar : unmanaged, IUtfChar { FormatBigInteger(targetSpan: true, value, null, format, info, destination, out charsWritten, out bool spanSuccess); return spanSuccess; } - private static unsafe string? FormatBigInteger( - bool targetSpan, BigInteger value, - string? formatString, ReadOnlySpan formatSpan, - NumberFormatInfo info, Span destination, out int charsWritten, out bool spanSuccess) + private static unsafe string? FormatBigInteger(bool targetSpan, BigInteger value, string? formatString, ReadOnlySpan formatSpan, NumberFormatInfo info, Span destination, out int charsWritten, out bool spanSuccess) + where TChar : unmanaged, IUtfChar { Debug.Assert(formatString == null || formatString.Length == formatSpan.Length); @@ -775,7 +778,15 @@ internal static bool TryFormatBigInteger(BigInteger value, ReadOnlySpan fo if (targetSpan) { - spanSuccess = value._sign.TryFormat(destination, out charsWritten, formatSpan, info); + if (typeof(TChar) == typeof(Utf8Char)) + { + spanSuccess = value._sign.TryFormat(Unsafe.BitCast, Span>(destination), out charsWritten, formatSpan, info); + } + else + { + Debug.Assert(typeof(TChar) == typeof(Utf16Char)); + spanSuccess = value._sign.TryFormat(Unsafe.BitCast, Span>(destination), out charsWritten, formatSpan, info); + } return null; } else @@ -822,8 +833,8 @@ internal static bool TryFormatBigInteger(BigInteger value, ReadOnlySpan fo if (fmt == 'g' || fmt == 'G' || fmt == 'd' || fmt == 'D' || fmt == 'r' || fmt == 'R') { int strDigits = Math.Max(digits, valueDigits); - string? sNegative = value.Sign < 0 ? info.NegativeSign : null; - int strLength = strDigits + (sNegative?.Length ?? 0); + ReadOnlySpan sNegative = value.Sign < 0 ? info.NegativeSignTChar() : default; + int strLength = strDigits + sNegative.Length; if (targetSpan) { @@ -834,10 +845,10 @@ internal static bool TryFormatBigInteger(BigInteger value, ReadOnlySpan fo } else { - sNegative?.CopyTo(destination); - fixed (char* ptr = &MemoryMarshal.GetReference(destination)) + sNegative.CopyTo(destination); + fixed (TChar* ptr = &MemoryMarshal.GetReference(destination)) { - BigIntegerToDecChars((Utf16Char*)ptr + strLength, base1E9Value, digits); + BigIntegerToDecChars(ptr + strLength, base1E9Value, digits); } charsWritten = strLength; spanSuccess = true; @@ -846,16 +857,26 @@ internal static bool TryFormatBigInteger(BigInteger value, ReadOnlySpan fo } else { + Debug.Assert(typeof(TChar) == typeof(Utf16Char)); + spanSuccess = false; charsWritten = 0; + fixed (uint* ptr = base1E9Value) { - strResult = string.Create(strLength, (digits, ptr: (IntPtr)ptr, base1E9Value.Length, sNegative), static (span, state) => + var state = new InterpolatedStringHandlerState + { + digits = digits, + base1E9Value = base1E9Value, + sNegative = Unsafe.BitCast, ReadOnlySpan>(sNegative), + }; + + strResult = string.Create(strLength, state, static (span, state) => { - state.sNegative?.CopyTo(span); + state.sNegative.CopyTo(span); fixed (char* ptr = &MemoryMarshal.GetReference(span)) { - BigIntegerToDecChars((Utf16Char*)ptr + span.Length, new ReadOnlySpan((void*)state.ptr, state.Length), state.digits); + BigIntegerToDecChars((Utf16Char*)ptr + span.Length, state.base1E9Value, state.digits); } }); } @@ -876,7 +897,7 @@ internal static bool TryFormatBigInteger(BigInteger value, ReadOnlySpan fo number.Scale = valueDigits; number.IsNegative = value.Sign < 0; - scoped var vlb = new ValueListBuilder(stackalloc Utf16Char[CharStackBufferSize]); // arbitrary stack cut-off + scoped var vlb = new ValueListBuilder(stackalloc TChar[CharStackBufferSize]); // arbitrary stack cut-off if (fmt != 0) { @@ -889,14 +910,23 @@ internal static bool TryFormatBigInteger(BigInteger value, ReadOnlySpan fo if (targetSpan) { - spanSuccess = vlb.TryCopyTo(MemoryMarshal.Cast(destination), out charsWritten); + spanSuccess = vlb.TryCopyTo(destination, out charsWritten); strResult = null; } else { charsWritten = 0; spanSuccess = false; - strResult = MemoryMarshal.Cast(vlb.AsSpan()).ToString(); + + if (typeof(TChar) == typeof(Utf8Char)) + { + strResult = Encoding.UTF8.GetString(Unsafe.BitCast, ReadOnlySpan>(vlb.AsSpan())); + } + else + { + Debug.Assert(typeof(TChar) == typeof(Utf16Char)); + strResult = Unsafe.BitCast, ReadOnlySpan>(vlb.AsSpan()).ToString(); + } } vlb.Dispose(); @@ -915,6 +945,13 @@ internal static bool TryFormatBigInteger(BigInteger value, ReadOnlySpan fo return strResult; } + private unsafe ref struct InterpolatedStringHandlerState + { + public int digits; + public ReadOnlySpan base1E9Value; + public ReadOnlySpan sNegative; + } + private static unsafe TChar* BigIntegerToDecChars(TChar* bufferEnd, ReadOnlySpan base1E9Value, int digits) where TChar : unmanaged, IUtfChar { @@ -1353,7 +1390,7 @@ public void MultiplyPowerOfTen(ReadOnlySpan left, int trailingZeroCount, S internal interface IBigIntegerHexOrBinaryParser where TParser : struct, IBigIntegerHexOrBinaryParser - where TChar : unmanaged, IBinaryInteger + where TChar : unmanaged, IUtfChar { static abstract int BitsPerDigit { get; } @@ -1366,12 +1403,15 @@ internal interface IBigIntegerHexOrBinaryParser [MethodImpl(MethodImplOptions.AggressiveInlining)] static virtual bool TryParseUnalignedBlock(ReadOnlySpan input, out uint result) { - if (typeof(TChar) == typeof(char)) + if (typeof(TChar) == typeof(Utf8Char)) { - return uint.TryParse(MemoryMarshal.Cast(input), TParser.BlockNumberStyle, null, out result); + return uint.TryParse(Unsafe.BitCast, ReadOnlySpan>(input), TParser.BlockNumberStyle, null, out result); + } + else + { + Debug.Assert(typeof(TChar) == typeof(Utf16Char)); + return uint.TryParse(Unsafe.BitCast, ReadOnlySpan>(input), TParser.BlockNumberStyle, null, out result); } - - throw new NotSupportedException(); } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -1398,7 +1438,7 @@ static virtual bool TryParseWholeBlocks(ReadOnlySpan input, Span de } internal readonly struct BigIntegerHexParser : IBigIntegerHexOrBinaryParser, TChar> - where TChar : unmanaged, IBinaryInteger + where TChar : unmanaged, IUtfChar { public static int BitsPerDigit => 4; @@ -1410,31 +1450,28 @@ static virtual bool TryParseWholeBlocks(ReadOnlySpan input, Span de [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool TryParseWholeBlocks(ReadOnlySpan input, Span destination) { - if (typeof(TChar) == typeof(char)) + if ((typeof(TChar) == typeof(Utf8Char)) + ? (Convert.FromHexString(Unsafe.BitCast, ReadOnlySpan>(input), MemoryMarshal.AsBytes(destination), out _, out _) != OperationStatus.Done) + : (Convert.FromHexString(Unsafe.BitCast, ReadOnlySpan>(input), MemoryMarshal.AsBytes(destination), out _, out _) != OperationStatus.Done)) { - if (Convert.FromHexString(MemoryMarshal.Cast(input), MemoryMarshal.AsBytes(destination), out _, out _) != OperationStatus.Done) - { - return false; - } - - if (BitConverter.IsLittleEndian) - { - MemoryMarshal.AsBytes(destination).Reverse(); - } - else - { - destination.Reverse(); - } + return false; + } - return true; + if (BitConverter.IsLittleEndian) + { + MemoryMarshal.AsBytes(destination).Reverse(); + } + else + { + destination.Reverse(); } - throw new NotSupportedException(); + return true; } } internal readonly struct BigIntegerBinaryParser : IBigIntegerHexOrBinaryParser, TChar> - where TChar : unmanaged, IBinaryInteger + where TChar : unmanaged, IUtfChar { public static int BitsPerDigit => 1; diff --git a/src/libraries/System.Runtime.Numerics/src/System/Number.Polyfill.cs b/src/libraries/System.Runtime.Numerics/src/System/Number.Polyfill.cs index 9c1c2a89daf5ef..519cba467cf6ba 100644 --- a/src/libraries/System.Runtime.Numerics/src/System/Number.Polyfill.cs +++ b/src/libraries/System.Runtime.Numerics/src/System/Number.Polyfill.cs @@ -1,10 +1,16 @@ // 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; using System.Diagnostics; using System.Globalization; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; +using System.Runtime.Intrinsics; +using System.Runtime.Intrinsics.Arm; +using System.Runtime.Intrinsics.Wasm; +using System.Runtime.Intrinsics.X86; +using System.Text; namespace System { @@ -78,91 +84,92 @@ internal static bool AllowHyphenDuringParsing(this NumberFormatInfo info) } [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static ReadOnlySpan PositiveSignTChar(this NumberFormatInfo info) + internal static bool IsWhiteSpace(this ReadOnlySpan span) where TChar : unmanaged, IUtfChar { - Debug.Assert(typeof(TChar) == typeof(Utf16Char)); - return MemoryMarshal.Cast(info.PositiveSign); + int elemsConsumed; + + for (int i = 0; i < span.Length; i += elemsConsumed) + { + if (DecodeFromUtfChar(span, out Rune rune, out elemsConsumed) != OperationStatus.Done) + { + return false; + } + + if (!Rune.IsWhiteSpace(rune)) + { + return false; + } + } + + return true; } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static ReadOnlySpan NegativeSignTChar(this NumberFormatInfo info) + internal static OperationStatus DecodeFromUtfChar(ReadOnlySpan span, out Rune result, out int elemsConsumed) where TChar : unmanaged, IUtfChar { - Debug.Assert(typeof(TChar) == typeof(Utf16Char)); - return MemoryMarshal.Cast(info.NegativeSign); + return (typeof(TChar) == typeof(Utf8Char)) + ? Rune.DecodeFromUtf8(Unsafe.BitCast, ReadOnlySpan>(span), out result, out elemsConsumed) + : Rune.DecodeFromUtf16(Unsafe.BitCast, ReadOnlySpan>(span), out result, out elemsConsumed); } [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static ReadOnlySpan CurrencySymbolTChar(this NumberFormatInfo info) + internal static ReadOnlySpan FromString(string value) where TChar : unmanaged, IUtfChar { - Debug.Assert(typeof(TChar) == typeof(Utf16Char)); - return MemoryMarshal.Cast(info.CurrencySymbol); + if (typeof(TChar) == typeof(Utf8Char)) + { + return Unsafe.BitCast, ReadOnlySpan>(Encoding.UTF8.GetBytes(value)); + } + else + { + Debug.Assert(typeof(TChar) == typeof(Utf16Char)); + return Unsafe.BitCast, ReadOnlySpan>(value); + } } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static ReadOnlySpan PositiveSignTChar(this NumberFormatInfo info) + where TChar : unmanaged, IUtfChar => FromString(info.PositiveSign); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static ReadOnlySpan NegativeSignTChar(this NumberFormatInfo info) + where TChar : unmanaged, IUtfChar => FromString(info.NegativeSign); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static ReadOnlySpan CurrencySymbolTChar(this NumberFormatInfo info) + where TChar : unmanaged, IUtfChar => FromString(info.CurrencySymbol); + [MethodImpl(MethodImplOptions.AggressiveInlining)] internal static ReadOnlySpan PercentSymbolTChar(this NumberFormatInfo info) - where TChar : unmanaged, IUtfChar - { - Debug.Assert(typeof(TChar) == typeof(Utf16Char)); - return MemoryMarshal.Cast(info.PercentSymbol); - } + where TChar : unmanaged, IUtfChar => FromString(info.PercentSymbol); [MethodImpl(MethodImplOptions.AggressiveInlining)] internal static ReadOnlySpan PerMilleSymbolTChar(this NumberFormatInfo info) - where TChar : unmanaged, IUtfChar - { - Debug.Assert(typeof(TChar) == typeof(Utf16Char)); - return MemoryMarshal.Cast(info.PerMilleSymbol); - } + where TChar : unmanaged, IUtfChar => FromString(info.PerMilleSymbol); [MethodImpl(MethodImplOptions.AggressiveInlining)] internal static ReadOnlySpan CurrencyDecimalSeparatorTChar(this NumberFormatInfo info) - where TChar : unmanaged, IUtfChar - { - Debug.Assert(typeof(TChar) == typeof(Utf16Char)); - return MemoryMarshal.Cast(info.CurrencyDecimalSeparator); - } + where TChar : unmanaged, IUtfChar => FromString(info.CurrencyDecimalSeparator); [MethodImpl(MethodImplOptions.AggressiveInlining)] internal static ReadOnlySpan CurrencyGroupSeparatorTChar(this NumberFormatInfo info) - where TChar : unmanaged, IUtfChar - { - Debug.Assert(typeof(TChar) == typeof(Utf16Char)); - return MemoryMarshal.Cast(info.CurrencyGroupSeparator); - } + where TChar : unmanaged, IUtfChar => FromString(info.CurrencyGroupSeparator); [MethodImpl(MethodImplOptions.AggressiveInlining)] internal static ReadOnlySpan NumberDecimalSeparatorTChar(this NumberFormatInfo info) - where TChar : unmanaged, IUtfChar - { - Debug.Assert(typeof(TChar) == typeof(Utf16Char)); - return MemoryMarshal.Cast(info.NumberDecimalSeparator); - } + where TChar : unmanaged, IUtfChar => FromString(info.NumberDecimalSeparator); [MethodImpl(MethodImplOptions.AggressiveInlining)] internal static ReadOnlySpan NumberGroupSeparatorTChar(this NumberFormatInfo info) - where TChar : unmanaged, IUtfChar - { - Debug.Assert(typeof(TChar) == typeof(Utf16Char)); - return MemoryMarshal.Cast(info.NumberGroupSeparator); - } + where TChar : unmanaged, IUtfChar => FromString(info.NumberGroupSeparator); [MethodImpl(MethodImplOptions.AggressiveInlining)] internal static ReadOnlySpan PercentDecimalSeparatorTChar(this NumberFormatInfo info) - where TChar : unmanaged, IUtfChar - { - Debug.Assert(typeof(TChar) == typeof(Utf16Char)); - return MemoryMarshal.Cast(info.PercentDecimalSeparator); - } + where TChar : unmanaged, IUtfChar => FromString(info.PercentDecimalSeparator); [MethodImpl(MethodImplOptions.AggressiveInlining)] internal static ReadOnlySpan PercentGroupSeparatorTChar(this NumberFormatInfo info) - where TChar : unmanaged, IUtfChar - { - Debug.Assert(typeof(TChar) == typeof(Utf16Char)); - return MemoryMarshal.Cast(info.PercentGroupSeparator); - } + where TChar : unmanaged, IUtfChar => FromString(info.PercentGroupSeparator); } } diff --git a/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigInteger.cs b/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigInteger.cs index 2a621618fdc5b5..76c6859464708b 100644 --- a/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigInteger.cs +++ b/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigInteger.cs @@ -699,7 +699,12 @@ public static bool TryParse([NotNullWhen(true)] string? value, NumberStyles styl public static BigInteger Parse(ReadOnlySpan value, NumberStyles style = NumberStyles.Integer, IFormatProvider? provider = null) { - return Number.ParseBigInteger(value, style, NumberFormatInfo.GetInstance(provider)); + return Number.ParseBigInteger(MemoryMarshal.Cast(value), style, NumberFormatInfo.GetInstance(provider)); + } + + public static BigInteger Parse(ReadOnlySpan utf8Text, NumberStyles style = NumberStyles.Integer, IFormatProvider? provider = null) + { + return Number.ParseBigInteger(MemoryMarshal.Cast(utf8Text), style, NumberFormatInfo.GetInstance(provider)); } public static bool TryParse(ReadOnlySpan value, out BigInteger result) @@ -709,7 +714,17 @@ public static bool TryParse(ReadOnlySpan value, out BigInteger result) public static bool TryParse(ReadOnlySpan value, NumberStyles style, IFormatProvider? provider, out BigInteger result) { - return Number.TryParseBigInteger(value, style, NumberFormatInfo.GetInstance(provider), out result) == Number.ParsingStatus.OK; + return Number.TryParseBigInteger(MemoryMarshal.Cast(value), style, NumberFormatInfo.GetInstance(provider), out result) == Number.ParsingStatus.OK; + } + + public static bool TryParse(ReadOnlySpan utf8Text, out BigInteger result) + { + return TryParse(utf8Text, NumberStyles.Integer, NumberFormatInfo.CurrentInfo, out result); + } + + public static bool TryParse(ReadOnlySpan utf8Text, NumberStyles style, IFormatProvider? provider, out BigInteger result) + { + return Number.TryParseBigInteger(MemoryMarshal.Cast(utf8Text), style, NumberFormatInfo.GetInstance(provider), out result) == Number.ParsingStatus.OK; } public static int Compare(BigInteger left, BigInteger right) @@ -1670,7 +1685,12 @@ private string DebuggerDisplay public bool TryFormat(Span destination, out int charsWritten, [StringSyntax(StringSyntaxAttribute.NumericFormat)] ReadOnlySpan format = default, IFormatProvider? provider = null) { - return Number.TryFormatBigInteger(this, format, NumberFormatInfo.GetInstance(provider), destination, out charsWritten); + return Number.TryFormatBigInteger(this, format, NumberFormatInfo.GetInstance(provider), MemoryMarshal.Cast(destination), out charsWritten); + } + + public bool TryFormat(Span utf8Destination, out int bytesWritten, [StringSyntax(StringSyntaxAttribute.NumericFormat)] ReadOnlySpan format = default, IFormatProvider? provider = null) + { + return Number.TryFormatBigInteger(this, format, NumberFormatInfo.GetInstance(provider), MemoryMarshal.Cast(utf8Destination), out bytesWritten); } private static BigInteger Add(ReadOnlySpan leftBits, int leftSign, ReadOnlySpan rightBits, int rightSign) @@ -5168,5 +5188,15 @@ static bool INumberBase.TryConvertToTruncating(BigInteger va /// public static bool TryParse(ReadOnlySpan s, IFormatProvider? provider, out BigInteger result) => TryParse(s, NumberStyles.Integer, provider, out result); + + // + // IUtf8SpanParsable + // + + /// + public static BigInteger Parse(ReadOnlySpan utf8Text, IFormatProvider? provider) => Parse(utf8Text, NumberStyles.Integer, provider); + + /// + public static bool TryParse(ReadOnlySpan utf8Text, IFormatProvider? provider, out BigInteger result) => TryParse(utf8Text, NumberStyles.Integer, provider, out result); } } diff --git a/src/libraries/System.Runtime.Numerics/src/System/Numerics/Complex.cs b/src/libraries/System.Runtime.Numerics/src/System/Numerics/Complex.cs index e162bab1738051..8366521e850486 100644 --- a/src/libraries/System.Runtime.Numerics/src/System/Numerics/Complex.cs +++ b/src/libraries/System.Runtime.Numerics/src/System/Numerics/Complex.cs @@ -1,11 +1,13 @@ // 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; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; +using System.Text; namespace System.Numerics { @@ -1464,6 +1466,16 @@ public static Complex Parse(ReadOnlySpan s, NumberStyles style, IFormatPro return result; } + /// + public static Complex Parse(ReadOnlySpan utf8Text, NumberStyles style, IFormatProvider? provider) + { + if (!TryParse(utf8Text, style, provider, out Complex result)) + { + ThrowHelper.ThrowOverflowException(); + } + return result; + } + /// public static Complex Parse(string s, NumberStyles style, IFormatProvider? provider) { @@ -2088,14 +2100,22 @@ static bool INumberBase.TryConvertToTruncating(Complex value, [ /// public static bool TryParse(ReadOnlySpan s, NumberStyles style, IFormatProvider? provider, out Complex result) + => TryParse(MemoryMarshal.Cast(s), style, provider, out result); + + /// + public static bool TryParse(ReadOnlySpan utf8Text, NumberStyles style, IFormatProvider? provider, out Complex result) + => TryParse(MemoryMarshal.Cast(utf8Text), style, provider, out result); + + private static bool TryParse(ReadOnlySpan text, NumberStyles style, IFormatProvider? provider, out Complex result) + where TChar : unmanaged, IUtfChar { ValidateParseStyleFloatingPoint(style); - int openBracket = s.IndexOf('<'); - int semicolon = s.IndexOf(';'); - int closeBracket = s.IndexOf('>'); + int openBracket = text.IndexOf(TChar.CastFrom('<')); + int semicolon = text.IndexOf(TChar.CastFrom(';')); + int closeBracket = text.IndexOf(TChar.CastFrom('>')); - if ((s.Length < 5) || (openBracket == -1) || (semicolon == -1) || (closeBracket == -1) || (openBracket > semicolon) || (openBracket > closeBracket) || (semicolon > closeBracket)) + if ((text.Length < 5) || (openBracket == -1) || (semicolon == -1) || (closeBracket == -1) || (openBracket > semicolon) || (openBracket > closeBracket) || (semicolon > closeBracket)) { // We need at least 5 characters for `<0;0>` // We also expect a to find an open bracket, a semicolon, and a closing bracket in that order @@ -2104,7 +2124,7 @@ public static bool TryParse(ReadOnlySpan s, NumberStyles style, IFormatPro return false; } - if ((openBracket != 0) && (((style & NumberStyles.AllowLeadingWhite) == 0) || !s.Slice(0, openBracket).IsWhiteSpace())) + if ((openBracket != 0) && (((style & NumberStyles.AllowLeadingWhite) == 0) || !text.Slice(0, openBracket).IsWhiteSpace())) { // The opening bracket wasn't the first and we either didn't allow leading whitespace // or one of the leading characters wasn't whitespace at all. @@ -2113,26 +2133,37 @@ public static bool TryParse(ReadOnlySpan s, NumberStyles style, IFormatPro return false; } - if (!double.TryParse(s.Slice(openBracket + 1, semicolon - openBracket - 1), style, provider, out double real)) + ReadOnlySpan slice = text.Slice(openBracket + 1, semicolon - openBracket - 1); + + if ((typeof(TChar) == typeof(Utf8Char)) + ? !double.TryParse(Unsafe.BitCast, ReadOnlySpan>(slice), style, provider, out double real) + : !double.TryParse(Unsafe.BitCast, ReadOnlySpan>(slice), style, provider, out real)) { result = default; return false; } - if (char.IsWhiteSpace(s[semicolon + 1])) + if (Number.DecodeFromUtfChar(text[(semicolon + 1)..], out Rune rune, out int elemsConsumed) == OperationStatus.Done) { - // We allow a single whitespace after the semicolon regardless of style, this is so that - // the output of `ToString` can be correctly parsed by default and values will roundtrip. - semicolon += 1; + if (Rune.IsWhiteSpace(rune)) + { + // We allow a single whitespace after the semicolon regardless of style, this is so that + // the output of `ToString` can be correctly parsed by default and values will roundtrip. + semicolon += elemsConsumed; + } } - if (!double.TryParse(s.Slice(semicolon + 1, closeBracket - semicolon - 1), style, provider, out double imaginary)) + slice = text.Slice(semicolon + 1, closeBracket - semicolon - 1); + + if ((typeof(TChar) == typeof(Utf8Char)) + ? !double.TryParse(Unsafe.BitCast, ReadOnlySpan>(slice), style, provider, out double imaginary) + : !double.TryParse(Unsafe.BitCast, ReadOnlySpan>(slice), style, provider, out imaginary)) { result = default; return false; } - if ((closeBracket != (s.Length - 1)) && (((style & NumberStyles.AllowTrailingWhite) == 0) || !s.Slice(closeBracket).IsWhiteSpace())) + if ((closeBracket != (text.Length - 1)) && (((style & NumberStyles.AllowTrailingWhite) == 0) || !text.Slice(closeBracket).IsWhiteSpace())) { // The closing bracket wasn't the last and we either didn't allow trailing whitespace // or one of the trailing characters wasn't whitespace at all. @@ -2143,23 +2174,23 @@ public static bool TryParse(ReadOnlySpan s, NumberStyles style, IFormatPro result = new Complex(real, imaginary); return true; + } - static void ValidateParseStyleFloatingPoint(NumberStyles style) + private static void ValidateParseStyleFloatingPoint(NumberStyles style) + { + // Check for undefined flags or hex number + if ((style & (InvalidNumberStyles | NumberStyles.AllowHexSpecifier)) != 0) { - // Check for undefined flags or hex number - if ((style & (InvalidNumberStyles | NumberStyles.AllowHexSpecifier)) != 0) - { - ThrowInvalid(style); + ThrowInvalid(style); - static void ThrowInvalid(NumberStyles value) + static void ThrowInvalid(NumberStyles value) + { + if ((value & InvalidNumberStyles) != 0) { - if ((value & InvalidNumberStyles) != 0) - { - throw new ArgumentException(SR.Argument_InvalidNumberStyles, nameof(style)); - } - - throw new ArgumentException(SR.Arg_HexStyleNotSupported); + throw new ArgumentException(SR.Argument_InvalidNumberStyles, nameof(style)); } + + throw new ArgumentException(SR.Arg_HexStyleNotSupported); } } } @@ -2198,41 +2229,40 @@ public static bool TryParse([NotNullWhen(true)] string? s, NumberStyles style, I /// public bool TryFormat(Span destination, out int charsWritten, [StringSyntax(StringSyntaxAttribute.NumericFormat)] ReadOnlySpan format = default, IFormatProvider? provider = null) => - TryFormatCore(destination, out charsWritten, format, provider); + TryFormat(MemoryMarshal.Cast(destination), out charsWritten, format, provider); /// public bool TryFormat(Span utf8Destination, out int bytesWritten, [StringSyntax(StringSyntaxAttribute.NumericFormat)] ReadOnlySpan format = default, IFormatProvider? provider = null) => - TryFormatCore(utf8Destination, out bytesWritten, format, provider); + TryFormat(MemoryMarshal.Cast(utf8Destination), out bytesWritten, format, provider); - private bool TryFormatCore(Span destination, out int charsWritten, ReadOnlySpan format, IFormatProvider? provider) where TChar : unmanaged, IBinaryInteger + private bool TryFormat(Span destination, out int charsWritten, ReadOnlySpan format, IFormatProvider? provider) + where TChar : unmanaged, IUtfChar { - Debug.Assert(typeof(TChar) == typeof(char) || typeof(TChar) == typeof(byte)); + Debug.Assert(typeof(TChar) == typeof(Utf8Char) || typeof(TChar) == typeof(Utf16Char)); // We have at least 6 more characters for: <0; 0> if (destination.Length >= 6) { - int realChars; - if (typeof(TChar) == typeof(char) ? - m_real.TryFormat(Unsafe.BitCast, Span>(destination.Slice(1)), out realChars, format, provider) : - m_real.TryFormat(Unsafe.BitCast, Span>(destination.Slice(1)), out realChars, format, provider)) + if ((typeof(TChar) == typeof(Utf8Char)) + ? m_real.TryFormat(Unsafe.BitCast, Span>(destination.Slice(1)), out int realChars, format, provider) + : m_real.TryFormat(Unsafe.BitCast, Span>(destination.Slice(1)), out realChars, format, provider)) { - destination[0] = TChar.CreateTruncating('<'); + destination[0] = TChar.CastFrom('<'); destination = destination.Slice(1 + realChars); // + 1 for < // We have at least 4 more characters for: ; 0> if (destination.Length >= 4) { - int imaginaryChars; - if (typeof(TChar) == typeof(char) ? - m_imaginary.TryFormat(Unsafe.BitCast, Span>(destination.Slice(2)), out imaginaryChars, format, provider) : - m_imaginary.TryFormat(Unsafe.BitCast, Span>(destination.Slice(2)), out imaginaryChars, format, provider)) + if ((typeof(TChar) == typeof(Utf8Char)) + ? m_imaginary.TryFormat(Unsafe.BitCast, Span>(destination.Slice(2)), out int imaginaryChars, format, provider) + : m_imaginary.TryFormat(Unsafe.BitCast, Span>(destination.Slice(2)), out imaginaryChars, format, provider)) { // We have 1 more character for: > if ((uint)(2 + imaginaryChars) < (uint)destination.Length) { - destination[0] = TChar.CreateTruncating(';'); - destination[1] = TChar.CreateTruncating(' '); - destination[2 + imaginaryChars] = TChar.CreateTruncating('>'); + destination[0] = TChar.CastFrom(';'); + destination[1] = TChar.CastFrom(' '); + destination[2 + imaginaryChars] = TChar.CastFrom('>'); charsWritten = realChars + imaginaryChars + 4; return true; @@ -2262,5 +2292,15 @@ private bool TryFormatCore(Span destination, out int charsWritten, /// public static Complex operator +(Complex value) => value; + + // + // IUtf8SpanParsable + // + + /// + public static Complex Parse(ReadOnlySpan utf8Text, IFormatProvider? provider) => Parse(utf8Text, DefaultNumberStyle, provider); + + /// + public static bool TryParse(ReadOnlySpan utf8Text, IFormatProvider? provider, out Complex result) => TryParse(utf8Text, DefaultNumberStyle, provider, out result); } } diff --git a/src/libraries/System.Runtime.Numerics/src/System/ThrowHelper.cs b/src/libraries/System.Runtime.Numerics/src/System/ThrowHelper.cs index a81589e2526d2f..7f9ba87714cd7f 100644 --- a/src/libraries/System.Runtime.Numerics/src/System/ThrowHelper.cs +++ b/src/libraries/System.Runtime.Numerics/src/System/ThrowHelper.cs @@ -32,5 +32,11 @@ internal static void ThrowFormatException_BadFormatSpecifier() { throw new FormatException(SR.Argument_BadFormatSpecifier); } + + [DoesNotReturn] + internal static void ThrowUnreachableException() + { + throw new UnreachableException(); + } } } diff --git a/src/libraries/System.Runtime.Numerics/tests/BigInteger/BigIntegerToStringTests.cs b/src/libraries/System.Runtime.Numerics/tests/BigInteger/BigIntegerToStringTests.cs index db3e579662f7c6..d54300593da263 100644 --- a/src/libraries/System.Runtime.Numerics/tests/BigInteger/BigIntegerToStringTests.cs +++ b/src/libraries/System.Runtime.Numerics/tests/BigInteger/BigIntegerToStringTests.cs @@ -5,6 +5,7 @@ using System.Diagnostics; using System.Globalization; using System.Tests; +using System.Text; using Xunit; namespace System.Numerics.Tests @@ -531,12 +532,19 @@ public static void ToString_ValidLargeFormat() // Check ParseFormatSpecifier in FormatProvider.Number.cs with `E` format. // Currently disabled since these would still allocate a 2GB buffer before // returning, leading to OOM in CI. - //Assert.False(b.TryFormat(Span.Empty, out _, format: "E999999999")); // Should not throw - //Assert.False(b.TryFormat(Span.Empty, out _, format: "E00000999999999")); // Should not throw + // Assert.False(b.TryFormat(Span.Empty, out _, format: "E999999999")); // Should not throw + // Assert.False(b.TryFormat(Span.Empty, out _, format: "E00000999999999")); // Should not throw + // + // Assert.False(b.TryFormat(Span.Empty, out _, format: "E999999999")); // Should not throw + // Assert.False(b.TryFormat(Span.Empty, out _, format: "E00000999999999")); // Should not throw // Check ParseFormatSpecifier in Number.BigInteger.cs with `G` format Assert.False(b.TryFormat(Span.Empty, out _, format: "G999999999")); // Should not throw Assert.False(b.TryFormat(Span.Empty, out _, format: "G00000999999999")); // Should not throw + + // Check ParseFormatSpecifier in Number.BigInteger.cs with `G` format + Assert.False(b.TryFormat(Span.Empty, out _, format: "G999999999")); // Should not throw + Assert.False(b.TryFormat(Span.Empty, out _, format: "G00000999999999")); // Should not throw } [Fact] @@ -1625,6 +1633,7 @@ private static void VerifyToString(string test, string format, IFormatProvider p } VerifyTryFormat(test, format, provider, expectError, expectedResult); + VerifyTryFormatUtf8(test, format, provider, expectError, expectedResult); } private static void VerifyExpectedStringResult(string expectedResult, string result) @@ -1675,6 +1684,30 @@ static void VerifyTryFormat(string test, string format, IFormatProvider provider } } + static void VerifyTryFormatUtf8(string test, string format, IFormatProvider provider, bool expectError, string expectedResult) + { + try + { + BigInteger bi = BigInteger.Parse(test, provider); + + byte[] utf8Destination = expectedResult != null ? new byte[Encoding.UTF8.GetByteCount(expectedResult)] : Array.Empty(); + Assert.True(bi.TryFormat(utf8Destination, out int bytesWritten, format, provider)); + Assert.False(expectError); + + VerifyExpectedStringResult(expectedResult, Encoding.UTF8.GetString(utf8Destination[..bytesWritten])); + + if (expectedResult.Length > 0) + { + Assert.False(bi.TryFormat(new byte[Encoding.UTF8.GetByteCount(expectedResult) - 1], out bytesWritten, format, provider)); + Assert.Equal(0, bytesWritten); + } + } + catch (FormatException) + { + Assert.True(expectError); + } + } + private static string GetDigitSequence(int min, int max, Random random) { string result = string.Empty; diff --git a/src/libraries/System.Runtime.Numerics/tests/BigInteger/parse.cs b/src/libraries/System.Runtime.Numerics/tests/BigInteger/parse.cs index e9f249a7710e0f..bdfde88395a326 100644 --- a/src/libraries/System.Runtime.Numerics/tests/BigInteger/parse.cs +++ b/src/libraries/System.Runtime.Numerics/tests/BigInteger/parse.cs @@ -2,14 +2,11 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Collections.Generic; -using System.Diagnostics; using System.Globalization; +using System.Linq; using System.Tests; -using System.Text.RegularExpressions; -using System.Threading; -using Microsoft.DotNet.RemoteExecutor; +using System.Text; using Xunit; -using static System.Net.Mime.MediaTypeNames; namespace System.Numerics.Tests { @@ -81,6 +78,17 @@ public static void RunParseToStringTests(CultureInfo culture) Assert.Equal("1", junk.ToString("d")); }); + AssertExtensions.Throws("style", () => + { + BigInteger.Parse("1"u8, invalid).ToString("d"); + }); + AssertExtensions.Throws("style", () => + { + BigInteger junk; + BigInteger.TryParse("1"u8, invalid, null, out junk); + Assert.Equal("1", junk.ToString("d")); + }); + //FormatProvider tests RunFormatProviderParseStrings(); } @@ -107,6 +115,16 @@ public static void Parse_Subspan_Success(string input, int offset, int length, s Eval(test, expected); } + [Theory] + [MemberData(nameof(Parse_Subspan_Success_TestData))] + public static void ParseUtf8_Subspan_Success(string input, int offset, int length, string expected) + { + byte[] utf8Input = Encoding.UTF8.GetBytes(input); + Eval(BigInteger.Parse(utf8Input.AsSpan(offset, length)), expected); + Assert.True(BigInteger.TryParse(utf8Input.AsSpan(offset, length), out BigInteger test)); + Eval(test, expected); + } + [Fact] public static void Parse_EmptySubspan_Fails() { @@ -115,7 +133,19 @@ public static void Parse_EmptySubspan_Fails() Assert.False(BigInteger.TryParse("12345".AsSpan(0, 0), out result)); Assert.Equal(0, result); - Assert.False(BigInteger.TryParse([], out result)); + Assert.False(BigInteger.TryParse(ReadOnlySpan.Empty, out result)); + Assert.Equal(0, result); + } + + [Fact] + public static void ParseUtf8_EmptySubspan_Fails() + { + BigInteger result; + + Assert.False(BigInteger.TryParse("12345"u8.Slice(0, 0), out result)); + Assert.Equal(0, result); + + Assert.False(BigInteger.TryParse(ReadOnlySpan.Empty, out result)); Assert.Equal(0, result); } @@ -169,6 +199,56 @@ public void Parse_Hex32Bits() }); } + [Fact] + public void ParseUtf8_Hex32Bits() + { + // Regression test for: https://github.com/dotnet/runtime/issues/54251 + BigInteger result; + + Assert.True(BigInteger.TryParse("80000000"u8, NumberStyles.HexNumber, null, out result)); + Assert.Equal(int.MinValue, result); + + Assert.True(BigInteger.TryParse("080000001"u8, NumberStyles.HexNumber, null, out result)); + Assert.Equal(0x80000001u, result); + + Assert.True(BigInteger.TryParse("F0000001"u8, NumberStyles.HexNumber, null, out result)); + Assert.Equal(-0xFFFFFFFL, result); + + Assert.True(BigInteger.TryParse("0F0000001"u8, NumberStyles.HexNumber, null, out result)); + Assert.Equal(0xF0000001u, result); + + Assert.True(BigInteger.TryParse("F00000001"u8, NumberStyles.HexNumber, null, out result)); + Assert.Equal(-0xFFFFFFFFL, result); + + Assert.True(BigInteger.TryParse("0F00000001"u8, NumberStyles.HexNumber, null, out result)); + Assert.Equal(0xF00000001u, result); + + // Regression test for: https://github.com/dotnet/runtime/issues/74758 + Assert.True(BigInteger.TryParse("FFFFFFFFE"u8, NumberStyles.HexNumber, null, out result)); + Assert.Equal(new BigInteger(-2), result); + Assert.Equal(-2, result); + + Assert.True(BigInteger.TryParse("F"u8, NumberStyles.HexNumber, null, out result)); + Assert.Equal(-1, result); + + for (int i = 0; i < 40; i++) + { + byte[] test = [(byte)'F', .. Enumerable.Repeat((byte)'0', i)]; + Assert.True(BigInteger.TryParse(test, NumberStyles.HexNumber, null, out result)); + Assert.Equal(BigInteger.MinusOne << (4 * i), result); + } + + Assert.Throws(() => + { + BigInteger.Parse("zzz"u8, NumberStyles.HexNumber); + }); + + AssertExtensions.Throws("style", () => + { + BigInteger.Parse("1"u8, NumberStyles.AllowHexSpecifier | NumberStyles.AllowCurrencySymbol); + }); + } + [Theory] [InlineData("1", -1L)] [InlineData("01", 1L)] @@ -183,6 +263,31 @@ public void Parse_BinSpecialCases(string input, long expectedValue) Assert.Equal(expectedValue, result); } + [Fact] + public void ParseUtf8_BinSpecialCases() + { + Assert.True(BigInteger.TryParse("1"u8, NumberStyles.BinaryNumber, null, out BigInteger result)); + Assert.Equal(-1, result); + + Assert.True(BigInteger.TryParse("01"u8, NumberStyles.BinaryNumber, null, out result)); + Assert.Equal(1, result); + + Assert.True(BigInteger.TryParse("10000000000000000000000000000000"u8, NumberStyles.BinaryNumber, null, out result)); + Assert.Equal(int.MinValue, result); + + Assert.True(BigInteger.TryParse("010000000000000000000000000000001"u8, NumberStyles.BinaryNumber, null, out result)); + Assert.Equal(0x080000001, result); + + Assert.True(BigInteger.TryParse("111111111111111111111111111111110"u8, NumberStyles.BinaryNumber, null, out result)); + Assert.Equal(-2, result); + + Assert.True(BigInteger.TryParse("100000000000000000000000000000001"u8, NumberStyles.BinaryNumber, null, out result)); + Assert.Equal(-0xFFFFFFFF, result); + + Assert.True(BigInteger.TryParse("0111111111111111111111111111111111"u8, NumberStyles.BinaryNumber, null, out result)); + Assert.Equal(0x1FFFFFFFF, result); + } + public static IEnumerable RegressionIssueRuntime94610_TestData() { yield return new object[] @@ -686,24 +791,25 @@ private static void VerifyNumberStyles(NumberStyles ns, Random random) private static void VerifyParseToString(string num1) { - BigInteger test; + string expected = Fix(num1.Trim()); + Eval(BigInteger.Parse(num1), expected); - Eval(BigInteger.Parse(num1), Fix(num1.Trim())); - Assert.True(BigInteger.TryParse(num1, out test)); - Eval(test, Fix(num1.Trim())); + Assert.True(BigInteger.TryParse(num1, out BigInteger test)); + Eval(test, expected); } private static void VerifyFailParseToString(string num1, Type expectedExceptionType) { - BigInteger test; - Assert.False(BigInteger.TryParse(num1, out test), string.Format("Expected TryParse to fail on {0}", num1)); + Assert.False(BigInteger.TryParse(num1, out _), string.Format("Expected TryParse to fail on {0}", num1)); if (num1 == null) { Assert.Throws(() => { BigInteger.Parse(num1).ToString("d"); }); } else { - Assert.Throws(() => { BigInteger.Parse(num1).ToString("d"); }); + byte[] utf8Num1 = Encoding.UTF8.GetBytes(num1); + Assert.False(BigInteger.TryParse(utf8Num1, out _), string.Format("Expected TryParse to fail on {0}", num1)); + Assert.Throws(() => { BigInteger.Parse(utf8Num1).ToString("d"); }); } } @@ -714,40 +820,50 @@ private static void VerifyParseToString(string num1, NumberStyles ns, bool failu static void VerifyParseSpanToString(string num1, NumberStyles ns, bool failureNotExpected, string expected) { + byte[] utf8Num1 = Encoding.UTF8.GetBytes(num1); + if (failureNotExpected) { Eval(BigInteger.Parse(num1.AsSpan(), ns), expected); + Eval(BigInteger.Parse(utf8Num1, ns), expected); Assert.True(BigInteger.TryParse(num1.AsSpan(), ns, provider: null, out BigInteger test)); Eval(test, expected); + Assert.True(BigInteger.TryParse(utf8Num1, ns, provider: null, out test)); + Eval(test, expected); + if (ns == NumberStyles.Integer) { Assert.True(BigInteger.TryParse(num1.AsSpan(), out test)); Eval(test, expected); + + Assert.True(BigInteger.TryParse(utf8Num1, out test)); + Eval(test, expected); } } else { Assert.Throws(() => { BigInteger.Parse(num1.AsSpan(), ns); }); + Assert.Throws(() => { BigInteger.Parse(utf8Num1, ns); }); - Assert.False(BigInteger.TryParse(num1.AsSpan(), ns, provider: null, out BigInteger test)); + Assert.False(BigInteger.TryParse(num1.AsSpan(), ns, provider: null, out _)); + Assert.False(BigInteger.TryParse(utf8Num1, ns, provider: null, out _)); if (ns == NumberStyles.Integer) { - Assert.False(BigInteger.TryParse(num1.AsSpan(), out test)); + Assert.False(BigInteger.TryParse(num1.AsSpan(), out _)); + Assert.False(BigInteger.TryParse(utf8Num1, out _)); } } } private static void VerifyParseToString(string num1, NumberStyles ns, bool failureNotExpected, string expected) { - BigInteger test; - if (failureNotExpected) { Eval(BigInteger.Parse(num1, ns), expected); - Assert.True(BigInteger.TryParse(num1, ns, null, out test)); + Assert.True(BigInteger.TryParse(num1, ns, null, out BigInteger test)); Eval(test, expected); } else @@ -760,7 +876,7 @@ private static void VerifyParseToString(string num1, NumberStyles ns, bool failu { Assert.Throws(() => { BigInteger.Parse(num1, ns); }); } - Assert.False(BigInteger.TryParse(num1, ns, null, out test), string.Format("Expected TryParse to fail on {0}", num1)); + Assert.False(BigInteger.TryParse(num1, ns, null, out _), string.Format("Expected TryParse to fail on {0}", num1)); } if (num1 != null) @@ -771,33 +887,40 @@ private static void VerifyParseToString(string num1, NumberStyles ns, bool failu static void VerifySimpleFormatParseSpan(string num1, NumberFormatInfo nfi, BigInteger expected, bool failureExpected) { + byte[] utf8Num1 = Encoding.UTF8.GetBytes(num1); + if (!failureExpected) { Assert.Equal(expected, BigInteger.Parse(num1.AsSpan(), provider: nfi)); Assert.True(BigInteger.TryParse(num1.AsSpan(), NumberStyles.Any, nfi, out BigInteger test)); Assert.Equal(expected, test); + + Assert.Equal(expected, BigInteger.Parse(utf8Num1, provider: nfi)); + Assert.True(BigInteger.TryParse(utf8Num1, NumberStyles.Any, nfi, out test)); + Assert.Equal(expected, test); } else { Assert.Throws(() => { BigInteger.Parse(num1.AsSpan(), provider: nfi); }); - Assert.False(BigInteger.TryParse(num1.AsSpan(), NumberStyles.Any, nfi, out BigInteger test), string.Format("Expected TryParse to fail on {0}", num1)); + Assert.False(BigInteger.TryParse(num1.AsSpan(), NumberStyles.Any, nfi, out _), string.Format("Expected TryParse to fail on {0}", num1)); + + Assert.Throws(() => { BigInteger.Parse(utf8Num1, provider: nfi); }); + Assert.False(BigInteger.TryParse(utf8Num1, NumberStyles.Any, nfi, out _), string.Format("Expected TryParse to fail on {0}", num1)); } } private static void VerifySimpleFormatParse(string num1, NumberFormatInfo nfi, BigInteger expected, bool failureExpected = false) { - BigInteger test; - if (!failureExpected) { Assert.Equal(expected, BigInteger.Parse(num1, nfi)); - Assert.True(BigInteger.TryParse(num1, NumberStyles.Any, nfi, out test)); + Assert.True(BigInteger.TryParse(num1, NumberStyles.Any, nfi, out BigInteger test)); Assert.Equal(expected, test); } else { Assert.Throws(() => { BigInteger.Parse(num1, nfi); }); - Assert.False(BigInteger.TryParse(num1, NumberStyles.Any, nfi, out test), string.Format("Expected TryParse to fail on {0}", num1)); + Assert.False(BigInteger.TryParse(num1, NumberStyles.Any, nfi, out _), string.Format("Expected TryParse to fail on {0}", num1)); } if (num1 != null) @@ -808,16 +931,25 @@ private static void VerifySimpleFormatParse(string num1, NumberFormatInfo nfi, B static void VerifyFormatParseSpan(string num1, NumberStyles ns, NumberFormatInfo nfi, BigInteger expected, bool failureExpected) { + byte[] utf8Num1 = Encoding.UTF8.GetBytes(num1); + if (!failureExpected) { Assert.Equal(expected, BigInteger.Parse(num1.AsSpan(), ns, nfi)); Assert.True(BigInteger.TryParse(num1.AsSpan(), NumberStyles.Any, nfi, out BigInteger test)); Assert.Equal(expected, test); + + Assert.Equal(expected, BigInteger.Parse(utf8Num1, ns, nfi)); + Assert.True(BigInteger.TryParse(utf8Num1, NumberStyles.Any, nfi, out test)); + Assert.Equal(expected, test); } else { Assert.Throws(() => { BigInteger.Parse(num1.AsSpan(), ns, nfi); }); - Assert.False(BigInteger.TryParse(num1.AsSpan(), ns, nfi, out BigInteger test), string.Format("Expected TryParse to fail on {0}", num1)); + Assert.False(BigInteger.TryParse(num1.AsSpan(), ns, nfi, out _), string.Format("Expected TryParse to fail on {0}", num1)); + + Assert.Throws(() => { BigInteger.Parse(utf8Num1, ns, nfi); }); + Assert.False(BigInteger.TryParse(utf8Num1, ns, nfi, out _), string.Format("Expected TryParse to fail on {0}", num1)); } } diff --git a/src/libraries/System.Runtime.Numerics/tests/ComplexTests.cs b/src/libraries/System.Runtime.Numerics/tests/ComplexTests.cs index 953a81490813c7..5e1cf0fa21b0bf 100644 --- a/src/libraries/System.Runtime.Numerics/tests/ComplexTests.cs +++ b/src/libraries/System.Runtime.Numerics/tests/ComplexTests.cs @@ -149,6 +149,8 @@ public static IEnumerable Parse_Valid_TestData() public static void Parse(string valueScalar, NumberStyles style, IFormatProvider provider, double expectedScalar) { string value = $"<{valueScalar}; {valueScalar}>"; + byte[] utf8Value = Encoding.UTF8.GetBytes(value); + Complex expected = new Complex(expectedScalar, expectedScalar); bool isDefaultProvider = provider == null || provider == NumberFormatInfo.CurrentInfo; @@ -161,17 +163,26 @@ public static void Parse(string valueScalar, NumberStyles style, IFormatProvider Assert.True(Complex.TryParse(value, null, out result)); Assert.Equal(expected, result); + Assert.True(Complex.TryParse(utf8Value, null, out result)); + Assert.Equal(expected, result); + Assert.Equal(expected, Complex.Parse(value, null)); + Assert.Equal(expected, Complex.Parse(utf8Value, null)); } Assert.Equal(expected, Complex.Parse(value, provider)); + Assert.Equal(expected, Complex.Parse(utf8Value, provider)); } // Use Parse(string, NumberStyles, IFormatProvider) Assert.True(Complex.TryParse(value, style, provider, out result)); Assert.Equal(expected, result); + Assert.True(Complex.TryParse(utf8Value, style, provider, out result)); + Assert.Equal(expected, result); + Assert.Equal(expected, Complex.Parse(value, style, provider)); + Assert.Equal(expected, Complex.Parse(utf8Value, style, provider)); if (isDefaultProvider) { @@ -179,8 +190,14 @@ public static void Parse(string valueScalar, NumberStyles style, IFormatProvider Assert.True(Complex.TryParse(value, style, NumberFormatInfo.CurrentInfo, out result)); Assert.Equal(expected, result); + Assert.True(Complex.TryParse(utf8Value, style, NumberFormatInfo.CurrentInfo, out result)); + Assert.Equal(expected, result); + Assert.Equal(expected, Complex.Parse(value, style, null)); Assert.Equal(expected, Complex.Parse(value, style, NumberFormatInfo.CurrentInfo)); + + Assert.Equal(expected, Complex.Parse(utf8Value, style, null)); + Assert.Equal(expected, Complex.Parse(utf8Value, style, NumberFormatInfo.CurrentInfo)); } }