Skip to content

Commit

Permalink
Implement IUtf8SpanFormattable on Complex (dotnet#84779)
Browse files Browse the repository at this point in the history
  • Loading branch information
stephentoub authored Apr 17, 2023
1 parent e3f55cb commit d414646
Show file tree
Hide file tree
Showing 3 changed files with 125 additions and 53 deletions.
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("<");
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

0 comments on commit d414646

Please sign in to comment.