负数的模运算
Posted
技术标签:
【中文标题】负数的模运算【英文标题】:Modulo operation with negative numbers 【发布时间】:2012-07-28 01:42:33 【问题描述】:在一个 C 程序中,我尝试了以下操作(只是为了检查行为)
x = 5 % (-3);
y = (-5) % (3);
z = (-5) % (-3);
printf("%d ,%d ,%d", x, y, z);
它在 gcc 中给了我(2, -2 , -2)
的输出。我每次都期待一个积极的结果。模数可以是负数吗?任何人都可以解释这种行为吗?
【问题讨论】:
***.com/questions/4003232/…的可能重复 Modulo operator with negative values的可能重复 【参考方案1】:模运算的结果取决于分子的符号,因此 y 和 z
得到 -2这是参考
http://www.chemie.fu-berlin.de/chemnet/use/info/libc/libc_14.html
整数除法
本节介绍执行整数除法的函数。 这些函数在 GNU C 库中是多余的,因为在 GNU C '/' 运算符总是向零舍入。但在其他 C 实现,'/' 可能会以不同的方式舍入负参数。 div 和 ldiv 很有用,因为它们指定了如何舍入 商:趋近于零。余数与 分子。
【讨论】:
您指的是有关 ANSI C 的文本。这是 C 的一个相当古老的规范。不确定该文本对于 ANSI C 是否正确,但对于 C99 绝对不正确。在 C99 §6.5.5 中,整数除法被定义为始终向零截断。【参考方案2】:C99 要求当a/b
是可表示的:
(a/b) * b
+ a%b
应等于 a
这在逻辑上是有道理的。对吧?
让我们看看这会导致什么:
示例 A.5/(-3)
是 -1
=> (-1) * (-3)
+ 5%(-3)
= 5
只有当5%(-3)
为 2 时才会发生这种情况。
示例 B。(-5)/3
是 -1
=> (-1) * 3
+ (-5)%3
= -5
只有当(-5)%3
是-2
时才会发生这种情况
【讨论】:
编译器是否应该足够聪明并检测到一个无符号模另一个无符号总是正数?目前(好吧,GCC 5.2)编译器似乎认为“%”在这种情况下返回一个“int”,而不是“unsigned”,即使两个操作数都是 uint32_t 或更大。 @FrederickNord 你有例子可以展示that behavior吗? 明白你描述的是mod的常用int(a/b)(截断)描述。但也有可能规则是 floor(a/b) (Knuth)。在 Knuth 的情况下,-5/3
是 -2
,并且 mod 变为 1。简而言之:一个模块有一个跟随被除数符号(截断)的符号,另一个模块有一个跟随除数符号(Knuth)的符号。
这是 C 标准完全不是我想要的情况。我从不想截断为零或负模数,但经常想要相反的结果并且需要解决 C.【参考方案3】:
C 中的%
运算符不是模 运算符,而是余数 运算符。
模数和余数运算符在负值方面有所不同。
对于余数运算符,结果的符号与被除数(分子)的符号相同,而对于模运算符,结果的符号与除数(分母)相同。
C 将a % b
的%
操作定义为:
a == (a / b * b) + a % b
使用/
进行整数除法,并截断到0
。这是针对0
(而不是针对负无穷大)进行的截断,将%
定义为余数运算符而不是模运算符。
【讨论】:
Remainder is the result of modulo operation 根据定义。应该没有余数运算符,因为没有余数运算,它被称为取模。 @gronostaj 不在 CS 中。查看更高级的语言,如 Haskell 或 Scheme,它们都定义了两个不同的运算符(Scheme 中的remainder
和 modulo
,Haskell 中的 rem
和 mod
)。这些运算符的规范在这些语言上的除法方式有所不同:截断为 0 或朝向负无穷大。顺便说一句,C 标准从不将 %
称为 模运算符,他们只是将其命名为 % 运算符。
不要与 C 中的 remainder
函数 混淆,该函数在除法中实现 IEEE 余数,并具有向最近舍入的语义
@gronostaj 您提供的链接明确区分了最小正余数和最小绝对余数,这显然并不总是正数。它给出了-2
作为43 / 5
的最小绝对余数(因为43 = 9 * 5 - 2
)。 CS 的定义再次不同。但值得指出的是,仅仅因为我们在 10 岁时学到了一些东西,可能还是有一些微妙之处。例如,在 Python 中尝试 round(2.5)
。它是 2,而不是 3。这在数学上是正确的,以防止统计时刻出现偏差。【参考方案4】:
基于 C99 规范:a == (a / b) * b + a % b
我们可以写一个函数来计算(a % b) == a - (a / b) * b
!
int remainder(int a, int b)
return a - (a / b) * b;
对于模运算,我们可以有如下函数(假设b > 0
)
int mod(int a, int b)
int r = a % b;
return r < 0 ? r + b : r;
我的结论是 C 中的 a % b
是余数运算,而不是模运算。
【讨论】:
当b
为负数时,这不会给出肯定的结果(事实上,对于r
和b
都是负数,它给出的结果小于-b
)。为了确保所有输入的积极结果,您可以使用r + abs(b)
或匹配b
s 符号,您可以将条件更改为r*b < 0
。
@MartinEnder r + abs(b)
在 b == INT_MIN
时是 UB。【参考方案5】:
其他答案已在 C99 或更高版本中解释,涉及负操作数的整数除法总是向零截断。
请注意,在 C89 中,向上或向下舍入的结果是由实现定义的。因为(a/b) * b + a%b
在所有标准中都等于a
,所以%
涉及负操作数的结果也在C89中实现定义。
【讨论】:
【参考方案6】:我认为没有必要检查数字是否为负数。
找到正模的简单函数是这个 -
编辑:假设N > 0
和N + N - 1 <= INT_MAX
int modulo(int x,int N)
return (x % N + N) %N;
这适用于 x 的正负值。
原始 PS: 也正如 @chux 所指出的,如果您的 x 和 N 可能分别达到 INT_MAX-1 和 INT_MAX 之类的值,只需将 int
替换为 long long int
。
如果它们也超过了 long long 的限制(即接近 LLONG_MAX),那么您应该按照此处其他答案中的说明分别处理正面和负面情况。
【讨论】:
请注意,N < 0
时,结果可能为负,如modulo(7, -3) --> -2
。 x % N + N
也可以溢出 int
数学,这是未定义的行为。例如modulo(INT_MAX - 1,INT_MAX)
可能会导致 -3。
是的,在这种情况下,您可以简单地使用long long int
,或者单独处理否定情况(以失去简单性为代价)。【参考方案7】:
在数学中,这些约定源自于,没有断言模算术应该产生积极的结果。
例如。
1 mod 5 = 1,但也可以等于 -4。也就是说,1/5 从 0 中得到余数 1,或者从 5 中得到 -4。(两个因数都是 5)
同样, -1 mod 5 = -1,但它也可以等于 4。也就是说,-1/5 从 0 产生余数 -1 或从 -5 产生余数 4。 (都是 5 的因数)
如需进一步阅读,请查看数学中的equivalence classes。
【讨论】:
等价类是一个不同的概念,模的定义非常严格。假设我们有两个整数a
和 b
,b <> 0
。根据欧几里得除法定理,恰好存在一对整数m
、r
,其中a = m * b + r
和0 <= r < abs( b )
。所述r
是(数学)模运算的结果,根据定义是非负数。***上的更多阅读和更多链接:en.wikipedia.org/wiki/Euclidean_division
这不是真的。 1 mod 5
始终为 1。-4 mod 5
也可能为 1,但它们是不同的东西。【参考方案8】:
模运算符给出余数。 c中的模运算符通常采用分子的符号
-
x = 5 % (-3) - 这里的分子是正数,因此它的结果是 2
y = (-5) % (3) - 这里的分子是负数,因此结果是 -2
z = (-5) % (-3) - 这里的分子是负数,因此结果是 -2
还有模数(余数)运算符只能用于整数类型,不能用于浮点数。
【讨论】:
如果你能用外部资源的链接来备份它会很好。【参考方案9】:根据C99 standard,6.5.5 节 乘法运算符,以下是必需的:
(a / b) * b + a % b = a
结论
余数运算结果的符号,根据 到 C99,与除数相同。
让我们看一些例子 (dividend / divisor
):
只有股息为负时
(-3 / 2) * 2 + -3 % 2 = -3
(-3 / 2) * 2 = -2
(-3 % 2) must be -1
只有除数为负时
(3 / -2) * -2 + 3 % -2 = 3
(3 / -2) * -2 = 2
(3 % -2) must be 1
当除数和被除数均为负数时
(-3 / -2) * -2 + -3 % -2 = -3
(-3 / -2) * -2 = -2
(-3 % -2) must be -1
6.5.5 乘法运算符
语法
乘法表达式:
cast-expression
multiplicative-expression * cast-expression
multiplicative-expression / cast-expression
multiplicative-expression % cast-expression
约束
每个操作数都应具有算术类型。这 % 运算符的操作数应为整数类型。
语义
通常的算术转换是在 操作数。
二元 * 运算符的结果是 操作数。
/ 运算符的结果是 第一个操作数除以第二个操作数;这 % 运算符的结果是余数。同时 操作,如果第二个操作数的值为零, 行为未定义。
整数除法时,/运算符的结果 是任何小数部分的代数商 丢弃[1]。如果商
a/b
是可表示的, 表达式(a/b)*b + a%b
应等于a
。[1]:这通常被称为“向零截断”。
【讨论】:
【参考方案10】:模数可以是负数吗?
%
可以是负数,因为它是 remainder operator,除法后的余数,而不是 Euclidean_division 后的余数。从 C99 开始,结果可能为 0、负数或正数。
// a % b
7 % 3 --> 1
7 % -3 --> 1
-7 % 3 --> -1
-7 % -3 --> -1
模 OP 需要的是经典的 Euclidean modulo,而不是 %
。
我每次都期待一个积极的结果。
要在定义a/b
时执行明确定义的欧几里得模数,a,b
是任何符号并且结果永远不会是负数:
int modulo_Euclidean(int a, int b)
int m = a % b;
if (m < 0)
// m += (b < 0) ? -b : b; // avoid this form: it is UB when b == INT_MIN
m = (b < 0) ? m - b : m + b;
return m;
modulo_Euclidean( 7, 3) --> 1
modulo_Euclidean( 7, -3) --> 1
modulo_Euclidean(-7, 3) --> 2
modulo_Euclidean(-7, -3) --> 2
【讨论】:
【参考方案11】:我认为考虑mod
会更有用,因为它是在抽象算术中定义的;不是作为一种运算,而是作为一个完全不同的算术类,具有不同的元素和不同的运算符。这意味着mod 3
中的添加与“正常”添加不同;那是;整数加法。
所以当你这样做时:
5 % -3
您正在尝试将 整数 5 映射到 mod -3
集合中的一个元素。这些是mod -3
的元素:
0, -2, -1
所以:
0 => 0, 1 => -2, 2 => -1, 3 => 0, 4 => -2, 5 => -1
假设你因为某种原因必须熬夜 30 个小时,那一天你还剩下多少个小时? 30 mod -24
.
但是 C 实现的不是mod
,而是余数。无论如何,关键是返回负数确实有意义。
【讨论】:
【参考方案12】:看来问题是/
不是楼操作。
int mod(int m, float n)
return m - floor(m/n)*n;
【讨论】:
以上是关于负数的模运算的主要内容,如果未能解决你的问题,请参考以下文章