dotnet / runtime

.NET is a cross-platform runtime for cloud, mobile, desktop, and IoT apps.
https://docs.microsoft.com/dotnet/core/
MIT License
15.26k stars 4.73k forks source link

[API Proposal]: Getting the positive remainder (Mod) when dividing a number by another #65408

Open JansthcirlU opened 2 years ago

JansthcirlU commented 2 years ago

Background and motivation

The case for positive remainders (number division)

Background

This proposal is inspired by a previous request to add an operator dedicated to returning the non-negative remainder when dividing two numbers, but adding a whole operator is way beyond the scope of this API request. Nonetheless, I will be summarising the anecdotal use cases and arguments in favour of adding this functionality to the runtime.

The existing % operator for calculating the remainder

Currently there exists a % operator in C# that yields a remainder when dividing some number a by some other number b, for example 14 % 3 yields 2 because 14 is 2 more than 12, which is the biggest multiple of 3 that fits into 14. Similarly, 8 % 2 is 0 because 8 already is a multiple of 2. This is most likely the most common use case for the % operator, and it works just fine for that purpose.

Issues with the existing % operator

Judging by the example above, you might think that the % operator works like the so-called modular arithmetic, also known as clock arithmetic. Imagine it's 7 PM and you want to know what time it will be in 8 hours. After 5 hours it is 12 AM, so after another 3 hours it will be 3 AM. In other words (7 + 8) % 12 should be equal to 3, which it is in C#. What about going backwards in time? If it's 3 AM, then 8 hours ago it was 7 PM. So does that mean that (3 - 8) % 12 is equal to 7 in C#? Nope, it isn't. For historical reasons, the % operator sometimes yields negative remainders, and (3 - 8) % 12 actually returns -5.

When you only use positive numbers, then the % operator should cause you no headaches. But once negative numbers are involved and you'd like to use it like clock arithmetic, like in the examples above, you're out of luck; you'll have to write your own methods to return a non-negative remainder (modulus) for the various existing numerical types.

For those who are more interested in the differences between the C# remainder and the modulus, Eric Lippert wrote an article on his blog about this very topic. As you can tell from the comments on that article, many developers were not aware of the differences, but are puzzled as to why the operator doesn't produce the more intuitive non-negative modulus.

Practical use cases for a non-negative remainder

Building on the basic clock arithmetic example, a non-negative remainder greatly increases the quality of life for programming anything that is cyclic in nature, such as times, dates, and angles.

Working with angles

It's common to normalise an angle theta so that it always falls within the interval 0 ≤ theta < 2π, for example in robotics and other branches of maths and engineering. If you try to do this with %, then you will run into issues when theta inevitably becomes less than 0. On the other hand, the non-negative modulus will always satisfy this condition.

Cyclical map design (Pacman)

When you make a 2D game that works like Pacman, you may define a playable area where each position can be represented as some coordinate (x, y) where 0 ≤ x < width and 0 ≤ y < height. To allow the player to cross over from one side of the map to the other, you might use the following logic:

// Player movement logic in time increments of dt (delta time)
if (Input.GetKey ("w"))
{
    // Yields a negative position when pos.y - speed * dt is less than zero
    pos.y = (pos.y - speed * dt) % mapHeight;
}
if (Input.GetKey ("a"))
{
    // Yields a negative position when pos.x - speed * dt is less than zero
    pos.x = (pos.x - speed * dt) % mapWidth;
}
if (Input.GetKey ("s"))
{
    pos.y = (pos.y + speed * dt) % mapHeight;
}
if (Input.GetKey ("d"))
{
    pos.x = (pos.x + speed * dt) % mapWidth;
}

As you can see, if the player keeps going up or left, their x or y position will be negative and they will effectively disappear from the playing field. With a nonnegative remainder, their position will always be between 0 and the maximum allowed value.

Cycling over array indices

In this reply to the original discussion, somebody tried to implement cyclical or circular array indexing using the existing % operator. But similarly to the wrapped playing field of Pacman, using negative indices causes an IndexOutOfRangeException when you wouldn't expect one.

With the new range/index features in C# 8, you can actually call elements at an index that counts back from the end of the array. This is something that could easily be accomplished with a non-negative modulus, but rather than adding an out-of-the-box modulus, a whole new ^ operator was added to make backwards indexing possible. If adding the range operator was worth the possible risks, why not instead add a proper modulus operator to keep things nice and uniform?

int[] test = new int[5] { 1, 2, 3, 4, 5 };
Console.WriteLine("Iterating over the array twice."); // Expected result: 1234512345
for (int i = 0; i < 10; i++)
{
    Console.Write(test[i % test.Length]); // Works when i >= 0
}
Console.WriteLine("\nIterating backwards over the array twice."); // Expected result 5432154321
for (int i = -1; i >= -10 ; i--)
{
    int index = i % test.Length;
    // Console.Write(test[index]); // Throws IndexOutOfRangeException
    Console.Write(test[index < 0 ? ^-index : index]); // Is this really better than having a proper modulus?
}
Console.WriteLine("\nProgram has finished.");

Conversion from Python (and possibly other languages)

In Python, the same % operator exists, but contrary to C# it already returns the modulus. When you create a software in Python using this operator, and you want to convert your Python code to C# code, it's not unreasonable to think that the operator implementations are the same. Unfortunately, like this person says in the aforementioned discussion, their C# code behaved differently to the Python code they had written.

Why should this be standard?

The scenarios above may seem rather uncommon or niche, and it is true that the average "corporate" programmer may never need this functionality to write an "ordinary" application. However, C# is being used more and more in game development and in embedded development (e.g. robotics, computer vision, etc.), two rather maths-heavy industries. Programmers who are involved with anything geometrical will benefit greatly from a proper modulus function in the runtime.

At the time of writing, there are over 100k results on Github for C# alone when you look up code that contains Math.Mod. Sure, only a fraction of those results are actually custom implementations of the non-negative remainder, but it is being used quite a lot in scientific areas as well as in cryptography.

The modulus function is quickly written, and there certainly are NuGet packages out there for maths extensions like MathNet.Numerics that probably contain a modulus function somewhere, but if it's just this one method that doesn't depend on a whole host of other functionalities, why not just include it in the runtime? The existing % operator is utterly useless half the time, after all.

People who need a modulus function in C# will definitely start looking for it in the Math library first, and go to stack overflow next, eventually finding their way to the discussion that inspired this API request.

API Proposal

namespace System
{
    public static partial class Math
    {
        /// <summary>Calculates the positive remainder when dividing a dividend by a divisor.</summary>
        public static double Mod(double dividend, double divisor)
        {
            double remainder = dividend % divisor;
            if (remainder < 0)
            {
                if (divisor < 0)
                {
                    return remainder - divisor;
                }
                return remainder + divisor;
            }
            return remainder;
        }
    }
}

(similar implementation for other numerical types)

API Usage

Usage examples

Normalising angles

public class Program
{
    public static void Main()
    {
        double theta = 45.0; // theta is between 0° and 360°
        double thetaRad = fortyFive.ToRadians(); // thetaRad is between 0 and 2π
        Console.WriteLine(thetaRad == thetaRad.Normalise()); // true

        double thetaBig = 405.0; // thetaBig is NOT between 0° and 360°
        double thetaBigRad = thetaBig.ToRadians(); // thetaBigRad is NOT between 0 and 2π
        Console.WriteLine(thetaBigRad == thetaBigRad.Normalise()); // false
        Console.WriteLine(thetaRad == thetaBigRad.Normalise()); // true because 405 = 45 + 360
    }
    public static class AngleExtensions
    {
        public static double Normalise(this double radians)
            => Math.Mod(radians, 2 * Math.PI);
        public static double ToDegrees(this double radians)
            => radians * 180.0 / Math.PI;
        public static double ToRadians(this double degrees)
            => degrees * Math.PI / 180.0;
    }
}

Cyclically iterating over arrays

int[] test = new int[5] { 1, 2, 3, 4, 5 };
Console.WriteLine("Iterating over the array twice."); // Expected result: 1234512345
for (int i = 0; i < 10; i++)
{
    Console.Write(test[Math.Mod(i, test.Length]); // Works like i % test.Length
}
Console.WriteLine("\nIterating backwards over the array twice."); // Expected result 5432154321
for (int i = -1; i >= -10 ; i--)
{
    Console.Write(test[Math.Mod(i, test.Length]); // Still works despite i < 0
}
Console.WriteLine("\nProgram has finished.");

Or, if the modulus had had a dedicated operator as proposed in the aforementioned discussion, you could've even written i %% test.Length, which is way more readable than Math.Mod(i, test.Length).

For the other scenarios, replace a % b by Math.Mod(a, b).

Alternative Designs

While a shared Math method is possible, the modulus function might be more appropriate as a static method on the numerical types themselves, for example int.Mod, double.Mod and so on. A similar case can be made for DateOnly.Mod and TimeOnly.Mod.

Risks

I don't see how adding a new method to the Math library or to individual numerical classes would pose any big risks. There is a slight concern for possible bugs because the modulus function would have to be implemented separately for each numerical type (and non-numerical types that would benefit from it), so a simple copy and paste won't cut it, unless the modulus function can be implemented generically somehow.

dotnet-issue-labeler[bot] commented 2 years ago

I couldn't figure out the best area label to add to this issue. If you have write-permissions please help me learn by adding exactly one area label.

ghost commented 2 years ago

Tagging subscribers to this area: @dotnet/area-system-numerics See info in area-owners.md if you want to be subscribed.

Issue Details
### Background and motivation # The case for positive remainders (number division) ## Background This proposal is inspired by [a previous request](https://github.com/dotnet/csharplang/discussions/4744) to add an operator dedicated to returning the non-negative remainder when dividing two numbers, but adding a whole operator is way beyond this API request. Nonetheless, I will be summarising the anecdotal use cases and arguments in favour of adding this functionality to the runtime. ## The existing `%` operator for calculating the remainder Currently there exists a `%` operator in C# that yields a remainder when dividing some number `a` by some other number `b`, for example `14 % 3` yields `2` because `14` is `2` more than `12`, which is the biggest multiple of `3` that fits into `14`. Similarly, `8 % 2` is `0` because `8` already is a multiple of `2`. This is most likely the most common use case for the `%` operator, and it works just fine for that purpose. ## Issues with the existing `%` operator Judging by the example above, you might think that the `%` operator works like the so-called [modular arithmetic](https://en.wikipedia.org/wiki/Modular_arithmetic), also known as clock arithmetic. Imagine it's 7 PM and you want to know what time it will be in 8 hours. After 5 hours it is 12 AM, so after another 3 hours it will be 3 AM. In other words `(7 + 8) % 12` should be equal to `3`, which it is in C#. What about going backwards in time? If it's 3 AM, then 8 hours ago it was 7 PM. So does that mean that `(3 - 8) % 12` is equal to `7` in C#? Nope, it isn't. For historical reasons, the `%` operator sometimes yields negative remainders, and `(3 - 8) % 12` actually returns `-5`. When you only use positive numbers, then the `%` operator should cause you no headaches. But once negative numbers are involved and you'd like to use it like clock arithmetic, like in the examples above, you're out of luck; you'll have to write your own methods to return a non-negative remainder (modulo) for the various existing numerical types. ## Practical use cases for a non-negative remainder Building on the basic clock arithmetic example, a non-negative remainder greatly increases the quality of life for programming anything that is cyclic in nature, such as times, dates, and angles. ### Working with angles It's common to normalise or sanitise an angle `theta` so that it always falls within the interval 0 ≤ `theta` < 2π, for example in robotics and other branches of maths and engineering. If you try to do this with `%`, then you will run into issues when `theta` inevitably becomes less than 0. On the other hand, the non-negative modulus will always satisfy this condition. ### Cyclical map design (Pacman) When you make a 2D game that works like Pacman, you may define a playable area where each position can be represented as some coordinate `(x, y)` where 0 ≤ x ≤ `width` and 0 ≤ y ≤ `height`. To allow the player to cross over from one side of the map to the other, you might use the following logic: ```csharp // Player movement logic in time increments of dt (delta time) if (Input.GetKey ("w")) { // Yields a negative position when pos.y - speed * dt is less than zero pos.y = (pos.y - speed * dt) % mapHeight; } if (Input.GetKey ("a")) { // Yields a negative position when pos.x - speed * dt is less than zero pos.x = (pos.x - speed * dt) % mapWidth; } if (Input.GetKey ("s")) { pos.y = (pos.y + speed * dt) % mapHeight; } if (Input.GetKey ("d")) { pos.x = (pos.x + speed * dt) % mapWidth; } ``` As you can see, if the player keeps going up or left, their `x` or `y` position will be negative and they will effectively disappear from the playing field. With a nonnegative remainder, their position will always be between 0 and the maximum allowed value. ### Cycling over array indices In [this reply](https://github.com/dotnet/csharplang/discussions/4744#discussioncomment-729851) to the original discussion, somebody tried to implement cyclical or circular array indexing using the existing `%` operator. But similarly to the *wrapped* playing field of Pacman, using negative indices causes an `IndexOutOfRangeException` when you wouldn't expect one. ### Conversion from Python (and possibly other languages) In Python, the same `%` operator exists, but it always returns a positive remainder. When you create a software in Python using this operator, and you want to convert your Python code to C# code, it's not unreasonable to think that the operator implementations are the same. Unfortunately, like [this person says](https://github.com/dotnet/csharplang/discussions/4744#discussioncomment-2181603) in the aforementioned discussion, their C# code behaved differently to the Python code they had written. ## Why should this be standard? The scenarios above may seem rather uncommon or niche, and it is true that the average "corporate" programmer may never need this functionality to write an "ordinary" application. However, C# is being used more and more in game development and in embedded development (e.g. robotics, computer vision, etc.), two rather maths-heavy industries. Programmers who are involved with anything geometrical will benefit greatly from a proper modulo function in the runtime. At the time of writing, there are [over 100k results](https://github.com/search?l=C%23&q=Math.Mod&type=Code) on Github for C# alone when you look up code that contains `Math.Mod`. Sure, only a fraction of those results are actually custom implementations of the non-negative remainder, but it is being used quite a lot in scientific areas as well as in cryptography. The modulo function is quickly written, and there certainly are NuGet packages out there for maths extensions like [MathNet.Numerics](https://www.nuget.org/packages/MathNet.Numerics/5.0.0-alpha09) that probably contain a modulo function somewhere, but if it's just this one method that doesn't depend on a whole host of other functionalities, why not just include it in the runtime? The existing `%` operator is utterly useless half the time, after all. People who need a modulo function in C# will definitely start looking for it in the `Math` library first, and go to [stack overflow](https://stackoverflow.com/questions/10065080/mod-explanation) next, eventually finding their way to the discussion that inspired this API request. ### API Proposal ```csharp namespace System { public static partial class Math { /// Calculates the positive remainder when dividing a dividend by a divisor. public static double Mod(double dividend, double divisor) { double remainder = dividend % divisor; if (remainder < 0) { if (divisor < 0) { return remainder - divisor; } return remainder + divisor; } return remainder; } } } ``` ### API Usage # Usage examples ## Sanitising angles ```csharp public class Program { public static void Main() { double theta = 45.0; // theta is between 0° and 360° double thetaRad = fortyFive.ToRadians(); // thetaRad is between 0 and 2π Console.WriteLine(thetaRad == thetaRad.Sanitise()); // true double thetaBig = 405.0; // thetaBig is NOT between 0° and 360° double thetaBigRad = thetaBig.ToRadians(); // thetaBigRad is NOT between 0 and 2π Console.WriteLine(thetaBigRad == thetaBigRad.Sanitise()); // false Console.WriteLine(thetaRad == thetaBigRad.Sanitise()); // true because 405 = 45 + 360 } public static class AngleExtensions { public static double Sanitise(this double radians) => Math.Mod(radians, 2 * Math.PI); public static double ToDegrees(this double radians) => radians * 180.0 / Math.PI; public static double ToRadians(this double degrees) => degrees * Math.PI / 180.0; } } ``` For the other scenarios, replace `a % b` by `Math.Mod(a, b)`. ### Alternative Designs While a shared `Math` method is possible, the modulo function might be more appropriate as a static method on the numerical types themselves, for example `int.Mod`, `double.Mod` and so on. A similar case can be made for `DateOnly.Mod` and `TimeOnly.Mod`. ### Risks I don't see how adding a new method to the `Math` library or to individual numerical classes would pose any big risks. There is a slight concern for possible bugs because the modulo function would have to be implemented separately for each numerical type (and non-numerical types that would benefit from it), so a simple copy and paste won't cut it, unless the modulo function can be implemented generically somehow.
Author: JansthcirlU
Assignees: -
Labels: `api-suggestion`, `area-System.Numerics`, `untriaged`
Milestone: -
aaronfranke commented 2 years ago

I am in agreement with this API proposal except for a few things:

JansthcirlU commented 2 years ago

@aaronfranke

* The behavior should allow for wrapping into negatives when given a negative divisor value. For example, `Mod(x, 3)` can return 0, 1, or 2, while `Mod(x, -3)` should return 0, -1, or -2. This means the sign is dependent on the second argument, unlike the current behavior of the `%` operator where the sign is dependent on the first argument.

Hard disagree. The proposal is for a positive remainder for a reason. As for your other points, I leave that up to the people working in Numerics but if you have any extra implementation details that you want me to include in the alternatives section let me know.

tannergooding commented 2 years ago

There are optimization opportunities that the C# compiler should take into account that we should define. For example, with Mod(x, 16) where x is an integer, this can be optimized to a bitwise AND operation: x & 15.

The C# compiler couldn't and wouldn't optimize this without it being "built-in" to the language. Even if it was built-in, this is really more of a JIT optimization and should be dependent on what's best for the target platform.

tannergooding commented 2 years ago

Overall, I likewise thing this is a good API addition, but the API surface likely needs to be tweaked a bit.

Notably, we are introducing a new feature Generic Math which will involve exposing almost all of the API from Math and MathF on the relevant primitive types. So rather than Math.Max(x, y), you will be able to do int.Max(x, y) (or in a generic context; T.Max(x, y)), etc.

This actually brings the primitive types inline with how most other types in .NET expose their operations. We don't have things like BigIntegerMath, ComplexMath, VectorMath, TensorMath, RationalMath, DateTimeMath, etc. Most types (in the BCL and outside as well) expose these "static 'operator like' methods" on the actual type. So you have BigInteger.Max instead.

There are also versioning issues with exposing methods on Math (this is why we introduced MathF for float) and once a given set of types are supported, adding overloads for new types can be breaking. Due to this (and as from a previous API review), the APIs added to Math and MathF will likely be rare moving forward and new APIs will target the primitives by default.

As such, the API proposal should likely be updated to explicitly call these out as being added as Double.Mod, Single.Mod, and Half.Mod. If the intent is to also cover integer types, they should also explicitly call out and cover Byte, Int16, Int32, Int64, IntPtr, SByte, UInt16, UInt32, UInt64, and UIntPtr.

The name should also be spelled out. Mod itself isn't enough and can be confused between Modular, Modulo, Modulus, etc. Not everything is consistent on which one the term Mod means (and unfortunately the % operator is internally already called op_Modulus in IL).

tannergooding commented 2 years ago

However, I also want to call out that I think some of the motivating scenarios aren't, in my own opinion, realistic as described and while it does allow for a "simpler" algorithm (for some meaning of the word), many of the scenarios where its being raised as applicable wouldn't actually use the operation here.

The generic Mod implementation (like division and remainder) is a relatively "expensive" operation and while the JIT might be able to optimize certain scenarios (namely those involving constants), there are many real scenarios where it couldn't optimize. For cases like games where you may want screen wrapping behavior, its actually much simpler and more efficient to simply do a simple compare and replace/subtract/other operation.

Likewise, with concepts like rotation, normalization is not always appropriate nor is assuming that negative values are invalid. Consider for a moment a clock. The clock face has 12 numbers on it and each number is ~30 degrees apart. Because of this, rotation of a clock hand by 30 and rotation of -330 will both end up with the hand on the 1. However, these are not the same operations. Namely, rotation by 30 degrees will rotate the hand clockwise and so it will cross from twelve down to one and not pass any other numbers. While rotation by -330 degrees will cross from twelve, through 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, and then finish on 1. If you are doing any kind of animations, interpolation, or the like then these are very different and observing them part way through will show different results.

Even with time, while you may just want to know "it was 7", that's technically losing important information. Namely, that there is a transition here between AM and PM (and possibly even day) that is carried by the sign. This means that for the regular % operator, 0 means "no transition", positive means a forward transition, negative means a reverse transition. In the negative case you add to the upper bound (so 12 + (-5)) and in the positive case you add to the lower bound (0 + x). This gives you a correct result and preserves all the relevant information on if the AM/PM transition or a day transition occurred (particularly when used in conjunction with the regular division operation).

So while it is a generally useful operation and I do think its worth adding, we should ensure the use-cases and issues around it are clearly specified and are account for the practical cases where there is a better alternative or where the current % may be more desirable.

ghost commented 2 years ago

This issue has been marked needs-author-action since it may be missing important information. Please refer to our contribution guidelines for tips on how to report issues effectively.

aaronfranke commented 2 years ago

Likewise, with concepts like rotation, normalization is not always appropriate nor is assuming that negative values are invalid.

...which is why you would only use Mod (or %%) when you specifically don't want them.

Namely, that there is a transition here between AM and PM (and possibly even day) that is carried by the sign. This means that for the regular % operator, 0 means "no transition", positive means a forward transition, negative means a reverse transition.

It's not. That information is not carried by the sign. (7 + 8) % 12 is 3 which transitions between AM/PM, and (7 + 2) % 12 is 9 which does not transition. Both outputs are positive but there is a different in whether there is a transition. Your claim of "0 means "no transition", positive means a forward transition" is just simply false.

tannergooding commented 2 years ago

Both outputs are positive but there is a different in whether there is a transition. Your claim of "0 means "no transition", positive means a forward transition" is just simply false.

You're right, I messed up the statement on the positive case. The negative case is still correct however and it is important information (and also often hardware accelerated/special-cased).

tannergooding commented 2 years ago

...which is why you would only use Mod (or %%) when you specifically don't want them.

Right, which is what I was calling out here. It is important to differentiate possible use-cases vs real use-cases.

Many of the mentioned scenarios are possible use-cases where in practice there is a better or simpler alternative, particularly where performance is important.

As I stated, I'm fine with this being taken through to API review. The API surface just needs a few tweaks and it would be helpful to ensure that the use-cases called out are real use cases where using mod is the best and most correct thing.

No API is perfect and there are always drawbacks, downsides, and alternatives. Explicitly listing those will actually help in clarifying where this will be used and therefore increase the chances of the API review team, in total, agreeing that it should be exposed.

JansthcirlU commented 2 years ago

@tannergooding I understand that in terms of scientific computing and real-time computational effort, the proposed modulo function would be too slow and undesirable, but I'm mostly raising this proposal as a quality-of-life feature.

Consider the atan2(y,x) function for calculating the arc tangent as an analogy. atan2 exists because its single-argument counterpart, atan(d), doesn't distinguish between angles that are 180° apart, meaning you would get the wrong result about half the time if you asked something like atan(tan(theta)) == theta for random angles.

atan2 may or may not be as expensive to implement as the proposed mod function, but there clearly is an overhead compared to the single-argument function (comparing the values of x and y, computing the original atan, adding or subtracting π/2). In the case of atan2, the added cost was deemed acceptable given the benefits provided by this function.

I'm simply arguing that the inclusion of a real mod in C# would make the language more readable, elegant and uniform.

Naming concerns

Concerning the naming conventions, within the context of integer division, modulo is the name of the operation itself (i.e. Euclidean long division) while the modulus is the remainder that results from doing the operation.

Using Wikipedia as a reference, the term modulo is pretty much exclusive to modular arithmetic. The term modulus is a bit more general, but I personally believe that people won't primarily associate the term with complex numbers, especially when they're working with regular real scalars (C# numerical types). The word Mod itself is also pretty much only used to mean modulo, module or modification, the latter two being unrelated to mathematics and numbers.

I think Mod as a name should be good enough, given the context and prospective use cases, if it weren't for...

Generic Math

It's unfortunate that the interface for % is called IModulusOperators, when it currently represents a different type of remainder. That being said, in an ideal world, it would just be a matter of implementing the IModulusOperators interface correctly for the standard numerical types in .NET right? That way, the caveats that concern the existing % implementation would be resolved even more elegantly than by adding a new function to each type.

But I'm guessing that at that point, the performance concerns that you brought up would start coming into play, right?

tannergooding commented 2 years ago

I'm simply arguing that the inclusion of a real mod in C# would make the language more readable, elegant and uniform.

Yes, and as I've said you don't need to argue to me for its inclusion. I think it's a good idea to provide.

I'm trying to call out what kind of questions, interests, and concerns the rest of API review will be raising when it does go to API review. I'm calling them out so the original post can be updated to provide accurate context of where this will be realistically used, where it will provide quality of life, to call out the scenarios it could be used but where there are better alternatives, etc. All of this will actually help simplify the API review process and help in getting it approved.

I think Mod as a name should be good enough, given the context and prospective use cases, if it weren't for...

It will likely not be. It goes against the normal .NET naming conventions and framework design guidelines. We prefer explicit names, particularly where there might be ambiguous interpretations.

The default suggestion should be an appropriate full name and we can call out that Mod might be a viable alternative, but with "x, y, and z" as considerations.

That being said, in an ideal world, it would just be a matter of implementing the IModulusOperators interface correctly for the standard numerical types in .NET right?

That's for some definition of "ideal". In either case, its 20 years too late to do anything here and there is a fixed convention that these new interfaces will be following.

We won't be modifying what % or op_Modulus means and we won't be changing the default behavior for these for generic math, etc.

The new method being proposed needs to stand on its own and alongside the considerations around the existing operator and its underlying IL name.

squeakyneb commented 2 years ago

Likewise, with concepts like rotation, normalization is not always appropriate nor is assuming that negative values are invalid.

If you're doing a transform to a domain where negative numbers are used, you generally still start with a range of 0..$range, then shift it with addition/subtraction. So degrees of a circle might be mod(x, 360) for 0..360, and if you were working -180..180 you'd do mod(x + 180, 360) - 180. The maths you would do to put something in a -1..1 range would, given a negative input, deliver it to a -3..-1 space, which is not useful-1&v2=true&f3(x,t)=&v3=false&f4(x,t)=&v4=true&f5(x,t)=&v5=false&f6(x,t)=&v6=false&grid=1&coords=0,0,12).

For positive inputs, % operator already works like this, but for negative inputs it instead dunks you in a second domain of negative numbers. That's often not useful. Negative hue values are invalid for example. If I animate the position of something one direction (say, like mod(startPos + time, X)) it'll work fine, but if I go the other direction (mod(startPos + time, X)) it'll go once and then disappear into the shadow realm of negative coordinates which just don't exist on my display device. (this is similar to the Pacman example)

The problem is isn't to do with negatives necessarily. Modulo is used to fold a domain over itself. The existing % operator will fold your domain into one of two different spaces, depending on the sign of the input, which is not useful for these classes of math. The input is a continuous spectrum of values, and 0 is just an arbitrary point of no real significance, the meaning of the maths should not change when crossing it.

That is, the purpose of modulo is different to the purpose of remainder. C# has remainder in %, it has no modulo . The two are often confused, but have different applications.

This may seem relatively minor to someone who doesn't do much mathematical coding, but for some of us this is bread and butter. Yes, we could fix the sign ourselves, but that feels to me very much like not having - subtraction because you can just add the negation of your second number.

tannergooding commented 2 years ago

Yes, I understand the differences and nuance here. My statement wasn't one on we shouldn't have this, it was one on requesting the examples used and given be accurate representations with callouts for where it will actually get used.

Mod is most often used when you need wrapping behavior and it largely has to consider three scenarios:

The first is a very common thing in code and its why users are often able to successfully use % for "wrapping" like behavior. The second is where users often get "tripped" up with % and it often occurs in things like dates, times, or other scenarios where you want to move backwards. The third can happen, but is the least common of them.

A general purpose mod API has to take all three of these scenarios into account and that also means it has to do additional checks that, outside constant folding, can't be optimized away. For something that is perf oriented, this also means that a general-purpose mod is likely doing more work than necessary and that will often make it a suboptimal choice. However, the move from rem to mod is trivial for the middle scenario as its largely just y + rem, meaning it can be simplified to a single branch, and so if you are doing something like games or multimedia, its often better to have a "roll your own" that just considers that only one will be negative (often x).

My callout was only on this and that I expect using it in high-perf scenarios won't be warranted or done in practice since the naive algorithm (((x % y) + y) % y) is very costly and the optimized algorithm still has 2-3 branches/checks required (floating-point can be even worse).

No API is "perfect" and it is just as important to call out the negatives and real world downsides as it is to call out the positives it will bring. This will help API review understand the considerations and make the best decision as to its support.

castholm commented 2 years ago

Glad to see this being proposed. Many times have I found myself in situations where I've needed a floored/Euclidean remainder and wished there was a Math.Mod or Math.EuclideanRem instead of needing to implement it on my own over and over again.

One thing I'd like to add to the discussion is that the remainder is not the only interesting result of Euclidean division and that the quotient is also useful. For example, when grouping numbers into regular intervals of 10 (e.g. integers.GroupBy(x => x / 10)) you usually wouldn't want -9 and 9 to be grouped into the same interval, which the default truncating division will do.

Therefore it would feel like a missed opportunity to add a function that returns the remainder of Euclidean division but not a corresponding function that returns the quotient. And because the canonical algorithm already needs the remainder to compute the quotient, why not also include a third function that efficiently computes and returns both?

/// <summary>Returns the quotient of the Euclidean division of two values.</summary>
public static int EuclideanDiv(int left, int right);
public static double EuclideanDiv(double left, double right);

/// <summary>Returns the remainder of the Euclidean division of two values.</summary>
public static int EuclideanRem(int left, int right);
public static double EuclideanRem(double left, double right);

/// <summary>Returns the quotient and the remainder of the Euclidean division of two values.</summary>
public static (int Quotient, int Remainder) EuclideanDivRem(double left, double right);
public static (double Quotient, double Remainder) EuclideanDivRem(double left, double right);

(I'm choosing the names "div" and "rem" here because there is already a precedent in the form of Math.DivRem and because I think it is helpful to include the name of the algorithm in the method name (compare to Java's floorDiv or Rust's div_euclid/rem_euclid) but I do not feel very strongly about the names. I'm also omitting other numeric types for brevity.)

crozone commented 1 year ago

@tannergooding

My callout was only on this and that I expect using it in high-perf scenarios won't be warranted or done in practice since the naive algorithm (((x % y) + y) % y) is very costly and the optimized algorithm still has 2-3 branches/checks required (floating-point can be even worse).

I'd think that if absolute performance was really critical when using this API, you'd profile the code and go from there. Either the JIT will be able to emit the special case for you (and you'll see it in he decompilation), if not you roll your own implementation to guarantee you get the right performance.

Most of the time the b argument is going to be constant-ish. How much would tiered compilation help with this?

Lastly, even if different APIs were provided for each of the cases (eg one for positive b and one for negative b), wouldn't they need to branch to check that they are working with valid inputs? Or should they just produce invalid data?

aaronfranke commented 1 year ago

If b is a power of two, then this can be optimized to bitwise AND. This is an optimization that is already done with % if you use uint, but having a %% operator or a Mod method would allow this optimization with %%. @tannergooding is looking at the worst-case scenario for optimization being worse, but the best-case scenario for optimization is better.

PavielKraskouski commented 1 year ago

Another use case is texture wrapping. It can be used to repeat (uv mod 1) or mirror (abs(((uv - 1) mod 2) - 1)) a texture if the texture coordinates are outside the 0..1 range.

kaisadilla commented 1 year ago

Just want to add my support for this proposal. I find myself needing modulo a lot more often than I need remainder (which is almost never). In more high-level / business apps, you don't usually care. But when you are working on anything related to technical implementations (e.g. graphic libraries) or math, modulo is useful a lot of the time, while remainder usually isn't. To add an example, wrapping behavior (i.e. when you have a range between x and y, and y + 1 should return x, and x - 1 should return y) is quite common and you usually need to calculate modulo (not remainder) to implement said behavior.

I don't see any reason why remainder should need a specific operator (%) but modulo shouldn't. Either %% or a "mod" keyword would be a nice addition to the language. Being operations that can be easily optimized by compilers, because they are very close to the metal so to say, being a language feature rather than just another function in a Math library more clearly communicates how "basic" this operation really is.

aaronfranke commented 1 year ago

@kaisadilla For the proposal of this being an operator, see this discussion. Feel free to upvote and share it :)

tannergooding commented 1 year ago

@tannergooding is looking at the worst-case scenario for optimization being worse, but the best-case scenario for optimization is better.

The JIT is already able to understand and optimize various cases of int % int when both are "never negative" and cases of int % cns otherwise. This is even true when Array.Length and as of a couple days ago Span<T>/ROSpan<T>.Length is used. There is always room for improvement, but it's already generally doing the correct thing and we will continue optimizing other cases where relevant.

how "basic" this operation really is.

It being "basic" is debatable. It is an operation that several languages provide and which can be beneficial to expose for some domains. However, it is not something that you universally find support for nor is it something that is even regularly supported in hardware.

What C# exposes as the % operator however is quite the opposite. Computing the remainder is a primitive operation supported and accelerated by a wide range of hardware and which is the default for a majority of programming languages (particularly those that .NET is similar to). It likewise behaves the same as "mod" for the most common inputs, which are "never negative" so it is not often an issue.

It can be used to repeat (uv mod 1) or mirror (abs(((uv - 1) mod 2) - 1)) a texture if the texture coordinates are outside the 0..1 range.

Can be used and "should" be used are not always the same thing. Particularly when it comes to floating-point, "mod" is not efficient and it would be the "wrong" thing to use for any kind of graphics processing.

For the common case of wrapping to be between 0..1, you're better off doing a truncation and then adjusting if x was negative. This can be done branchlessly and also works well with SIMD or SIMT (GPU logic). -- However, even without SIMD and with branching, it's still more efficient than mod.


I am not against this API, but I am against misrepresenting its usage scenarios and the criticalness of such an API.

The following need clear and concise answers before it could move forward:

squeakyneb commented 1 year ago

Can be used and "should" be used are not always the same thing. Particularly when it comes to floating-point, "mod" is not efficient and it would be the "wrong" thing to use for any kind of graphics processing.

For the common case of wrapping to be between 0..1, you're better off doing a truncation and then adjusting if x was negative.

It may be the case that this specific example can be truncated like that, but mod as described does come up an awful lot in those sorts of contexts. There are domains of maths where the sign of a number is simply not significant, and what you want to do is fold the domain of numbers into a space [0..n). In those terms, it's silly that I would want to transform a number into a range of "[0..n) but if it's negative [0..-n) and also the order is reversed" but that's what the existing % operator does. From a mathematical perspective, it's a fairly core primitive operation.

The amount of interaction on these threads and StackOverflow posts about it make that clear that there is desire for this operation. As I noted in my comment on the prior thread, "it's really quite frustrating that not only does my maths no longer work [...] but there's no replacement on-hand". The fact that so many people are hand-writing their own implementation of the very same and very simple maths operation feels like a very good hint that something is missing.

All that to say: I don't think the examples being contrived is really very important to discuss. Examples are generally fairly contrived anyway.

This ideally should give examples of what it is called in other languages/libraries (preferably well known ones)

This is going to be a difficult problem. There's many definitions in maths generally, and again referring to my previous comment "C++ [...] brings along std::fmod and std::remainder (ironically, fmod does what C# calls remainder, and remainder does what is being asked for here)", so there's already big disagreements between significant languages. Despite it being documented as "Remainder operator %" apparently it's also "op_Modulus" internally, so both names usually used by other languages to differentiate them have already collided in C#.

Rust calls it "rem_euclid" as inspired by Euclidean division (and has corresponding Euclidean division functions), which might be a good idea to follow.

Daynvheur commented 3 months ago

Hello, long time inactive, this topic is still important nowadays. I've had a use case where I needed to get the modulus and the diviser parts in one go, so even if that's not the exact op's point, it may act as a working example:

static (int entier, double reste) GetDivMod(double valeur, double baseValeur, bool forceSigne = false)
{
    double reste = valeur;
    int entier = 0;
    if (baseValeur > 0)
    {
        while (reste >= baseValeur)
        {
            entier++;
            reste -= baseValeur;
        }
        while (forceSigne && reste < 0)
        {
            entier++;
            reste += baseValeur;
        }
    }
    else if (baseValeur < 0)
    {
        while (reste <= baseValeur)
        {
            entier--;
            reste -= baseValeur;
        }
        while (forceSigne && reste > 0)
        {
            entier--;
            reste += baseValeur;
        }
    }
    //else noop; 

    return (entier, reste);
}

I didn't checked the performance, only those use cases:

Too Long;Didn't Read: implementation with false is current % behaviour, whereas true keeps the sign of the modulus part. Actually relevant to the discussion is the reste part of the tuple, while entier counts the number of times required to come in modulus' range.

How far away is it to have the Math lib having an operator like that baked in?