如何获得负股息的正模数

时间:2016-11-29 08:29:13

标签: assembly x86 integer-division

我想在[0 ... n-1]范围内得到一个模数n(0

指令idiv n将EDX:EAX(64位)除以n,并离开
EAX =商,EDX =余数。 (n是我的代码中的注册表)

我的问题是,当EDX:EAX的内容为负时,我在EDX中得到否定结果。

我找到的最简单的解决方案是:

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

是否有更干净/更容易/更快捷的方法来获得积极的余数?

2 个答案:

答案 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 r64idiv r32慢得多。