; Floating Point IO Procedures (floatio.asm) COMMENT @ Authors: W. A. Barrett, San Jose State University, James Brink, Pacific Lutheran University Used by Permission (July 2005). Read and Write Float -- these work from keyboard or to screen, using Irvine's character fetching and putting functions. ReadFloat -- accept a float in various formats, returning it in the top stack position of the FPU WriteFloat -- Top stack value in the FPU is written out in a standard format. ShowFPUStack -- Displays the floating-point unit's stack. Requires 32-bit protected mode Updates: 7/18/05: Minor editing and formatting by Kip Irvine 7/19/05 Added checks for infinity and NaN. James Brink, Pacific Lutheran University (lines marked with *********) 7/20/05 WriteFloat no longer pops the stack. Kip Irvine. 6/13/06 ShowFPUStack no longer displays empty stack positions 7/20/09 Minor code change to ReadFloat (Kip Irvine) 4/19/11 Removed ReadFloat_old THINGS TO FIX: 1. If the exponent is over 999, it is shown incorrectly. 2. If a negative exponent is over 3 digits, the procedure halts. 3. Increase the number of digits to 16 (for REAL8 types). 4. Optional: Use FSTENV and FLDENV to check the Tag Word Register for information about each register's contents. @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@2 INCLUDE irvine32.inc INCLUDE macros.inc ; set this to 1 to display the FPU stack TOS value: DOSHOWTOP=0 ;-------------------------------------------------------- showTop MACRO msg local smsg ; ; this macro supports the "showTop msg" scheme ; ;-------------------------------------------------------- IF DOSHOWTOP .data smsg byte 0dh, 0ah, msg,0 .code mov edx,offset smsg call WriteString call showTopF ENDIF ENDM .data ; variables shared by two or more procedures: pwr10 DWORD 1,10,100,1000,10000,100000,1000000,10000000,100000000,1000000000 ErrMsg BYTE 0dh,0ah,"Floating point error",0dh,0ah,0 .code ;--------------------------------------------------------- ReadFloat PROC USES eax ebx ecx LOCAL expsign:SDWORD, sign:byte ; ; Reads a decimal floating-point number from the keyboard ; and translates to binary floating point. The value is ; placed in SP(0) of the floating-point stack. ; Thanks to Mike Byrne (May 09) for suggesting a way to remove ; redundant lines. ; Updated: 11/20/09, by Irvine ;---------------------------------------------------------- .data expint SDWORD 0 itmp SDWORD ? power REAL8 ? .code showTop "R begin: " call fpuSet mov sign,0 ; look for an optional + or - first call GetChar cmp al,'+' jne R1 ; is a '+' -- ignore it; sign= 0 call GetChar jmp R2 R1: cmp al,'-' jne R2 ; is a '-' -- sign= 1 call GetChar inc sign ; here we are done with the optional sign flag R2: ; look for a digit in the mantissa part .IF (al >= '0' && al <= '9') || (al == '.') fldz ; push a 0. ONE thing in FPU stack .WHILE (al >= '0' && al <= '9') sub al,'0' and eax,0Fh mov itmp,eax fmul ten fild itmp fadd call GetChar .ENDW ; decimal point in the mantissa? .IF (al == '.') call GetChar fldz ; start the fractional part fld ten ; get the power part started fstp power ; will be 10, 100, 1000, etc. .WHILE (al >= '0' && al <= '9') sub al,'0' and eax,0Fh mov itmp,eax fild itmp fdiv power fadd fld power fmul ten fstp power call GetChar .ENDW fadd ; add the front end to the back end .ENDIF .ELSE ; neither ddd.ddd nor .ddd ; so it's a syntax error mov edx,OFFSET ErrMsg call WriteString fldz ; return a 0.0 in any case call fpuReset showTop "R end: " ret .ENDIF ; OK -- now we have the ddd.ddd part in ST(0) ; Now look for an exponent ; We still have the mantissa in the stack: ONE thing .IF (al=='E' || al=='e') mov expsign,1 call GetChar .IF (al=='+') call GetChar .ELSEIF (al=='-') mov expsign,-1 call GetChar .ENDIF mov expint,0 .WHILE (al>='0' && al<= '9') sub al,'0' and eax,0FFh mov ebx,eax mov eax,10 mul expint add eax,ebx mov expint,eax call GetChar .ENDW ; power10 gets expsign*10^expint, stuffs it in exponent. ; Result returned in FPU. mov eax,expint imul expsign call power10 ; TWO things in the FPU stack fmul ; mantissa is sitting underneath; ONE thing left over .ENDIF .IF (sign==1) fchs .ENDIF call fpuReset ; shouldn't affect stack position showTop "R end: " ret ; result should be in FPU top ReadFloat ENDP ;------------------------------------------------------------ ShowFPUStack PROC USES eax ; LOCAL temp:REAL8 ; ; Prints the FPU stack in decimal exponential format. ; Written by James Brink, Pacific Lutheran University. ; Used by permission. ; ; Adapted by Kip Irvine, 7/18/05. ; Revised 7/20/05. ; ; Receives: Nothing ; Returns: Nothing ; ; Technique: ; Uses FINCSTP move the stack top, effectly popping ; the stack without actually removing values. ; Note: ; This procedure clears the exception bits in the FPU status register ; before it terminates. This includes B, ES, SF, PE, UE, OE, ZE, DE, ; and IE bits. ; Uses: ; WriteFloat, mWrite, Crlf, WriteDec ;---------------------------------------------------------------- ControlWordMask = 0000000000111111b ; Used to mask exception bits .data SavedCWord WORD ? ; Control word when the procedure is started UsedCWord WORD ? ; Control word used by procedure .code ; Write a header message. mWrite <0Dh,0Ah,"------ FPU Stack ------", 0Dh, 0Ah> ; Set the control word to mask the exception bits fclex ; Clear pending exceptions fstcw SavedCWord ; Get copy of CW used to restore original fstcw UsedCWord or UsedCWord, ControlWordMask fldcw usedCWord ; Mask exception bits ; Set up counter n for SP(n) mov eax,0 ; Display the stack (loop) LDisplay: ; added 6/13/06 by KRI ftst call fChkNaN jz next_stack_value ;------ end of 6/13/06 mWrite "ST(" ; Display stack index call WriteDec mWrite "): " ; Write the value of ST(n) and go to new line. ; WriteFloat pops the value from the stack, so we save ; and restore it to compensate. call WriteFloat ; write ST(0) & pop call Crlf ; Move the top of stack pointer. next_stack_value: fincstp ; Increment count and repeat for 8 values. inc eax cmp eax,8 jl LDisplay LReturn: ; clear any exceptions and restore original control word before returning fclex ; clear exceptions fstcw SavedCWord ret ShowFPUStack ENDP ;-------------------------------------------------------------- WriteFloat PROC USES eax ecx edx ; ; Writes the floating point value in the top of the FPU stack ; to the console window. Displays in exponential format. The ; value remains on the stack. ;-------------------------------------------------------------- .data temp_01 REAL8 ? ; KRI iten SDWORD 10 mantissa REAL8 ? zeroes BYTE "+0.", 7 DUP('0'), "E+000",0 NaNStr BYTE "NaN", 0 ;****** InfinityStr BYTE "infinity", 0 ;****** .code fst temp_01 ; KRI: save a copy showTop "W begin: " call fpuSet ftst ;****** call fChkNaN ; check for NaN ****** jnz W0 ; jump if not NaN ****** mov edx,offset NaNStr ; print NaN ****** jmp W0a ; otherwise this is like a zero ****** W0: ;****** ftst call fcompare ; look at the sign bit jnz W1 ; here the thing is all zeroes mov edx,offset zeroes W0a: call writeString W0b: ;fstp mantissa fst mantissa ; KRI 7/20/05: changed fstp to fst call fpuReset showTop "W end: " ret W1: mov al,'+' jge W2 mov al,'-' fchs ; now have value >= 0 W2: call WriteChar ; the sign call fChkInfinity ; Check for infinity ****** jne w2a ; if not continue normally ****** mov al, 0ECh ; Print "infinity sign" ****** call writeChar ;****** jmp W0b ; finish like for zeros ****** W2a: ; Suppose the number's value is V. We first find an exponent E ; and mantissa M such that 10^8 <= M < 10^9 and M*10^-8*10^E = V. ; (E will be in 'exponent', M will be in ST(0)) call splitup fistp itmp ; save as an integer & POP mov eax,itmp cmp eax,pwr10+9*4 ; 10^9 jl W4 xor edx,edx ; it's > 10^9 add eax,5 ; for rounding div iten ; divide by 10 inc exponent W4: ; start with the MSD mov edx,pwr10+8*4 xor edx,edx div pwr10+8*4 and al,0Fh add al,'0' call WriteChar mov al,'.' call WriteChar mov eax,edx mov ecx,7 call wrdigits ; that takes care of the decimals after the decimal point ; now work on the exponent part mov al,'E' call WriteChar .IF (exponent < 0) mov al,'-' neg exponent .ELSE mov al,'+' .ENDIF call WriteChar movzx eax,exponent mul iten mov ecx,3 call wrdigits call fpuReset showTop "W end: " fld temp_01 ; KRI: restore saved value ret WriteFloat endp ;********************************************************************** ; PRIVATE PROCEDURES ; ;********************************************************************** ;------------------------------------------------------ GetChar PROC ; ; Reads a single character from input, ; echoes end of line character to console window. ; ; Modified by Irvine (7/18/05): removed check for Ctl-C. ;------------------------------------------------------ call ReadChar ; get a character from keyboard .IF (al == 0dh) ; Enter key? call Crlf .ELSE call WriteChar ; and echo it back .ENDIF ret GetChar ENDP MAXEXPONENT=99 COMMENT # fpuSet This sets the RC (10,11) & PC (8,9) bits of the FPU control word also the exception masks (bits 0..5) It saves the current control word fpuReset Resets FPU to the saved control word RC: xx00 0000 0000b 00 - round to nearest (even) 01 - round down (toward -inf) 10 - round up (toward +inf) 11 - round toward zero (truncate) PC: xx 0000 0000b 00 - single precision (24 bits) 01 - reserved 10 - double precision (53 bits) 11 - double ext. precision (64 bits) # ; truncate, double precision, all exceptions masked out stdMask = 1111000011000000b stdRMask = 0000111100111111b stdControl= 0000111000111111b .data stmp word ? sw word ? .code ;------------------------------------------------------- fpuSet PROC uses ax ; ;------------------------------------------------------- fstcw sw ; save current control word mov ax,sw and ax,stdMask or ax,stdControl mov stmp,ax fldcw stmp ; load masked control word ret fpuSet ENDP ;-------------------------------------------------------- fpuReset PROC uses ax bx ; ; This resets the control word ; bits defined by the stdMask ;-------------------------------------------------------- fstcw stmp ; get current control word mov ax,stmp ; save it in AX and ax,stdMask ; clear bits 6-7, 11-14 mov bx,sw ; get saved control word and bx,stdRMask or ax,bx ; set bits 0-5, 8-11 mov stmp,ax fldcw stmp ret fpuReset ENDP ;-------------------------------------------------------- power10 PROC uses ebx ecx ; ; power10 expects: EAX (signed exponent) ; This returns 10.0^(sign*EAX) in the FPU ;-------------------------------------------------------- .data binpwr10 REAL8 1.0E64, 1.0E32, 1.0E16, 1.0E8, 1.0E4, 1.0E2, 1.0E1 TOPPWR= ($-binpwr10)/type binpwr10 .code .IF (eax == 0) fld1 ; load a 1 ret .ENDIF ; get the sign of eax mov bl,0 .IF (sdword ptr eax < 0) neg eax inc bl .ENDIF ; check for too-large exponent .IF (sdword ptr eax > MAXEXPONENT) ; complain mov edx,OFFSET ErrMsg call WriteString ; ...but return 1.0 fld1 ret .ENDIF ; now for the computation ; The general idea is that if eax= 11101b, then the value wanted ; is 10^(11101b)= 10^(2^4) * 10^(2^3) * 10^(2^2) * 10^(2^0) ; So we use a table of these powers, binpwr10. ; we start with 10^1 fld1 mov ecx,TOPPWR mov esi,(type binpwr10)*(TOPPWR-1) P1: test eax,1 jz P2 fmul binpwr10[esi] P2: sub esi,type binpwr10 shr eax,1 loopnz P1 .IF (bl != 0) ; take the reciprocal fld1 fdivr ; reverse division .ENDIF ret power10 ENDP .data status dw 0 showMsg byte "stack top= " stbyte byte 0, 0dh, 0ah, 0 .code IF DOSHOWTOP ;-------------------------------------------------------- showTopF PROC uses eax edx ; ; showTopF prints the FPU TOP register (0-7) to screen ; This is a diagnostic routine ;-------------------------------------------------------- fstsw status fwait mov ax,status shr ax,11 and al,7 ; TOP is 3 bits add al,'0' mov stbyte,al mov edx,offset showMsg call WriteString ret showTopF ENDP ENDIF ;-------------------------------------------------------- ****** fChkNaN PROC uses ax ;****** ; ****** ; Check the results of the last FTST instruction to see ****** ; the Z flag is set if indeed the value was NaN ****** ; 5432109876543210 ****** C3C2C0 = 0100010100000000b ;****** fnstsw ax ; mov the status word to AX ****** and ax, C3C2C0 ; get the C3, C2, C0 bits from the status word cmp ax, C3C2C0 ; are all the bits 0 ****** ret ;****** fChkNaN ENDP ;****** ;-------------------------------------------------------- ****** fChkInfinity PROC ;****** .data ;****** temp REAL10 ? ;****** .code ;****** fstp temp ; store value, fst can't store REAL10 ;****** fld temp ; restore the stack ;****** mov ax, WORD PTR temp cmp WORD PTR temp, 0000h; is Exponent all 1 bits ;****** jne CF1 ;****** cmp DWORD PTR (temp+2), 00000000h ;****** jne CF1 ; check first 4 bytes of the mantissa ;****** cmp DWORD PTR (temp+6), 7FFF8000h ;****** ; this checks last four bytes of the mantissa ;****** CF1: ;****** ret ;****** fChkInfinity ENDP ;****** ;-------------------------------------------------------- fcompare PROC uses ax ; ; Compares two floating-point values. ; Transfers ZF & SF registers from the FPU status word ; to the CPU, so we can do branches on them ; ;-------------------------------------------------------- fstsw status mov ah,byte ptr status+1 mov al,ah and ah,040h and al,1 ror al,1 or ah,al sahf ret fcompare ENDP ;-------------------------------------------------------- normalize PROC ; ; shifts ST(0) into range 10^8 <= V < 10^9 ; and adjusts the exponent in the process ; ;-------------------------------------------------------- .data tenp8 REAL8 10.0E8 onep8 REAL8 1.0E8 exponent SWORD 0 .code N1: fcom tenp8 ; compare to 10^9 call fcompare jl N2 fdiv ten inc exponent jmp N1 N2: fcom onep8 ; compare to 10^8 call fcompare jge N3 fmul ten dec exponent jmp N2 N3: ret normalize ENDP ;--------------------------------------------------------- splitup PROC USES ecx esi edi ; ; Receives a non-negative number in ST(0). ; Suppose the number's value is V. The goal is to find an exponent E ; and integer mantissa M such that 10^8 <= M < 10^9 and ; V= M*10^-8 * 10^E ; (E will be in 'exponent', M will be in ST(0) on return) ; This uses the pwr10 table in an attempt to narrow down the ; appropriate power using a kind of binary search and reduction ; ;--------------------------------------------------------- .data onehalf REAL8 0.5 one REAL8 1.0 ten REAL8 10.0 bpwr10 WORD 64,32,16,8,4,2,1 binpwrM10 REAL8 1.0E-64, 1.0E-32, 1.0E-16, 1.0E-8, 1.0E-4, 1.0E-2, 1.0E-1 .code mov exponent,0 ; see if == 0.0 ftst call fcompare jne S1 ret S1: ; start by seeing if it's greater than 10 fcom ten call fcompare jge S2 ; >= 10 ; see if it's < 10 fcom one call fcompare jge S4 ; it's >= 10 jmp S3 ; it's < 10 S2: ; here, it's > 10 ; so we'll reduce it using the binpwr10 table mov ecx,TOPPWR mov esi,0 ; index to binpwr10 mov edi,0 ; index to bpwr10 S2a: fcom binpwr10[esi] call fcompare jl S2c fdiv binpwr10[esi] mov ax,bpwr10[edi] add exponent,ax S2c: add esi,type binpwr10 add edi,type bpwr10 loop S2a jmp S4 S3: ; here, it's < 1.0 mov ecx,TOPPWR mov esi,0 ; index to binpwrM10 mov edi,0 ; index to mpwr10 S3a: fcom binpwrM10[esi] call fcompare jge S3c fdiv binpwrM10[esi] mov ax,bpwr10[edi] sub exponent,ax S3c: add esi,type binpwr10 add edi,type bpwr10 loop S3a S4: fmul onep8 ; multiply by 10^8 ; adjust to range 10^8 <= V < 10^9 call normalize ; Round the mantissa to 8 decimal places fadd onehalf ; add one half frndint ; should truncate fractional part ; readjust to 10^8 <= V < 10^9 call normalize ret splitup ENDP ;--------------------------------------------------------- wrdigits PROC PRIVATE ; ; (Helper procedure) Writes 'ecx' digits of register eax ; as decimal digits, with leading zeros. ; ;--------------------------------------------------------- WR1: mov edx,pwr10[ecx*4] xor edx,edx div pwr10[ecx*4] and al,0Fh add al,'0' call WriteChar mov eax,edx loop WR1 ret wrdigits ENDP END