229

我正在尝试修改一个整数以获得一个数组位置,以便它循环。对正数做i % arrayLength的很好,但对于负数,一切都会出错。

 4 % 3 == 1
 3 % 3 == 0
 2 % 3 == 2
 1 % 3 == 1
 0 % 3 == 0
-1 % 3 == -1
-2 % 3 == -2
-3 % 3 == 0
-4 % 3 == -1

所以我需要一个实现

int GetArrayIndex(int i, int arrayLength)

这样

GetArrayIndex( 4, 3) == 1
GetArrayIndex( 3, 3) == 0
GetArrayIndex( 2, 3) == 2
GetArrayIndex( 1, 3) == 1
GetArrayIndex( 0, 3) == 0
GetArrayIndex(-1, 3) == 2
GetArrayIndex(-2, 3) == 1
GetArrayIndex(-3, 3) == 0
GetArrayIndex(-4, 3) == 2

我以前做过这个,但由于某种原因,它今天融化了我的大脑:(

4

14 回答 14

333

我总是使用自己的mod函数,定义为

int mod(int x, int m) {
    return (x%m + m)%m;
}

当然,如果你对两次调用模数运算感到烦恼,你可以把它写成

int mod(int x, int m) {
    int r = x%m;
    return r<0 ? r+m : r;
}

或其变体。

它起作用的原因是“x%m”总是在 [-m+1, m-1] 范围内。因此,如果它是负数,添加 m 会将其置于正范围内,而不会改变其模 m 的值。

于 2009-07-04T20:35:50.110 回答
98

请注意,C# 和 C++ 的 % 运算符实际上不是模数,而是余数。在您的情况下,您想要的模数公式是:

float nfmod(float a,float b)
{
    return a - b * floor(a / b);
}

您必须在 C#(或 C++)中重新编码,但这是获得模数而不是余数的方式。

于 2011-06-19T04:07:56.347 回答
21

%仅使用一次的单行实现:

int mod(int k, int n) {  return ((k %= n) < 0) ? k+n : k;  }
于 2014-04-22T08:27:50.500 回答
13

比较两个主要答案

(x%m + m)%m;

int r = x%m;
return r<0 ? r+m : r;

没有人真正提到第一个可能会抛出OverflowException而第二个不会的事实。更糟糕的是,使用默认的未经检查的上下文,第一个答案可能会返回错误的答案(参见mod(int.MaxValue - 1, int.MaxValue)示例)。所以第二个答案不仅看起来更快,而且更正确。

于 2018-06-25T07:49:53.723 回答
7

ShreevatsaR 的答案不适用于所有情况,即使您添加“if(m<0) m=-m;”,如果您考虑负股息/除数。

例如,-12 mod -10 将是 8,它应该是 -2。

以下实现适用于正数和负数除数/除数,并符合其他实现(即 Java、Python、Ruby、Scala、Scheme、Javascript 和 Google 的计算器):

internal static class IntExtensions
{
    internal static int Mod(this int a, int n)
    {
        if (n == 0)
            throw new ArgumentOutOfRangeException("n", "(a mod 0) is undefined.");

        //puts a in the [-n+1, n-1] range using the remainder operator
        int remainder = a%n;

        //if the remainder is less than zero, add n to put it in the [0, n-1] range if n is positive
        //if the remainder is greater than zero, add n to put it in the [n-1, 0] range if n is negative
        if ((n > 0 && remainder < 0) ||
            (n < 0 && remainder > 0))
            return remainder + n;
        return remainder;
    }
}

使用 xUnit 的测试套件:

    [Theory]
    [PropertyData("GetTestData")]
    public void Mod_ReturnsCorrectModulo(int dividend, int divisor, int expectedMod)
    {
        Assert.Equal(expectedMod, dividend.Mod(divisor));
    }

    [Fact]
    public void Mod_ThrowsException_IfDivisorIsZero()
    {
        Assert.Throws<ArgumentOutOfRangeException>(() => 1.Mod(0));
    }

    public static IEnumerable<object[]> GetTestData
    {
        get
        {
            yield return new object[] {1, 1, 0};
            yield return new object[] {0, 1, 0};
            yield return new object[] {2, 10, 2};
            yield return new object[] {12, 10, 2};
            yield return new object[] {22, 10, 2};
            yield return new object[] {-2, 10, 8};
            yield return new object[] {-12, 10, 8};
            yield return new object[] {-22, 10, 8};
            yield return new object[] { 2, -10, -8 };
            yield return new object[] { 12, -10, -8 };
            yield return new object[] { 22, -10, -8 };
            yield return new object[] { -2, -10, -2 };
            yield return new object[] { -12, -10, -2 };
            yield return new object[] { -22, -10, -2 };
        }
    }
于 2014-01-04T18:08:44.037 回答
4

只需将您的模数(arrayLength)添加到 % 的负结果中,您就可以了。

于 2009-07-04T20:31:52.147 回答
3

增加一些理解。

根据欧几里得定义,mod 结果必须始终为正。

前任:

 int n = 5;
 int x = -3;

 int mod(int n, int x)
 {
     return ((n%x)+x)%x;
 }

输出:

 -1
于 2013-10-29T10:32:24.290 回答
3

我喜欢 Peter N Lewis 在此线程上提出的技巧:“如果 n 的范围有限,那么您只需将 [除数] 的已知常数倍数相加即可获得所需的结果,该倍数大于最低限度。”

因此,如果我有一个以度为单位的值d并且我想取

d % 180f

如果d是负数,我想避免这些问题,那么我只是这样做:

(d + 720f) % 180f

这假设尽管d可能是负数,但已知它永远不会比 -720 更负数。

于 2013-04-15T19:12:25.617 回答
3

您期望的行为与 c# 中 % 运算符的记录行为相反 - 可能是因为您期望它以您更习惯的另一种语言工作的方式工作。关于 c# 状态的文档(强调我的):

对于整数类型的操作数,a % b 的结果是 a - (a / b) * b 产生的值。非零余数的符号与左侧操作数的符号相同

你想要的值可以通过一个额外的步骤来计算:

int GetArrayIndex(int i, int arrayLength){
    int mod = i % arrayLength;
    return (mod>=0) : mod ? mod + arrayLength;
}
于 2020-01-23T14:32:04.370 回答
3

对于更注重性能的开发人员

uint wrap(int k, int n) ((uint)k)%n

一个小的性能比较

Modulo: 00:00:07.2661827 ((n%x)+x)%x)
Cast:   00:00:03.2202334 ((uint)k)%n
If:     00:00:13.5378989 ((k %= n) < 0) ? k+n : k

至于 cast to uint 的性能成本,请看这里

于 2015-07-31T00:29:20.377 回答
1

如果您的除数是肯定的,这里的所有答案都很好,但它并不完整。这是我的实现,它总是在 范围内返回[0, b),这样输出的符号与除数的符号相同,允许负除数作为输出范围的端点。

PosMod(5, 3)退货2
PosMod(-5, 3)退货1
PosMod(5, -3)退货-1
PosMod(-5, -3)退货-2

    /// <summary>
    /// Performs a canonical Modulus operation, where the output is on the range [0, b).
    /// </summary>
    public static real_t PosMod(real_t a, real_t b)
    {
        real_t c = a % b;
        if ((c < 0 && b > 0) || (c > 0 && b < 0)) 
        {
            c += b;
        }
        return c;
    }

real_t可以是任何数字类型)

于 2019-01-24T03:05:09.593 回答
0

dcastro答案的单行实现(最符合其他语言):

int Mod(int a, int n)
{
    return (((a %= n) < 0) && n > 0) || (a > 0 && n < 0) ? a + n : a;
}

如果您想继续使用%运算符(您不能在 C# 中重载本机运算符):

public class IntM
{
    private int _value;

    private IntM(int value)
    {
        _value = value;
    }

    private static int Mod(int a, int n)
    {
        return (((a %= n) < 0) && n > 0) || (a > 0 && n < 0) ? a + n : a;
    }

    public static implicit operator int(IntM i) => i._value;
    public static implicit operator IntM(int i) => new IntM(i);
    public static int operator %(IntM a, int n) => Mod(a, n);
    public static int operator %(int a, IntM n) => Mod(a, n);
}

用例,两者都有效:

int r = (IntM)a % n;

// Or
int r = a % n(IntM);
于 2020-04-30T13:26:22.887 回答
0

该函数有许多实现mod,我认为列出所有这些是值得的——至少根据Wikipedia,我敢肯定还有更多。

// Important to be able to use `MathF`.
using System;

public static class MathFUtils {
    public static class Mod {
        public static float Trunc(float a, float b) =>
            a - b * ((int)(a / b));

        public static float Round(float a, float b) =>
            a - b * MathF.Round(a / b);

        public static float Floor(float a, float b) =>
            a - b * MathF.Floor(a / b);

        public static float Ceil(float a, float b) =>
            a - b * MathF.Ceiling(a / b);

        public static float Euclidean(float a, float b) =>
            a - MathF.Abs(b) * MathF.Floor(a / MathF.Abs(b));
    }
}

根据维基百科(以及我的经验)坚持Euclidean. 它在数学和概率属性方面是最有用的。如果您需要Trunc,那么我相信%就是这样做的。

另外,对于那些可能对他们每个人做什么以及如何做感到困惑的人,我强烈建议您阅读 Wikipedia 文章(即使很难)并查看每个表示的图像。

当然,这些不一定是性能最高的,但它们确实有效。如果您担心性能,我建议您找一位当地的 C# 大神,或者在他们通过我们的凡人位面时询问一位。

于 2022-02-02T18:10:06.630 回答
0

这是我的一个正整数衬里,基于这个答案

用法:

(-7).Mod(3); // returns 2

执行:

static int Mod(this int a, int n) => (((a %= n) < 0) ? n : 0) + a;
于 2020-12-23T22:45:08.383 回答