对于给定的数字x
,y
和n
,我想在C中计算x-y mod n
。看看这个例子:
int substract_modulu(int x, int y, int n)
{
return (x-y) % n;
}
只要x>y
,我们就可以了。然而,在另一种情况下,modulu操作是undefined。
你可以想到x,y,n>0
。我希望结果是正面的,所以如果(x-y)<0
,则((x-y)-substract_modulu(x,y,n))/ n
应为整数。
你知道的最快算法是什么?是否有一个可以避免任何if
和operator?
?
答案 0 :(得分:7)
正如许多人所指出的那样,在当前的C和C ++标准中,x % n
不再是x
和n
的任何值的实现定义。在x / n
未定义的情况下,它是未定义的行为[1]。此外,x - y
在整数溢出的情况下是未定义的行为,如果x
和y
的符号可能不同,则可能这样做。
因此,一般解决方案的主要问题是避免整数溢出,无论是在除法还是减法中。如果我们知道x
和y
是非负的且n
是正数,则溢出和除零是不可能的,我们可以自信地说(x - y) % n
是定义。不幸的是,x - y
可能是否定的,在这种情况下,%
运算符的结果也是如此。
如果我们知道n
是正面的,那么很容易纠正结果是否定的;我们所要做的就是无条件地添加n
并执行另一个modulo
操作。这不太可能是最好的解决方案,除非你的计算机的分区比分支更快。
如果条件加载指令可用(这些天很常见),那么编译器可能会很好地使用以下代码,这些代码是可移植的并且定义明确,受x,y ≥ 0 ∧ n > 0
的限制:
((x - y) % n) + ((x >= y) ? 0 : n)
例如,gcc为我的核心I5生成了这个代码(虽然它足够通用,可用于任何非古生代的intel芯片):
idivq %rcx
cmpq %rsi, %rdi
movl $0, %eax
cmovge %rax, %rcx
leaq (%rdx,%rcx), %rax
快乐无分支。 (条件移动通常比分支快很多。)
另一种方法是(除了需要编写函数sign
):
((x - y) % n) + (sign(x - y) & (unsigned long)n)
其中sign
如果其参数为负,则全为1,否则为0.符号的一种可能实现(改编自bithacks)是
unsigned long sign(unsigned long x) {
return x >> (sizeof(long) * CHAR_BIT - 1);
}
这是可移植的(定义了无符号的负整数值),但在缺少高速移位的架构上可能会很慢。它不可能比之前的解决方案更快,但是YMMV。 TIAS。
对于可能出现整数溢出的一般情况,这些都不会产生正确的结果。处理整数溢出非常困难。 (一个特别令人烦恼的情况是n == -1
,虽然你可以测试它并返回0而不使用%
。)此外,你需要决定你对模数为{{1}的结果的偏好。 }}。我个人更喜欢n
为0或与x%n
具有相同符号的定义 - 否则为什么要使用负除数 - 但应用程序不同。
如果n
不是n
且-1
没有溢出,那么Tom Tanner提出的三模解决方案将会起作用。如果n + n
或n == -1
为x
,则y
将失败,如果{INT_MIN
使用abs(n)
而非n
的简单修复将失败1}}是n
。 INT_MIN
具有较大绝对值的情况可以用比较代替,但是存在很多极端情况,并且由于标准不需要2的补码算法而变得更加复杂,因此它不容易预测角落案例是什么[2]。
最后,一些诱人的解决方案不起作用。您不能只取n
的绝对值:
(x - y)
(除非(-z) % n == -(z % n) == n - (z % n) ≠ z % n
恰好是z % n
)
并且,出于同样的原因,你不能只取模数结果的绝对值。
此外,您不能只将n / 2
转换为无符号:
(x - y)
(unsigned)z == z + 2k (for some k) if z < 0
除非(z + 2k) % n == (z % n) + (2k % n) ≠ z % n
(2k % n) == 0
, [1] x/n
和x%n
都是未定义的。但是如果n==0
“不可表示”(即存在整数溢出),x%n
也是未定义的,这将发生在二进制补码上
如果x/n
是最负面可表示的数字和x
,则机器(即您关心的所有机器)。很明显为什么n == -1
在这种情况下应该是未定义的,但在x/n
的情况下稍微不那么明确,因为该值是(数学上)x%n
。
[2]大多数抱怨难以预测浮点运算结果的人都没有花太多时间来编写真正可移植的整数算术代码:)
答案 1 :(得分:3)
如果您想避免未定义的行为,如果没有if,则以下方法可以正常工作
return (x % n - y % n + n) % n;
效率取决于模运算的实现,但我怀疑涉及if
的算法会更快。
或者,您可以将x
和y
视为未签名。在这种情况下,没有涉及负数,也没有未定义的行为。
答案 2 :(得分:2)
使用C ++ 11,删除了未定义的行为。根据您想要的确切行为,您可以坚持使用
return (x-y) % n;
如需完整解释,请阅读以下答案:
https://stackoverflow.com/a/13100805/1149664
您仍然会获得n == 0的未定义行为,或者x-y无法存储在您正在使用的类型中。
答案 3 :(得分:1)
分支是否重要将在某种程度上取决于CPU。根据文档abs
(on MSDN)具有内在行为,它可能根本不是瓶颈。这个你必须测试。
如果你无条件地计算东西,可以从 Bit Twiddling Hacks site中改编几种不错的方法。
int v; // we want to find the absolute value of v
unsigned int r; // the result goes here
int const mask = v >> sizeof(int) * CHAR_BIT - 1;
r = (v + mask) ^ mask;
但是,如果没有关于硬件目标和测试的更多信息,我不知道这对您的情况是否有帮助。
出于好奇,我必须自己测试,当你看到编译器生成的程序集时,我们可以看到使用abs
时没有真正的开销。
unsigned r = abs(i);
====
00381006 cdq
00381007 xor eax,edx
00381009 sub eax,edx
以下只是上述示例的另一种形式,根据Bit Twiddling Site未获得专利(而Visual C ++ 2008编译器使用的版本是)。
在我的回答中,我一直在使用MSDN和Visual C ++,但我认为任何理智的编译器都有类似的行为。
答案 4 :(得分:1)
假设0 <= x < n
和0 <= y < n
,(x + n - y) % n
怎么样?那么x + n肯定会大于y,减去y总会得到一个正整数,最后的mod n会在必要时减少结果。
答案 5 :(得分:0)
我猜这里的情况并非如此,但我想提一下,如果您采用模数的值是2的幂,那么使用“AND”方法要快得多(我将忽略xy,只显示它对单个x的工作原理,因为xy不是这里的等式的一部分):
int modpow2(int x, int n)
{
return x & (n-1);
}
如果你想确保你的代码没有做任何愚蠢的事情,你可以添加ASSERT(!(n & n-1));
- 这会检查n
中只有一个位集(所以,{{1}是两个人的力量)。
答案 6 :(得分:0)
这是我在竞争性编程中使用的CPP代码:
#include <iostream>
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define mod 1000000007
ll subtraction_modulo(ll x, ll y ){
return ( ( (x - y) % mod ) + mod ) % mod;
}
在这里
ll-> long long int
mod->要使用的全局定义的mod值。