C#中的数学模运算

34

C#中有没有一个库函数可以用于计算数字的模数 - 具体而言,当负整数模正整数时,应产生一个正结果。

编辑后提供一个示例:

-5模3应返回1

8个回答

30

试试这个方法,它能够正确地工作:

(a % b) * Math.Sign(a)

static int MathMod(int a, int b) {
    return (Math.Abs(a * b) + a) % b;
}

3
+1因为你是第一个发布正确算法的人(但要注意溢出!)。严格回答他的问题,没有内置于框架中可以实现此功能的内容。 - Craig Stuntz
13
虽然被认为是第一个正确的算法,但我认为更倾向于使用 ((a % b) + b) % b。 - penguat
7
在这个实现中,Math.Abs非常慢,而((a % b) + b) % b在我的基准测试中使用c# .net 4是最快的。 - thalm
2
此方法可能会导致溢出警告!! - Cyril Gandon
2
我唯一想改变的是变量的名称,分别改为被除数和除数,就像http://www.arduino.cc/en/Reference/Modulo中所示。 - Hamish Grubijan
显示剩余2条评论

8
x < 0 ? ((x % m) + m) % m : x % m;

为什么要使用三元运算符? - penguat
@penguat:因为如果x是非负数,那么就没有必要进行额外的工作。 - Michael Petito
我不确定条件语句和固定加法模运算哪个平均更费力,但在我的情况下这并不重要。无论如何,这个答案是正确的。谢谢。 - penguat
1
@penguat:很难说;我总是认为除法和模运算相对较昂贵(除非它们是微不足道的,比如除以2或取模2)。我想三元运算符的开销仅为检查和跳转的几个周期。此外,在这个计算中,如果x是非负数,就没有溢出的可能性。然而,如果没有三元运算符,当x = 1m = int.MaxValue(或在任何0 < x < m且x + m > int.MaxValue的情况下)时,你可能会溢出。 - Michael Petito
1
我肯定猜测在现代处理器上,使用额外的模数运算速度比分支更快。但是,我从未进行过测试,所以我不太确定。此外,这是一个相当微小的区别。我不确定哪个更易读。一开始我想说不使用三目运算符更容易读懂,但是当我仔细想想, x < 0确实告诉读者为什么我们要做一些花哨的事情,而无需解释。 - clahey

4

如果我没记错,mod的定义应该是这样的:

a mod b = a - b * floor(a/b)

这个方法可能比较慢,而且要注意整数除法,就像内置的modulus一样。

另一个选项是根据操作数的符号修改内置模数的结果。可以尝试以下代码:

if(a < 0 && b > 0)
{
    return (a % b + b) % b;
}
else if ....

2
a < 0 ? ((a+1)%b + b-1) : (a%b);

这只需要一个%运算符(和一个三目运算符),不需要乘法。

1

如果您正在使用这些算法并且需要进行除法运算,请不要忘记在适当时候减去1。

I.e.,

如果-5%2 = -1-5 / 2 = -2,并且如果您关心-5 / 2 * 2 + -5%2 = -5,那么当您计算-5%2 = 1时,也要计算-5 / 2 = -3

1
我不太确定你在这里想表达什么......通常我会保留原始数字而不是尝试重构它。 - penguat
我并不建议你重新构建它。我只是担心你的算法也可能使用 / 运算符,并且依赖于它们的一致性。 - clahey

0

我知道这个问题并没有要求,但我刚刚编写和测试了一个方法,它可以返回商。当我正在寻找这个方法时,并没有找到相关的内容,所以我认为我应该将它分享出来。

/// <summary>
/// Compute integer quotient and remainder of <paramref name="dividend"/> / <paramref name="divisor"/>
/// where the <paramref name="remainder"/> has the same sign as <paramref name="divisor"/>, and is
/// between zero (inclusive) and the <paramref name="divisor"/> (exclusive). As always,
/// (quotientResult * <paramref name="divisor"/> + <paramref name="remainder"/> == <paramref name="dividend"/>).
/// </summary>
public static int DivRemPeriodic(int dividend, int divisor, out int remainder) {
    var quotient = Math.DivRem(dividend, divisor, out remainder);
    if (divisor > 0 ? remainder < 0 : remainder > 0) {
        remainder += divisor;
        quotient -= 1;
    }
    return quotient;
}

这里的余数等同于我所要求的模数 - 还包括商。对于小于零的除数,结果可能会有所不同。 - penguat
1
糟糕!是的,我想说“同时返回商”。已更正。它适用于具有注释文档中给出结果的负除数。余数(又称“模数”)与除数具有相同的符号。对于负除数,模数在范围(-除数,0]内周期性地变化。 - Keith Robertson

0
修复:

(ans=a%b)<0 ? (a<0 && b<0 ? (ans-b)%(-b) : (ans+b)%b) : ans


那么-10 mod 3呢?你的解决方案会给出-1,而需要的答案是2。 - penguat
@aenguat a=-10,b=3 答案为-1 < 0,因此解决方案为(-1+3) % 3 =2 有什么问题吗? - Mister_Egg_Head

-3

当执行-5 % 2时,这并不会产生积极的结果。 - Snake
他希望-5%2的结果是**+1**,而不是-1。 - SLaks

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接