负数的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更负。