我想在 指令 我的问题是,当EDX:EAX的内容为负时,我在EDX中得到否定结果。 我找到的最简单的解决方案是: 是否有更干净/更容易/更快捷的方法来获得积极的余数?[0 ... n-1]
范围内得到一个模数n(0 idiv n
将EDX:EAX(64位)除以n,并离开
EAX =商,EDX =余数。 (n
是我的代码中的注册表)cdq ; extend EAX sign bit to EDX
idiv n ; edx = (possibly neg.) remainder
add edx, n
mov eax, edx ; eax = remainder + n
cdq
idiv n ; edx = positive remainder
答案 0 :(得分:4)
-5 mod 3 = -2 (余数,-1商)
修补剩余部分: -2 + 3 = +1 ,这就是你想要的,对吗?
商数则为-1 - 1 = -2。
验证:-2 * 3 + +1 = -5
cdq ; extend EAX sign bit to EDX
idiv n ; edx = (possibly neg.) remainder
mov eax, edx ; eax = copy of remainder
add edx, n ; negative remainder modified to positive one (for --quotient)
; when going from negative to positive, CF will be set (unsigned overflow)
cmovc eax,edx ; when CF, load eax with modified remainder
; eax = to-be-positive-adjusted remainder
我没有在调试器中验证它,我只是醒了,所以先测试一下。
答案 1 :(得分:3)
产生非负模数的除法类型称为Euclidean division。
对于C实现和更多背景,请参阅Division and Modulus for Computer Scientists。 (也相关:What's the difference between “mod” and “remainder”?)。
这是一个特殊情况,我们知道除数是正数,这样可以更快地实现Ped7g的实现。它可能是最佳的,或者至少接近它。
有趣的是,我们可以用C编写它的方式编译成与Ped7g的手写版本相同的asm(或接近它,取决于gcc与clang)。见on the Godbolt compiler explorer。
// https://stackoverflow.com/questions/40861023/how-do-i-get-a-positive-modulo-on-a-negative-dividend
// uses unsigned carry to detect when a negative 2's complement integer wrapped to non-negative
long modE_positive_divisor_2s_complement( long D, long d )
{
long r = D%d;
unsigned long tmp = (unsigned long)r + (unsigned long)d;
// detect carry by looking for unsigned wraparound:
// that means remainder was negative (not zero), so adding the (non-negative) divisor is what we need
r = (tmp < (unsigned long)r) ? (long)tmp : r;
return r;
}
使用clang3.9 -O3,我们得到:
mov rax, rdi
cqo
idiv rsi
add rsi, rdx
cmovae rsi, rdx
mov rax, rsi
ret
gcc6.2在CMOV之前使用额外的CMP :(
在Godbolt上,我包含了原始的通用案例C函数和一个告诉编译器假设为正d
(使用__builtin_unreachable()
)的版本。对于后者,gcc与此相同,使用test rdx,rdx
/ cmovs
进行条件添加。
对于32位与64位long
(使用EAX而不是RAX等),它的工作方式相同,因此如果您关心性能并且不需要64-,请使用int32_t
位整数。 (idiv r64
比idiv r32
慢得多。