EDX-EAX 寄存器对除法导致大商

EDX-EAX register pair divison resulting in big quotient

如果我在 EDX-EAX 中有一个 64 位 数字,并且我 除法 相对的数,可能变成比32位大的数。 所以在那个时候 div operator 只设置 carry flag?

我的问题是,我想在 EDX-EAX 中处理一个数字并将其写出 每个数字 ,所以在这种情况下,我必须 EDX-EAX 中的数字 10 才能得到最后一位。

没有。 DIV 在 64b/32b 中的最大商为 232-1.

Overflow is indicated with the #DE (divide error) exception rather than with the CF flag.

如果 64b 数量有一些限制, 使用完整的 64b(例如 261 最大值),那么您可以先拆分它首先 div 109 (最接近左边的 232 )然后分别做两个 "halves" div 10。但正如 Jester 指出的那样,64b div 太慢了,用 10 的幂做 sub 听起来是个更好的主意,代码也会更简单。


既然如此简单,那为什么不添加代码,对吧?大约 5 分钟 ... 大约 60 分钟后(我对此不是很满意,我认为它可以用更短的代码以更优雅的方式完成......在任何一种情况下都不会打扰性能,这个可以肯定会进行优化,至少要在重要的地方对齐循环,但它至少可以工作,所以它可以是你的 "reference version" 到 compare/verify with)...

NASM 32b linux executable,存入uint64toascii.asm
建造:nasm -f elf *.asm; ld -m elf_i386 -s -o uint64toascii *.o

section .text
    global _start       ;must be declared for using gcc
_start:                 ;tell linker entry point

    ; allocate 24B temporary buffer for ASCII number
    sub     esp,24
    ; output test numbers in loop
    mov     esi,testnumbers
testNumbersLoop:
    mov     eax,[esi]
    mov     edx,[esi+4]
    mov     edi,esp
    ; call the routine
    call    integer64btoascii
    ; add new line to output
    mov     [edi],byte 10
    inc     edi
    ; display number string
    mov     edx,edi
    sub     edx,esp     ; output length
    mov     ecx,esp     ; output buffer address
    mov     ebx,1       ; file descriptor (stdout)
    mov     eax,4       ; system call number (sys_write)
    int     0x80        ; call kernel
    ; loop through test numbers
    add     esi,8
    cmp     esi,testnumbersEND
    jb      testNumbersLoop
    ; exit
    add     esp,24      ; release temporary buffer
    mov     eax, 1      ; system call number (sys_exit)
    int     0x80        ; call kernel

integer64btoascii:
    ; edx:eax = number to convert, edi = buffer to output (at least 20B)
    ; returns edi pointing after last character
    push    eax
    push    edx
    push    esi
    push    ebx
    push    ebp
    push    ecx
    ; test for zero in edx:eax -> special handling
    mov     esi,edx
    or      esi,eax
    jz      .zeroNumber
    ; convert other numbers by subtracting 10^k powers
    mov     esi,pow10table-8
.skipLeadingZero:
    add     esi,8
    cmp     edx,[esi+4]
    jc      .skipLeadingZero
    jne     .next10powerInit
    cmp     eax,[esi]
    jc      .skipLeadingZero
    jmp     .next10powerInit
    ; since here every power of 10 is counted and set into output
.next10power:
    mov     [edi],cl    ; write counter digit of previous 10th power
    inc     edi
.next10powerInit:
    mov     ebx,[esi]
    mov     ebp,[esi+4] ; ebp:ebx = 10^k
    test    ebx,ebx
    jz      .finish     ; only zero terminator can have lower 32b == 0
    mov     cl,'0'
    add     esi,8
.compare10power:
    cmp     edx,ebp
    jc      .next10power
    jnz     .sub10power
    cmp     eax,ebx
    jc      .next10power
.sub10power:
    sub     eax,ebx
    sbb     edx,ebp
    inc     cl
    jmp     .compare10power

.zeroNumber:
    mov     [edi],byte '0'
    inc     edi

.finish:
    pop     ecx
    pop     ebp
    pop     ebx
    pop     esi
    pop     edx
    pop     eax
    ret

section .rodata

pow10table:
    dq  10000000000000000000
    dq  1000000000000000000
    dq  100000000000000000
    dq  10000000000000000
    dq  1000000000000000
    dq  100000000000000
    dq  10000000000000
    dq  1000000000000
    dq  100000000000
    dq  10000000000
    dq  1000000000
    dq  100000000
    dq  10000000
    dq  1000000
    dq  100000
    dq  10000
    dq  1000
    dq  100
    dq  10
    dq  1
    dq  0       ; terminator

testnumbers:
    dq  ~0          ; max 2^64-1 = 18446744073709551615
    dq  0           ; looks like zero to me
    dd  0, 1        ; 2^32 = 4294967296
    dq  1234567890  ; < 2^32 (edx = 0)
    dq  9999999999999999999
    dq  101001000100101
testnumbersEND:

您可以在 http://www.tutorialspoint.com/compile_assembly_online.php 上在线试用(将源复制到那里)


而且因为我不太喜欢第一个版本,所以我一直在玩它,主要是为了实现一个简短的 (LoC) 代码,而不是特别关心性能或代码大小(懒得测量除了编辑器中的行数之外的任何内容。

构建命令行+现场演示与之前的案例相同:

section .text
    global _start       ;must be declared for using gcc
_start:                 ;tell linker entry point

    ; allocate 24B temporary buffer for ASCII number
    sub     esp,24
    ; output test numbers in loop
    mov     esi,testnumbers
testNumbersLoop:
    mov     eax,[esi]
    mov     edx,[esi+4]
    mov     edi,esp
    ; call the routine
    call    integer64btoascii
    ; add new line to output
    mov     [edi],byte 10
    inc     edi
    ; display number string
    mov     edx,edi
    sub     edx,esp     ; output length
    mov     ecx,esp     ; output buffer address
    mov     ebx,1       ; file descriptor (stdout)
    mov     eax,4       ; system call number (sys_write)
    int     0x80        ; call kernel
    ; loop through test numbers
    add     esi,8
    cmp     esi,testnumbersEND
    jb      testNumbersLoop
    ; exit
    add     esp,24      ; release temporary buffer
    mov     eax, 1      ; system call number (sys_exit)
    int     0x80        ; call kernel

integer64btoascii:
    ; edx:eax = number to convert, edi = buffer to output (at least 20B)
    ; returns edi pointing after last character
    push    eax
    push    edx
    push    esi
    push    ecx
    mov     ch,'1'          ; test value for skipping leading zeroes
    mov     esi,pow10table
.nextPow10:                 ; [esi+4]:[esi] = 10^k
    mov     cl,'0'-1
.countPow10:                ; subtract 10^k from edx:eax + count it
    sub     eax,[esi]
    sbb     edx,[esi+4]
    inc     cl              ; preserves CF
    jnc     .countPow10
    ; subtraction overflow, did "one too many" of them
    add     eax,[esi]       ; restore edx:eax to previous value
    adc     edx,[esi+4]
    cmp     cl,ch
    mov     [edi],cl        ; write the digit into output
    sbb     edi,-1          ; advance edi as needed (when cl>=ch)
    cmp     cl,ch
    lea     esi,[esi+8]     ; next power of 10
    adc     ch,-1           ; disable zero skip when non-zero found
    cmp     esi,pow10tableEND
    jb      .nextPow10      ; until all powers of 10 were processed
    cmp     ch,'1'          ; all zeroes output => edx:eax == 0, CF=0
    sbb     edi,-1          ; advance edi when CF=0 (all zeroes)
    pop     ecx
    pop     esi
    pop     edx
    pop     eax
    ret

section .rodata

pow10table:
    dq  10000000000000000000
    dq  1000000000000000000
    dq  100000000000000000
    dq  10000000000000000
    dq  1000000000000000
    dq  100000000000000
    dq  10000000000000
    dq  1000000000000
    dq  100000000000
    dq  10000000000
    dq  1000000000
    dq  100000000
    dq  10000000
    dq  1000000
    dq  100000
    dq  10000
    dq  1000
    dq  100
    dq  10
    dq  1
pow10tableEND:

testnumbers:
    dq  ~0          ; max 2^64-1 = 18446744073709551615
    dq  0           ; looks like zero to me
    dd  0, 1        ; 2^32 = 4294967296 (eax = 0)
    dq  1234567890  ; < 2^32 (edx = 0)
    dq  10000000000000000000    ; largest 10^k to fit into 64b
    dq  9999999999999999999     ; to verify "9"
    dq  10200300040000500000    ; to verify "0" in-between/at-end
testnumbersEND:

顺便说一句,如果 CPU 不会因部分注册 chcl 冲突而停滞太多(恕我直言,因为值的更新有点不同并且很少发生碰撞),那么我相信第二个版本会比第一个版本表现更好,因为分支更简单。

但是"believe"是这里的关键词,如果你追求的是性能,profile! (并在关键循环上使用"align 8 or 16 (or maybe just 4)",验证列表+性能哪个更好)


另一个版本,可能更容易理解前导零测试逻辑,而且可以很容易地扩展到 128b 整数(ebxebp 被保留下来,因此它们可能包含另一个输入数字的 64b),以及任何其他任意数量的位,当数字 + 减法在内存中时(因为 256b 不适合 32b x86 模式下的寄存器)。这也可以修改为在 64b 模式下工作,只需很少的更改即可使用更多寄存器(当然,所有这些都需要更大的 table 的 10k 次幂,直到最大值一个用于所需的位数)。

我发布了这个,因为我对最终解决 "zero" 难题感到非常高兴 - 这让我整个周末都很烦恼。

最后,优雅(且更快)的解决方案存在:在 101 次方后退出减法,所以 eax 的值是 0-9.然后将该值写入输出而不进行任何 "skip" 测试,因为在非零数字的情况下它属于正确的输出,而在 edx:eax == 0 输入的情况下它将创建单个 '0'字符。呸!

此外,我设法更改了 "skip leading zeroes" 逻辑以在生成的任意数量的数字中存活下来,而不仅仅是 48。

section .text
    global _start       ;must be declared for using gcc
_start:                 ;tell linker entry point
    ; allocate 24B temporary buffer for ASCII number
    sub     esp,24
    ; output test numbers in loop
    mov     esi,testnumbers
testNumbersLoop:
    mov     eax,[esi]
    mov     edx,[esi+4]
    mov     edi,esp
    ; call the routine
    call    integer64btoascii
    ; add new line to output
    mov     [edi],byte 10
    inc     edi
    ; display number string
    mov     edx,edi
    sub     edx,esp     ; output length
    mov     ecx,esp     ; output buffer address
    mov     ebx,1       ; file descriptor (stdout)
    mov     eax,4       ; system call number (sys_write)
    int     0x80        ; call kernel
    ; loop through test numbers
    add     esi,8
    cmp     esi,testnumbersEND
    jb      testNumbersLoop
    ; exit
    add     esp,24      ; release temporary buffer
    mov     eax, 1      ; system call number (sys_exit)
    int     0x80        ; call kernel

integer64btoascii:
    ; edx:eax = number to convert, edi = buffer to output (at least 20B)
    ; returns edi pointing after last character
    push    eax
    push    edx
    push    esi
    push    ecx
    mov     ch,'0'          ; test value to detect leading zeroes
    mov     esi,pow10table
.nextPow10:                 ; [esi+4]:[esi] = 10^k
    mov     cl,'0'-1        ; count of 10^k power in ASCII digit
.countPow10:                ; subtract 10^k from edx:eax + count it
    sub     eax,[esi]
    sbb     edx,[esi+4]
    inc     cl              ; preserves CF
    jnc     .countPow10     ; loop till subtraction overflows
    ; subtraction overflow, did "one too many" of them
    or      ch,cl           ; merge digit into test_leading_zeroes
    add     eax,[esi]       ; restore edx:eax to previous value
    adc     edx,[esi+4]
    cmp     ch,'1'          ; test is still '0'? => CF=1
    mov     [edi],cl        ; write the digit into output
    lea     esi,[esi+8]     ; next power of 10
    sbb     edi,-1          ; advance edi as needed (test value > '0')
    cmp     esi,pow10tableEND
    jb      .nextPow10      ; until all table powers of 10 were processed
    or      al,'0'          ; remaining eax = 0..9, convert to ASCII
    mov     [edi],al        ; store last digit
    inc     edi             ; last digit will advance edi always
    pop     ecx
    pop     esi
    pop     edx
    pop     eax
    ret

section .rodata

pow10table:
    dq  10000000000000000000
    dq  1000000000000000000
    dq  100000000000000000
    dq  10000000000000000
    dq  1000000000000000
    dq  100000000000000
    dq  10000000000000
    dq  1000000000000
    dq  100000000000
    dq  10000000000
    dq  1000000000
    dq  100000000
    dq  10000000
    dq  1000000
    dq  100000
    dq  10000
    dq  1000
    dq  100
    dq  10
pow10tableEND:

testnumbers:
    dq  ~0          ; max 2^64-1 = 18446744073709551615
    dq  0           ; looks like zero to me
    dd  0, 1        ; 2^32 = 4294967296 (eax = 0)
    dq  1234567890  ; < 2^32 (edx = 0)
    dq  10000000000000000000    ; largest 10^k to fit into 64b
    dq  9999999999999999999     ; to verify "9"
    dq  10200300040000500000    ; to verify "0" in-between/at-end
testnumbersEND:

为了获得更好的性能,应该可以进行反转值(倒数)乘法以获得 div 10 乘以 imul,但这超出了我的理解范围。

这三个版本对于学习汇编的人来说可能足够简单以理解它们,而且它们说明了几天来的思维进步。