EDX-EAX寄存器对分裂导致大商数

时间:2016-12-11 09:57:25

标签: assembly x86 division

如果我在 EDX-EAX 中有 64位数字,并且我除以 em> number,可能会变成大于 32位的数字。 那么此时div 运算符只设置进位标志

我的问题是,我想在 EDX-EAX 中处理一个数字并将其写出数字,所以在这种情况下我必须 10 10

1 个答案:

答案 0 :(得分:3)

没有。 64b / 32b中的DIV具有最大商2 32 -1。

  

溢出用#DE(除错误)异常表示,而不是CF标志。

如果64b号码有一些限制,使用完整的64b(例如2 61 max),那么您可以先将其拆分为{{ 1}} 10 9 (最左边2 32 )首先然后做两个"一半"分别由div。但正如杰斯特所指出的那样,64b div 10是如此之慢,以10为幂的sub做声听起来更好,而且代码也会更简单。

因为如果简单,那么为什么不添加代码,对吧?将是〜5分钟......〜60分钟后(我对它不是很满意,我认为可以通过更短的代码以更优雅的方式完成...在任何一种情况下都不会打扰性能,这个一个可以肯定优化,最小化对齐重要的循环,但它至少有效,所以它可以是你的"参考版本"比较/验证... ...

NASM 32b linux可执行文件,保存到div
建立:uint64toascii.asm

nasm -f elf *.asm; ld -m elf_i386 -s -o uint64toascii *.o

您可以在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    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:
顺便说一下,如果CPU不会因部分注册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: vs ch冲突而停顿太多(恕我直言不应该,因为价值更新有点分开并且只会发生碰撞很少),然后我相信第二个版本的性能会比第一个版本好,因为分支更加简化了。

但是&#34;相信&#34;这里是关键字,如果你是表演后,个人资料! (并使用&#34;对齐8或16(或者可能只是4)&#34;在关键循环上,验证列表+性能哪一个更好)

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

我发布了这个,因为我对如何解决&#34;零&#34;我非常高兴。最后的谜题 - 这整个周末让我烦恼。

最后,优雅(和更快)的解决方案存在:只需在10 1 幂之后退出减法,因此ebp留下值eax。然后只需将该值写入输出而不需要任何&#34;跳过&#34;测试,如果是非零数字,它属于正确的输出,如果输入0-9,它将创建单个edx:eax == 0字符。杜!

另外,我设法改变&#34;跳过前导零&#34;生存任意数量的数字的逻辑,不仅仅是48。

'0'

为了获得更好的性能,应该可以通过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,但这已经超出了我的想法。

这三个版本可能很简单,有些人可以学习大会来理解它们,而且它们可以说明几天内心灵的进步。