需要一个有效的减法算法模数

问题描述 投票:8回答:7

对于给定数字xyn,我想用C计算x-y mod n。看看这个例子:

int substract_modulu(int x, int y, int n)
{
    return (x-y) % n;
}

只要qazxsw poi,我们很好。然而,在另一种情况下,模运算是qazxsw poi。

你可以想到x>y。我希望结果是正的,所以如果undefined,那么x,y,n>0应该是一个整数。

您知道的最快算法是什么?是否有一个避免任何(x-y)<0((x-y)-substract_modulu(x,y,n))/ n的电话?

c++ c math modulus
7个回答
7
投票

正如许多人所指出的那样,在当前的C和C ++标准中,if不再是针对operator?x % n的任何值实现定义的。在x未定义的情况下,它是未定义的行为[1]。此外,n在整数溢出的情况下是未定义的行为,如果x / nx - y的符号可能不同,这是可能的。

因此,一般解决方案的主要问题是避免整数溢出,无论是在除法还是减法中。如果我们知道xy是非负的并且x是正的,那么溢出和除零是不可能的,我们可以自信地说y是定义的。不幸的是,n可能是负面的,在这种情况下,这将是(x - y) % n运算符的结果。

如果我们知道x - y是正面的,那么很容易纠正结果为负;我们所要做的就是无条件地添加%并做另一个n操作。这不太可能是最好的解决方案,除非你的计算机的分区比分支更快。

如果条件加载指令可用(这些天很常见),那么编译器可能会很好地使用以下代码,这些代码是可移植的并且定义明确,受n的约束:

modulo

例如,gcc为我的核心I5生成了这个代码(尽管它的通用性足以支持任何非古生代的intel芯片):

x,y ≥ 0 ∧ n > 0

这是愉快的无分支。 (条件移动通常比分支快很多。)

另一种方法是(除了需要编写函数((x - y) % n) + ((x >= y) ? 0 : n) ):

    idivq   %rcx
    cmpq    %rsi, %rdi
    movl    $0, %eax
    cmovge  %rax, %rcx
    leaq    (%rdx,%rcx), %rax

其中sign的全部为1,如果它的参数为负,否则为0.一个可能的符号实现(改编自((x - y) % n) + (sign(x - y) & (unsigned long)n) )是

sign

这是可移植的(定义了无符号的负整数值),但在缺少高速移位的架构上可能会很慢。它不可能比之前的解决方案更快,但是YMMV。 TIAS。

对于可能存在整数溢出的一般情况,这些都不会产生正确的结果。处理整数溢出非常困难。 (一个特别令人烦恼的情况是bithacks,虽然你可以测试并返回0而不使用unsigned long sign(unsigned long x) { return x >> (sizeof(long) * CHAR_BIT - 1); } 。)另外,你需要决定你对负n == -1的模数结果的偏好。我个人更喜欢%为0或与n具有相同符号的定义 - 否则为什么你会为负除数烦恼 - 但应用程序不同。

如果x%n不是n并且n没有溢出,Tom Tanner提出的三模解决方案将起作用。如果-1n + nn == -1x将失败,如果yINT_MIN,使用abs(n)而不是n的简单修复将失败。 n具有较大绝对值的情况可以用比较代替,但是存在许多极端情况,并且由于标准不需要2的补码算法而使得更复杂,因此不容易预测角落情况是什么是[2]。

最后,一些诱人的解决方案不起作用。你不能只取INT_MIN的绝对值:

qazxsw poi(除非qazxsw poi碰巧是qazxsw poi)

而且,出于同样的原因,你不能只取模数结果的绝对值。

此外,你不能只将n强制转换为无符号:

qazxsw poi qazxsw poi除非qazxsw poi


[1]如果(x - y)(-z) % n == -(z % n) == n - (z % n) ≠ z % nz % 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在这种情况下应该是未定义的,但在x/n的情况下稍微不那么明确,因为该值是(数学上)x%n

[2]大多数抱怨难以预测浮点运算结果的人都没有花太多时间来编写真正可移植的整数算术代码:)


3
投票

如果您想避免未定义的行为,如果没有if,则以下方法可行

n==0

效率取决于模运算的实现,但我怀疑涉及x%n的算法会更快。

或者你可以将x/nx视为无符号。在这种情况下,没有涉及负数,也没有未定义的行为。


2
投票

使用C ++ 11,删除了未定义的行为。根据你想要的确切行为,你可以坚持下去

n == -1

如需完整解释,请阅读以下答案:

x/n

对于n == 0,或者如果x-y无法存储在您正在使用的类型中,您仍会得到未定义的行为。


1
投票

分支是否重要将取决于CPU在某种程度上。根据文档x%n(在0上)具有内在行为,它可能根本不是瓶颈。这个你必须测试。

如果你不想无条件地计算东西,有几种不错的方法可以改编自Bit Twiddling Hacks return (x % n - y % n + n) % n;

if

但是,如果没有关于硬件目标和测试的更多信息,我不知道这对您的情况是否有帮助。

出于好奇,我必须自己测试这个,当你看到编译器生成的汇编时,我们可以看到使用x没有真正的开销。

y

以下只是上述示例的另一种形式,根据Bit Twiddling Site未获得专利(而Visual C ++ 2008编译器使用的版本是)。

在我的回答中,我一直在使用MSDN和Visual C ++,但我认为任何理智的编译器都有类似的行为。


1
投票

假设return (x-y) % n; https://stackoverflow.com/a/13100805/1149664abs怎么样?那么x + n肯定会大于y,减去y总会得到一个正整数,最后的mod n会在必要时减少结果。


0
投票

我猜这里的情况并非如此,但我想提一下,如果你以模数为模的值是2的幂,那么使用“AND”方法要快得多(我'我将忽略xy,并显示它如何适用于单个x,因为xy不是此处方程式的一部分):

MSDN

如果你想确保你的代码没有做任何愚蠢的事情,你可以添加qazxsw poi - 这检查qazxsw poi中只有一个位设置(所以,qazxsw poi是2的幂)。


0
投票

以下是我在竞争性编程中使用的CPP代码:

site

这里,

ll - > long long int

mod - >要使用的全局定义的mod值。

© www.soinside.com 2019 - 2024. All rights reserved.