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

Provide support for alternative rounding modes for division and remainder of division #106795

Open
wants to merge 4 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
37 changes: 37 additions & 0 deletions src/libraries/Common/tests/System/GenericMathHelpers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,43 @@ public static class BinaryIntegerHelper<TSelf>
{
public static (TSelf Quotient, TSelf Remainder) DivRem(TSelf left, TSelf right) => TSelf.DivRem(left, right);

public static (TSelf Quotient, TSelf Remainder) DivRem(TSelf left, TSelf right, DivisionRounding mode) => TSelf.DivRem(left, right, mode);

public static TSelf Divide(TSelf left, TSelf right, DivisionRounding mode) => TSelf.Divide(left, right, mode);

public static TSelf Remainder(TSelf left, TSelf right, DivisionRounding mode) => TSelf.Remainder(left, right, mode);

public static TSelf DivideRoundingCorrection(TSelf left, TSelf right, DivisionRounding mode) => mode switch
{
_ when left % right == TSelf.Zero => TSelf.Zero,
DivisionRounding.Truncate => TSelf.Zero,
DivisionRounding.Floor when right > TSelf.Zero ^ left > TSelf.Zero => -TSelf.One,
DivisionRounding.Ceiling when right > TSelf.Zero ^ left < TSelf.Zero => TSelf.One,
DivisionRounding.AwayFromZero => (right > TSelf.Zero ^ left > TSelf.Zero) ? -TSelf.One : TSelf.One,
DivisionRounding.Euclidean when left < TSelf.Zero => (right > TSelf.Zero) ? -TSelf.One : TSelf.One,
_ => TSelf.Zero,
};

public static TSelf RemainderRoundingCorrection(TSelf left, TSelf right, DivisionRounding mode) => mode switch
{
_ when left % right == TSelf.Zero => TSelf.Zero,
DivisionRounding.Truncate => TSelf.Zero,
DivisionRounding.Floor when right > TSelf.Zero ^ left > TSelf.Zero => right,
DivisionRounding.Ceiling when right > TSelf.Zero ^ left < TSelf.Zero => -right,
DivisionRounding.AwayFromZero => (right > TSelf.Zero ^ left > TSelf.Zero) ? right : -right,
DivisionRounding.Euclidean when left < TSelf.Zero => (right > TSelf.Zero) ? right : -right,
_ => TSelf.Zero,
};

public static (TSelf Quotient, TSelf Remainder) DivRemExpected(TSelf left, TSelf right, DivisionRounding mode) =>
(left / right + DivideRoundingCorrection(left, right, mode), left % right + RemainderRoundingCorrection(left, right, mode));

public static TSelf DivideExpected(TSelf left, TSelf right, DivisionRounding mode)
=> left / right + DivideRoundingCorrection(left, right, mode);

public static TSelf RemainderExpected(TSelf left, TSelf right, DivisionRounding mode)
=> left % right + RemainderRoundingCorrection(left, right, mode);

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

public static TSelf PopCount(TSelf value) => TSelf.PopCount(value);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2767,6 +2767,7 @@
<ItemGroup>
<Compile Include="$(MSBuildThisFileDirectory)System\IParsable.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\ISpanParsable.cs" />
<Compile Include="$(MSBuildThisFileDirectory)system\Numerics\DivisionRounding.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Numerics\IAdditionOperators.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Numerics\IAdditiveIdentity.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Numerics\IBinaryFloatingPointIeee754.cs" />
Expand Down Expand Up @@ -2807,4 +2808,4 @@
<Compile Include="$(MSBuildThisFileDirectory)System\Threading\Wasi\WasiPollWorld.wit.imports.wasi.io.v0_2_1.IPoll.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Threading\Wasi\WasiPollWorld.wit.imports.wasi.io.v0_2_1.PollInterop.cs" />
</ItemGroup>
</Project>
</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

namespace System.Numerics
{
/// <summary>
/// Specifies the rounding strategy to use when performing division.
/// </summary>
public enum DivisionRounding
{
/// <summary>
/// Truncated division (rounding toward zero) — round the division result towards zero.
/// Graph for truncated division with positive divisor https://www.wolframalpha.com/input?i=Plot%5B%7BIntegerPart%5Bn%5D%2C+n+-+IntegerPart%5Bn%5D%7D%2C+%7Bn%2C+-3%2C+3%7D%5D
/// Graph for truncated division with negative divisor https://www.wolframalpha.com/input?i=Plot%5B%7BIntegerPart%5B-n%5D%2C+n+%2B+IntegerPart%5B-n%5D%7D%2C+%7Bn%2C+-3%2C+3%7D%5D
AlexRadch marked this conversation as resolved.
Show resolved Hide resolved
/// </summary>
Truncate = 0,

/// <summary>
/// Floor division (rounding down) — round the division result down to the next lower integer.
/// Graph for floor division with positive divisor https://www.wolframalpha.com/input?i=Plot%5B%7BFloor%5Bn%5D%2C+n+-+Floor%5Bn%5D%7D%2C+%7Bn%2C+-3%2C+3%7D%5D
/// Graph for floor division with negative divisor https://www.wolframalpha.com/input?i=Plot%5B%7BFloor%5B-n%5D%2C+n+%2B+Floor%5B-n%5D%7D%2C+%7Bn%2C+-3%2C+3%7D%5D
/// </summary>
Floor = 1,

/// <summary>
/// Ceiling division (rounding up) — round the division result up to the next higher integer.
/// Graph for ceiling division with positive divisor https://www.wolframalpha.com/input?i=Plot%5B%7BCeiling%5Bn%5D%2C+n+-+Ceiling%5Bn%5D%7D%2C+%7Bn%2C+-3%2C+3%7D%5D
/// Graph for ceiling division with negative divisor https://www.wolframalpha.com/input?i=Plot%5B%7BCeiling%5B-n%5D%2C+n+%2B+Ceiling%5B-n%5D%7D%2C+%7Bn%2C+-3%2C+3%7D%5D
/// </summary>
Ceiling = 2,

/// <summary>
/// AwayFromZero division (rounding away zero — round the division result away from zero to the nearest integer.
/// Graph for AwayFromZero division with positive divisor https://www.wolframalpha.com/input?i=Plot%5B%7BIntegerPart%5Bn%5D+%2B+Sign%5Bn%5D%2C+n+-+IntegerPart%5Bn%5D+-+Sign%5Bn%5D%7D%2C+%7Bn%2C+-3%2C+3%7D%5D
/// Graph for AwayFromZero division with negative divisor https://www.wolframalpha.com/input?i=Plot%5B%7BIntegerPart%5B-n%5D+%2B+Sign%5B-n%5D%2C+n+%2B+IntegerPart%5B-n%5D+%2B+Sign%5B-n%5D%7D%2C+%7Bn%2C+-3%2C+3%7D%5D
/// </summary>
AwayFromZero = 3,

/// <summary>
/// Euclidean division ensures a non-negative remainder:
/// for positive divisor — round the division result down to the next lower integer (rounding down)
/// for negative divisor — round the division result up to the next higher integer (rounding up)
/// Graph for Euclidean division with positive divisor https://www.wolframalpha.com/input?i=Plot%5B%7BFloor%5Bn%5D%2C+n+-+Floor%5Bn%5D%7D%2C+%7Bn%2C+-3%2C+3%7D%5D
/// Graph for Euclidean division with negative divisor https://www.wolframalpha.com/input?i=Plot%5B%7BCeiling%5B-n%5D%2C+n+%2B+Ceiling%5B-n%5D%7D%2C+%7Bn%2C+-3%2C+3%7D%5D
/// </summary>
Euclidean = 4,
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,192 @@ 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 using the specified division rounding mode.</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="mode">The <see cref="DivisionRounding"/> mode.</param>
/// <returns>The quotient and remainder of <paramref name="left" /> divided-by <paramref name="right" /> with the specified division rounding mode.</returns>
static virtual (TSelf Quotient, TSelf Remainder) DivRem(TSelf left, TSelf right, DivisionRounding mode)
{
(TSelf quotient, TSelf remainder) = TSelf.DivRem(left, right);

if (remainder == TSelf.Zero)
return (quotient, remainder);

switch (mode)
{
case DivisionRounding.Floor:
if (right > TSelf.Zero ^ left > TSelf.Zero)
AlexRadch marked this conversation as resolved.
Show resolved Hide resolved
{
quotient--;
remainder += right;
}
break;
case DivisionRounding.Ceiling:
{
if (right > TSelf.Zero ^ left < TSelf.Zero)
{
quotient++;
remainder -= right;
}
break;
}
case DivisionRounding.AwayFromZero:
{
if (right > TSelf.Zero ^ left > TSelf.Zero)
{
quotient--;
remainder += right;
}
else
{
quotient++;
remainder -= right;
}
break;
}
case DivisionRounding.Euclidean:
{
if (left < TSelf.Zero)
{
if (right > TSelf.Zero)
{
quotient--;
remainder += right;
}
else
{
quotient++;
remainder -= right;
}
}
break;
}
}

return (quotient, remainder);
}

/// <summary>Computes the quotient of two values using the specified division rounding mode.</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="mode">The <see cref="DivisionRounding"/> mode.</param>
/// <returns>The quotient of <paramref name="left" /> divided-by <paramref name="right" /> with the specified division rounding mode.</returns>
static virtual TSelf Divide(TSelf left, TSelf right, DivisionRounding mode)
{
(TSelf quotient, TSelf remainder) = TSelf.DivRem(left, right);

if (remainder == TSelf.Zero)
return quotient;

switch (mode)
{
case DivisionRounding.Floor:
if (right > TSelf.Zero ^ left > TSelf.Zero)
{
quotient--;
}
break;
case DivisionRounding.Ceiling:
{
if (right > TSelf.Zero ^ left < TSelf.Zero)
{
quotient++;
}
break;
}
case DivisionRounding.AwayFromZero:
{
if (right > TSelf.Zero ^ left > TSelf.Zero)
{
quotient--;
}
else
{
quotient++;
}
break;
}
case DivisionRounding.Euclidean:
{
if (left < TSelf.Zero)
{
if (right > TSelf.Zero)
{
quotient--;
}
else
{
quotient++;
}
}
break;
}
}

return quotient;
}

/// <summary>Computes the remainder of two values using the specified division rounding mode.</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="mode">The <see cref="DivisionRounding"/> mode.</param>
/// <returns>The remainder of <paramref name="left" /> divided-by <paramref name="right" /> with the specified division rounding mode.</returns>
static virtual TSelf Remainder(TSelf left, TSelf right, DivisionRounding mode)
{
TSelf remainder = left % right;

if (remainder == TSelf.Zero)
return remainder;

switch (mode)
{
case DivisionRounding.Floor:
if (right > TSelf.Zero ^ left > TSelf.Zero)
{
remainder += right;
}
break;
case DivisionRounding.Ceiling:
{
if (right > TSelf.Zero ^ left < TSelf.Zero)
{
remainder -= right;
}
break;
}
case DivisionRounding.AwayFromZero:
{
if (right > TSelf.Zero ^ left > TSelf.Zero)
{
remainder += right;
}
else
{
remainder -= right;
}
break;
}
case DivisionRounding.Euclidean:
{
if (left < TSelf.Zero)
{
if (right > TSelf.Zero)
{
remainder += right;
}
else
{
remainder -= right;
}
}
break;
}
}

return 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
11 changes: 11 additions & 0 deletions src/libraries/System.Runtime/ref/System.Runtime.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11142,6 +11142,14 @@ public static partial class BitOperations
[System.CLSCompliantAttribute(false)]
public static int TrailingZeroCount(nuint value) { throw null; }
}
public enum DivisionRounding
{
Truncate = 0,
Floor = 1,
Ceiling = 2,
AwayFromZero = 3,
Euclidean = 4,
}
public partial interface IAdditionOperators<TSelf, TOther, TResult> where TSelf : System.Numerics.IAdditionOperators<TSelf, TOther, TResult>?
{
static abstract TResult operator +(TSelf left, TOther right);
Expand All @@ -11156,7 +11164,10 @@ 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 Divide(TSelf left, TSelf right, System.Numerics.DivisionRounding mode) { throw null; }
static virtual (TSelf Quotient, TSelf Remainder) DivRem(TSelf left, TSelf right) { throw null; }
static virtual (TSelf Quotient, TSelf Remainder) DivRem(TSelf left, TSelf right, System.Numerics.DivisionRounding mode) { throw null; }
static virtual TSelf Remainder(TSelf left, TSelf right, System.Numerics.DivisionRounding mode) { throw null; }
int GetByteCount();
int GetShortestBitLength();
static virtual TSelf LeadingZeroCount(TSelf value) { throw null; }
Expand Down
Loading
Loading