Skip to content

Provide support for alternative rounding modes for division and remainder of division. #93568

@tannergooding

Description

@tannergooding

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    api-approvedAPI was approved in API review, it can be implementedarea-System.Numericsin-prThere is an active PR which will close this issue when it is merged

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions