如何在处理负数的C / C ++ / Obj-C中编写一个模(%)运算符

我的一个C语言的宠物讨厌(作为一个math家)是这样的

(-1) % 8 // comes out as -1, and not 7 fmodf(-1,8) // fails similarly 

什么是最好的解决scheme?

C ++允许模板和运算符重载的可能性,但这两个对我来说都是阴暗的水域。 感激地收到的例子。

首先我想说明的是,你甚至不能依靠(-1) % 8 == -1的事实。 你唯一可以依赖的是(x / y) * y + ( x % y) == x 。 然而,其余部分是否是否定的,是由实现定义的

现在为什么使用模板? 整数和长整数可能会超负荷。

 int mod (int a, int b) { int ret = a % b; if(ret < 0) ret+=b; return ret; } 

现在你可以把它称为mod(-1,8),它会显示为7。

编辑:我发现我的代码中的错误。 如果b是负数,它将不起作用。 所以我觉得这样比较好:

 int mod (int a, int b) { if(b < 0) //you can check for b == 0 separately and do what you want return mod(a, -b); int ret = a % b; if(ret < 0) ret+=b; return ret; } 

参考:C ++ 03第5.6节第4节:

二进制/运算符产生商,二进制%运算符产生第一个expression除以第二个expression式的余数。 如果/或%的第二个操作数是零,则行为是未定义的; 否则(a / b)* b + a%b等于a。 如果两个操作数都是非负的,那么余数是非负的; 如果不是,剩余的符号是实现定义的

这是一个C函数,可以处理两个正整数或负整数或小数值

 #include <math.h> float mod(float a, float N) {return a - N*floor(a/N);} //return in range [0, N) 

从mathangular度来看,这无疑是最优雅的解决scheme。 不过,我不确定在处理整数时它是否强健。 当转换int – > fp – > int时,浮点错误有时会蔓延。

我使用这个代码为非int,并为int单独的函数。

注意:需要陷阱N = 0!

testing代码:

 #include <math.h> float mod(float a, float N) { float ret = a - N * floor (a / N); printf("%f.1 mod %f.1 = %f.1 \n", a, N, ret); return ret; } int main (char* argc, char** argv) { printf ("fmodf(-10.2, 2.0) = %f.1 == FAIL! \n\n", x); float x; x = mod(10.2f, 2.0f); x = mod(10.2f, -2.0f); x = mod(-10.2f, 2.0f); x = mod(-10.2f, -2.0f); return 0; } 

(注意:你可以直接从CodePad编译并运行它: http ://codepad.org/UOgEqAMA)

输出:

fmodf(-10.2,2.0)= -0.20 == FAIL!

10.2 mod 2.0 = 0.2
10.2 mod -2.0 = -1.8
-10.2 mod 2.0 = 1.8
-10.2 mod -2.0 = -0.2

我刚刚注意到,Bjarne Stroustrup将%作为余数运算符, 而不是模运算符。

我敢打赌,这是ANSI C&C ++规范中的正式名称,而且术语的滥用已经悄然而至。有没有人知道这个事实?

但是如果是这种情况,那么C的fmodf()函数(可能还有其他的)是非常误导的。 他们应该被标记为fremf()等

对于整数这很简单。 做就是了

 (((x < 0) ? ((x % N) + N) : x) % N) 

我认为N是正数,可以表示为x的types。 你最喜欢的编译器应该能够优化这个,这样在汇编器中只需要一个mod操作。

对于math家来说,最好的解决scheme是使用Python。

C ++运算符重载与它无关。 内置types不能重载运算符。 你想要的只是一个function。 当然你可以使用C ++模板来为所有相关的types实现这个function,只需要一段代码。

标准的C库为浮点types提供了fmod ,如果我正确地记住了这个名字。

对于整数,你可以定义一个C ++函数模板,总是返回非负余数(对应于欧几里德除法)…

 #include <stdlib.h> // abs template< class Integer > auto mod( Integer a, Integer b ) -> Integer { Integer const r = a%b; return (r < 0? r + abs( b ) : r); } 

…只写mod(a, b)而不是a%b

这里Integertypes需要是一个有符号的整数types。

如果你想要其余的符号与除数的符号相同的共同的math行为,那么你可以做例如

 template< class Integer > auto floor_div( Integer const a, Integer const b ) -> Integer { bool const a_is_negative = (a < 0); bool const b_is_negative = (b < 0); bool const change_sign = (a_is_negative != b_is_negative); Integer const abs_b = abs( b ); Integer const abs_a_plus = abs( a ) + (change_sign? abs_b - 1 : 0); Integer const quot = abs_a_plus / abs_b; return (change_sign? -quot : quot); } template< class Integer > auto floor_mod( Integer const a, Integer const b ) -> Integer { return a - b*floor_div( a, b ); } 

…在Integer上有相同的约束,这是一个签名types。


¹因为Python的整数部分正向负无穷。

哦,我讨厌这个%的devise…

您可以通过如下方式将股息转换为无符号的:

 unsigned int offset = (-INT_MIN) - (-INT_MIN)%divider result = (offset + dividend) % divider 

其中offset最接近模块的(-INT_MIN)倍数,所以加减它不会改变模数。 请注意,它有无符号types,结果将是整数。 不幸的是,它不能正确地转换值INT_MIN …( – 偏移-1),因为它们造成了严重的溢出。 但是这种方法在使用常量分频器时,每个操作只有单独的附加算术(并且没有条件),所以它可以在类似DSP的应用中使用。

有特殊情况,分频器是2 N (2的整数次幂),可以使用简单的算术和按位逻辑计算模数为

 dividend&(divider-1) 

例如

 x mod 2 = x & 1 x mod 4 = x & 3 x mod 8 = x & 7 x mod 16 = x & 15 

更常见也更不棘手的方法是使用这个函数来取模(仅适用于正分频器):

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

这只是正确的结果,如果它是负面的。

你也可能欺骗:

(p%q + q)%q

这是非常短暂的,但使用通常很慢的两个%-s。

我相信这个问题的另一个解决scheme将被用于longtypes而不是inttypes的variables。

我只是在一些代码中,%运算符返回一个负值导致了一些问题(在[0,1]上生成统一的随机variables,你真的不想要负数:)),但是在将variables切换到inputlong,一切运行顺利,结果与我在python中运行相同的代码时得到的结果相符(对于我来说很重要,因为我希望能够在多个平台上生成相同的“随机”数字。

 / *警告:macrosMOD多次评估其参数的副作用。  * /
 ((r)%(m))+((r)<0)?(m):0)

…或者只是习惯于获得等价类的代表。

基于这篇微软研究报告和其中的参考资料,这是一个老问题的新答案。

请注意,从C11和C ++ 11开始, div的语义已经被截断为零 (参见[expr.mul]/4 )。 而且,对于除以d D ,C ++ 11保证了商qT和余数rT的以下内容

 auto const qT = D / d; auto const rT = D % d; assert(D == d * qT + rT); assert(abs(rT) < abs(d)); assert(signum(rT) == signum(D)); 

其中signum映射到-1,0,+ 1,取决于它的参数是否是<,==,>大于0(参见源码的Q&A )。

截断除法, 余数的符号等于被除数D的符号 ,即-1 % 8 == -1 。 C ++ 11还提供了一个std::div函数,该函数根据截断除法返回一个包含成员quotrem的结构体。

还有其他的定义是可能的,例如所谓的地板分割可以用内置的截断分割来定义

 auto const I = signum(rT) == -signum(d) ? 1 : 0; auto const qF = qT - I; auto const rF = rT + I * d; assert(D == d * qF + rF); assert(abs(rF) < abs(d)); assert(signum(rF) == signum(d)); 

在地板分区中, 其余的符号等于除数d的符号 。 在Haskell和Oberon等语言中,内置分区的内置操作符。 在C ++中,你需要使用上面的定义来编写一个函数。

另一种方法是欧几里德分割 ,也可以根据内置的截断分割来定义

 auto const I = rT >= 0 ? 0 : (d > 0 ? 1 : -1); auto const qE = qT - I; auto const rE = rT + I * d; assert(D == d * qE + rE); assert(abs(rE) < abs(d)); assert(signum(rE) != -1); 

用欧几里德分割, 剩下的符号总是正的

find正模的最简单的一般函数就是这个 – 它将对x的正值和负值都有效。

 int modulo(int x,int N){ return (x % N + N) %N; } 

C ++的示例模板

 template< class T > T mod( T a, T b ) { T const r = a%b; return ((r!=0)&&((r^b)<0) ? r + b : r); } 

使用此模板,返回的余数将为零,或者具有与除数(分母)相同的符号(相当于向负无穷大舍入),而不是余数为零的C ++行为或与分数具有相同符号(分子)(等于舍入为零)。

 define MOD(a, b) ((((a)%(b))+(b))%(b)) 
 unsigned mod(int a, unsigned b) { return (a >= 0 ? a % b : b - (-a) % b); } 

这个解决scheme(当mod是正数时使用)避免了所有的负分或剩余操作:

 int core_modulus(int val, int mod) { if(val>=0) return val % mod; else return val + mod * ((mod - val - 1)/mod); } 

我会做:

 ((-1)+8) % 8 

在按照需要进行模7给出之前,这将第一个数加到第一个数上。 这应该适用于-8的任何数字。 为-9添加2 * 8。