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

Number creation without sign propagation #108037

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
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
3 changes: 3 additions & 0 deletions src/libraries/Common/tests/System/GenericMathHelpers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -344,6 +344,9 @@ public static TSelf CreateSaturating<TOther>(TOther value)
public static TSelf CreateTruncating<TOther>(TOther value)
where TOther : INumberBase<TOther> => TSelf.CreateTruncating(value);

public static TSelf CreateTruncating<TOther>(TOther value, bool signPropagation)
where TOther : INumberBase<TOther> => TSelf.CreateTruncating(value, signPropagation);

public static bool IsCanonical(TSelf value) => TSelf.IsCanonical(value);

public static bool IsComplexNumber(TSelf value) => TSelf.IsComplexNumber(value);
Expand Down
86 changes: 85 additions & 1 deletion src/libraries/System.Private.CoreLib/src/System/Int32.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1145,6 +1145,84 @@ private static bool TryConvertFromTruncating<TOther>(TOther value, out int resul
}
}

/// <inheritdoc cref="INumberBase{TSelf}.TryConvertFromTruncating{TOther}(TOther, bool, out TSelf)" />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
static bool INumberBase<int>.TryConvertFromTruncating<TOther>(TOther value, bool signPropagation, out int result) => TryConvertFromTruncating(value, signPropagation, out result);

[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static bool TryConvertFromTruncating<TOther>(TOther value, bool signPropagation, out int result)
where TOther : INumberBase<TOther>
{
// In order to reduce overall code duplication and improve the inlinabilty of these
// methods for the corelib types we have `ConvertFrom` handle the same sign and
// `ConvertTo` handle the opposite sign. However, since there is an uneven split
// between signed and unsigned types, the one that handles unsigned will also
// handle `Decimal`.
//
// That is, `ConvertFrom` for `int` will handle the other signed types and
// `ConvertTo` will handle the unsigned types

if (signPropagation)
return TryConvertFromTruncating(value, out result);

if (typeof(TOther) == typeof(double))
{
double actualValue = (double)(object)value;
result = (actualValue >= MaxValue) ? MaxValue :
(actualValue <= MinValue) ? MinValue : (int)actualValue;
return true;
}
else if (typeof(TOther) == typeof(Half))
{
Half actualValue = (Half)(object)value;
result = (actualValue == Half.PositiveInfinity) ? MaxValue :
(actualValue == Half.NegativeInfinity) ? MinValue : (int)actualValue;
return true;
}
else if (typeof(TOther) == typeof(short))
{
ushort actualValue = (ushort)(short)(object)value;
result = actualValue;
return true;
}
else if (typeof(TOther) == typeof(long))
{
ulong actualValue = (ulong)(long)(object)value;
result = (int)actualValue;
return true;
}
else if (typeof(TOther) == typeof(Int128))
{
UInt128 actualValue = (UInt128)(Int128)(object)value;
result = (int)actualValue;
return true;
}
else if (typeof(TOther) == typeof(nint))
{
nuint actualValue = (nuint)(nint)(object)value;
result = (int)actualValue;
return true;
}
else if (typeof(TOther) == typeof(sbyte))
{
byte actualValue = (byte)(sbyte)(object)value;
result = actualValue;
return true;
}
else if (typeof(TOther) == typeof(float))
{
float actualValue = (float)(object)value;
result = (actualValue >= MaxValue) ? MaxValue :
(actualValue <= MinValue) ? MinValue : (int)actualValue;
return true;
}
else
{
result = default;
return false;
}
}

/// <inheritdoc cref="INumberBase{TSelf}.TryConvertToChecked{TOther}(TSelf, out TOther)" />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
static bool INumberBase<int>.TryConvertToChecked<TOther>(int value, [MaybeNullWhen(false)] out TOther result)
Expand Down Expand Up @@ -1286,7 +1364,9 @@ static bool INumberBase<int>.TryConvertToSaturating<TOther>(int value, [MaybeNul

/// <inheritdoc cref="INumberBase{TSelf}.TryConvertToTruncating{TOther}(TSelf, out TOther)" />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
static bool INumberBase<int>.TryConvertToTruncating<TOther>(int value, [MaybeNullWhen(false)] out TOther result)
static bool INumberBase<int>.TryConvertToTruncating<TOther>(int value, [MaybeNullWhen(false)] out TOther result) => TryConvertToTruncating(value, out result);

private static bool TryConvertToTruncating<TOther>(int value, [MaybeNullWhen(false)] out TOther result)
{
// In order to reduce overall code duplication and improve the inlinabilty of these
// methods for the corelib types we have `ConvertFrom` handle the same sign and
Expand Down Expand Up @@ -1352,6 +1432,10 @@ static bool INumberBase<int>.TryConvertToTruncating<TOther>(int value, [MaybeNul
}
}

/// <inheritdoc cref="INumberBase{TSelf}.TryConvertToTruncating{TOther}(TSelf, bool, out TOther)" />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
static bool INumberBase<int>.TryConvertToTruncating<TOther>(int value, bool signPropagation, [MaybeNullWhen(false)] out TOther result) => TryConvertToTruncating(value, out result);

//
// IParsable
//
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,32 @@ static virtual TSelf CreateTruncating<TOther>(TOther value)
return result;
}

/// <summary>Creates an instance of the current type from a value, truncating any values that fall outside the representable range of the current type.</summary>
/// <typeparam name="TOther">The type of <paramref name="value" />.</typeparam>
/// <param name="value">The value which is used to create the instance of <typeparamref name="TSelf" />.</param>
/// <param name="signPropagation"></param>
/// <returns>An instance of <typeparamref name="TSelf" /> created from <paramref name="value" />, truncating if <paramref name="value" /> falls outside the representable range of <typeparamref name="TSelf" />.</returns>
/// <exception cref="NotSupportedException"><typeparamref name="TOther" /> is not supported.</exception>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
static virtual TSelf CreateTruncating<TOther>(TOther value, bool signPropagation)
#nullable disable
where TOther : INumberBase<TOther>
#nullable restore
{
TSelf? result;

if (typeof(TOther) == typeof(TSelf))
{
result = (TSelf)(object)value;
}
else if (!TSelf.TryConvertFromTruncating(value, signPropagation, out result) && !TOther.TryConvertToTruncating<TSelf>(value, signPropagation, out result))
{
ThrowHelper.ThrowNotSupportedException();
}

return result;
}

/// <summary>Determines if a value is in its canonical representation.</summary>
/// <param name="value">The value to be checked.</param>
/// <returns><c>true</c> if <paramref name="value" /> is in its canonical representation; otherwise, <c>false</c>.</returns>
Expand Down Expand Up @@ -376,6 +402,24 @@ protected static abstract bool TryConvertFromTruncating<TOther>(TOther value, [M
where TOther : INumberBase<TOther>;
#nullable restore

/// <summary>Tries to convert a value to an instance of the current type, truncating any values that fall outside the representable range of the current type.</summary>
/// <typeparam name="TOther">The type of <paramref name="value" />.</typeparam>
/// <param name="value">The value which is used to create the instance of <typeparamref name="TSelf" />.</param>
/// <param name="signPropagation"></param>
/// <param name="result">On return, contains an instance of <typeparamref name="TSelf" /> converted from <paramref name="value" />.</param>
/// <returns><c>false</c> if <typeparamref name="TOther" /> is not supported; otherwise, <c>true</c>.</returns>
protected static virtual bool TryConvertFromTruncating<TOther>(TOther value, bool signPropagation, [MaybeNullWhen(false)] out TSelf result)
#nullable disable
where TOther : INumberBase<TOther>
#nullable restore
{
if (signPropagation || !TOther.IsNegative(value))
return TSelf.TryConvertFromTruncating(value, out result);

result = default;
return false;
}

/// <summary>Tries to convert an instance of the current type to another type, throwing an overflow exception for any values that fall outside the representable range of the current type.</summary>
/// <typeparam name="TOther">The type to which <paramref name="value" /> should be converted.</typeparam>
/// <param name="value">The value which is used to create the instance of <typeparamref name="TOther" />.</param>
Expand Down Expand Up @@ -407,6 +451,24 @@ protected static abstract bool TryConvertToTruncating<TOther>(TSelf value, [Mayb
where TOther : INumberBase<TOther>;
#nullable restore

/// <summary>Tries to convert an instance of the current type to another type, truncating any values that fall outside the representable range of the current type.</summary>
/// <typeparam name="TOther">The type to which <paramref name="value" /> should be converted.</typeparam>
/// <param name="value">The value which is used to create the instance of <typeparamref name="TOther" />.</param>
/// <param name="signPropagation"></param>
/// <param name="result">On return, contains an instance of <typeparamref name="TOther" /> converted from <paramref name="value" />.</param>
/// <returns><c>false</c> if <typeparamref name="TOther" /> is not supported; otherwise, <c>true</c>.</returns>
protected static virtual bool TryConvertToTruncating<TOther>(TSelf value, bool signPropagation, [MaybeNullWhen(false)] out TOther result)
#nullable disable
where TOther : INumberBase<TOther>
#nullable restore
{
if (signPropagation || !TSelf.IsNegative(value))
return TSelf.TryConvertToTruncating(value, out result);

result = default;
return false;
}

/// <summary>Tries to parse a string into a value.</summary>
/// <param name="s">The string to parse.</param>
/// <param name="style">A bitwise combination of number styles that can be present in <paramref name="s" />.</param>
Expand Down
152 changes: 152 additions & 0 deletions src/libraries/System.Private.CoreLib/src/System/SByte.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1067,6 +1067,84 @@ private static bool TryConvertFromTruncating<TOther>(TOther value, out sbyte res
}
}

/// <inheritdoc cref="INumberBase{TSelf}.TryConvertFromTruncating{TOther}(TOther, bool, out TSelf)" />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
static bool INumberBase<sbyte>.TryConvertFromTruncating<TOther>(TOther value, bool signPropagation, out sbyte result) => TryConvertFromTruncating(value, signPropagation, out result);

[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static bool TryConvertFromTruncating<TOther>(TOther value, bool signPropagation, out sbyte result)
where TOther : INumberBase<TOther>
{
// In order to reduce overall code duplication and improve the inlinabilty of these
// methods for the corelib types we have `ConvertFrom` handle the same sign and
// `ConvertTo` handle the opposite sign. However, since there is an uneven split
// between signed and unsigned types, the one that handles unsigned will also
// handle `Decimal`.
//
// That is, `ConvertFrom` for `sbyte` will handle the other signed types and
// `ConvertTo` will handle the unsigned types

if (signPropagation)
return TryConvertFromTruncating(value, out result);

if (typeof(TOther) == typeof(double))
{
double actualValue = (double)(object)value;
result = (actualValue >= MaxValue) ? MaxValue :
(actualValue <= MinValue) ? MinValue : (sbyte)actualValue;
return true;
}
else if (typeof(TOther) == typeof(Half))
{
Half actualValue = (Half)(object)value;
result = (actualValue >= MaxValue) ? MaxValue :
(actualValue <= MinValue) ? MinValue : (sbyte)actualValue;
return true;
}
else if (typeof(TOther) == typeof(short))
{
ushort actualValue = (ushort)(short)(object)value;
result = (sbyte)actualValue;
return true;
}
else if (typeof(TOther) == typeof(int))
{
uint actualValue = (uint)(int)(object)value;
result = (sbyte)actualValue;
return true;
}
else if (typeof(TOther) == typeof(long))
{
ulong actualValue = (ulong)(long)(object)value;
result = (sbyte)actualValue;
return true;
}
else if (typeof(TOther) == typeof(Int128))
{
UInt128 actualValue = (UInt128)(Int128)(object)value;
result = (sbyte)actualValue;
return true;
}
else if (typeof(TOther) == typeof(nint))
{
nuint actualValue = (nuint)(nint)(object)value;
result = (sbyte)actualValue;
return true;
}
else if (typeof(TOther) == typeof(float))
{
float actualValue = (float)(object)value;
result = (actualValue >= MaxValue) ? MaxValue :
(actualValue <= MinValue) ? MinValue : (sbyte)actualValue;
return true;
}
else
{
result = default;
return false;
}
}

/// <inheritdoc cref="INumberBase{TSelf}.TryConvertToChecked{TOther}(TSelf, out TOther)" />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
static bool INumberBase<sbyte>.TryConvertToChecked<TOther>(sbyte value, [MaybeNullWhen(false)] out TOther result)
Expand Down Expand Up @@ -1206,6 +1284,9 @@ static bool INumberBase<sbyte>.TryConvertToSaturating<TOther>(sbyte value, [Mayb
/// <inheritdoc cref="INumberBase{TSelf}.TryConvertToTruncating{TOther}(TSelf, out TOther)" />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
static bool INumberBase<sbyte>.TryConvertToTruncating<TOther>(sbyte value, [MaybeNullWhen(false)] out TOther result)
=> TryConvertToTruncating(value, out result);

private static bool TryConvertToTruncating<TOther>(sbyte value, [MaybeNullWhen(false)] out TOther result)
{
// In order to reduce overall code duplication and improve the inlinabilty of these
// methods for the corelib types we have `ConvertFrom` handle the same sign and
Expand Down Expand Up @@ -1271,6 +1352,77 @@ static bool INumberBase<sbyte>.TryConvertToTruncating<TOther>(sbyte value, [Mayb
}
}

/// <inheritdoc cref="INumberBase{TSelf}.TryConvertToTruncating{TOther}(TSelf, bool, out TOther)" />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
static bool INumberBase<sbyte>.TryConvertToTruncating<TOther>(sbyte value, bool signPropagation, [MaybeNullWhen(false)] out TOther result)
{
// In order to reduce overall code duplication and improve the inlinabilty of these
// methods for the corelib types we have `ConvertFrom` handle the same sign and
// `ConvertTo` handle the opposite sign. However, since there is an uneven split
// between signed and unsigned types, the one that handles unsigned will also
// handle `Decimal`.
//
// That is, `ConvertFrom` for `sbyte` will handle the other signed types and
// `ConvertTo` will handle the unsigned types

if (signPropagation)
return TryConvertToTruncating(value, out result);

if (typeof(TOther) == typeof(byte))
{
byte actualResult = (byte)value;
result = (TOther)(object)actualResult;
return true;
}
else if (typeof(TOther) == typeof(char))
{
char actualResult = (char)(byte)value;
result = (TOther)(object)actualResult;
return true;
}
else if (typeof(TOther) == typeof(decimal))
{
decimal actualResult = (byte)value;
result = (TOther)(object)actualResult;
return true;
}
else if (typeof(TOther) == typeof(ushort))
{
ushort actualResult = (byte)value;
result = (TOther)(object)actualResult;
return true;
}
else if (typeof(TOther) == typeof(uint))
{
uint actualResult = (byte)value;
result = (TOther)(object)actualResult;
return true;
}
else if (typeof(TOther) == typeof(ulong))
{
ulong actualResult = (byte)value;
result = (TOther)(object)actualResult;
return true;
}
else if (typeof(TOther) == typeof(UInt128))
{
UInt128 actualResult = (byte)value;
result = (TOther)(object)actualResult;
return true;
}
else if (typeof(TOther) == typeof(nuint))
{
nuint actualResult = (byte)value;
result = (TOther)(object)actualResult;
return true;
}
else
{
result = default;
return false;
}
}

//
// IParsable
//
Expand Down
Loading
Loading