windows-nt/Source/XPSP1/NT/base/ntdll/i386/emlsbcd.asm
2020-09-26 16:20:57 +08:00

280 lines
7.3 KiB
NASM

subttl emlsbcd.asm - FBSTP and FBLD instructions
page
;*******************************************************************************
;emlsbcd.asm - FBSTP and FBLD instructions
;
; Microsoft Confidential
;
; Copyright (c) Microsoft Corporation 1991
; All Rights Reserved
;
;Purpose:
; FBSTP and FBLD instructions.
;
; These routines convert between 64-bit integer and 18-digit packed BCD
; format. They work by splitting the number being converted in half
; and converting the two halves separately. This works well because
; 9 decimal digits fit nicely within 30 binary bits, so converion of
; each half is strictly a 32-bit operation.
;
;Inputs:
; edi = [CURstk]
; dseg:esi = pointer to memory operand
;
;Revision History:
;
; [] 09/05/91 TP Initial 32-bit version.
;
;*******************************************************************************
;******
eFBLD:
;******
mov eax,dseg:[esi+5] ;Get high 8 digits
or eax,eax ;Anything there?
jz HighDigitsZero
mov ecx,8
call ReadDigits ;Convert first 8 digits to binary
mov eax,dseg:[esi+1] ;Get next 8 digits
xor edi,edi
shld edi,eax,4 ;Shift ninth digit into edi
imul ebx,10
add edi,ebx ;Accumulate ninth digit
SecondNineDigits:
xor ebx,ebx ;In case eax==0
shl eax,4 ;Keep digits left justified
jz LastTwoDigits
mov ecx,7
call ReadDigits ;Convert next 7 digits to binary
LastTwoDigits:
mov al,dseg:[esi] ;Get last two digits
shl eax,24 ;Left justify
mov ecx,2
call InDigitLoop ;Accumulate last two digits
;edi = binary value of high 9 digits
;ebx = binary value of low 9 digits
mov eax,1000000000 ;One billion: shift nine digits left
mul edi ;Left shift 9 digits. 9 cl. if edi==0
add ebx,eax ;Add in low digits
adc edx,0
BcdReadyToNorm:
;edx:ebx = integer converted to binary
mov eax,dseg:[esi+6] ;Get sign to high bit of eax
mov esi,ebx
mov ebx,edx
mov edi,EMSEG:[CURstk]
;mantissa in ebx:esi, sign in high bit of eax
;edi = [CURstk]
jmp NormQuadInt ;in emload.asm
HighDigitsZero:
mov eax,dseg:[esi+1] ;Get next 8 digits
or eax,eax ;Anything there?
jz CheckLastTwo
xor edi,edi
shld edi,eax,4 ;Shift ninth digit into edi
jmp SecondNineDigits
CheckLastTwo:
mov bl,dseg:[esi] ;Get last two digits
or bl,bl
jz ZeroBCD
mov al,bl
shr al,4 ;Bring down upper digit
imul eax,10
and ebx,0FH ;Keep lowest digit only
add ebx,eax
xor edx,edx
jmp BcdReadyToNorm
ZeroBCD:
mov ecx,bTAG_ZERO ;Exponent is zero
mov ch,dseg:[esi+9] ;Get sign byte to ch
xor ebx,ebx
mov esi,ebx
;mantissa in ebx:esi, exp/sign in ecx
;edi = [CURstk]
jmp FldCont ;in emload.asm
;*** ReadDigits
;
;Inputs:
; eax = packed BCD digits, left justified, non-zero
; ecx = no. of digits, 7 or 8
;Outputs:
; ebx = number
SkipZeroDigits:
sub ecx,3
shl eax,12
ReadDigits:
;We start by scanning off leading zeros. This costs 16 cl./nybble in
;the ScanZero loop. To reduce this cost for many leading zeros, we
;check for three leading zeros at a time. Adding this test saves
;26 cl. for 3 leading zeros, 57 cl. for 6 leading zeros, at a cost
;of only 5 cl. if less than 3 zeros. We choose 3 at a time so we
;can repeat it once (there are never more than 7 zeros).
test eax,0FFF00000H ;Check first 3 nybbles for zero
jz SkipZeroDigits
xor ebx,ebx
ScanZero:
;Note that bsr is 3 cl/bit, or 12 cl/nybble. Add in the overhead and
;this loop of 16 cl/nybble is cheaper for the 1 - 3 digits it does.
dec ecx
shld ebx,eax,4 ;Shift digit into ebx
rol eax,4 ;Left justify **Doesn't affect ZF!**
jz ScanZero ;Skip to next digit if zero
jecxz ReadDigitsX
InDigitLoop:
;eax = digits to convert, left justified
;ebx = result accumulation
;ecx = number of digits to convert
xor edx,edx
shld edx,eax,4 ;Shift digit into edx
shl eax,4 ;Keep digits left justified
imul ebx,10 ;Only 10 clocks on 386!
add ebx,edx ;Accumulate number
dec ecx
jnz InDigitLoop
ReadDigitsX:
ret
;*******************************************************************************
ChkInvalidBCD:
ja SetInvalidBCD
cmp edi,0A7640000H ;(1000000000*1000000000) and 0ffffffffh
jb ValidBCD
SetInvalidBCD:
mov EMSEG:[CURerr],Invalid
InvalidBCD:
test EMSEG:[CWmask],Invalid ;Is it masked?
jz ReadDigitsX ;No--leave memory unchanged
;Store Indefinite
mov dword ptr dseg:[esi],0
mov dword ptr dseg:[esi+4],0
mov word ptr dseg:[esi+8],-1 ;0FF00000000H for packed BCD indefinite
jmp PopStack ;in emstore.asm
;******
eFBSTP:
;******
call RoundToInteger ;Get integer in ebx:edi, sign in ch
jc InvalidBCD
cmp ebx,0DE0B6B3H ;(1000000000*1000000000) shr 32
jae ChkInvalidBCD
ValidBCD:
and ch,bSign
mov dseg:[esi+9],ch ;Fill in sign byte
mov edx,ebx
mov eax,edi ;Get number to edx:eax for division
mov ebx,1000000000
div ebx ;Break into two 9-digit halves
xor ecx,ecx ;Initial digits
mov edi,eax ;Save quotient
mov eax,edx
or eax,eax
jz SaveLowBCD
call WriteDigits
shrd ecx,eax,4 ;Pack 8th digit
xor al,al
shl eax,20 ;Move digit in ah to high end
SaveLowBCD:
mov dseg:[esi],ecx ;Save low 8 digits
mov ecx,eax ;Get ready for next 8 digits
mov eax,edi
or eax,eax
jz ZeroHighBCD
call WriteDigits
shl ah,4 ;Move digit to upper nybble
or al,ah ;Combine last two digits
SaveHighBCD:
mov dseg:[esi+4],ecx ;Save lower 8 digits
mov dseg:[esi+8],al
jmp PopStack
ZeroHighBCD:
shr ecx,28 ;Position 9th digit
jmp SaveHighBCD
;*** WriteDigits
;
;Inputs:
; eax = binary number < 1,000,000,000 and > 0
; ecx = Zero or had one BCD digit left justified
;Purpose:
; Convert binary integer to BCD.
;
; The time required for the DIV instruction is dependent on operand
; size, at 6 + (no. of bits) clocks for 386. (In contrast, multiply
; by 10 as used in FBLD/ReadDigits above takes the same amount of
; time regardless of operand size--only 10 clocks.)
;
; The easy way to do this conversion would be to repeatedly do a
; 32-bit division by 10 (at 38 clocks/divide). Instead, the number
; is broken down so that mostly 8-bit division is used (only 14 clocks).
; AAM (17 clocks) is also used to save us from having to load the
; constant 10 and zero ah. AAM is faster than DIV on the 486sx.
;
;Outputs:
; ecx has seven more digits packed into it (from left)
; ah:al = most significant two digits (unpacked)
;esi,edi preserved
WriteDigits:
;eax = binary number < 1,000,000,000
cdq ;Zero edx
mov ebx,10000
div ebx ;Break into 4-digit and 5-digit pieces
mov bl,100
or edx,edx
jz ZeroLowDigits
xchg edx,eax ;Get 4-digit remainder to eax
;Compute low 4 digits
; 0 < eax < 10000
div bl ;Get two 2-digit pieces. 14cl on 386
mov bh,al ;Save high 2 digits
mov al,ah ;Get low digits
aam
shl ah,4 ;Move digit to upper nybble
or al,ah
shrd ecx,eax,8
mov al,bh ;Get high 2 digits
aam
shl ah,4 ;Move digit to upper nybble
or al,ah
shrd ecx,eax,8
;Compute high 5 digits
mov eax,edx ;5-digit quotient to eax
or eax,eax
jz ZeroHighDigits
ConvHigh5:
cdq ;Zero edx
shld edx,eax,16 ;Put quotient in dx:ax
xor bh,bh ;bx = 100
div bx ;Get 2- and 3-digit pieces. 22cl on 386
xchg edx,eax ;Save high 3 digits, get log 2 digits
aam
shl ah,4 ;Move digit to upper nybble
or al,ah
shrd ecx,eax,8
mov eax,edx ;Get high 3 digits
mov bl,10
div bl
mov bl,ah ;Remainder is next digit
shrd ecx,ebx,4
aam ;Get last two digits
;Last two digits in ah:al
ret
ZeroLowDigits:
shr ecx,16
jmp ConvHigh5
ZeroHighDigits:
shr ecx,12
ret