; ------------------------------
; Restoring
; IN (edx:eax,ecx:ebx) OUT (edx:eax,ecx:ebx,CF)
uDiv: test ecx, ecx
jnz .uDiv
cmp edx, ebx
jnb .uDiv
test ebx, ebx
jz .STC ; Division by 0
div ebx ; EDX:EAX / EBX --> EAX Quotient, EDX Remainder
mov ebx, edx ; Remainder to ECX:EBX
xor edx, edx ; Quotient to EDX:EAX
ret ; CF=0
.STC: stc
ret
; - - - - - - - - - - - - - - -
; IN (edx:eax,ecx:ebx) OUT (edx:eax,ecx:ebx,CF)
.uDiv: push esi edi
mov esi, ebx
or esi, ecx
stc
jz .NOK ; (i) Division by 0
xor edi, edi
push edi edi ; (1) 64-bit Quotient under construction
call .BSR ; -> ESI ZF
jz .NIL ; (ii) Dividend is 0
mov edi, esi ; Save position highest set bit in Dividend
xchg ebx, eax ; -> ECX:EBX is Dividend, EDX:EAX is Divisor
xchg ecx, edx
call .BSR ; -> ESI ZF=0 (because we know Divisor <> 0)
sub edi, esi ; Delta between positions of highest set bits
jb .OK ; (iii) Small Dividend / Big Divisor
jz .d ; No shifting required
cmp edi, 32 ; Shift up Divisor so it aligns with Dividend
jb .a
xchg edx, eax ; Before EDX was 0
.a: xchg ecx, edi
shld edx, eax, cl ; CPU uses CL Mod 32
shl eax, cl
xchg ecx, edi
jmp .d
.b: add ebx, eax ; Restore Dividend after failed subtraction
adc ecx, edx
.c: dec edi
js .OK ; We're (back) at the original Divisor
shr edx, 1 ; Next try a smaller Divisor (/2)
rcr eax, 1
.d: sub ebx, eax ; Try subtracting Divisor from Dividend
sbb ecx, edx
jb .b ; Cannot
bts [esp], edi ; -(1)- Add partial quotient
mov esi, ebx ; Is Dividend reduced to zero ?
or esi, ecx
jnz .c ; Not yet
.NIL: xor ebx, ebx ; Zero Remainder
xor ecx, ecx
.OK: pop eax edx ; (1) EDX:EAX is Quotient, ECX:EBX is Remainder
clc
.NOK: pop edi esi
ret
; - - - - - - - - - - - - - - -
; IN (edx:eax) OUT (esi,ZF)
.BSR: bsr esi, edx
jnz @f
bsr esi, eax ; -> ZF
ret
@@: add esi, 32 ; -> ESI=[32,63] ZF=0
ret
; ------------------------------
; ------------------------------
; Non-restoring
; IN (edx:eax,ecx:ebx) OUT (edx:eax,ecx:ebx,CF)
uDiv: test ecx, ecx
jnz .uDiv
cmp edx, ebx
jnb .uDiv
test ebx, ebx
jz .STC ; Division by 0
div ebx ; EDX:EAX / EBX --> EAX Quotient, EDX Remainder
mov ebx, edx ; Remainder to ECX:EBX
xor edx, edx ; Quotient to EDX:EAX
ret ; CF=0
.STC: stc
ret
; - - - - - - - - - - - - - - -
; IN (edx:eax,ecx:ebx) OUT (edx:eax,ecx:ebx,CF)
.uDiv: push esi edi
mov esi, ebx
or esi, ecx
stc
jz .NOK ; (i) Division by 0
xor edi, edi
push edi edi ; (1) 64-bit Quotient under construction
call .BSR ; -> ESI ZF
jz .NIL ; (ii) Dividend is 0
mov edi, esi ; Save position highest set bit in Dividend
xchg ebx, eax ; -> ECX:EBX is Dividend, EDX:EAX is Divisor
xchg ecx, edx
call .BSR ; -> ESI ZF=0 (because we know Divisor <> 0)
sub edi, esi ; Delta between positions of highest set bits
jb .OK ; (iii) Small Dividend / Big Divisor
jz .d ; No shifting required
cmp edi, 32 ; Shift up Divisor so it aligns with Dividend
jb .a
xchg edx, eax ; Before EDX was 0
.a: xchg ecx, edi
shld edx, eax, cl ; CPU uses CL Mod 32
shl eax, cl
xchg ecx, edi
jmp .d
.OK_: add ebx, eax ; Final restore to obtain Remainder
adc ecx, edx
jmp .OK
ALIGN 16
.b: dec edi
js .OK_ ; We're (back) at the original Divisor
shr edx, 1 ; Next try a smaller Divisor (/2)
rcr eax, 1
add ebx, eax ; This ADD/ADC replaces the restoring
adc ecx, edx ; ADD/ADC followed by a further SUB/SBB
js .b
jmp .e
ALIGN 16
.c: dec edi
js .OK ; We're (back) at the original Divisor
shr edx, 1 ; Next try a smaller Divisor (/2)
rcr eax, 1
.d: sub ebx, eax ; Try subtracting Divisor from Dividend
sbb ecx, edx
jb .b ; Cannot
.e: bts [esp], edi ; -(1)- Add partial quotient
mov esi, ebx ; Is Dividend reduced to zero ?
or esi, ecx
jnz .c ; Not yet
.NIL: xor ebx, ebx ; Zero Remainder
xor ecx, ecx
.OK: pop eax edx ; (1) EDX:EAX is Quotient, ECX:EBX is Remainder
clc
.NOK: pop edi esi
ret
; - - - - - - - - - - - - - - -
; IN (edx:eax) OUT (esi,ZF)
.BSR: bsr esi, edx
jnz @f
bsr esi, eax ; -> ZF
ret
@@: add esi, 32 ; -> ESI=[32,63] ZF=0
ret
; ------------------------------
3条答案
按热度按时间z4bn682m1#
滥用x87
正如Peter Cordes提到的,在默认的FP舍入模式下,它舍入到最近,绑定到偶数。SSE 3中引入的
FISTTP
允许截断,因此其行为类似于IDIV
指令。它有两个舍入步骤,但由于x87保留64位,除数最多为263-1,增加或减少被除数会使商的变化大于eps。因此,它的行为就像第一步是准确的。
zf9nrax12#
EDX:EAX中的64位值除以ECX:EBX中的64位值的无符号除法
在32位x86 CPU上,the
div
instruction可以将EDX:EAX中的32位值除以任何32位值。如果我们保持被除数扩展(EDX)中的值小于32位除数,我们甚至可以在EDX:EAX中(成功地)划分大于32位的值。因此,如果我们希望能够将 * 任何 * 64位值除以 * 任何 * 其他64位值,我们需要为此编写代码。这个答案将使用名为Division by Partial Quotients aka Chunking的技术。
建议的算法不会击败内部
div
指令即使你使用的是64位数据类型,实践告诉我,(通用)程序中的大多数除法仍然可以只使用内置的
div
指令。这就是为什么我在代码中添加了一个检测机制来检查ECX:EBX中的除数是否小于4GB(因此适合EBX),以及EDX中被除数的扩展是否小于EBX中的除数。如果满足这些条件,并且我们没有尝试除以零,那么普通的div
指令就会完成这项工作,而且速度也更快。由于某种原因(如学校)使用div
是不允许的,那么只需删除前缀代码即可。算法描述
代码试图尽快摆脱特殊情况:
bsr
instruction查找被除数和除数中为ON的最高位:此时的除数已经成为一系列试减中使用的第一个值:
举例说明:34 / 5
被除数34是00100010 b,除数5是00000101 b。
它将产生商00000110 b(6)和余数00000100 b(4)。
二进制表示法当然还有56个0位!
优化
一个很好的改进是,在减法失败的情况下不必恢复被除数。
与前面的示例相同:34 / 5
我们现在将恢复加法 * 加40* 与进一步减法 * 减20* 组合成单个加法 * 加20*。
当然,最后,当没有更多的减法跟随时,我们仍然需要最终的恢复来获得余数。
在代码中:
EDX:EAX中的64位值除以ECX:EBX中的64位值的带符号除法
最简单的方法是围绕我们上面讨论的无符号除法编写wrapper:
.c:
)的 * 正数 * 值8000'0000'0000'0000 h。.d:
)。.e:
)相同。如果您需要显示EDX:EAX中的64位数字,请查看此转换算法。
6ovsh4lw3#
x86知道一个64 × 32位的除法,其中余数和商的结果为32位,如下所示
你不想除以一个不适合32位的64位数,因为你会失去10位的精度,最低限度。
如果你坚持下去,你可以从中组成一个64/64的除法,然后分析你是否真的喜欢它。
如果你知道如何将一个2位数的十进制数除以一个2位数的十进制数(长除法),你可以使用上面的方法来完成将一个2位数的2^32基数除以一个2位数的2^32基数。这是留给读者的练习。