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

P i*_*P i 81 c c++ operator-overloading modulo c++11

我厌恶的C语言(作为一名数学家)就是这样的

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

fmodf(-1,8) // fails similarly
Run Code Online (Sandbox Code Playgroud)

什么是最好的解决方案?

C++允许模板和运算符重载的可能性,但这些对我来说都是模糊的.感激地收到了例子.

Arm*_*yan 72

首先,我想指出你甚至不能依赖这个事实(-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;
}
Run Code Online (Sandbox Code Playgroud)

现在你可以像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;
}
Run Code Online (Sandbox Code Playgroud)

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

二元/运算符产生商,二元%运算符从第一个表达式除以第二个表达式得到余数.如果/或%的第二个操作数为零,则行为未定义; 否则(a/b)*b + a%b等于a.如果两个操作数都是非负的,那么余数是非负的; 如果没有,余数的符号是​​实现定义的.

  • -1.自定义实施以来已经过去了11年.ISO 9899:1999定义了它,不幸的是选择了错误的定义. (5认同)
  • @Armen:您方便地删除了脚注<quote> ...整数除法遵循ISO Fortran标准ISO/IEC 1539:1991中定义的规则,其中商总是向零舍入</ quote>.新的C++标准将此行为从"首选"升级为强制,就像Fortran和C. (3认同)
  • @Ohmu:是的,这是在 C++ 标准中。&lt;quote&gt; 对于整数操作数, / 操作符产生代数商,其中任何小数部分都被丢弃;如果商 a/b 可以用结果的类型表示,则 (a/b)*b + a%b 等于 a。&lt;/quote&gt; (2认同)
  • @Armen:旧的规格被破坏了,但破碎与标志问题不同,在你看新的措辞之前很容易错过.C++ 03没有"如果商a/b在结果类型中可表示",这会导致`INT_MIN/-1`出现问题(在二进制补码实现上).在旧的规范下,`-32768%-1`可能必须评估为`-65536`(也不在16位类型的范围内,哎!)以便保持身份. (2认同)

P i*_*P i 12

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

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

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

我将此代码用于非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;
}
Run Code Online (Sandbox Code Playgroud)

(注意:您可以直接从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


Uda*_*ukh 9

找到正模数的最简单的通用函数是这个 - 它适用于 x 的正值和负值。

int modulo(int x,int N){
    return (x % N + N) %N;
}
Run Code Online (Sandbox Code Playgroud)


P i*_*P i 8

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

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

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


Jen*_*edt 5

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

(((x < 0) ? ((x % N) + N) : x) % N)
Run Code Online (Sandbox Code Playgroud)

我认为这N是积极的,可以代表的类型x.您最喜欢的编译器应该能够优化它,这样它最终只能在汇编程序中进行一次mod操作.

  • 不起作用:对于`int x = -9001; unsigned int N = 2000;`它给出了2295,而不是999. (3认同)
  • @SamHocevar:我认为这里的问题是奇怪的C整数提升规则.签名提升为无符号并将负有符号整数值提升为无符号在C中调用未定义的行为. (2认同)
  • 我相信一个更简单(更有效)的形式是:`(x &lt; 0) ? (x % N + N) : (x % N)`。 (2认同)