Skip to content

Commit

Permalink
Iterative generic math changes to match API review (#69391)
Browse files Browse the repository at this point in the history
* Change GetShortestBitLength to return int and add WriteBigEndian APIs

* Add WriteExponentBigEndian and WriteSignificandBigEndian APIs

* Update System.Char to explicitly implement the numeric interfaces

* Ensure BigInteger.TryWriteBigEndian correctly offsets the address

* Fixing two usages of `var` in decimal

* Ensure lo64 is ulong and hi32 is uint

* Ensure Int128/UInt128 implement TryWriteBigEndian and return `int` for `GetShortestBitLength`

* Ensure that `GetByteCount` and `GetShortestBitLength` correctly handle the one's complement format

* Ensure the ReverseEndianness calls in TryWrite* for Int128/UInt128 are correct

* Ensure BigInteger.TryWriteLittleEndian has a correct assert
  • Loading branch information
tannergooding committed May 19, 2022
1 parent 7bb207c commit 0c6d412
Show file tree
Hide file tree
Showing 45 changed files with 2,510 additions and 273 deletions.
18 changes: 12 additions & 6 deletions src/libraries/Common/tests/System/GenericMathHelpers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,6 @@ public static class BinaryIntegerHelper<TSelf>
{
public static (TSelf Quotient, TSelf Remainder) DivRem(TSelf left, TSelf right) => TSelf.DivRem(left, right);

public static int GetByteCount(TSelf value) => value.GetByteCount();

public static long GetShortestBitLength(TSelf value) => value.GetShortestBitLength();

public static TSelf LeadingZeroCount(TSelf value) => TSelf.LeadingZeroCount(value);

public static TSelf PopCount(TSelf value) => TSelf.PopCount(value);
Expand All @@ -39,6 +35,12 @@ public static class BinaryIntegerHelper<TSelf>

public static TSelf TrailingZeroCount(TSelf value) => TSelf.TrailingZeroCount(value);

public static int GetByteCount(TSelf value) => value.GetByteCount();

public static int GetShortestBitLength(TSelf value) => value.GetShortestBitLength();

public static bool TryWriteBigEndian(TSelf value, Span<byte> destination, out int bytesWritten) => value.TryWriteBigEndian(destination, out bytesWritten);

public static bool TryWriteLittleEndian(TSelf value, Span<byte> destination, out int bytesWritten) => value.TryWriteLittleEndian(destination, out bytesWritten);
}

Expand Down Expand Up @@ -103,14 +105,18 @@ public static class FloatingPointHelper<TSelf>
{
public static int GetExponentByteCount(TSelf value) => value.GetExponentByteCount();

public static long GetExponentShortestBitLength(TSelf value) => value.GetExponentShortestBitLength();
public static int GetExponentShortestBitLength(TSelf value) => value.GetExponentShortestBitLength();

public static int GetSignificandByteCount(TSelf value) => value.GetSignificandByteCount();

public static long GetSignificandBitLength(TSelf value) => value.GetSignificandBitLength();
public static int GetSignificandBitLength(TSelf value) => value.GetSignificandBitLength();

public static bool TryWriteExponentBigEndian(TSelf value, Span<byte> destination, out int bytesWritten) => value.TryWriteExponentBigEndian(destination, out bytesWritten);

public static bool TryWriteExponentLittleEndian(TSelf value, Span<byte> destination, out int bytesWritten) => value.TryWriteExponentLittleEndian(destination, out bytesWritten);

public static bool TryWriteSignificandBigEndian(TSelf value, Span<byte> destination, out int bytesWritten) => value.TryWriteSignificandBigEndian(destination, out bytesWritten);

public static bool TryWriteSignificandLittleEndian(TSelf value, Span<byte> destination, out int bytesWritten) => value.TryWriteSignificandLittleEndian(destination, out bytesWritten);
}

Expand Down
20 changes: 19 additions & 1 deletion src/libraries/System.Private.CoreLib/src/System/Byte.cs
Original file line number Diff line number Diff line change
Expand Up @@ -333,11 +333,29 @@ object IConvertible.ToType(Type type, IFormatProvider? provider)
public static byte TrailingZeroCount(byte value) => (byte)(BitOperations.TrailingZeroCount(value << 24) - 24);

/// <inheritdoc cref="IBinaryInteger{TSelf}.GetShortestBitLength()" />
long IBinaryInteger<byte>.GetShortestBitLength() => (sizeof(byte) * 8) - LeadingZeroCount(m_value);
int IBinaryInteger<byte>.GetShortestBitLength() => (sizeof(byte) * 8) - LeadingZeroCount(m_value);

/// <inheritdoc cref="IBinaryInteger{TSelf}.GetByteCount()" />
int IBinaryInteger<byte>.GetByteCount() => sizeof(byte);

/// <inheritdoc cref="IBinaryInteger{TSelf}.TryWriteBigEndian(Span{byte}, out int)" />
bool IBinaryInteger<byte>.TryWriteBigEndian(Span<byte> destination, out int bytesWritten)
{
if (destination.Length >= sizeof(byte))
{
byte value = m_value;
Unsafe.WriteUnaligned(ref MemoryMarshal.GetReference(destination), value);

bytesWritten = sizeof(byte);
return true;
}
else
{
bytesWritten = 0;
return false;
}
}

/// <inheritdoc cref="IBinaryInteger{TSelf}.TryWriteLittleEndian(Span{byte}, out int)" />
bool IBinaryInteger<byte>.TryWriteLittleEndian(Span<byte> destination, out int bytesWritten)
{
Expand Down
60 changes: 37 additions & 23 deletions src/libraries/System.Private.CoreLib/src/System/Char.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1166,29 +1166,47 @@ public static int ConvertToUtf32(string s, int index)
//

/// <inheritdoc cref="IBinaryInteger{TSelf}.DivRem(TSelf, TSelf)" />
public static (char Quotient, char Remainder) DivRem(char left, char right) => ((char, char))Math.DivRem(left, right);
static (char Quotient, char Remainder) IBinaryInteger<char>.DivRem(char left, char right) => ((char, char))Math.DivRem(left, right);

/// <inheritdoc cref="IBinaryInteger{TSelf}.LeadingZeroCount(TSelf)" />
public static char LeadingZeroCount(char value) => (char)(BitOperations.LeadingZeroCount(value) - 16);
static char IBinaryInteger<char>.LeadingZeroCount(char value) => (char)(BitOperations.LeadingZeroCount(value) - 16);

/// <inheritdoc cref="IBinaryInteger{TSelf}.PopCount(TSelf)" />
public static char PopCount(char value) => (char)BitOperations.PopCount(value);
static char IBinaryInteger<char>.PopCount(char value) => (char)BitOperations.PopCount(value);

/// <inheritdoc cref="IBinaryInteger{TSelf}.RotateLeft(TSelf, int)" />
public static char RotateLeft(char value, int rotateAmount) => (char)((value << (rotateAmount & 15)) | (value >> ((16 - rotateAmount) & 15)));
static char IBinaryInteger<char>.RotateLeft(char value, int rotateAmount) => (char)((value << (rotateAmount & 15)) | (value >> ((16 - rotateAmount) & 15)));

/// <inheritdoc cref="IBinaryInteger{TSelf}.RotateRight(TSelf, int)" />
public static char RotateRight(char value, int rotateAmount) => (char)((value >> (rotateAmount & 15)) | (value << ((16 - rotateAmount) & 15)));
static char IBinaryInteger<char>.RotateRight(char value, int rotateAmount) => (char)((value >> (rotateAmount & 15)) | (value << ((16 - rotateAmount) & 15)));

/// <inheritdoc cref="IBinaryInteger{TSelf}.TrailingZeroCount(TSelf)" />
public static char TrailingZeroCount(char value) => (char)(BitOperations.TrailingZeroCount(value << 16) - 16);
static char IBinaryInteger<char>.TrailingZeroCount(char value) => (char)(BitOperations.TrailingZeroCount(value << 16) - 16);

/// <inheritdoc cref="IBinaryInteger{TSelf}.GetShortestBitLength()" />
long IBinaryInteger<char>.GetShortestBitLength() => (sizeof(char) * 8) - LeadingZeroCount(m_value);
int IBinaryInteger<char>.GetShortestBitLength() => (sizeof(char) * 8) - ushort.LeadingZeroCount(m_value);

/// <inheritdoc cref="IBinaryInteger{TSelf}.GetByteCount()" />
int IBinaryInteger<char>.GetByteCount() => sizeof(char);

/// <inheritdoc cref="IBinaryInteger{TSelf}.TryWriteBigEndian(Span{byte}, out int)" />
bool IBinaryInteger<char>.TryWriteBigEndian(Span<byte> destination, out int bytesWritten)
{
if (destination.Length >= sizeof(char))
{
ushort value = BitConverter.IsLittleEndian ? BinaryPrimitives.ReverseEndianness(m_value) : m_value;
Unsafe.WriteUnaligned(ref MemoryMarshal.GetReference(destination), value);

bytesWritten = sizeof(char);
return true;
}
else
{
bytesWritten = 0;
return false;
}
}

/// <inheritdoc cref="IBinaryInteger{TSelf}.TryWriteLittleEndian(Span{byte}, out int)" />
bool IBinaryInteger<char>.TryWriteLittleEndian(Span<byte> destination, out int bytesWritten)
{
Expand All @@ -1212,10 +1230,10 @@ bool IBinaryInteger<char>.TryWriteLittleEndian(Span<byte> destination, out int b
//

/// <inheritdoc cref="IBinaryNumber{TSelf}.IsPow2(TSelf)" />
public static bool IsPow2(char value) => BitOperations.IsPow2((uint)value);
static bool IBinaryNumber<char>.IsPow2(char value) => ushort.IsPow2(value);

/// <inheritdoc cref="IBinaryNumber{TSelf}.Log2(TSelf)" />
public static char Log2(char value) => (char)BitOperations.Log2(value);
static char IBinaryNumber<char>.Log2(char value) => (char)(ushort.Log2(value));

//
// IBitwiseOperators
Expand Down Expand Up @@ -1331,15 +1349,14 @@ bool IBinaryInteger<char>.TryWriteLittleEndian(Span<byte> destination, out int b
static char INumber<char>.Abs(char value) => value;

/// <inheritdoc cref="INumber{TSelf}.Clamp(TSelf, TSelf, TSelf)" />
public static char Clamp(char value, char min, char max) => (char)Math.Clamp(value, min, max);
static char INumber<char>.Clamp(char value, char min, char max) => (char)Math.Clamp(value, min, max);

/// <inheritdoc cref="INumber{TSelf}.CopySign(TSelf, TSelf)" />
static char INumber<char>.CopySign(char value, char sign) => value;

/// <inheritdoc cref="INumber{TSelf}.CreateChecked{TOther}(TOther)" />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static char CreateChecked<TOther>(TOther value)
where TOther : INumber<TOther>
static char INumber<char>.CreateChecked<TOther>(TOther value)
{
if (typeof(TOther) == typeof(byte))
{
Expand Down Expand Up @@ -1406,8 +1423,7 @@ public static char CreateChecked<TOther>(TOther value)

/// <inheritdoc cref="INumber{TSelf}.CreateSaturating{TOther}(TOther)" />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static char CreateSaturating<TOther>(TOther value)
where TOther : INumber<TOther>
static char INumber<char>.CreateSaturating<TOther>(TOther value)
{
if (typeof(TOther) == typeof(byte))
{
Expand Down Expand Up @@ -1491,8 +1507,7 @@ public static char CreateSaturating<TOther>(TOther value)

/// <inheritdoc cref="INumber{TSelf}.CreateTruncating{TOther}(TOther)" />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static char CreateTruncating<TOther>(TOther value)
where TOther : INumber<TOther>
static char INumber<char>.CreateTruncating<TOther>(TOther value)
{
if (typeof(TOther) == typeof(byte))
{
Expand Down Expand Up @@ -1561,24 +1576,23 @@ public static char CreateTruncating<TOther>(TOther value)
static bool INumber<char>.IsNegative(char value) => false;

/// <inheritdoc cref="INumber{TSelf}.Max(TSelf, TSelf)" />
public static char Max(char x, char y) => (char)Math.Max(x, y);
static char INumber<char>.Max(char x, char y) => (char)Math.Max(x, y);

/// <inheritdoc cref="INumber{TSelf}.MaxMagnitude(TSelf, TSelf)" />
static char INumber<char>.MaxMagnitude(char x, char y) => Max(x, y);
static char INumber<char>.MaxMagnitude(char x, char y) => (char)Math.Max(x, y);

/// <inheritdoc cref="INumber{TSelf}.Min(TSelf, TSelf)" />
public static char Min(char x, char y) => (char)Math.Min(x, y);
static char INumber<char>.Min(char x, char y) => (char)Math.Min(x, y);

/// <inheritdoc cref="INumber{TSelf}.MinMagnitude(TSelf, TSelf)" />
static char INumber<char>.MinMagnitude(char x, char y) => Min(x, y);
static char INumber<char>.MinMagnitude(char x, char y) => (char)Math.Min(x, y);

/// <inheritdoc cref="INumber{TSelf}.Sign(TSelf)" />
public static int Sign(char value) => (value == 0) ? 0 : 1;
static int INumber<char>.Sign(char value) => (value == 0) ? 0 : 1;

/// <inheritdoc cref="INumber{TSelf}.TryCreate{TOther}(TOther, out TSelf)" />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool TryCreate<TOther>(TOther value, out char result)
where TOther : INumber<TOther>
static bool INumber<char>.TryCreate<TOther>(TOther value, out char result)
{
if (typeof(TOther) == typeof(byte))
{
Expand Down
65 changes: 56 additions & 9 deletions src/libraries/System.Private.CoreLib/src/System/Decimal.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1150,15 +1150,39 @@ object IConvertible.ToType(Type type, IFormatProvider? provider)
// IFloatingPoint
//

/// <inheritdoc cref="IFloatingPoint{TSelf}.GetExponentByteCount()" />
int IFloatingPoint<decimal>.GetExponentByteCount() => sizeof(sbyte);

/// <inheritdoc cref="IFloatingPoint{TSelf}.GetExponentShortestBitLength()" />
long IFloatingPoint<decimal>.GetExponentShortestBitLength()
int IFloatingPoint<decimal>.GetExponentShortestBitLength()
{
sbyte exponent = Exponent;
return (sizeof(sbyte) * 8) - sbyte.LeadingZeroCount(exponent);
}

/// <inheritdoc cref="IFloatingPoint{TSelf}.GetExponentByteCount()" />
int IFloatingPoint<decimal>.GetExponentByteCount() => sizeof(sbyte);
/// <inheritdoc cref="IFloatingPoint{TSelf}.GetSignificandByteCount()" />
int IFloatingPoint<decimal>.GetSignificandByteCount() => sizeof(ulong) + sizeof(uint);

/// <inheritdoc cref="IFloatingPoint{TSelf}.GetSignificandBitLength()" />
int IFloatingPoint<decimal>.GetSignificandBitLength() => 96;

/// <inheritdoc cref="IFloatingPoint{TSelf}.TryWriteExponentBigEndian(Span{byte}, out int)" />
bool IFloatingPoint<decimal>.TryWriteExponentBigEndian(Span<byte> destination, out int bytesWritten)
{
if (destination.Length >= sizeof(sbyte))
{
sbyte exponent = Exponent;
Unsafe.WriteUnaligned(ref MemoryMarshal.GetReference(destination), exponent);

bytesWritten = sizeof(sbyte);
return true;
}
else
{
bytesWritten = 0;
return false;
}
}

/// <inheritdoc cref="IFloatingPoint{TSelf}.TryWriteExponentLittleEndian(Span{byte}, out int)" />
bool IFloatingPoint<decimal>.TryWriteExponentLittleEndian(Span<byte> destination, out int bytesWritten)
Expand All @@ -1178,19 +1202,42 @@ bool IFloatingPoint<decimal>.TryWriteExponentLittleEndian(Span<byte> destination
}
}

/// <inheritdoc cref="IFloatingPoint{TSelf}.GetSignificandBitLength()" />
long IFloatingPoint<decimal>.GetSignificandBitLength() => 96;
/// <inheritdoc cref="IFloatingPoint{TSelf}.TryWriteSignificandBigEndian(Span{byte}, out int)" />
bool IFloatingPoint<decimal>.TryWriteSignificandBigEndian(Span<byte> destination, out int bytesWritten)
{
if (destination.Length >= (sizeof(uint) + sizeof(ulong)))
{
uint hi32 = _hi32;
ulong lo64 = _lo64;

/// <inheritdoc cref="IFloatingPoint{TSelf}.GetSignificandByteCount()" />
int IFloatingPoint<decimal>.GetSignificandByteCount() => sizeof(ulong) + sizeof(uint);
if (BitConverter.IsLittleEndian)
{
hi32 = BinaryPrimitives.ReverseEndianness(hi32);
lo64 = BinaryPrimitives.ReverseEndianness(lo64);
}

ref byte address = ref MemoryMarshal.GetReference(destination);

Unsafe.WriteUnaligned(ref address, hi32);
Unsafe.WriteUnaligned(ref Unsafe.AddByteOffset(ref address, sizeof(uint)), lo64);

bytesWritten = sizeof(uint) + sizeof(ulong);
return true;
}
else
{
bytesWritten = 0;
return false;
}
}

/// <inheritdoc cref="IFloatingPoint{TSelf}.TryWriteSignificandLittleEndian(Span{byte}, out int)" />
bool IFloatingPoint<decimal>.TryWriteSignificandLittleEndian(Span<byte> destination, out int bytesWritten)
{
if (destination.Length >= (sizeof(ulong) + sizeof(uint)))
{
var lo64 = _lo64;
var hi32 = _hi32;
ulong lo64 = _lo64;
uint hi32 = _hi32;

if (!BitConverter.IsLittleEndian)
{
Expand Down
62 changes: 55 additions & 7 deletions src/libraries/System.Private.CoreLib/src/System/Double.cs
Original file line number Diff line number Diff line change
Expand Up @@ -661,8 +661,11 @@ public static bool IsPow2(double value)
/// <inheritdoc cref="IFloatingPoint{TSelf}.Truncate(TSelf)" />
public static double Truncate(double x) => Math.Truncate(x);

/// <inheritdoc cref="IFloatingPoint{TSelf}.GetExponentByteCount()" />
int IFloatingPoint<double>.GetExponentByteCount() => sizeof(short);

/// <inheritdoc cref="IFloatingPoint{TSelf}.GetExponentShortestBitLength()" />
long IFloatingPoint<double>.GetExponentShortestBitLength()
int IFloatingPoint<double>.GetExponentShortestBitLength()
{
short exponent = Exponent;

Expand All @@ -676,8 +679,35 @@ long IFloatingPoint<double>.GetExponentShortestBitLength()
}
}

/// <inheritdoc cref="IFloatingPoint{TSelf}.GetExponentByteCount()" />
int IFloatingPoint<double>.GetExponentByteCount() => sizeof(short);
/// <inheritdoc cref="IFloatingPoint{TSelf}.GetSignificandByteCount()" />
int IFloatingPoint<double>.GetSignificandByteCount() => sizeof(ulong);

/// <inheritdoc cref="IFloatingPoint{TSelf}.GetSignificandBitLength()" />
int IFloatingPoint<double>.GetSignificandBitLength() => 53;

/// <inheritdoc cref="IFloatingPoint{TSelf}.TryWriteExponentBigEndian(Span{byte}, out int)" />
bool IFloatingPoint<double>.TryWriteExponentBigEndian(Span<byte> destination, out int bytesWritten)
{
if (destination.Length >= sizeof(short))
{
short exponent = Exponent;

if (BitConverter.IsLittleEndian)
{
exponent = BinaryPrimitives.ReverseEndianness(exponent);
}

Unsafe.WriteUnaligned(ref MemoryMarshal.GetReference(destination), exponent);

bytesWritten = sizeof(short);
return true;
}
else
{
bytesWritten = 0;
return false;
}
}

/// <inheritdoc cref="IFloatingPoint{TSelf}.TryWriteExponentLittleEndian(Span{byte}, out int)" />
bool IFloatingPoint<double>.TryWriteExponentLittleEndian(Span<byte> destination, out int bytesWritten)
Expand All @@ -703,11 +733,29 @@ bool IFloatingPoint<double>.TryWriteExponentLittleEndian(Span<byte> destination,
}
}

/// <inheritdoc cref="IFloatingPoint{TSelf}.GetSignificandBitLength()" />
long IFloatingPoint<double>.GetSignificandBitLength() => 53;
/// <inheritdoc cref="IFloatingPoint{TSelf}.TryWriteSignificandBigEndian(Span{byte}, out int)" />
bool IFloatingPoint<double>.TryWriteSignificandBigEndian(Span<byte> destination, out int bytesWritten)
{
if (destination.Length >= sizeof(ulong))
{
ulong significand = Significand;

/// <inheritdoc cref="IFloatingPoint{TSelf}.GetSignificandByteCount()" />
int IFloatingPoint<double>.GetSignificandByteCount() => sizeof(ulong);
if (BitConverter.IsLittleEndian)
{
significand = BinaryPrimitives.ReverseEndianness(significand);
}

Unsafe.WriteUnaligned(ref MemoryMarshal.GetReference(destination), significand);

bytesWritten = sizeof(ulong);
return true;
}
else
{
bytesWritten = 0;
return false;
}
}

/// <inheritdoc cref="IFloatingPoint{TSelf}.TryWriteSignificandLittleEndian(Span{byte}, out int)" />
bool IFloatingPoint<double>.TryWriteSignificandLittleEndian(Span<byte> destination, out int bytesWritten)
Expand Down
Loading

0 comments on commit 0c6d412

Please sign in to comment.