简体   繁体   English

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

[英]How to code a modulo (%) operator in C/C++/Obj-C that handles negative numbers

One of my pet hates of C-derived languages (as a mathematician) is that我最讨厌 C 派生语言(作为数学家)之一是

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

fmodf(-1,8) // fails similarly

What's the best solution?最好的解决办法是什么?

C++ allows the possibility of templates and operator overloading, but both of these are murky waters for me. C++ 允许模板和运算符重载的可能性,但这两者对我来说都是浑水。 examples gratefully received.示例感谢收到。

First of all I'd like to note that you cannot even rely on the fact that (-1) % 8 == -1 .首先,我想指出的是,您甚至不能依赖(-1) % 8 == -1的事实。 the only thing you can rely on is that (x / y) * y + ( x % y) == x .您唯一可以依赖的是(x / y) * y + ( x % y) == x However whether or not the remainder is negative is implementation-defined .然而,余数是否为负是实现定义的

Now why use templates here?现在为什么要在这里使用模板? An overload for ints and longs would do.整数和长整数的重载就可以了。

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

and now you can call it like mod(-1,8) and it will appear to be 7.现在你可以像 mod(-1,8) 一样调用它,它看起来是 7。

Edit: I found a bug in my code.编辑:我在我的代码中发现了一个错误。 It won't work if b is negative.如果 b 是负数,它将不起作用。 So I think this is better:所以我认为这是更好的:

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

Reference: C++03 paragraph 5.6 clause 4:参考:C++03第5.6段第4条:

The binary / operator yields the quotient, and the binary % operator yields the remainder from the division of the first expression by the second.二元 / 运算符产生商,二元 % 运算符产生第一个表达式除以第二个表达式的余数。 If the second operand of / or % is zero the behavior is undefined;如果 / 或 % 的第二个操作数为零,则行为未定义; otherwise (a/b)*b + a%b is equal to a.否则 (a/b)*b + a%b 等于 a。 If both operands are nonnegative then the remainder is nonnegative;如果两个操作数都为非负,则余数为非负; if not, the sign of the remainder is implementation-defined .如果不是,则余数的符号是​​ implementation-defined

Here is a C function that handles positive OR negative integer OR fractional values for BOTH OPERANDS这是一个 C 函数,它处理两个操作数的正或负整数或小数值

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

This is surely the most elegant solution from a mathematical standpoint.从数学的角度来看,这无疑是最优雅的解决方案。 However, I'm not sure if it is robust in handling integers.但是,我不确定它在处理整数方面是否稳健。 Sometimes floating point errors creep in when converting int -> fp -> int.有时在转换 int -> fp -> int 时会出现浮点错误。

I am using this code for non-int s, and a separate function for int.我将此代码用于非 int s,并为 int 使用单独的函数。

NOTE: need to trap N = 0!注意:需要捕获 N = 0!

Tester code:测试代码:

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

(Note: You can compile and run it straight out of CodePad: http://codepad.org/UOgEqAMA ) (注意:您可以直接从 CodePad 编译并运行它: http ://codepad.org/UOgEqAMA )

Output:输出:

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

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

I have just noticed that Bjarne Stroustrup labels % as the remainder operator, not the modulo operator.我刚刚注意到 Bjarne Stroustrup 将%标记为余数运算符,而不是模运算符。

I would bet that this is its formal name in the ANSI C & C++ specifications, and that abuse of terminology has crept in. Does anyone know this for a fact?我敢打赌,这是它在 ANSI C 和 C++ 规范中的正式名称,而且术语的滥用已经悄然出现。有人知道这是事实吗?

But if this is the case then C's fmodf() function (and probably others) are very misleading.但是,如果是这种情况,那么 C 的 fmodf() 函数(可能还有其他函数)就非常具有误导性。 they should be labelled fremf(), etc它们应该被标记为 fremf() 等

The simplest general function to find the positive modulo would be this- It would work on both positive and negative values of x.找到正模数的最简单的通用函数是这个 - 它适用于 x 的正值和负值。

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

For integers this is simple.对于整数,这很简单。 Just do就做

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

where I am supposing that N is positive and representable in the type of x .我假设N是正数并且可以在x类型中表示。 Your favorite compiler should be able to optimize this out, such that it ends up in just one mod operation in assembler.你最喜欢的编译器应该能够优化它,这样它就可以在汇编程序中完成一个 mod 操作。

Here's a new answer to an old question, based on thisMicrosoft Research paper and references therein.这是一个旧问题的新答案,基于这篇Microsoft Research 论文和其中的参考资料。

Note that from C11 and C++11 onwards, the semantics of div has become truncation towards zero (see [expr.mul]/4 ).请注意,从 C11 和 C++11 开始, div的语义已变为向零截断(参见[expr.mul]/4 )。 Furthermore, for D divided by d , C++11 guarantees the following about the quotient qT and remainder rT此外,对于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) || rT == 0);

where signum maps to -1, 0, +1, depending on whether its argument is <, ==, > than 0 (see this Q&A for source code).其中signum映射到 -1、0、+1,具体取决于其参数是否为 <、==、> 大于 0(请参阅此问答以获取源代码)。

With truncated division, the sign of the remainder is equal to the sign of the dividend D , ie -1 % 8 == -1 .对于截断除法,余数的符号等于被除数D的符号,即-1 % 8 == -1 C++11 also provides a std::div function that returns a struct with members quot and rem according to truncated division. C++11 还提供了一个std::div函数,它根据截断的除法返回一个带有成员quotrem的结构体。

There are other definitions possible, eg so-called floored division can be defined in terms of the builtin truncated division还有其他可能的定义,例如所谓的地板除法可以根据内置的截断除法来定义

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));

With floored division, the sign of the remainder is equal to the sign of the divisor d .使用地板除法时,余数的符号等于除数d的符号 In languages such as Haskell and Oberon, there are builtin operators for floored division.在 Haskell 和 Oberon 等语言中,有用于地板除法的内置运算符。 In C++, you'd need to write a function using the above definitions.在 C++ 中,您需要使用上述定义编写一个函数。

Yet another way is Euclidean division , which can also be defined in terms of the builtin truncated division另一种方法是欧几里得除法,它也可以根据内置的截断除法来定义

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) >= 0);

With Euclidean division, the sign of the remainder is always non-negative .使用欧几里得除法,余数的符号总是非负的

The best solution ¹for a mathematician is to use Python.对于数学家来说,最好的解决方案是使用 Python。

C++ operator overloading has little to do with it. C++ 运算符重载与此无关。 You can't overload operators for built-in types.您不能重载内置类型的运算符。 What you want is simply a function.你想要的只是一个函数。 Of course you can use C++ templating to implement that function for all relevant types with just 1 piece of code.当然,您可以使用 C++ 模板,通过 1 段代码为所有相关类型实现该功能。

The standard C library provides fmod , if I recall the name correctly, for floating point types.标准 C 库为浮点类型提供fmod ,如果我没fmod名字的话。

For integers you can define a C++ function template that always returns non-negative remainder (corresponding to Euclidian division) as ...对于整数,您可以定义一个 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);
}

... and just write mod(a, b) instead of a%b . ...并且只写mod(a, b)而不是a%b

Here the type Integer needs to be a signed integer type.这里Integer类型需要是有符号整数类型。

If you want the common math behavior where the sign of the remainder is the same as the sign of the divisor, then you can do eg如果您想要余数的符号与除数的符号相同的常见数学行为,那么您可以执行例如

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

… with the same constraint on Integer , that it's a signed type. ...对Integer具有相同的约束,即它是有符号类型。


¹ Because Python's integer division rounds towards negative infinity. ¹ 因为 Python 的整数除法向负无穷大舍入。

I believe another solution to this problem would be use to variables of type long instead of int.我相信这个问题的另一个解决方案是使用 long 类型的变量而不是 int 类型的变量。

I was just working on some code where the % operator was returning a negative value which caused some issues (for generating uniform random variables on [0,1] you don't really want negative numbers :) ), but after switching the variables to type long, everything was running smoothly and the results matched the ones I was getting when running the same code in python (important for me as I wanted to be able to generate the same "random" numbers across several platforms.我只是在处理一些代码,其中 % 运算符返回了一个负值,这导致了一些问题(为了在 [0,1] 上生成统一的随机变量,你并不真正想要负数 :) ),但是在将变量切换到输入 long,一切运行顺利,结果与我在 python 中运行相同代码时得到的结果相匹配(对我来说很重要,因为我希望能够在多个平台上生成相同的“随机”数字。

Oh, I hate % design for this too....哦,我也讨厌这种设计......

You may convert dividend to unsigned in a way like:您可以通过以下方式将股息转换为无符号:

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

result = (offset + dividend) % divider

where offset is closest to (-INT_MIN) multiple of module, so adding and subtracting it will not change modulo.其中 offset 最接近 (-INT_MIN) 模数的倍数,因此加减它不会改变模数。 Note that it have unsigned type and result will be integer.请注意,它具有无符号类型,结果将为整数。 Unfortunately it cannot correctly convert values INT_MIN...(-offset-1) as they cause arifmetic overflow.不幸的是,它无法正确转换值 INT_MIN...(-offset-1),因为它们会导致 arifmetic 溢出。 But this method have advandage of only single additional arithmetic per operation (and no conditionals) when working with constant divider, so it is usable in DSP-like applications.但是这种方法在使用常量除法器时每个操作只有一个附加算术(并且没有条件)的优点,因此它可用于类似 DSP 的应用程序。

There's special case, where divider is 2 N (integer power of two), for which modulo can be calculated using simple arithmetic and bitwise logic as有一种特殊情况,其中除法器为 2 N (2 的整数幂),可以使用简单的算术和按位逻辑计算模数为

dividend&(divider-1)

for example例如

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

More common and less tricky way is to get modulo using this function (works only with positive divider):更常见且不太棘手的方法是使用此函数求模(仅适用于正分频器):

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

This just correct result if it is negative.如果它是负数,这只是正确的结果。

Also you may trick:你也可以欺骗:

(p%q + q)%q (p%q + q)%q

It is very short but use two %-s which are commonly slow.它很短,但使用两个通常很慢的 %-s。

For a solution that uses no branches and only 1 mod, you can do the following对于不使用分支且仅使用 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);
}
/* Warning: macro mod evaluates its arguments' side effects multiple times. */
#define mod(r,m) (((r) % (m)) + ((r)<0)?(m):0)

... or just get used to getting any representative for the equivalence class. ...或者只是习惯于获得等价类的任何代表。

Example template for C++ C++ 的示例模板

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

With this template, the returned remainder will be zero or have the same sign as the divisor (denominator) (the equivalent of rounding towards negative infinity), instead of the C++ behavior of the remainder being zero or having the same sign as the dividend (numerator) (the equivalent of rounding towards zero).使用此模板,返回的余数将为零或与除数(分母)具有相同的符号(相当于向负无穷大舍入),而不是余数为零或与被除数具有相同符号的 C++ 行为(分子)(相当于向零舍入)。

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

I would do:我会这样做:

((-1)+8) % 8 

This adds the latter number to the first before doing the modulo giving 7 as desired.在根据需要进行模数给出 7 之前,这会将后一个数字添加到第一个数字上。 This should work for any number down to -8.这应该适用于低至 -8 的任何数字。 For -9 add 2*8.对于 -9 添加 2*8。

This solution (for use when mod is positive) avoids taking negative divide or remainder operations all together:此解决方案(用于当mod为正时)避免同时进行负除法或余数运算:

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM