Description
Background and Motivation
Most computer hardware implements division as a truncated division
. Correspondingly, many languages implement their division (x / y
) and remainder (x % y
) support following this. However, some languages default to floored division
and have their remainder be the counterpart to this instead. This ultimately has the same behavior as truncated
when the signs of x
and y
match, but a different result when the signs do not. Some other languages still may use other types of rounding in their division operations which may include Euclidean
, Ceiling
, or Rounded
.
There are ultimately benefits and drawbacks to each of the approaches and what is correct for one scenario may not be correct for all. Likewise, while computing any of them is fairly trivial, the most efficient implementation is often non-obvious. As such, it is proposed we expose a set of new APIs that allow developers to pick the one that is right for them.
Proposed API
namespace System.Numerics;
public enum DivisionRounding
{
Truncate = 0, // Towards Zero
Floor = 1, // Towards -Infinity
Ceiling = 2, // Towards +Infinity
AwayFromZero = 3, // Away from Zero
Euclidean = 4, // floor(x / abs(n)) * sign(n)
}
public partial interface IBinaryInteger<T>
{
// Existing:
static virtual (TSelf Quotient, TSelf Remainder) DivRem(TSelf left, TSelf right);
// Proposed:
static virtual TSelf Divide(TSelf left, TSelf right, DivisionRounding mode);
static virtual (TSelf Quotient, TSelf Remainder) DivRem(TSelf left, TSelf right, DivisionRounding mode);
static virtual TSelf Remainder(TSelf left, TSelf right, DivisionRounding mode);
}
-- These APIs would have DIMs and would be publicly implemented on the built-in types that implement IBinaryInteger
, such as Int32
, Int64
, and Byte
.
Additional Considerations
The following gives an overview of the most common variations of the remainder (or modulo) operation: https://en.wikipedia.org/wiki/Modulo#Variants_of_the_definition
The following gives some examples of what is used for various languages: https://en.wikipedia.org/wiki/Modulo#In_programming_languages
Rust is experimenting with its own similar APIs via the int_roundings
feature, which expands on some of the existing support like rem_euclid
: https://doc.rust-lang.org/std/primitive.i32.html#method.div_floor and rust-lang/rust#88581
It may be desirable to also support this functionality for floating-point, but it can be quite a bit more expensive to support there.
The general terminology in this space is incredibly overloaded. Several languages use competing terminology, but a general consistency is that rem
is often referring to remainder of truncated division
and mod
is referring to the remainder of floored division
. We may want to consider or incorporate this into the naming to help users identify the one they want.