负数的Mod正在融化我的大脑

我试图修改一个整数来获得一个数组的位置,以便它将循环。 做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 

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

我总是使用我自己的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。

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

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

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

使用%的单行实现只有一次:

 int mod(int k, int n) { return ((k %= n) < 0) ? k+n : k; } 

增加一些理解。

通过欧几里得定义 ,mod的结果必须总是正的。

例如:

  int n = 5; int x = -3; int mod(int n, int x) { return ((n%x)+x)%x; } 

输出:

  -1 

只需将你的模数(arrayLength)加到%的否定结果上,你就会好起来的。

ShreevatsaR的答案不适用于所有情况,即使您添加“如果(m <0)m = -m;”,如果您计入负的分红/除数。

例如,-12模-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的testing套件:

  [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 }; } } 

对于更多性能感知的开发人员

 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 

至于投给uint的性能成本看看这里

我喜欢Peter N Lewis在这个线程中提出的技巧:“如果n有一个有限的范围,那么你可以简单地通过添加一个已知的[divisor]的常数倍数来得到你想要的结果,最小“。

所以如果我有一个价值是在度,我想要

 d % 180f 

如果d是负值,我想避免这个问题,相反,我只是这样做:

 (d + 720f) % 180f 

这假设尽piped可能是负的,但是已知它将永远不会比-720更负。