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 Complex #84779

Merged
merged 1 commit into from
Apr 17, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -240,7 +240,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<byte> destination, out int bytesWritten, bool isUnsigned = false, bool isBigEndian = false) { throw null; }
}
public readonly partial struct Complex : System.IEquatable<System.Numerics.Complex>, System.IFormattable, System.IParsable<System.Numerics.Complex>, System.ISpanFormattable, System.ISpanParsable<System.Numerics.Complex>, System.Numerics.IAdditionOperators<System.Numerics.Complex, System.Numerics.Complex, System.Numerics.Complex>, System.Numerics.IAdditiveIdentity<System.Numerics.Complex, System.Numerics.Complex>, System.Numerics.IDecrementOperators<System.Numerics.Complex>, System.Numerics.IDivisionOperators<System.Numerics.Complex, System.Numerics.Complex, System.Numerics.Complex>, System.Numerics.IEqualityOperators<System.Numerics.Complex, System.Numerics.Complex, bool>, System.Numerics.IIncrementOperators<System.Numerics.Complex>, System.Numerics.IMultiplicativeIdentity<System.Numerics.Complex, System.Numerics.Complex>, System.Numerics.IMultiplyOperators<System.Numerics.Complex, System.Numerics.Complex, System.Numerics.Complex>, System.Numerics.INumberBase<System.Numerics.Complex>, System.Numerics.ISignedNumber<System.Numerics.Complex>, System.Numerics.ISubtractionOperators<System.Numerics.Complex, System.Numerics.Complex, System.Numerics.Complex>, System.Numerics.IUnaryNegationOperators<System.Numerics.Complex, System.Numerics.Complex>, System.Numerics.IUnaryPlusOperators<System.Numerics.Complex, System.Numerics.Complex>
public readonly partial struct Complex : System.IEquatable<System.Numerics.Complex>, System.IFormattable, System.IParsable<System.Numerics.Complex>, System.ISpanFormattable, System.ISpanParsable<System.Numerics.Complex>, System.Numerics.IAdditionOperators<System.Numerics.Complex, System.Numerics.Complex, System.Numerics.Complex>, System.Numerics.IAdditiveIdentity<System.Numerics.Complex, System.Numerics.Complex>, System.Numerics.IDecrementOperators<System.Numerics.Complex>, System.Numerics.IDivisionOperators<System.Numerics.Complex, System.Numerics.Complex, System.Numerics.Complex>, System.Numerics.IEqualityOperators<System.Numerics.Complex, System.Numerics.Complex, bool>, System.Numerics.IIncrementOperators<System.Numerics.Complex>, System.Numerics.IMultiplicativeIdentity<System.Numerics.Complex, System.Numerics.Complex>, System.Numerics.IMultiplyOperators<System.Numerics.Complex, System.Numerics.Complex, System.Numerics.Complex>, System.Numerics.INumberBase<System.Numerics.Complex>, System.Numerics.ISignedNumber<System.Numerics.Complex>, System.Numerics.ISubtractionOperators<System.Numerics.Complex, System.Numerics.Complex, System.Numerics.Complex>, System.Numerics.IUnaryNegationOperators<System.Numerics.Complex, System.Numerics.Complex>, System.Numerics.IUnaryPlusOperators<System.Numerics.Complex, System.Numerics.Complex>, System.IUtf8SpanFormattable
{
private readonly int _dummyPrimitive;
public static readonly System.Numerics.Complex ImaginaryOne;
Expand Down Expand Up @@ -376,7 +376,8 @@ 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<char> destination, out int charsWritten, System.ReadOnlySpan<char> format, System.IFormatProvider? provider) { throw null; }
public bool TryFormat(System.Span<char> destination, out int charsWritten, [System.Diagnostics.CodeAnalysis.StringSyntaxAttribute("NumericFormat")] System.ReadOnlySpan<char> format = default, System.IFormatProvider? provider = null) { throw null; }
public bool TryFormat(System.Span<byte> utf8Destination, out int bytesWritten, [System.Diagnostics.CodeAnalysis.StringSyntaxAttribute("NumericFormat")] System.ReadOnlySpan<char> format = default, System.IFormatProvider? provider = null) { throw null; }
public static bool TryParse(System.ReadOnlySpan<char> s, System.Globalization.NumberStyles style, System.IFormatProvider? provider, out System.Numerics.Complex result) { throw null; }
public static bool TryParse(System.ReadOnlySpan<char> 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; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ public readonly struct Complex
: IEquatable<Complex>,
IFormattable,
INumberBase<Complex>,
ISignedNumber<Complex>
ISignedNumber<Complex>,
IUtf8SpanFormattable
{
private const NumberStyles DefaultNumberStyle = NumberStyles.Float | NumberStyles.AllowThousands;

Expand Down Expand Up @@ -393,14 +394,23 @@ public bool Equals(Complex value)

public override int GetHashCode() => HashCode.Combine(m_real, m_imaginary);

public override string ToString() => $"<{m_real}; {m_imaginary}>";
public override string ToString() => ToString(null, null);

public string ToString([StringSyntax(StringSyntaxAttribute.NumericFormat)] string? format) => ToString(format, null);

public string ToString(IFormatProvider? provider) => ToString(null, provider);

public string ToString([StringSyntax(StringSyntaxAttribute.NumericFormat)] string? format, IFormatProvider? provider) =>
$"<{m_real.ToString(format, provider)}; {m_imaginary.ToString(format, provider)}>";
public string ToString([StringSyntax(StringSyntaxAttribute.NumericFormat)] string? format, IFormatProvider? provider)
{
// $"<{m_real.ToString(format, provider)}; {m_imaginary.ToString(format, provider)}>";
var handler = new DefaultInterpolatedStringHandler(4, 2, provider, stackalloc char[512]);
handler.AppendLiteral("<");
stephentoub marked this conversation as resolved.
Show resolved Hide resolved
handler.AppendFormatted(m_real, format);
handler.AppendLiteral("; ");
handler.AppendFormatted(m_imaginary, format);
handler.AppendLiteral(">");
return handler.ToStringAndClear();
}

public static Complex Sin(Complex value)
{
Expand Down Expand Up @@ -2199,46 +2209,52 @@ public static bool TryParse([NotNullWhen(true)] string? s, NumberStyles style, I
//

/// <inheritdoc cref="ISpanFormattable.TryFormat(Span{char}, out int, ReadOnlySpan{char}, IFormatProvider?)" />
public bool TryFormat(Span<char> destination, out int charsWritten, ReadOnlySpan<char> format, IFormatProvider? provider)
{
int charsWrittenSoFar = 0;

// We have at least 6 more characters for: <0; 0>
if (destination.Length < 6)
{
charsWritten = charsWrittenSoFar;
return false;
}
public bool TryFormat(Span<char> destination, out int charsWritten, [StringSyntax(StringSyntaxAttribute.NumericFormat)] ReadOnlySpan<char> format = default, IFormatProvider? provider = null) =>
TryFormatCore(destination, out charsWritten, format, provider);

destination[charsWrittenSoFar++] = '<';
public bool TryFormat(Span<byte> utf8Destination, out int bytesWritten, [StringSyntax(StringSyntaxAttribute.NumericFormat)] ReadOnlySpan<char> format = default, IFormatProvider? provider = null) =>
TryFormatCore(utf8Destination, out bytesWritten, format, provider);

bool tryFormatSucceeded = m_real.TryFormat(destination.Slice(charsWrittenSoFar), out int tryFormatCharsWritten, format, provider);
charsWrittenSoFar += tryFormatCharsWritten;
private bool TryFormatCore<TChar>(Span<TChar> destination, out int charsWritten, ReadOnlySpan<char> format, IFormatProvider? provider) where TChar : unmanaged, IBinaryInteger<TChar>
{
Debug.Assert(typeof(TChar) == typeof(char) || typeof(TChar) == typeof(byte));

// We have at least 4 more characters for: ; 0>
if (!tryFormatSucceeded || (destination.Length < (charsWrittenSoFar + 4)))
// We have at least 6 more characters for: <0; 0>
if (destination.Length >= 6)
{
charsWritten = charsWrittenSoFar;
return false;
}

destination[charsWrittenSoFar++] = ';';
destination[charsWrittenSoFar++] = ' ';

tryFormatSucceeded = m_imaginary.TryFormat(destination.Slice(charsWrittenSoFar), out tryFormatCharsWritten, format, provider);
charsWrittenSoFar += tryFormatCharsWritten;
int realChars;
if (typeof(TChar) == typeof(char) ?
m_real.TryFormat(MemoryMarshal.Cast<TChar, char>(destination.Slice(1)), out realChars, format, provider) :
m_real.TryFormat(MemoryMarshal.Cast<TChar, byte>(destination.Slice(1)), out realChars, format, provider))
{
destination[0] = TChar.CreateTruncating('<');
destination = destination.Slice(1 + realChars); // + 1 for <

// We have at least 1 more character for: >
if (!tryFormatSucceeded || (destination.Length < (charsWrittenSoFar + 1)))
{
charsWritten = charsWrittenSoFar;
return false;
// We have at least 4 more characters for: ; 0>
if (destination.Length >= 4)
{
int imaginaryChars;
if (typeof(TChar) == typeof(char) ?
m_imaginary.TryFormat(MemoryMarshal.Cast<TChar, char>(destination.Slice(2)), out imaginaryChars, format, provider) :
m_imaginary.TryFormat(MemoryMarshal.Cast<TChar, byte>(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('>');

charsWritten = realChars + imaginaryChars + 4;
return true;
}
}
}
}
}

destination[charsWrittenSoFar++] = '>';

charsWritten = charsWrittenSoFar;
return true;
charsWritten = 0;
return false;
}

//
Expand Down
83 changes: 69 additions & 14 deletions src/libraries/System.Runtime.Numerics/tests/ComplexTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@

using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Runtime.CompilerServices;

using System.Text;
using Xunit;

namespace System.Numerics.Tests
Expand Down Expand Up @@ -1730,25 +1731,79 @@ public static void Tanh_Advanced(double real, double imaginary, double expectedR
public static void ToStringTest(double real, double imaginary)
{
var complex = new Complex(real, imaginary);
NumberFormatInfo numberFormatInfo = CultureInfo.CurrentCulture.NumberFormat;

string expected = "<" + real.ToString() + "; " + imaginary.ToString() + ">";
string actual = complex.ToString();
Assert.Equal(expected, actual);
Assert.Equal($"<{real}; {imaginary}>", complex.ToString());

NumberFormatInfo numberFormatInfo = CultureInfo.CurrentCulture.NumberFormat;
expected = "<" + real.ToString(numberFormatInfo) + "; " + imaginary.ToString(numberFormatInfo) + ">";
actual = complex.ToString(numberFormatInfo);
Assert.Equal(expected, complex.ToString(numberFormatInfo));
Assert.Equal($"<{real.ToString(numberFormatInfo)}; {imaginary.ToString(numberFormatInfo)}>", complex.ToString(numberFormatInfo));
Assert.Equal($"<{real.ToString((string)null)}; {imaginary.ToString((string)null)}>", complex.ToString((string)null));
Assert.Equal($"<{real.ToString((string)null, numberFormatInfo)}; {imaginary.ToString((string)null, numberFormatInfo)}>", complex.ToString((string)null, numberFormatInfo));

foreach (string format in s_supportedStandardNumericFormats)
{
expected = "<" + real.ToString(format) + "; " + imaginary.ToString(format) + ">";
actual = complex.ToString(format);
Assert.Equal(expected, actual);
Assert.Equal($"<{real.ToString(format)}; {imaginary.ToString(format)}>", complex.ToString(format));
Assert.Equal($"<{real.ToString(format, numberFormatInfo)}; {imaginary.ToString(format, numberFormatInfo)}>", complex.ToString(format, numberFormatInfo));
}
}

[Theory]
[MemberData(nameof(Boundaries_2_TestData))]
[MemberData(nameof(Primitives_2_TestData))]
[MemberData(nameof(Random_2_TestData))]
[MemberData(nameof(SmallRandom_2_TestData))]
[MemberData(nameof(Invalid_2_TestData))]
public static void TryFormatTest(double real, double imaginary)
{
var complex = new Complex(real, imaginary);

// UTF16
{
foreach (NumberFormatInfo numberFormatInfo in new[] { CultureInfo.CurrentCulture.NumberFormat, null })
{
foreach (string format in s_supportedStandardNumericFormats.Append(null))
{
string expected = $"<{real.ToString(format, numberFormatInfo)}; {imaginary.ToString(format, numberFormatInfo)}>";
int charsWritten;

// Just right or larger than required storage
for (int additional = 0; additional < 2; additional++)
{
char[] chars = new char[expected.Length + additional];
Assert.True(complex.TryFormat(chars, out charsWritten, format, numberFormatInfo));
Assert.Equal(expected.Length, charsWritten);
Assert.Equal(expected, new string(expected.AsSpan(0, expected.Length)));
}

// Too small storage
Assert.False(complex.TryFormat(new char[expected.Length - 1], out charsWritten, format, numberFormatInfo));
Assert.Equal(0, charsWritten);
}
}
}

expected = "<" + real.ToString(format, numberFormatInfo) + "; " + imaginary.ToString(format, numberFormatInfo) + ">";
actual = complex.ToString(format, numberFormatInfo);
Assert.Equal(expected, actual);
// UTF8
{
foreach (NumberFormatInfo numberFormatInfo in new[] { CultureInfo.CurrentCulture.NumberFormat, null })
{
foreach (string format in s_supportedStandardNumericFormats.Append(null))
{
byte[] expected = Encoding.UTF8.GetBytes($"<{real.ToString(format, numberFormatInfo)}; {imaginary.ToString(format, numberFormatInfo)}>");
int bytesWritten;

// Just right or larger than required storage
for (int additional = 0; additional < 2; additional++)
{
byte[] bytes = new byte[expected.Length + additional];
Assert.True(complex.TryFormat(bytes, out bytesWritten, format, numberFormatInfo));
Assert.Equal(expected.Length, bytesWritten);
Assert.Equal(expected, bytes.AsSpan(0, expected.Length).ToArray());
}

// Too small storage
Assert.False(complex.TryFormat(new byte[expected.Length - 1], out bytesWritten, format, numberFormatInfo));
Assert.Equal(0, bytesWritten);
}
}
}
}

Expand Down