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 不会因部分注册 ch
与 cl
冲突而停滞太多(恕我直言,因为值的更新有点不同并且很少发生碰撞),那么我相信第二个版本会比第一个版本表现更好,因为分支更简单。
但是"believe"是这里的关键词,如果你追求的是性能,profile! (并在关键循环上使用"align 8 or 16 (or maybe just 4)",验证列表+性能哪个更好)
另一个版本,可能更容易理解前导零测试逻辑,而且可以很容易地扩展到 128b 整数(ebx
和 ebp
被保留下来,因此它们可能包含另一个输入数字的 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
,但这超出了我的理解范围。
这三个版本对于学习汇编的人来说可能足够简单以理解它们,而且它们说明了几天来的思维进步。
如果我在 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 不会因部分注册 ch
与 cl
冲突而停滞太多(恕我直言,因为值的更新有点不同并且很少发生碰撞),那么我相信第二个版本会比第一个版本表现更好,因为分支更简单。
但是"believe"是这里的关键词,如果你追求的是性能,profile! (并在关键循环上使用"align 8 or 16 (or maybe just 4)",验证列表+性能哪个更好)
另一个版本,可能更容易理解前导零测试逻辑,而且可以很容易地扩展到 128b 整数(ebx
和 ebp
被保留下来,因此它们可能包含另一个输入数字的 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
,但这超出了我的理解范围。
这三个版本对于学习汇编的人来说可能足够简单以理解它们,而且它们说明了几天来的思维进步。