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

Add DivRem with DivisionRounding (WIP + Review request) #104589

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
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 @@ -307,6 +307,7 @@
<Compile Include="$(MSBuildThisFileDirectory)System\Diagnostics\Stopwatch.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Diagnostics\SymbolStore\ISymbolDocumentWriter.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Diagnostics\UnreachableException.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\DivisionRounding.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\DivideByZeroException.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\DllNotFoundException.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Double.cs" />
Expand Down
3 changes: 3 additions & 0 deletions src/libraries/System.Private.CoreLib/src/System/Byte.cs
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,9 @@ object IConvertible.ToType(Type type, IFormatProvider? provider)
/// <inheritdoc cref="IBinaryInteger{TSelf}.DivRem(TSelf, TSelf)" />
public static (byte Quotient, byte Remainder) DivRem(byte left, byte right) => Math.DivRem(left, right);

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

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

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

namespace System
{
public enum DivisionRounding
{
Truncate = 0,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: assigning underlying numeric value isn't required for implementation source.

Copy link
Contributor Author

@heathbm heathbm Jul 11, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Floor = 1,
Ceiling = 2,
AwayFromZero = 3,
Euclidean = 4,
}
}
3 changes: 3 additions & 0 deletions src/libraries/System.Private.CoreLib/src/System/Int32.cs
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,9 @@ object IConvertible.ToType(Type type, IFormatProvider? provider)
/// <inheritdoc cref="IBinaryInteger{TSelf}.DivRem(TSelf, TSelf)" />
public static (int Quotient, int Remainder) DivRem(int left, int right) => Math.DivRem(left, right);

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

/// <inheritdoc cref="IBinaryInteger{TSelf}.LeadingZeroCount(TSelf)" />
[Intrinsic]
public static int LeadingZeroCount(int value) => BitOperations.LeadingZeroCount((uint)value);
Expand Down
116 changes: 116 additions & 0 deletions src/libraries/System.Private.CoreLib/src/System/Math.cs
Original file line number Diff line number Diff line change
Expand Up @@ -501,6 +501,122 @@ public static (nuint Quotient, nuint Remainder) DivRem(nuint left, nuint right)
return (quotient, left - (quotient * right));
}

/// <summary>Produces the quotient and the remainder of two signed 32-bit numbers.</summary>
/// <param name="left">The dividend.</param>
/// <param name="right">The divisor.</param>
/// <param name="rounding">The rounding type.</param>
/// <returns>The quotient and the remainder of the specified numbers.</returns>
[NonVersionable]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static (int Quotient, int Remainder) DivRem(int left, int right, DivisionRounding rounding)
{
return rounding switch
{
DivisionRounding.Truncate => DivRem(left, right),
DivisionRounding.Floor => DivRemFloor(left, right),
DivisionRounding.Ceiling => DivRemCeiling(left, right),
DivisionRounding.AwayFromZero => DivRemAwayFromZero(left, right),
DivisionRounding.Euclidean => DivRemEuclidean(left, right),
_ => throw new ArgumentOutOfRangeException(nameof(rounding)),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should ideally use a throw helper.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated

};
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static (int quotient, int remainder) DivRemFloor(int left, int right)
{
int quotient = left / right;
int remainder = left - (quotient * right);

if (remainder != 0)
{
return FloorRounding(left, right, quotient, remainder);
}

return (quotient, remainder);
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static (int quotient, int remainder) FloorRounding(int left, int right, int quotient, int remainder)
{
if (Sign(left) != Sign(right))
{
quotient--;
remainder += right;
}

return (quotient, remainder);
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static (int Quotient, int Remainder) DivRemCeiling(int left, int right)
{
int quotient = left / right;
int remainder = left - (quotient * right);

if (remainder != 0)
{
return CeilingRounding(left, right, quotient, remainder);
}

return (quotient, remainder);
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static (int quotient, int remainder) CeilingRounding(int left, int right, int quotient, int remainder)
{
if (Sign(left) == Sign(right))
{
quotient++;
remainder -= right;
}

return (quotient, remainder);
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static (int Quotient, int Remainder) DivRemAwayFromZero(int left, int right)
{
int quotient = left / right;
int remainder = left - (quotient * right);

if (remainder != 0)
{
if (Sign(left) == Sign(right))
{
quotient++;
}
else
{
quotient--;
}
}

remainder = left - (quotient * right);

return (quotient, remainder);
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static (int Quotient, int Remainder) DivRemEuclidean(int left, int right)
{
int quotient = left / right;
int remainder = left - (quotient * right);

if (remainder != 0)
{
if (right > 0)
{
return FloorRounding(left, right, quotient, remainder);
}
else
{
return CeilingRounding(left, right, quotient, remainder);
}
}

return (quotient, remainder);
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static decimal Ceiling(decimal d)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,118 @@ static virtual (TSelf Quotient, TSelf Remainder) DivRem(TSelf left, TSelf right)
return (quotient, (left - (quotient * right)));
}

/// <summary>
/// Computes the quotient and remainder of two values.
/// Throws an <see cref="ArgumentOutOfRangeException"/> if <paramref name="rounding"/> is invalid.
/// </summary>
/// <param name="left">The value which <paramref name="right" /> divides.</param>
/// <param name="right">The value which divides <paramref name="left" />.</param>
/// <param name="rounding"></param>
/// <returns>The quotient and remainder of <paramref name="left" /> divided-by <paramref name="right" />.</returns>
/// <exception cref="ArgumentOutOfRangeException"></exception>
static virtual (TSelf Quotient, TSelf Remainder) DivRem(TSelf left, TSelf right, DivisionRounding rounding)
{
return rounding switch
{
DivisionRounding.Truncate => TSelf.DivRem(left, right),
DivisionRounding.Floor => DivRemFloor(left, right),
DivisionRounding.Ceiling => DivRemCeiling(left, right),
DivisionRounding.AwayFromZero => DivRemAwayFromZero(left, right),
DivisionRounding.Euclidean => DivRemEuclidean(left, right),
_ => throw new ArgumentOutOfRangeException(nameof(rounding)),
};
}

private static (TSelf quotient, TSelf remainder) DivRemFloor(TSelf left, TSelf right)
{
TSelf quotient = left / right;
TSelf remainder = left - (quotient * right);

if (remainder != TSelf.Zero)
{
return FloorRounding(left, right, quotient, remainder);
}

return (quotient, remainder);
}

private static (TSelf quotient, TSelf remainder) FloorRounding(TSelf left, TSelf right, TSelf quotient, TSelf remainder)
{
if (TSelf.Sign(left) != TSelf.Sign(right))
{
quotient--;
remainder += right;
}

return (quotient, remainder);
}

private static (TSelf Quotient, TSelf Remainder) DivRemCeiling(TSelf left, TSelf right)
{
TSelf quotient = left / right;
TSelf remainder = left - (quotient * right);

if (remainder != TSelf.Zero)
{
return CeilingRounding(left, right, quotient, remainder);
}

return (quotient, remainder);
}

private static (TSelf quotient, TSelf remainder) CeilingRounding(TSelf left, TSelf right, TSelf quotient, TSelf remainder)
{
if (TSelf.Sign(left) == TSelf.Sign(right))
{
quotient++;
remainder -= right;
}

return (quotient, remainder);
}

private static (TSelf Quotient, TSelf Remainder) DivRemAwayFromZero(TSelf left, TSelf right)
{
TSelf quotient = left / right;
TSelf remainder = left - (quotient * right);

if (remainder != TSelf.Zero)
{
if (TSelf.Sign(left) == TSelf.Sign(right))
{
quotient++;
}
else
{
quotient--;
}
}

remainder = left - (quotient * right);

return (quotient, remainder);
}

private static (TSelf Quotient, TSelf Remainder) DivRemEuclidean(TSelf left, TSelf right)
{
TSelf quotient = left / right;
TSelf remainder = left - (quotient * right);

if (remainder != TSelf.Zero)
{
if (right > TSelf.Zero)
{
return FloorRounding(left, right, quotient, remainder);
}
else
{
return CeilingRounding(left, right, quotient, remainder);
}
}

return (quotient, remainder);
}

/// <summary>Computes the number of leading zero bits in a value.</summary>
/// <param name="value">The value whose leading zero bits are to be counted.</param>
/// <returns>The number of leading zero bits in <paramref name="value" />.</returns>
Expand Down
12 changes: 12 additions & 0 deletions src/libraries/System.Runtime/ref/System.Runtime.cs
Original file line number Diff line number Diff line change
Expand Up @@ -944,6 +944,7 @@ public static void SetByte(System.Array array, int index, byte value) { }
public static byte CreateSaturating<TOther>(TOther value) where TOther : System.Numerics.INumberBase<TOther> { throw null; }
public static byte CreateTruncating<TOther>(TOther value) where TOther : System.Numerics.INumberBase<TOther> { throw null; }
public static (byte Quotient, byte Remainder) DivRem(byte left, byte right) { throw null; }
public static (byte Quotient, byte Remainder) DivRem(byte left, byte right, System.DivisionRounding rounding) { throw null; }
public bool Equals(byte obj) { throw null; }
public override bool Equals([System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] object? obj) { throw null; }
public override int GetHashCode() { throw null; }
Expand Down Expand Up @@ -2369,6 +2370,14 @@ protected DivideByZeroException(System.Runtime.Serialization.SerializationInfo i
public DivideByZeroException(string? message) { }
public DivideByZeroException(string? message, System.Exception? innerException) { }
}
public enum DivisionRounding
{
Truncate = 0,
Floor = 1,
Ceiling = 2,
AwayFromZero = 3,
Euclidean = 4,
}
public readonly partial struct Double : System.IComparable, System.IComparable<double>, System.IConvertible, System.IEquatable<double>, System.IFormattable, System.IParsable<double>, System.ISpanFormattable, System.ISpanParsable<double>, System.IUtf8SpanFormattable, System.IUtf8SpanParsable<double>, System.Numerics.IAdditionOperators<double, double, double>, System.Numerics.IAdditiveIdentity<double, double>, System.Numerics.IBinaryFloatingPointIeee754<double>, System.Numerics.IBinaryNumber<double>, System.Numerics.IBitwiseOperators<double, double, double>, System.Numerics.IComparisonOperators<double, double, bool>, System.Numerics.IDecrementOperators<double>, System.Numerics.IDivisionOperators<double, double, double>, System.Numerics.IEqualityOperators<double, double, bool>, System.Numerics.IExponentialFunctions<double>, System.Numerics.IFloatingPoint<double>, System.Numerics.IFloatingPointConstants<double>, System.Numerics.IFloatingPointIeee754<double>, System.Numerics.IHyperbolicFunctions<double>, System.Numerics.IIncrementOperators<double>, System.Numerics.ILogarithmicFunctions<double>, System.Numerics.IMinMaxValue<double>, System.Numerics.IModulusOperators<double, double, double>, System.Numerics.IMultiplicativeIdentity<double, double>, System.Numerics.IMultiplyOperators<double, double, double>, System.Numerics.INumber<double>, System.Numerics.INumberBase<double>, System.Numerics.IPowerFunctions<double>, System.Numerics.IRootFunctions<double>, System.Numerics.ISignedNumber<double>, System.Numerics.ISubtractionOperators<double, double, double>, System.Numerics.ITrigonometricFunctions<double>, System.Numerics.IUnaryNegationOperators<double, double>, System.Numerics.IUnaryPlusOperators<double, double>
{
private readonly double _dummyPrimitive;
Expand Down Expand Up @@ -3886,6 +3895,7 @@ public InsufficientMemoryException(string? message, System.Exception? innerExcep
public static int CreateSaturating<TOther>(TOther value) where TOther : System.Numerics.INumberBase<TOther> { throw null; }
public static int CreateTruncating<TOther>(TOther value) where TOther : System.Numerics.INumberBase<TOther> { throw null; }
public static (int Quotient, int Remainder) DivRem(int left, int right) { throw null; }
public static (int Quotient, int Remainder) DivRem(int left, int right, System.DivisionRounding rounding) { throw null; }
public bool Equals(int obj) { throw null; }
public override bool Equals([System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] object? obj) { throw null; }
public override int GetHashCode() { throw null; }
Expand Down Expand Up @@ -4471,6 +4481,7 @@ public static partial class Math
public static (byte Quotient, byte Remainder) DivRem(byte left, byte right) { throw null; }
public static (short Quotient, short Remainder) DivRem(short left, short right) { throw null; }
public static (int Quotient, int Remainder) DivRem(int left, int right) { throw null; }
public static (int Quotient, int Remainder) DivRem(int left, int right, System.DivisionRounding rounding) { throw null; }
public static int DivRem(int a, int b, out int result) { throw null; }
public static (long Quotient, long Remainder) DivRem(long left, long right) { throw null; }
public static long DivRem(long a, long b, out long result) { throw null; }
Expand Down Expand Up @@ -11138,6 +11149,7 @@ public partial interface IBinaryFloatingPointIeee754<TSelf> : System.IComparable
public partial interface IBinaryInteger<TSelf> : System.IComparable, System.IComparable<TSelf>, System.IEquatable<TSelf>, System.IFormattable, System.IParsable<TSelf>, System.ISpanFormattable, System.ISpanParsable<TSelf>, System.Numerics.IAdditionOperators<TSelf, TSelf, TSelf>, System.Numerics.IAdditiveIdentity<TSelf, TSelf>, System.Numerics.IBinaryNumber<TSelf>, System.Numerics.IBitwiseOperators<TSelf, TSelf, TSelf>, System.Numerics.IComparisonOperators<TSelf, TSelf, bool>, System.Numerics.IDecrementOperators<TSelf>, System.Numerics.IDivisionOperators<TSelf, TSelf, TSelf>, System.Numerics.IEqualityOperators<TSelf, TSelf, bool>, System.Numerics.IIncrementOperators<TSelf>, System.Numerics.IModulusOperators<TSelf, TSelf, TSelf>, System.Numerics.IMultiplicativeIdentity<TSelf, TSelf>, System.Numerics.IMultiplyOperators<TSelf, TSelf, TSelf>, System.Numerics.INumber<TSelf>, System.Numerics.INumberBase<TSelf>, System.Numerics.IShiftOperators<TSelf, int, TSelf>, System.Numerics.ISubtractionOperators<TSelf, TSelf, TSelf>, System.Numerics.IUnaryNegationOperators<TSelf, TSelf>, System.Numerics.IUnaryPlusOperators<TSelf, TSelf> where TSelf : System.Numerics.IBinaryInteger<TSelf>?
{
static virtual (TSelf Quotient, TSelf Remainder) DivRem(TSelf left, TSelf right) { throw null; }
static (TSelf Quotient, TSelf Remainder) DivRem(TSelf left, TSelf right, System.DivisionRounding rounding) { throw null; }
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This one is incorrect. It should be static virtual as the same of implementation.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For some reason runtime\src\libraries\System.Runtime\src>dotnet build /t:GenerateReferenceAssemblySource would remove the virtual from those methods. I've added it manually.

int GetByteCount();
int GetShortestBitLength();
static virtual TSelf LeadingZeroCount(TSelf value) { throw null; }
Expand Down
Loading