【问题标题】:power of 2 in x86 assembly, calculate 2^x that doesn't fit in a 32-bit registerx86 汇编中 2 的幂,计算不适合 32 位寄存器的 2^x
【发布时间】:2012-10-01 20:08:35
【问题描述】:

作为 ASM 编程的初学者,我需要在 Assembly 中获得 2 的 38 次方的结果,并且我需要您的帮助来理解为什么我的程序没有产生我需要的结果(它打印 4十进制):

.386
.model flat, stdcall
option casemap:none

include \masm32\include\windows.inc
include \masm32\include\msvcrt.inc
includelib \masm32\lib\msvcrt.lib

.data

formatstr db "%d",0

.code 
start:

mov eax , 2
mov ecx , 38
mov esi , eax
mov edx , 0

.while TRUE
    mul esi
    mov esi, edx
    add esi, eax
    mov edx, 0
    mov eax, 2
    dec ecx
    .break .if (!ecx)
.endw
    



invoke crt__cprintf, addr formatstr, esi


end start

如您所见,我正在使用 masm32 编写它(如果在这种情况下有任何问题)。

【问题讨论】:

  • 想一想存储 2^38 需要多少位以及结果寄存器需要多少位。
  • 请注意,2^38 对于(32 位)int 来说太大了 - 您的 printf 格式字符串需要是 %lld,而不是 %d,并且相应的参数需要是 64 位。

标签: assembly x86 masm exponent exponentiation


【解决方案1】:

2^38 显然不适合 eax 这样的一个 32 位寄存器。

要存储值2^38 (274877906944),您需要 39 位。在 32 位代码中,您可以使用例如。两个 32 位寄存器,例如 edx:eax。但是,在 32 位代码中,mul 只接受 32 位 因素(例如寄存器,其他总是 eax),因此在循环中使用 32 位 mul 会赢不行,因为您不能将中间结果存储在 32 位寄存器中以 再次相乘,即使 mul 将 64 位 结果 存储在 @987654331 @。

但是您可以使用rcl 来计算例如。 2^38 32 位代码:

    xor edx,edx
    mov eax,2    ; now you have 2 in edx:eax
    mov ecx,38   ; 2^n, in this case 2^38 (any value x, 1 <= x <= 63, is valid).

x1: dec ecx      ; decrease ecx by 1
    jz ready     ; if it's 2^1, we are ready.

    shl eax,1    ; shift eax left through carry flag (CF) (overflow makes edx:eax zero)
    rcl edx,1    ; rotate edx through carry flag (CF) left
    jmp x1

ready:            ; edx:eax contains now 2^38.

编辑: 受@Jagged O'Neill 的回答启发的非循环实现。这一项对于指数 >= 32 没有跳跃,对于指数 ecx 0,对于大于 63 组 edx:eax 到 0 的 ecx。

    mov     ecx,38          ; input (exponent) in ecx. 2^n, in this case 2^38.
                            ; (any value x, 0 <= x <= 63, is valid).
; the code begins here.

    xor     eax,eax
    xor     edx,edx         ; edx:eax is now prepared.

    cmp     cl,64           ; if (cl >= 64),
    setb    al              ; then set eax to 0, else set eax to 1.
    jae     ready           ; this is to handle cl >= 64.

; now we have 0 <= cl <= 63

    sub     ecx,1
    setnc   al              ; if (count == 0) then eax = 0, else eax = 1.
    lea     eax,[eax+1]     ; eax = eax + 1. does not modify any flags.
    jna     ready           ; 2^0 is 1, 2^1 = 2, those are ready now.
    mov     ebx,ecx         ; copy ecx to ebx
    cmp     cl,32           ; if (cl >= 32)
    jb      low_5_bits
    mov     cl,31           ; then shift first 31 bits to the left.
    shld    edx,eax,cl
    shl     eax,cl          ; now shifted 31 bits to the left.
    lea     ecx,[ebx-31]    ; cl = bl - 31

low_5_bits:
    shld    edx,eax,cl
    shl     eax,cl

ready:

【讨论】:

  • x1 循环不是应该从dec ecx 行开始吗?
  • 没有。 loop 命令递减ecx,如果ecx 不为零,则跳转到目标地址,在本例中为x1。 loop x1 实际上等同于 dec ecx; jnz x1。有条件地跳转到dec ecx 行只会移位 19 次而不是所需的 37 次。
【解决方案2】:

当您在 x86 上进行乘法运算时,edx 将保存结果的前 32 位,eax 将保存结果的后 32 位。当你这样做时:

mul esi
mov esi, edx
add esi, eax

结果只有在 edx 为 0 的情况下才有意义,所以 mov/add 基本上是在做 mov esi, eax。如果前 32 位不为零,那么您最终会得到一个相当无意义的高位和低位混搭。

【讨论】:

  • 我应该如何使用两个单独的寄存器作为输入来调用 printf ?我应该先把它放在分配的内存中吗?
  • @HananN.:如果您使用的 printf 具有可以接受 64 位整数的格式,则可以使用它。否则,您可能必须自己转换为数字,然后打印出字符串。
猜你喜欢
  • 2015-05-31
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-02-26
  • 1970-01-01
  • 1970-01-01
  • 2011-01-14
相关资源
最近更新 更多