Skip to content

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

Closed as duplicate of#93568
@JansthcirlU

Description

@JansthcirlU

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    api-suggestionEarly API idea and discussion, it is NOT ready for implementationarea-System.Numericsneeds-further-triageIssue has been initially triaged, but needs deeper consideration or reconsideration

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions