如何在C / C ++ / Obj-C中编码模(%)运算符以处理负数


86

我最讨厌C语言衍生的语言(作为数学家)是

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

fmodf(-1,8) // fails similarly

最好的解决方案是什么?

C ++允许模板和运算符重载,但对我而言,这两者都是晦涩难懂的地方。实例感激不尽。


1
在正式定义下,我认为这不是stackoverflow.com/questions/828092/…的“重复” 。这个问题的答案可以合并到那个答案中是不正确的,因为这个问题只询问模数,而不是除法。但是我认为这个问题已经覆盖了这个问题,因此已经很接近了。我的答案已经在那里,FWIW。
史蒂夫·杰索普

也许应该拆分该线程,因为它会问两个单独的问题。最好的方法是分别重新询问除法问题,然后将其指向该答案。我将把它留给更好地了解本网站机制的人。
P I

3
@Pi owhere被%认为是...就是余数
oldrinb 2012年

1
这是与它“重复”的另一个线程:stackoverflow.com/questions/1082917/…仅供参考%
leetNightshade 2015年

如果您仅将2的幂除,则使用和以下方法可能是一个更好的主意:(-1) & 8 == 7
Henricus V.

Answers:


74

首先我要指出,你甚至不能依靠事实(-1) % 8 == -1。您唯一可以依靠的是(x / y) * y + ( x % y) == x。但是,余数是否为负是实现定义的

现在为什么在这里使用模板?int和long的重载就可以了。

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节:

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


2
@Ohmu:是的,这是C ++标准。<quote>对于整数操作数,/运算符得出代数商,其中舍弃任何小数部分;如果商a / b在结果类型中可表示,则(a / b)* b + a%b等于a。</ quote>
Ben Voigt 2010年

5
-1。自定义实现以来已经11年了。ISO 9899:1999对其进行了定义,但不幸的是选择了错误的定义。
R .. GitHub停止帮助ICE,2010年

3
@Armen:您方便地删除了脚注<quote> ...,整数除法遵循ISO Fortran标准ISO / IEC 1539:1991中定义的规则,在该规则中,商始终四舍五入为零。新的C ++标准的升级,从“首选”这种行为的强制性,就像Fortran和C.
本·福格特

2
@Armen:旧规范已损坏,但损坏与标志问题不同,在您查看新措辞之前,很容易错过。C ++ 03没有“如果在结果的类型中商a / b是可表示的”,这会导致问题INT_MIN / -1(在2的补码实现中)。在旧规范下,-32768 % -1可能必须评估为-65536(也不在16位类型的范围内,yuck!),以便保留身份。
Ben Voigt

1
重新“但是,其余是否为负是实现定义的”,C ++ 11保证整数除法轮朝0
欢呼和第h。-Alf

12

这是一个处理两个操作数的正或负整数或小数的C函数

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

从数学的角度来看,这无疑是最优雅的解决方案。但是,我不确定它在处理整数方面是否健壮。有时在转换int-> fp-> int时会出现浮点错误。

我将此代码用于non-int,并将单独的函数用于int。

注意:需要陷阱N = 0!

测试人员代码:

#include <math.h>
#include <stdio.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", fmodf(-10.2, 2.0));

    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 ==失败!

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


不幸的是,这不适用于整数。必须先将它们转换为浮点数,然后才能使用floor()。另外,转换为float时可能会降低精度:试试看(float)1000000001/3,您会对结果感到惊讶!
cmaster-恢复莫妮卡

9

我刚刚注意到Bjarne Stroustrup标记%余数运算符,而不是模运算符。

我敢打赌,这是ANSI C&C ++规范中的正式名称,并且滥用术语已广为流行。有人知道这一点吗?

但是,如果是这种情况,那么C的fmodf()函数(可能还有其他函数)将极具误导性。它们应标记为fremf()等


1
C11标准(或确切的说是最终的公共草案)六次提及“模”,但仅涉及各种类型的表示。它没有一次提到与余数运算符(%)有关的“模” 。
NisseEngström17年

7

找到正模的最简单的通用函数是this-它对x的正值和负值都起作用。

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

6

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

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

我认为这N是积极的,可以代表的x。您最喜欢的编译器应该能够对此进行优化,以使其最终仅在汇编器中执行一次mod操作。


3
不起作用:因为int x=-9001; unsigned int N=2000;它给出2295,而不是999。–
休伯特·卡里奥

1
@HubertKario也许再次检查?模2000不能给2295,您一定犯了一个错误。
sam hocevar 2012年

2
@SamHocevar:我认为这里的问题是奇怪的C整数提升规则。签署促进无符号和促进C.整数值符号到无符号所调用不确定的行为负
datenwolf

1
我相信更简单(更有效)的形式是:(x < 0) ? (x % N + N) : (x % N)
克里斯·诺莱特

3

对于数学家来说,最好的解决方案是使用Python。

C ++运算符重载与此无关。您不能为内置类型重载运算符。您想要的只是一个函数。当然,您可以使用C ++模板为所有相关类型实现该功能,而只需编写一段代码。

标准C库提供 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

这里的类型 Integer必须是有符号整数类型。

如果您想要普通的数学行为,其中余数的符号与除数的符号相同,则可以执行例如

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,那就是有符号类型。


¹因为Python的整数除法向负无穷大舍入。


您的代码似乎与我编辑之前的代码存在相同的错误。如果b为负怎么办?:)
Armen Tsirunyan

1
@Armen:谢谢!但是我太懒了,无法编辑... :-)
干杯和hth。-Alf 2010年

@ArmenTsirunyan:该r结果必须使a=r + b*(a/b)真。不论整数除法如何实现,b*something都是的倍数br即使是负数,也可以得出有效的取模结果。您可以向其添加abs(b),它将仍然是有效的取模结果。
干杯和健康。-Alf 2014年

2
@downvoters:这个答案仍然是正确的,但是由于C ++ 11中的新保证,所选的“解决方案”现在包含不正确的注释。拒绝仍然是正确的答案真是讽刺。没有理由不让人们假设至少有2个具有几乎绝对的无知程度的关联人阅读了该问题的评论,并以与膝关节相关的方式被否决了。请解释一下你的投票。
干杯和健康。-Alf

1
数学上期望的结果是,余数为零或与除数(分母)具有相同的符号。如果除数为负,则余数应为零或负。C / C ++实现导致余数为零或与除数(分子)具有相同的符号。
rcgldr

2

噢,我也讨厌这个设计。

您可以通过以下方式将股息转换为未签名:

unsigned int offset = (-INT_MIN) - (-INT_MIN)%divider

result = (offset + dividend) % divider

其中offset最接近模块的(-INT_MIN)倍数,因此对其进行加法和减法运算不会改变模数。请注意,它具有无符号类型,结果将是整数。不幸的是,它无法正确转换值INT_MIN ...(-offset-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。


2

我相信解决此问题的另一种方法是使用long类型的变量而不是int类型。

我只是在处理一些代码,其中%运算符返回一个负值,这导致了一些问题(要在[0,1]上生成统一的随机变量,您实际上并不需要负数:)),但是在将变量切换为键入long,一切运行顺利,结果与在python中运行相同代码时得到的结果匹配(对我来说很重要,因为我希望能够在多个平台上生成相同的“随机”数字。


2

根据Microsoft Research论文及其中的参考文献,这是对旧问题的新答案。

请注意,从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(请参阅此问题与解答以获取源代码)。

截断除法时,余数的符号等于除数的符号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);

在欧几里得除法运算中,余数始终为正


2

对于不使用分支且仅使用1个mod的解决方案,您可以执行以下操作

// Works for other sizes too,
// assuming you change 63 to the appropriate value
int64_t mod(int64_t x, int64_t div) {
  return (x % div) + (((x >> 63) ^ (div >> 63)) & div);
}

1
/ *警告:宏mod会多次评估其参数的副作用。* /
#定义mod(r,m)(([[r)%(m))+((r)<0)?(m):0)

...或者只是习惯于接受等效课程的任何代表。


2
“习惯于接受等效课程的任何代表”吗?废话 如果您希望使用原始的“代表”,则可以使用r。该%运营商无关,与等价类。它是余数运算符,并且余数在代数上经过很好的定义,是非负数且小于除数。可悲的是,C用错误的方式定义了它。不过,+ 1是最好的答案之一。
R .. GitHub停止帮助ICE,2010年

0

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 ++行为为零或与除数具有相同的符号(分子)(相当于四舍五入)。



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

-1

此解决方案(在mod正数时使用)避免同时进行负数除法或余数运算:

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

-2

我会做:

((-1)+8) % 8 

这将后一个数字加到第一个数字上,然后根据需要进行模加7。这适用于任何数字(低至-8)。对于-9,请添加2 * 8。


2
对于一个变量,其值可能是-99999
基思·汤普森
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.