assembly x87可以对无符号四字整数执行精确除法吗?

quhf5bfb  于 2023-06-23  发布在  其他
关注(0)|答案(1)|浏览(108)

8086/8087/8088 MACRO ASSEMBLY LANGUAGE REFERENCE MANUAL(c)1980 Intel Corporation提到(强调我的):
... 8087提供了一个非常好的近似真实的系统。然而,重要的是要记住,它不是一个精确的表示,真实的的算术本质上是近似的。
相反,同样重要的是,8087确实对实数的整数子集执行精确的算术。也就是说,对两个整数的运算返回精确的整数结果,前提是真结果是整数并且在范围内。
最近的一本手册甚至更简洁(强调他们的):
内务部的处理器它们可以处理最多18位的十进制数而不会出现舍入错误,对大到2^64(或10^18)的整数执行精确算术
FPU支持的整数数据类型包括有符号字(16位)、有符号双字(32位)和有符号qword(64位)。从来没有提到过未签名。事实上,关于FPU的一切都是有符号的,甚至支持有符号的零(+0和-0)。
那么,有没有可能使用FPU来划分一对 * 无符号 * 64位数字,并获得精确的商和余数?
对于两个带符号的64位数字的除法,我编写了下一个代码。商看起来很好,但余数总是返回零。这是为什么呢?

; IN (edx:eax,ecx:ebx) OUT (edx:eax,ecx:ebx,CF)
FiDiv:  push    edi ecx ebx edx eax
        mov     edi, esp
        fninit
        fild    qword [edi]     ; Dividend
        fild    qword [edi+8]   ; Divisor
        fld
        fnstcw  [edi]
        or      word [edi], 0C00h ; Truncate Towards Zero
        fldcw   [edi]
        fdivr   st2             ; st0 = st2 / st0
        fld                     ; Duplicate because `fistp` does pop
        fistp   qword [edi]     ; Quotient
        fmulp                   ; st1 *= st0, pop st0
        fsubp                   ; st1 -= st0, pop st0
        fistp   qword [edi+8]   ; Remainder
        fnstsw  ax
        test    ax, 101b        ; #Z (Divide-By-Zero), #I (Invalid Arithmetic)
        pop     eax edx ebx ecx edi
        jnz     .NOK
        ret                     ; CF=0
.NOK:   stc                     ; Overflow on 8000000000000000h / -1
        ret                     ; or Division by zero
; ------------------------------

FPU舍入模式设置为“Truncate Towards Zero”(即“Chop”),以模拟ALU idiv指令的行为。

zyfwsgd6

zyfwsgd61#

零余数

fdivr   st2             ; st0 = st2 / st0
fld                     ; Duplicate because `fistp` does pop
fistp   qword [edi]     ; Quotient
fmulp                   ; st1 *= st0, pop st0
fsubp                   ; st1 -= st0, pop st0
fistp   qword [edi+8]   ; Remainder

此代码从以下公式计算余数:

Remainder = Dividend - (Quotient * Divisor)

由于舍入模式被设置为“Truncate Towards Zero”,因此fistp qword [edi]指令将在存储到内存之前将ST0中保存的商(的副本)转换为整数。然而,保留在(fpu)堆栈上的商的值仍然是带有分数的实数。一旦与除数相乘,它将再次产生被除数,结果是零余数。
缺少的是将商四舍五入为整数,并在(fpu)堆栈上执行:

fdivr   st2             ; st0 = st2 / st0
frndint
fld                     ; Duplicate because `fistp` does pop
fistp   qword [edi]     ; Quotient
fmulp                   ; st1 *= st0, pop st0
fsubp                   ; st1 -= st0, pop st0
fistp   qword [edi+8]   ; Remainder

但更快的方法是简单地从内存中重新加载整数商:

fdivr   st2             ; st0 = st2 / st0
fistp   qword [edi]     ; Quotient
fild    qword [edi]
fmulp                   ; st1 *= st0, pop st0
fsubp                   ; st1 -= st0, pop st0
fistp   qword [edi+8]   ; Remainder

无符号分割

在内部,FPU将64位专用于数字的有效位,另加一位用于数字的符号。FPU可以表示从-18 '446744073' 709551616到18 '446744073' 709551616范围内的每个整数。64位有效数允许我们处理范围从0到18 '446744073' 709551615的无符号64位整数。唯一的麻烦是如何加载和存储这些fildfistp无法处理的值(因为它们被限制在从-9'223372036 '854775808到9'223372036' 854775807的范围内操作)。
可以在无符号四字和扩展实数格式之间来回转换,因此我们可以使用fldfstp。另一种方法将无符号四字从其上半部和下半部加载/存储到其上半部和下半部。但是转换需要时间,所以我发现,通过消除足够多的特殊情况,剩下的唯一麻烦的操作是被除数的加载。其他一切都可以像往常一样使用fildfistp
特殊情况包括:

  • 除以零:CF = 1
  • 除以一:Quotient = dividend,余数= 0
  • 股息等于除数的除数:商= 1,余数= 0
  • 股息小于除数的除数:商= 0,余数=股利

在需要实际的fdiv的情况下,代码首先加载被除数的一半,在(fpu)堆栈上将其加倍,如果真正的被除数是奇数,则有条件地添加1。

; IN (edx:eax,ecx:ebx) OUT (edx:eax,ecx:ebx,CF)
FuDiv:  cmp     ebx, 1
        jbe     .TST            ; Divisor could be 0 or 1
.a:     cmp     edx, ecx
        jb      .LT             ; Dividend < Divisor
        ja      .b              ; Dividend > Divisor
        cmp     eax, ebx
        jb      .LT             ; Dividend < Divisor
        je      .GE             ; Dividend = Divisor
.b:     test    ecx, ecx
        js      .GE             ; Dividend > Divisor > 7FFFFFFFFFFFFFFFh

        shr     edx, 1          ; Halving the unsigned 64-bit Dividend
        rcr     eax, 1          ; (CF)      to get in range for `fild`
        push    edi ecx ebx edx eax
        mov     edi, esp
        fninit
        fild    qword [edi]     ; st0 = int(Dividend / 2)
        fadd    st0             ; st0 = {Dividend - 1, Dividend}
        jnc     .c              ; (CF)
        fld1
        faddp                   ; st0 = Dividend [0, FFFFFFFFFFFFFFFFh]
.c:     fild    qword [edi+8]   ; Divisor is [2, 7FFFFFFFFFFFFFFFh]
        fld
        fnstcw  [edi]
        or      word [edi], 0C00h ; Truncate Towards Zero
        fldcw   [edi]
        fdivr   st2             ; st0 = st2 / st0
        fistp   qword [edi]     ; Quotient
        fild    qword [edi]
        fmulp                   ; st1 *= st0, pop st0
        fsubp                   ; st1 -= st0, pop st0
        fistp   qword [edi+8]   ; Remainder
        pop     eax edx ebx ecx edi
        ret                     ; CF=0

.TST:   test    ecx, ecx
        jnz     .a
        cmp     ebx, 1          ; Divisor is 0 or 1
        jb      .NOK
.ONE:   dec     ebx             ; Remainder ECX:EBX is 0
.NOK:   ret

.GE:    sub     eax, ebx        ; Remainder ECX:EBX is Dividend - Divisor
        sbb     edx, ecx
        mov     ebx, eax
        mov     ecx, edx
        mov     eax, 1          ; Quotient EDX:EAX is 1
        cdq
        ret                     ; CF=0

.LT:    mov     ebx, eax        ; Remainder ECX:EBX is Dividend
        mov     ecx, edx
        xor     eax, eax        ; Quotient EDX:EAX is 0
        cdq
        ret                     ; CF=0
; ------------------------------

更快的结果

引用一个使用名为Chunking的技术来除两个64位整数的答案:
即使你使用的是64位数据类型,实践告诉我,(通用)程序中的大多数除法仍然可以只使用内置的div指令。这就是为什么我在代码中添加了一个检测机制来检查ECX:EBX中的除数是否小于4GB(因此适合EBX),以及EDX中被除数的扩展是否小于EBX中的除数。如果满足这些条件,则正常的div指令执行任务,并且执行速度也更快。由于某种原因(如学校)使用div是不允许的,然后只需删除前缀代码即可清除。
今天的代码可以从相同的前缀中受益,但事实证明,首先检测特殊情况更有益:

; IN (edx:eax,ecx:ebx) OUT (edx:eax,ecx:ebx,CF)
FuDiv:  cmp     ebx, 1
        jbe     .TST            ; Divisor could be 0 or 1
.a:     cmp     edx, ecx
        jb      .LT             ; Dividend < Divisor
        ja      .b              ; Dividend > Divisor
        cmp     eax, ebx
        jb      .LT             ; Dividend < Divisor
        je      .GE             ; Dividend = Divisor
.b:     test    ecx, ecx
        js      .GE             ; Dividend > Divisor > 7FFFFFFFFFFFFFFFh
; - - - - - - - - - - - - - - -
        jnz     .fdiv
        cmp     edx, ebx
        jnb     .fdiv
.div:   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
; - - - - - - - - - - - - - - -
.fdiv:  shr     edx, 1          ; Halving the unsigned 64-bit Dividend
        rcr     eax, 1          ; (CF)      to get in range for `fild`
        push    edi ecx ebx edx eax
        mov     edi, esp
        fninit
        fild    qword [edi]     ; st0 = int(Dividend / 2)
        fadd    st0             ; st0 = {Dividend - 1, Dividend}
        jnc     .c              ; (CF)
        fld1
        faddp                   ; st0 = Dividend [0, FFFFFFFFFFFFFFFFh]
.c:     fild    qword [edi+8]   ; Divisor is [2, 7FFFFFFFFFFFFFFFh]
        fld
        fnstcw  [edi]
        or      word [edi], 0C00h ; Truncate Towards Zero
        fldcw   [edi]
        fdivr   st2             ; st0 = st2 / st0
        fistp   qword [edi]     ; Quotient
        fild    qword [edi]
        fmulp                   ; st1 *= st0, pop st0
        fsubp                   ; st1 -= st0, pop st0
        fistp   qword [edi+8]   ; Remainder
        pop     eax edx ebx ecx edi
        ret                     ; CF=0

.TST:   test    ecx, ecx
        jnz     .a
        cmp     ebx, 1          ; Divisor is 0 or 1
        jb      .NOK
.ONE:   dec     ebx             ; Remainder ECX:EBX is 0
.NOK:   ret

.GE:    sub     eax, ebx        ; Remainder ECX:EBX is Dividend - Divisor
        sbb     edx, ecx
        mov     ebx, eax
        mov     ecx, edx
        mov     eax, 1          ; Quotient EDX:EAX is 1
        cdq
        ret                     ; CF=0

.LT:    mov     ebx, eax        ; Remainder ECX:EBX is Dividend
        mov     ecx, edx
        xor     eax, eax        ; Quotient EDX:EAX is 0
        cdq
        ret                     ; CF=0
; ------------------------------

相关问题