% (mod) 解释

49
今天我在写C#程序时,使用%来计算一些索引...但我的程序没能工作,所以我进行了调试,发现"%"不像我所知道的其他编程语言那样起作用。
例如:
在Python中,%返回这样的值:
for x in xrange (-5, 6):
     print x, "% 5 =", x % 5

-5 % 5 = 0
-4 % 5 = 1
-3 % 5 = 2
-2 % 5 = 3
-1 % 5 = 4
0 % 5 = 0
1 % 5 = 1
2 % 5 = 2
3 % 5 = 3
4 % 5 = 4
5 % 5 = 0
在C#中:
for (int i = -5; i < 6; i++)
{
    Console.WriteLine(i + " % 5 = " + i % 5);
}

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

我做错了什么还是%没有像应该的那样工作?


47
在C语言派生的编程语言中,%不是模运算符,而是取余数运算符。Eric Lippert在他的博客中解释了这一点。 - Raymond Chen
8
@RaymondChen 这看起来像是一个回答,而不是评论。如果你愿意,我可以点赞它。 - Mr Lister
1
我该如何在C#中使用取模运算符? - Wolfy
3
如果数值为负数,Wolfy会加上5。 - Cheeso
@c = @a % @b; 如果 (@c < 0) { @c += @b; } 返回 @c; - Aaron Franke
微软公司痛斥了Lippert的博客文章,@RaymondChen提到了这一点。幸运的是,archive.org有很多备份。这是其中之一编辑:实际上看起来他们把它移到了这里,但没有转发。/耸肩 - ruffin
3个回答

20

正如评论所解释的那样,不同的行为是有意设计的。不同的语言只是将 % 运算符归属于不同的含义。

您的问题是:

如何在 C# 中使用模数运算符?

您可以自定义一个与 Python 的 % 运算符行为相同的模数运算符:

int mod(int a, int n)
{
    int result = a % n;
    if ((result<0 && n>0) || (result>0 && n<0)) {
        result += n;
    }
    return result;
}

9
你的代码给出了错误的结果,例如 mod(-5, 5) == 5。请参考这个答案以获取一个能够正常工作的示例:https://dev59.com/X3NA5IYBdhLWcg3wH6EW - Drake
@Drake @DavidHeffernan 我已修复了这个答案代码中的错误。问题在于应该检查result的值,而不是a的值。 - Aaron Franke

10

两个答案都是正确的。虽然我个人认为“始终为正数”的答案更有意义。

您可以定义自己的模函数,只返回正数,例如:

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

1
mod(-5, 2) 返回什么? - David Heffernan
该死...好的,让我再试一次。 - Niet the Dark Absol
1
mod(-2, -5) 返回-2。您曾说过它应该始终返回正值,但有些输入会导致其返回负值。这是可以接受的吗?这是预期的吗? - Eric Lippert
1
我发现像这样编写代码效率更高: int c = a%b; if(c <0){c + = b; } return c; 请注意,它不考虑负除数。 - Aaron Franke
@EricLippert 理论上它应该返回0和第二个参数之间的值(因此,如果除数为负数,则结果应该为负数;如果除数为正数,则结果应该为正数,而不管被除数的符号如何),但负除数非常罕见。 - Aaron Franke
为了考虑负除数,请参见此答案 https://dev59.com/pGkw5IYBdhLWcg3wMHum#10065670。不过这种情况可能相对较少,所以对于大多数用例,上述答案都可以正常工作。 - Aaron Franke

7
模运算中,根据模数定义了数字的。换句话说,在模m算术中,一个数字n等同于(即相同)于n+mn-mn+2mn-2m等。
人们定义m个“篮子”,每个数字都属于其中一个(且仅属于一个)。

例子:可以说“现在是下午4点30分”或者可以说“现在是16点30分”。这两种形式表示完全相同的时间,但它们是不同的表达方式

因此,Python和C#的结果都是正确的!在你选择的模数5的算术中,这些数字是相同的。例如,返回(5,6,7,8,9)也是数学上正确的。只是有点奇怪。
至于表示法的选择(换句话说,如何表示负数),这只是两种语言之间不同设计选择的情况。
然而,在C#中,%运算符实际上并不是传统的模运算符;它是余数运算符。A % B运算符实际上回答了这个问题:“如果我使用整数算术将A除以B,余数会是多少?”
-Eric Lippert的有什么区别?余数与模数

获取规范模数的快速代码片段:

return ((n % m) + m) % m;

测试实现:

Mono/C#

machine:~ user$ cat mod.cs
using System;

public class Program
{
    public static void Main (string[] args)
    {
        Console.WriteLine(Mod(-2, 5));
        Console.WriteLine(Mod(-5, 5));
        Console.WriteLine(Mod(-2, -5));
    }

    public static int Mod (int n, int m)
    {
        return ((n % m) + m) % m;
    }
}

machine:~ user$ mono mod.exe
3
0
-2

Python

machine:~ user$ cat mod.py
print -2%5;
print -5%5;
print -2%-5;

machine:~ user$ python mod.py
3
0
-2

1
“规范模数”是-2和-5的什么?您的程序给出了-2,但我认为您打算给出非负结果。再试一次吗? - Eric Lippert
@EricLippert 它并不返回相同的结果。分别返回3和0。请查看我的编辑。 - Sklivvz
1
我向您保证,您的代码片段表明-2和-5的规范模数为-2。试一下:class P { static void Main() { int n = -2, m = -5; System.Console.WriteLine(((n%m)+m)%m)); } } 运行它,你会得到-2的输出。如果您的意图是使规范模数为非负数,则未能实现您的意图。是否愿意再试一次? - Eric Lippert
@Eric 啊,明白了!-5 是模数而不是另一个输入...好的,这仍然符合 Python 的规则:python -c 'print -2%-5;' 返回 -2。我想这就是 OP 所期望的结果。 - Sklivvz
对于一个能够正确处理负除数的版本,请参考此答案 https://dev59.com/pGkw5IYBdhLWcg3wMHum#10065670。不过,这种情况可能相对较少,所以对于大多数用例来说,上述答案都可以正常工作。 - Aaron Franke

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