; StdGrp group StdLib, StdData ; StdData segment para public 'sldata' ; ; Floating point package. ; ; ; Released to the public domain ; Created by: Randall Hyde ; Date: 8/13/90 ; 8/28/91 ; ; ; FP format: ; ; 80 bits: ; bit 79 bit 63 bit 0 ; | | | ; seeeeeee eeeeeeee mmmmmmmm m...m m...m m...m m...m m...m ; ; e = bias 16384 exponent ; m = 64 bit mantissa with NO implied bit! ; s = sign (for mantissa) ; ; ; 64 bits: ; bit 63 bit 51 bit 0 ; | | | ; seeeeeee eeeemmmm mmmmmmmm mmmmmmmm mmmmmmmm mmmmmmmm mmmmmmmm mmmmmmmm ; ; e = bias 1023 exponent. ; s = sign bit. ; m = mantissa bits. Bit 52 is an implied one bit. ; ; 32 bits: ; Bit 31 Bit 22 Bit 0 ; | | | ; seeeeeee emmmmmmm mmmmmmmm mmmmmmmm ; ; e = bias 127 exponent ; s = sign bit ; m = mantissa bits, bit 23 is an implied one bit. ; ; ; ; WARNING: Although this package uses IEEE format floating point numbers, ; it is by no means IEEE compliant. In particular, it does not ; support denormalized numbers, special rounding options, and ; so on. Why not? Two reasons: I'm lazy and I'm ignorant. ; I do not know all the little details surround the IEEE ; implementation and I'm not willing to spend more of my life ; (than I already have) figuring it out. There are more ; important things to do in life. Yep, numerical analysts can ; rip this stuff to shreads and come up with all kinds of degenerate ; cases where this package fails and the IEEE algorithms succeed, ; however, such cases are very rare. One should not get the idea ; that IEEE is perfect. It blows up with lots of degenerate cases ; too. They just designed it so that it handles a few additional ; cases that mediocre packages (like this one) do not. For most ; normal computations this package works just fine (what it lacks ; it good algorithms it more than makes up for by using an 88-bit ; internal format during internal computations). ; ; Moral of the story: If you need highly accurate routines which ; produce okay results in the worst of cases, look elsewhere please. ; I don't want to be responsible for your blowups. OTOH, if you need ; a fast floating point package which is reasonably accurate and ; you're not a statistician, astronomer, or other type for whom ; features like denormalized numbers are important, this package ; may work out just fine for you. ; ; Randy Hyde ; August 1990 ; (Hard to believe I started this ; a year ago and I'm just coming ; back to it now!) ; ; UC Riverside & ; Cal Poly Pomona. ; ; FPACC- Floating point accumuator. ; FPOP- Floating point operand. ; ; These variables use the following format: ; ; 88 bits: ; sxxxxxxx eeeeeeee eeeeeeee m..m m..m m..m m..m m..m m..m m..m m..m ; Sign exponent mantissa (64 bits) ; ; Only H.O. bit of Sign byte is significant. The rest is garbage. ; Exponent is bias 32767 exponent. ; Mantissa does NOT have an implied one bit. ; ; This format was picked for convenience (it is easy to work with) and it ; exceeds the 80-bit format used by Intel on the 80x87 chips. ; fptype struc Mantissa dw 4 dup (?) Exponent dw ? Sign db ? db ? ;Padding fptype ends ; ; ; ; public fpacc fpacc fptype <> ; public fpop fpop fptype <> ; ; ; FProd- Holds 144-bit result obtained by multiplying fpacc.mant x fpop.mant ; Quotient equ this word fprod dw 9 dup (?) ; ; ; Variables used by the floating point I/O routines: ; TempExp dw ? ExpSign db ? DecExponent dw ? DecSign db 0 DecDigits db 31 dup (?) ; ; ; StdData ends ; ; stdlib segment para public 'slcode' assume cs:stdgrp, ds:nothing, es:nothing, ss:nothing ; ; ; ; ; ; ; ; ; ; ;--------------------------------------------------------------------------- ; Floating Point Load/Store Routines ;--------------------------------------------------------------------------- ; ; sl_AccOp Copies the floating point accumulator to the floating point ; operand. ; public sl_AccOp sl_AccOp proc far assume ds:StdGrp push ax push ds mov ax, StdGrp mov ds, ax ; mov ax, FPacc.Exponent mov FPop.Exponent, ax mov ax, FPacc.Mantissa mov FPop.Mantissa, ax mov ax, FPacc.Mantissa+2 mov FPop.Mantissa+2, ax mov ax, FPacc.Mantissa+4 mov FPop.Mantissa+4, ax mov ax, FPacc.Mantissa+6 mov FPop.Mantissa+6, ax mov al, Fpacc.Sign mov FPop.Sign, al ; pop ds pop ax ret sl_AccOp endp assume ds:nothing ; ; ; sl_XAccOp- Exchanges the values in the floating point accumulator ; and floating point operand. ; public sl_XAccOp sl_XAccOp proc far assume ds:StdGrp push ax push ds mov ax, StdGrp mov ds, ax ; mov ax, FPacc.Exponent xchg ax, FPop.Exponent mov FPacc.Exponent, ax ; mov ax, FPacc.Mantissa xchg ax, FPop.Mantissa mov FPacc.Mantissa, ax ; mov ax, FPacc.Mantissa+2 xchg ax, FPop.Mantissa+2 mov FPacc.Mantissa+2, ax ; mov ax, FPacc.Mantissa+4 xchg ax, FPop.Mantissa+4 mov FPacc.Mantissa+4, ax ; mov ax, FPacc.Mantissa+6 xchg ax, FPop.Mantissa+6 mov FPacc.Mantissa+6, ax ; mov al, FPacc.Sign xchg al, FPop.Sign mov FPacc.Sign, al ; pop ds pop ax ret sl_XAccOp endp assume ds:nothing ; ; ; ; sl_LSFPA- Loads a single precision (32-bit) IEEE format number into ; the floating point accumulator. ES:DI points at the # to ; load into FPACC. ; public sl_LSFPA sl_LSFPA proc far push ax push bx mov ax, es:[di] mov word ptr StdGrp:fpacc.mantissa[5], ax mov ax, es:2[di] mov bx, ax shl ax, 1 mov al, ah mov ah, 0 add ax, 32767-127 ;Adjust exponent bias. mov word ptr StdGrp:fpacc.exponent, ax mov StdGrp:fpacc.sign, bh ;Save sign away. mov al, es:2[di] and al, 7fh ;Strip out L.O. exp bit. or al, 80h ;Add in implied bit. mov byte ptr StdGrp:fpacc.mantissa[7], al ;Save H.O. mant byte. xor ax, ax mov word ptr StdGrp:fpacc.mantissa, ax mov word ptr StdGrp:fpacc.mantissa[2], ax mov byte ptr StdGrp:fpacc.mantissa[4], al pop bx pop ax ret sl_LSFPA endp ; ; ; ; ; sl_SSFPA- Stores FPACC into the single precision variable pointed at by ; ES:DI. Performs appropriate rounding. Returns carry clear ; if the operation is successful, returns carry set if FPACC ; cannot fit into a single precision variable. ; public sl_SSFPA sl_SSFPA proc far assume ds:stdgrp push ds push ax push bx mov ax, StdGrp mov ds, ax push fpacc.Exponent push fpacc.Mantissa ;Save the stuff we tweak push fpacc.Mantissa[2] ; so that this operation push fpacc.Mantissa[4] ; will be non-destructive. push fpacc.Mantissa[6] ; ; First, round FPACC: ; add fpacc.Mantissa [4], 80h adc fpacc.Mantissa [6], 0 jnc StoreAway rcl fpacc.Mantissa [6], 1 rcl fpacc.Mantissa [4], 1 inc fpacc.Exponent jz BadSSFPA ;If exp overflows. ; ; Store the value away: ; StoreAway: mov ax, fpacc.Exponent sub ax, 32767-127 ;Convert to bias 127 cmp ah, 0 jne BadSSFPA mov bl, fpacc.Sign shl bl, 1 ;Merge in the sign bit. rcr al, 1 mov es:[di] + 3, al ;Save away exponent/sign pushf ;Save bit shifted out. mov ax, fpacc.Mantissa [6] shl ax, 1 ;Get rid of implied bit and popf ; shift in the L.O. exponent rcr ax, 1 ; bit. mov es:[di] + 1, ax mov al, byte ptr fpacc.Mantissa [5] mov es:[di], al clc jmp SSFPADone ; BadSSFPA: stc SSFPADone: pop fpacc.Mantissa[6] pop fpacc.Mantissa[4] pop fpacc.Mantissa[2] pop fpacc.Mantissa pop fpacc.Exponent pop bx pop ax pop ds ret assume ds:nothing sl_SSFPA endp ; ; ; sl_LDFPA- Loads the double precision (64-bit) IEEE format number pointed ; at by ES:DI into FPACC. ; public sl_LDFPA sl_LDFPA proc far push ax push bx push cx mov ax, es:6[di] mov StdGrp:fpacc.sign, ah ;Save sign bit. mov cl, 4 shr ax, cl ;Align exponent field. and ah, 111b ;Strip the sign bit. add ax, 32767-1023 ;Adjust bias mov StdGrp:fpacc.exponent, ax ; ; Get the mantissa bits and left justify them in the FPACC. ; mov ax, es:5[di] and ax, 0fffh ;Strip exponent bits. or ah, 10h ;Add in implied bit. mov cl, 3 shl ax, cl mov bx, es:3[di] rol bx, cl mov ch, bl and ch, 7 or al, ch mov StdGrp:fpacc.mantissa[6], ax ; and bl, 0f8h mov ax, es:1[di] rol ax, cl mov ch, al and ch, 7 or bl, ch mov StdGrp:fpacc.mantissa[4], bx ; and al, 0f8h mov bh, es:[di] rol bh, cl mov ch, bh and ch, 7 or al, ch mov StdGrp:fpacc.mantissa[2], ax and bh, 0f8h mov bl, 0 mov StdGrp:fpacc.Mantissa[0], bx ; pop cx pop bx pop ax ret sl_LDFPA endp ; ; ; ; ; sl_SDFPA- Stores FPACC into the double precision variable pointed ; at by ES:DI. ; public sl_sdfpa sl_SDFPA proc far assume ds:stdgrp push ds push ax push bx push cx push dx push di ; mov bx, StdGrp mov ds, bx ; push fpacc.Mantissa [0] push fpacc.Mantissa [2] push fpacc.Mantissa [4] push fpacc.Mantissa [6] push fpacc.Exponent ; ; First, round this guy to 52 bits: ; add byte ptr fpacc.Mantissa [1], 8 jnc SkipRndUp inc fpacc.Mantissa [2] jnz SkipRndUp inc fpacc.Mantissa [4] jnz SkipRndUp inc fpacc.Mantissa [6] jnz SkipRndUp ; ; Whoops! Got an overflow, fix that here: ; stc rcr fpacc.Mantissa [6], 1 rcr fpacc.Mantissa [4], 1 rcr fpacc.Mantissa [2], 1 rcr byte ptr fpacc.Mantissa [1], 1 inc fpacc.Exponent jz BadSDFPA ;In case exp was really big. ; ; Okay, adjust and store the exponent- ; SkipRndUp: mov ax, fpacc.Exponent sub ax, 32767-1023 ;Adjust bias cmp ax, 2048 ;Make sure the value will still jae BadSDFPA ; fit in an 8-byte real. mov cl, 5 shl ax, cl ;Move exponent into place. mov bl, fpacc.Sign shl bl, 1 rcr ax, 1 ;Merge in sign bit. ; ; Merge in the upper four bits of the Mantissa (don't forget that the H.O. ; Mantissa bit is lost due to the implied one bit). ; mov bl, byte ptr fpacc.Mantissa [7] shr bl, 1 shr bl, 1 shr bl, 1 and bl, 0fh ;Strip away H.O. mant bit. or al, bl mov es:[di]+6, ax ;Store away H.O. word. ; ; Okay, now adjust and store away the rest of the mantissa: ; mov ax, fpacc.Mantissa [0] mov bx, fpacc.Mantissa [2] mov cx, fpacc.Mantissa [4] mov dx, fpacc.Mantissa [6] ; ; Shift the bits to their appropriate places (to the left five bits): ; shl ax, 1 rcl bx, 1 rcl cx, 1 rcl dx, 1 ; shl ax, 1 rcl bx, 1 rcl cx, 1 rcl dx, 1 ; shl ax, 1 rcl bx, 1 rcl cx, 1 rcl dx, 1 ; shl ax, 1 rcl bx, 1 rcl cx, 1 rcl dx, 1 ; shl ax, 1 rcl bx, 1 rcl cx, 1 rcl dx, 1 ; ; Store away the results: ; mov es:[di], bx mov es:[di] + 2, cx mov es: [di] + 4, dx ; ; Okay, we're done. Return carry clear to denote success. ; clc jmp short QuitSDFPA ; BadSDFPA: stc ;If an error occurred. QuitSDFPA: pop fpacc.Exponent pop fpacc.Mantissa [6] pop fpacc.Mantissa [4] pop fpacc.Mantissa [2] pop fpacc.Mantissa [0] pop di pop dx pop cx pop bx pop ax pop ds ret ; assume ds:nothing sl_SDFPA endp ; ; ; ; ; sl_LEFPA- Loads an extended precision (80-bit) IEEE format number ; into the floating point accumulator. ES:DI points at the ; number to load into FPACC. ; public sl_LEFPA sl_LEFPA proc far push ax mov ax, es:8[di] mov StdGrp:fpacc.Sign, ah and ah, 7fh add ax, 4000h mov StdGrp:fpacc.Exponent, ax mov ax, es:[di] mov StdGrp:fpacc.Mantissa, ax mov ax, es:2[di] mov StdGrp:fpacc.Mantissa[2], ax mov ax, es:4[di] mov StdGrp:fpacc.Mantissa[4], ax mov ax, es:6[di] mov StdGrp:fpacc.Mantissa[6], ax pop ax ret sl_LEFPA endp ; ; ; sl_LEFPAL- Loads an extended precision (80-bit) IEEE format number ; into the floating point accumulator. The number to load ; into FPACC follows the call in the code stream. ; public sl_LEFPAL sl_LEFPAL proc far push bp mov bp, sp push es push di push ax les di, 2[bp] ; mov ax, es:8[di] mov StdGrp:fpacc.Sign, ah and ah, 7fh add ax, 4000h mov StdGrp:fpacc.Exponent, ax mov ax, es:[di] mov StdGrp:fpacc.Mantissa, ax mov ax, es:2[di] mov StdGrp:fpacc.Mantissa[2], ax mov ax, es:4[di] mov StdGrp:fpacc.Mantissa[4], ax mov ax, es:6[di] mov StdGrp:fpacc.Mantissa[6], ax ; ; Adjust the return address to point past the floating point number we ; just loaded. ; add word ptr 2[bp], 10 ; pop ax pop di pop es pop bp ret sl_LEFPAL endp ; ; ; sl_SEFPA- Stores FPACC into in the extended precision variable ; pointed at by ES:DI. ; public sl_sefpa sl_SEFPA proc far assume ds:stdgrp push ds push ax mov ax, StdGrp mov ds, ax push fpacc.Mantissa [0] push fpacc.Mantissa [2] push fpacc.Mantissa [4] push fpacc.Mantissa [6] push fpacc.Exponent ; mov ax, fpacc.Exponent sub ax, 4000h cmp ax, 8000h jae BadSEFPA test fpacc.Sign, 80h jz StoreSEFPA or ah, 80h StoreSEFPA: mov es:[di]+8, ax mov ax, fpacc.Mantissa [0] mov es:[di], ax mov ax, fpacc.Mantissa [2] mov es:[di] + 2, ax mov ax, fpacc.Mantissa [4] mov es:[di] + 4, ax mov ax, fpacc.Mantissa [6] mov es:[di] + 6, ax clc jmp SEFPADone ; BadSEFPA: stc SEFPADone: pop fpacc.Exponent pop fpacc.Mantissa[6] pop fpacc.Mantissa[4] pop fpacc.Mantissa[2] pop fpacc.Mantissa[0] pop ax pop ds ret assume ds:nothing sl_SEFPA endp ; ; ; ; sl_LSFPO- Loads a single precision (32-bit) IEEE format number into ; the floating point operand. ES:DI points at the # to ; load into FPOP. ; public sl_LSFPO sl_LSFPO proc far push ax push bx mov ax, es:[di] mov word ptr StdGrp:fpop.mantissa[5], ax mov ax, es:2[di] mov bx, ax shl ax, 1 mov al, ah mov ah, 0 add ax, 32767-127 ;Adjust exponent bias. mov word ptr StdGrp:fpop.exponent, ax mov StdGrp:fpop.sign, bh ;Save sign away. mov al, es:2[di] and al, 7fh ;Strip out L.O. exp bit. or al, 80h ;Add in implied bit. mov byte ptr StdGrp:fpop.mantissa[7], al xor ax, ax mov word ptr StdGrp:fpop.mantissa, ax mov word ptr StdGrp:fpop.mantissa[2], ax mov byte ptr StdGrp:fpop.mantissa[4], al pop bx pop ax ret sl_LSFPO endp ; ; ; ; ; ; sl_LDFPO- Loads the double precision (64-bit) IEEE format number pointed ; at by ES:DI into FPOP. ; public sl_LDFPO sl_LDFPO proc far push ax push bx push cx mov ax, es:6[di] mov StdGrp:fpop.sign, ah ;Save sign bit. mov cl, 4 shr ax, cl ;Align exponent field. and ah, 111b ;Strip the sign bit. add ax, 32767-1023 ;Adjust bias mov word ptr StdGrp:fpop.exponent, ax ; ; Get the mantissa bits and left justify them in the FPOP. ; mov ax, es:5[di] and ax, 0fffh ;Strip exponent bits. or ah, 10h ;Add in implied bit. mov cl, 3 shl ax, cl mov bx, es:3[di] rol bx, cl mov ch, bl and ch, 7 or al, ch mov word ptr StdGrp:fpop.mantissa[6], ax ; and bl, 0f8h mov ax, es:1[di] rol ax, cl mov ch, al and ch, 7 or bl, ch mov word ptr StdGrp:fpop.mantissa[4], bx ; and al, 0f8h mov bh, es:[di] rol bh, cl mov ch, bh and ch, 7 or al, ch mov word ptr StdGrp:fpop.mantissa[2], ax and bh, 0f8h mov bl, 0 mov word ptr StdGrp:fpop.Mantissa[0], bx ; pop cx pop bx pop ax ret sl_LDFPO endp ; ; ; ; ; ; sl_LEFPO- Loads an extended precision (80-bit) IEEE format number ; into the floating point operand. ES:DI points at the ; number to load into FPACC. ; public sl_LEFPO sl_LEFPO proc far push ax mov ax, es:8[di] mov StdGrp:fpop.Sign, ah and ah, 7fh add ax, 4000h mov StdGrp:fpop.Exponent, ax mov ax, es:[di] mov StdGrp:fpop.Mantissa, ax mov ax, es:2[di] mov StdGrp:fpop.Mantissa[2], ax mov ax, es:4[di] mov StdGrp:fpop.Mantissa[4], ax mov ax, es:6[di] mov StdGrp:fpop.Mantissa[6], ax pop ax ret sl_LEFPO endp ; ; ; ; ; sl_LEFPOL- Loads an extended precision (80-bit) IEEE format number ; into the floating point operand. The number to load ; follows the call instruction in the code stream. ; public sl_LEFPOL sl_LEFPOL proc far push bp mov bp, sp push es push di push ax les di, 2[bp] ; mov ax, es:8[di] mov StdGrp:fpop.Sign, ah and ah, 7fh add ax, 4000h mov StdGrp:fpop.Exponent, ax mov ax, es:[di] mov StdGrp:fpop.Mantissa, ax mov ax, es:2[di] mov StdGrp:fpop.Mantissa[2], ax mov ax, es:4[di] mov StdGrp:fpop.Mantissa[4], ax mov ax, es:6[di] mov StdGrp:fpop.Mantissa[6], ax ; add word ptr 2[bp], 10 ;Skip rtn adrs past #. ; pop ax pop di pop es pop bp ret sl_LEFPOL endp ; ; ; ; ; ; ; ;-------------------------------------------------------------------------- ; Integer <=> FP Conversions ;-------------------------------------------------------------------------- ; ; ; ; ITOF- Converts 16-bit signed value in AX to a floating point value ; in FPACC. ; public sl_itof sl_itof proc far assume ds:stdgrp push ds push ax push cx mov cx, StdGrp mov ds, cx ; mov cx, 800Fh ;Magic exponent value (65536). ; ; Set the sign of the result: ; mov fpacc.Sign, 0 ;Assume a positive value. or ax, ax ;Special case for zero! jz SetFPACC0 jns DoUTOF ;Take care of neg values. mov fpacc.sign, 80h ;This guy is negative! neg ax ;Work with abs(AX). jmp DoUTOF sl_ITOF endp ; ; ; UTOF- Like ITOF above except this guy works for unsigned 16-bit ; integer values. ; public sl_utof sl_UTOF proc far push ds push ax push cx ; ; mov cx, StdGrp mov ds, cx mov cx, 800Fh ;Magic exponent value (65536). or ax, ax jz SetFPACC0 mov fpacc.Sign, 0 ; sl_UTOF endp ; ; ; Okay, convert the number to a floating point value: ; Remember, we need to end up with a normalized number (one where the H.O. ; bit of the mantissa contains a one). The largest possible value (65535 or ; 0FFFFh) is equal to 800E FFFF 0000 0000 0000. All other values have an ; exponent less than or equal to 800Eh. If the H.O. bit of the value is ; not one, we must shift it to the left and dec the exp by 1. E.g., if AX ; contains 1, then we will need to shift it 15 times to normalize the value, ; decrementing the exponent each time produces 7fffh which is the proper ; exponent for "1". ; ; Note: this is not a proc! Making it a proc makes it incompatible with ; one or more different assemblers (TASM, OPTASM, MASM6). ; Besides, this has to be a near label with a far return! ; DoUTOF: UTOFWhlPos: dec cx shl ax, 1 jnc UTOFWhlPos rcr ax, 1 ;Put bit back. mov fpacc.Exponent, cx ;Save exponent value. mov fpacc.Mantissa [6], ax ;Save Mantissa value. xor ax, ax mov fpacc.Mantissa [4], ax ;Zero out the rest of the mov fpacc.Mantissa [2], ax ; mantissa. mov fpacc.Mantissa [0], ax jmp UTOFDone ; ; Special case for zero, must zero all bytes in FPACC. Note that AX already ; contains zero. ; SetFPACC0: mov fpacc.Exponent, ax mov fpacc.Mantissa [6], ax mov fpacc.Mantissa [4], ax mov fpacc.Mantissa [2], ax mov fpacc.Mantissa [0], ax mov fpacc.Sign, al ; UTOFDone: pop cx pop ax pop ds retf ; ; ; ; ; ; ; LTOF- Converts 32-bit signed value in DX:AX to a floating point ; value in FPACC. ; public sl_ltof sl_ltof proc far assume ds:stdgrp push ds push ax push cx push dx mov cx, StdGrp mov ds, cx ; ; Set the sign of the result: ; mov fpacc.Sign, 0 ;Assumed a positive value. mov cx, dx or cx, ax jz SetUL0 or dx, dx ;Special case for zero! jns DoULTOF ;Take care of neg values. mov fpacc.sign, 80h ;This guy is negative! neg dx ;Do a 32-bit NEG operation neg ax ; (yes, this really does sbb dx, 0 ; work!). jmp DoULTOF sl_LTOF endp ; ; ; ULTOF- Like LTOF above except this guy works for unsigned 32-bit ; integer values. ; public sl_ultof sl_ULTOF proc far push ds push ax push cx push dx ; mov cx, StdGrp mov ds, cx ; mov cx, dx or cx, ax jz SetUL0 mov fpacc.Sign, 0 ; sl_ULTOF endp ; ; ; DoULTOF: mov cx, 801Fh ;Magic exponent value (65536). ULTOFWhlPos: dec cx shl ax, 1 rcl dx, 1 jnc ULTOFWhlPos rcr dx, 1 ;Put bit back. rcr ax, 1 mov fpacc.Exponent, cx ;Save exponent value. mov fpacc.Mantissa [6], dx ;Save Mantissa value. mov fpacc.Mantissa [4], ax xor ax, ax ;Zero out the rest of the mov fpacc.Mantissa [2], ax ; mantissa. mov fpacc.Mantissa [0], ax jmp ULTOFDone ; ; Special case for zero, must zero all bytes in FPACC. Note that AX already ; contains zero. ; SetUL0: mov fpacc.Exponent, ax mov fpacc.Mantissa [6], ax mov fpacc.Mantissa [4], ax mov fpacc.Mantissa [2], ax mov fpacc.Mantissa [0], ax mov fpacc.Sign, al ; ULTOFDone: pop dx pop cx pop ax pop ds retf ; ; ; ; ; FTOI- Converts the floating point value in FPACC to a signed 16-bit ; integer and returns this integer in AX. ; Returns carry set if the number is too big to fit into AX. ; public sl_FTOI sl_FTOI proc far assume ds:stdgrp push ds push cx mov cx, StdGrp mov ds, cx ; mov cx, fpacc.Exponent cmp cx, 800eh jb FTOIok ; ; Handle special case of -32768: ; call DoFToU cmp ax, 8000h je FtoiOk2 stc jmp TooBig ; FTOIok: call DoFTOU FtoiOk2: cmp fpacc.Sign, 0 jns FTOIJustRight neg ax FTOIJustRight: clc TooBig: pop cx pop ds ret sl_FTOI endp ; ; ; ; ; FTOU- Like FTOI above, except this guy converts a floating point value ; to an unsigned integer in AX. ; Returns carry set if out of range (including negative numbers). ; public sl_FTOU sl_FTOU proc far assume ds:stdgrp push ds push cx mov cx, StdGrp mov ds, cx ; mov cx, fpacc.Exponent cmp cx, 800fh jb FTOUok BadU: stc jmp UTooBig ; FTOUok: call DoFTOU cmp fpacc.Sign, 0 js BadU ; FTOUJustRight: clc UTooBig: pop cx pop ds ret sl_FTOU endp ; ; ; DoFTOU- This code does the actual conversion! ; DoFTOU proc near mov ax, fpacc.Mantissa [6] cmp cx, 7fffh jb SetFTOU0 sub cx, 800eh neg cx shr ax, cl ret ; SetFTOU0: xor ax, ax ret DoFTOU endp ; ; ; ; ; ; FTOL- Converts the floating point value in FPACC to a signed 32-bit ; integer and returns this integer in DX:AX. ; Returns carry set if the number is too big to fit into DX:AX. ; public sl_FTOL sl_FTOL proc far assume ds:StdGrp push ds push cx mov cx, StdGrp mov ds, cx ; mov cx, fpacc.Exponent cmp cx, 801eh jb FTOLok stc jmp LTooBig ; FTOLok: call DoFTOUL cmp fpacc.Sign, 0 jns FTOLJustRight neg dx ;32-bit negate operation. neg ax sbb dx, 0 FTOLJustRight: clc LTooBig: pop cx pop ds ret sl_FTOL endp ; ; ; ; ; FTOUL-Like FTOL above, except this guy converts a floating point value ; to a 32-bit unsigned integer in DX:AX. ; Returns carry set if out of range (including negative numbers). ; public sl_FTOUL sl_FTOUL proc far assume ds:StdGrp push ds push cx mov cx, StdGrp mov ds, cx ; mov cx, fpacc.Exponent cmp cx, 801fh jb FTOULok BadUL: stc jmp ULTooBig ; FTOULok: call DoFTOUL cmp fpacc.Sign, 0 js BadUL ; clc ;If the # is okay. ULTooBig: pop cx pop ds ret sl_FTOUL endp ; ; ; DoFTOUL- This code does the actual conversion! ; DoFTOUL proc near mov dx, fpacc.Mantissa [6] mov ax, fpacc.Mantissa [4] cmp cx, 7fffh jb SetFTOUL0 sub cx, 801eh neg cx jcxz SetFTOULDone FTOULLp: shr dx, 1 rcr ax, 1 loop FTOULLp SetFToULDone: ret ; SetFTOUL0: xor ax, ax xor dx, dx ret DoFTOUL endp ; ; ; ; ; ; ; ; ; ; ; ; ; ;--------------------------------------------------------------------------- ; Floating Point Addition & Subtraction ;--------------------------------------------------------------------------- ; ; ; ; ; FADD- Adds FOP to FACC ; FSUB- Subtracts FOP from FACC ; These routines destroy the value in FPOP! ; public sl_fsub public sl_fadd ; assume ds:nothing sl_fsub proc far xor StdGrp:fpop.sign, 80h sl_fsub endp ; assume ds:StdGrp sl_fadd proc far push ds push ax push bx push cx push dx push si ; Use the current CS as the data segment to get direct access to ; the floating point accumulator and operands. mov ax, StdGrp mov ds, ax ; Kludge Alert! Check to see if either operand is zero. This code doesn't ; deal with zero very gracefully, so we have to specially check for zero ; here. mov ax, fpacc.Mantissa[0] or ax, fpacc.Mantissa[2] or ax, fpacc.Mantissa[4] or ax, fpacc.Mantissa[6] jne FPACCNot0 ; the whole thing is zero. mov ax, fpop.exponent ;If FPACC is zero, simply mov fpacc.exponent, ax ; copy FPOP to FPACC. mov ax, fpop.Mantissa[0] mov fpacc.Mantissa[0], ax mov ax, fpop.Mantissa[2] mov fpacc.Mantissa[2], ax mov ax, fpop.Mantissa[4] mov fpacc.Mantissa[4], ax mov ax, fpop.Mantissa[6] mov fpacc.Mantissa[6], ax mov al, fpop.Sign mov fpacc.Sign, al jmp Done FPACCNot0: mov ax, fpop.Mantissa[0] or ax, fpop.Mantissa[2] or ax, fpop.Mantissa[4] or ax, fpop.Mantissa[6] jne FPOPNot0 jmp Done ; Adjust the smaller of the two operands so that the exponents of the two ; objects are the same: FPOPNot0: mov cx, fpacc.exponent sub cx, fpop.exponent js gotoAdjustFPA jnz AdjustFPOP jmp Adjusted ;Only if exponents are equal. gotoAdjustFPA: jmp AdjustFPACC ; ; Since the difference of the exponents is negative, the magnitude of FPOP ; is smaller than the magnitude of fpacc. Adjust FPOP here. ; AdjustFPOP: cmp cx, 64 ;If greater than 64, forget jb short By16LoopTest ; it. Sum is equal to FPACC. jmp Done ; ; If the difference is greater than 16 bits, adjust FPOP a word at a time. ; Note that there may be multiple words adjusted in this fashion. ; By16Loop: mov ax, fpop.mantissa[2] mov fpop.mantissa[0], ax mov ax, fpop.mantissa[4] mov fpop.mantissa[2], ax mov ax, fpop.mantissa[6] mov fpop.mantissa[4], ax mov fpop.mantissa[6], 0 sub cx, 16 By16LoopTest: cmp cx, 16 jae By16Loop ; ; After adjusting sixteen bits at a time, see if there are at least eight ; bits. Note that this can only occur once, for if you could adjust by ; eight bits twice, you could have adjusted by 16 above. ; cmp cx, 8 jb NotBy8 mov ax, fpop.mantissa[1] mov fpop.mantissa[0], ax mov ax, fpop.mantissa[3] mov fpop.mantissa[2], ax mov ax, fpop.mantissa[5] mov fpop.mantissa[4], ax mov al, byte ptr fpop.mantissa [7] mov byte ptr fpop.mantissa [6], al mov byte ptr fpop.mantissa[7], 0 sub cx, 8 ; ; Well, now we're down to a bit at a time. ; NotBy8: jcxz AdjFPOPDone ; ; Load the mantissa into registers to save processing time. ; mov ax, fpop.mantissa[6] mov bx, fpop.mantissa[4] mov dx, fpop.mantissa[2] mov si, fpop.mantissa[0] By1Loop: shr ax, 1 rcr bx, 1 rcr dx, 1 rcr si, 1 loop By1Loop mov fpop.mantissa[6], ax ;Save result back into mov fpop.mantissa[4], bx ; fpop. mov fpop.mantissa[2], dx mov fpop.mantissa[0], si AdjFPOPDone: jmp Adjusted ; ; ; ; AdjustFPACC- FPACC was smaller than FPOP, so adjust its bits down here. ; This code is pretty much identical to the above, the same ; comments apply. ; AdjustFPACC: neg cx ;Take ABS(cx) cmp cx, 64 ;If greater than 64, forget jb By16LpTest ; it. jmp SetFPACC2Zero ; By16Lp: mov ax, fpacc.mantissa[2] mov fpacc.mantissa[0], ax mov ax, fpacc.mantissa[4] mov fpacc.mantissa[2], ax mov ax, fpacc.mantissa[6] mov fpacc.mantissa[4], ax mov fpacc.mantissa[6], 0 sub cx, 16 By16LpTest: cmp cx, 16 jae By16Lp ; cmp cx, 8 jb NotBy8a mov ax, fpacc.mantissa[1] mov fpacc.mantissa[0], ax mov ax, fpacc.mantissa[3] mov fpacc.mantissa[2], ax mov ax, fpacc.mantissa[5] mov fpacc.mantissa[4], ax mov al, byte ptr fpacc.mantissa [7] mov byte ptr fpacc.mantissa [6], al mov byte ptr fpacc.mantissa[7], 0 sub cx, 8 ; NotBy8a: jcxz Adjusted mov ax, fpacc.mantissa[6] mov bx, fpacc.mantissa[4] mov dx, fpacc.mantissa[2] mov si, fpacc.mantissa[0] By1Lp: shr ax, 1 rcr bx, 1 rcr dx, 1 rcr si, 1 loop By1Lp mov fpacc.mantissa[6], ax mov fpacc.mantissa[4], bx mov fpacc.mantissa[2], dx mov fpacc.mantissa[0], si mov ax, fpop.Exponent ;FPACC assumes the same mov fpacc.Exponent, ax ; exponent as FPOP. AdjFPACCDone: jmp Adjusted ; ; If FPACC is so much smaller than FPOP that it is insignificant, set ; it to zero. ; SetFPACC2Zero: xor ax, ax mov fpacc.mantissa[0], ax mov fpacc.mantissa[2], ax mov fpacc.mantissa[4], ax mov fpacc.mantissa[6], ax mov fpacc.exponent, ax mov fpacc.sign, al ; ; Now that the mantissas are aligned, let's add (or subtract) them. ; Adjusted: mov al, fpacc.sign xor al, fpop.sign js SubEm ; ; If the signs are the same, simply add the mantissas together here. ; mov ax, fpop.mantissa[0] add fpacc.mantissa[0], ax mov ax, fpop.mantissa[2] adc fpacc.mantissa[2], ax mov ax, fpop.mantissa[4] adc fpacc.mantissa[4], ax mov ax, fpop.mantissa[6] adc fpacc.mantissa[6], ax jnc Normalize ; ; If there was a carry out of the addition (quite possible since most ; fp values are normalized) then we need to shove the bit back into ; the number. ; rcr fpacc.mantissa[6], 1 rcr fpacc.mantissa[4], 1 rcr fpacc.mantissa[2], 1 rcr fpacc.mantissa[0], 1 inc fpacc.exponent ; ; If there was a carry out of the bottom, add it back in (this rounds the ; result). No need to worry about a carry out of the H.O. bit this time-- ; there is no way to add together two numbers to get a carry *and* all ; one bits in the result. Therefore, rounding at this point will not ; propagate all the way through. ; adc fpacc.Mantissa [0], 0 jnc Normalize inc fpacc.Mantissa [2] jnz Normalize inc fpacc.Mantissa [4] jnz Normalize inc fpacc.Mantissa [6] jmp Normalize ; ; ; ; If the signs are different, we've got to deal with four possibilities: ; ; 1) fpacc is negative and its magnitude is greater than fpop's. ; Result is negative, fpacc.mant := fpacc.mant - fpop.mant. ; ; 2) fpacc is positive and its magnitude is greater than fpop's. ; Result is positive, fpacc.mant := fpacc.mant - fpop.mant. ; ; 3) fpacc is negative and its magnitude is less than fpop's. ; Result is positive, fpacc.mant := fpop.mant - fpacc.mant. ; ; 4) fpacc is positive and its magnitude is less than fpop's. ; Result is negative, fpacc.mant := fpop.mant - fpacc.mant. ; SubEm: mov ax, fpacc.mantissa[0] mov bx, fpacc.mantissa[2] mov dx, fpacc.mantissa[4] mov si, fpacc.mantissa[6] sub ax, fpop.mantissa[0] sbb bx, fpop.mantissa[2] sbb dx, fpop.mantissa[4] sbb si, fpop.mantissa[6] jnc StoreFPACC ; ; Whoops! FPOP > FPACC, fix that down here. ; not ax not bx not dx not si inc ax jnz StoreFPACCSign inc bx jnz StoreFPAccSign inc dx jnz StoreFPAccSign inc si ; StoreFPAccSign: xor fpacc.sign, 80h ;Flip sign if case 3/4. ; StoreFPAcc: mov fpacc.mantissa[0], ax mov fpacc.mantissa[2], bx mov fpacc.mantissa[4], dx mov fpacc.mantissa[6], si ; Normalize the result down here. Start by shifting 16 bits at a time, ; then eight bits, then one bit at a time. Normalize: mov ax, fpacc.Mantissa[0] ;First, see if the result or ax, fpacc.Mantissa[2] ; is zero. Can't normalize or ax, fpacc.Mantissa[4] ; if this is the case. or ax, fpacc.Mantissa[6] jnz NormLoop mov fpacc.Exponent, ax ;Force everything to zero mov fpacc.Sign, al ; if result is zero. jmp Done NormLoop: mov ax, fpacc.mantissa[6] or ax, ax ;See if zero (which means we jnz Try8Bits ; can shift 16 bits). mov ax, fpacc.mantissa[4] mov fpacc.mantissa[6], ax mov ax, fpacc.mantissa[2] mov fpacc.mantissa[4], ax mov ax, fpacc.mantissa[0] mov fpacc.mantissa[2], ax mov fpacc.mantissa[0],0 sub fpacc.exponent, 16 jmp NormLoop ; ; Okay, see if we can normalize eight bits at a shot. ; Try8Bits: mov al, byte ptr fpacc.mantissa[7] cmp al, 0 jnz Try1Bit mov ax, fpacc.mantissa[5] mov fpacc.mantissa[6], ax mov ax, fpacc.mantissa[3] mov fpacc.mantissa[4], ax mov ax, fpacc.mantissa[1] mov fpacc.mantissa[3], ax mov al, byte ptr fpacc.mantissa[0] mov byte ptr fpacc.mantissa[1], al mov byte ptr fpacc.mantissa[0], 0 sub fpacc.exponent, 8 ; Try1Bit: mov ax, fpacc.mantissa[6] test ah, 80h jnz Done mov bx, fpacc.mantissa[4] mov dx, fpacc.mantissa[2] mov si, fpacc.mantissa[0] OneBitLp: dec fpacc.exponent shl si, 1 rcl dx, 1 rcl bx, 1 rcl ax, 1 or ax, ax ;See if bit 15 is set. jns OneBitLp mov fpacc.mantissa[6], ax mov fpacc.mantissa[4], bx mov fpacc.mantissa[2], dx mov fpacc.mantissa[0], si ; Done: pop si pop dx pop cx pop bx pop ax pop ds ret sl_fadd endp ; ; ; ; ; ; ; ; ; ; ;--------------------------------------------------------------------------- ; Floating point comparison. ;--------------------------------------------------------------------------- ; ; ; FCMP ; Compares value in FPACC to value in FPOP. ; Returns -1 in AX if FPACC is less than FPOP, ; Returns 0 in AX if FPACC is equal to FPOP, ; Returns 1 in AX if FPACC is greater than FPOP. ; ; Also returns this status in the flags (by comparing AX against zero ; before returning) so you can use JE, JNE, JG, JGE, JL, or JLE after this ; routine to test the comparison. ; public sl_fcmp sl_fcmp proc far assume ds:StdGrp push ds mov ax, StdGrp mov ds, ax ; ; First compare the signs of the mantissas. If they are different, the ; negative one is smaller. ; mov al, byte ptr FPACC+10 ;Get sign bit xor al, byte ptr FPOP+10 ;See if the signs are different jns SameSign ; ; If the signs are different, then the sign of FPACC determines the result ; test byte ptr FPACC+10, 80h jnz IsLT jmp short IsGT ; ; Down here the signs are the same. First order of business is to compare ; the exponents. The one with the larger exponent wins. If the exponents ; are equal, then we need to compare the mantissas. If the mantissas are ; the same then the two numbers are equal. If the mantissas are different ; then the larger one wins. Note that this discussion is for positive values ; only, if the numbers are negative, then we must reverse the win/loss value ; (win=GT). ; SameSign: mov ax, FPACC.exponent ;One thing cool about bias- cmp ax, FPOP.exponent ; 1023 exponents is that we ja MayBeGT ; can use an unsigned compare jb MayBeLT ; ; If the exponents are equal, we need to start comparing the mantissas. ; This straight line code turns out to be about the fastest way to do it. ; mov ax, word ptr FPACC.mantissa+6 cmp ax, word ptr FPOP.mantissa+6 ja MayBeGT jb MayBeLT mov ax, word ptr FPACC.mantissa+4 cmp ax, word ptr FPOP.mantissa+4 ja MayBeGT jb MayBeLT mov ax, word ptr FPACC.mantissa+2 cmp ax, word ptr FPOP.mantissa+2 ja MayBeGT jb MayBeLT mov ax, word ptr FPACC.mantissa cmp ax, word ptr FPOP.mantissa ja MayBeGT je IsEq ;They're equal at this point. ; ; MayBeLT- Looks like less than so far, but we need to check the sign of the ; numbers, if they are negative then FPACC is really GT FPOP. Remember, the ; sign is not part of the mantissa! ; MayBeLT: test FPACC.sign, 80h js IsGT ; IsLT: mov ax, -1 jmp short cmpRtn ; ; Same story here for MayBeGT ; MayBeGT: test FPACC.sign, 80h js IsLT ; IsGT: mov ax, 1 jmp short cmpRtn ; IsEq: xor ax, ax cmpRtn: pop ds cmp ax, 0 ;Set the flags as appropriate ret sl_fcmp endp assume ds:nothing ; ; ; ; ; ; ; ; ; ; ; ; ; ;--------------------------------------------------------------------------- ; Floating Point Multiplication ;--------------------------------------------------------------------------- ; ; ; ; ; sl_fmul- Multiplies facc by fop and leaves the result in facc. ; public sl_fmul sl_fmul proc far assume ds:StdGrp push ds push ax push bx push cx push dx push si push di ; mov ax, StdGrp mov ds, ax ; ; See if either operand is zero: ; mov ax, fpacc.mantissa[0] ;No need to check exponent! or ax, fpacc.mantissa[2] or ax, fpacc.mantissa[4] or ax, fpacc.mantissa[6] jz ProdIsZero ; mov ax, fpop.mantissa[0] or ax, fpop.mantissa[2] or ax, fpop.mantissa[4] or ax, fpop.mantissa[6] jnz ProdNotZero ; ProdIsZero: xor ax, ax ;Need this! mov fpacc.sign, al mov fpacc.exponent, ax mov fpacc.mantissa[0], ax mov fpacc.mantissa[2], ax mov fpacc.mantissa[4], ax mov fpacc.mantissa[6], ax jmp FMulDone ; ; If both operands are non-zero, compute the true product down here. ; ProdNotZero: mov al, fpop.sign ;Compute the new sign. xor fpacc.sign, al ; ; Eliminate bias in the exponents, add them, and check for 16-bit signed ; overflow. ; mov ax, fpop.exponent ;Compute new exponent. sub ax, 7fffh ;Subtract BIAS and adjust mov bx, fpacc.Exponent sub bx, 7fffh add ax, bx ; for fractional multiply. jno GoodExponent ; ; If the exponent overflowed, set up the overflow value here. ; mov ax, 0ffffh mov fpacc.exponent, ax ;Largest exponent value mov fpacc.mantissa[0], ax ; and largest mantissa, too! mov fpacc.mantissa[2], ax mov fpacc.mantissa[4], ax mov fpacc.mantissa[6], ax jmp FMulDone ; GoodExponent: add ax, 8000h ;Add the bias back in (note mov fpacc.Exponent, ax ; Mul64 below causes shift ; ; to force bias of 7fffh. ; Okay, compute the product of the mantissas down here. ; call Mul64 ; ; Normalize the product. Note: we know the product is non-zero because ; both of the original operands were non-zero. ; mov cx, fpacc.exponent jmp short TestNrmMul NrmMul1: sub cx, 16 mov ax, fprod[12] mov fprod[14], ax mov ax, fprod[10] mov fprod[12], ax mov ax, fprod[8] mov fprod[10], ax mov ax, fprod[6] mov fprod[8], ax mov ax, fprod[4] mov fprod[6], ax mov ax, fprod[2] mov fprod[4], ax mov ax, fprod[0] mov fprod[2], ax mov fprod[0], 0 TestNrmMul: cmp cx, 16 jb DoNrmMul8 mov ax, fprod[14] or ax, ax jz NrmMul1 ; ; See if we can shift the product a whole byte ; DoNrmMul8: cmp ah, 0 ;Contains fprod[15] from above. jnz DoOneBits cmp cx, 8 jb DoOneBits mov ax, fprod[13] mov fprod[14], ax mov ax, fprod[11] mov fprod[12], ax mov ax, fprod[9] mov fprod[10], ax mov ax, fprod[7] mov fprod[8], ax mov ax, fprod[5] mov fprod[6], ax mov ax, fprod[3] mov fprod[4], ax mov ax, fprod[1] mov fprod[2], ax mov al, byte ptr fprod[0] mov byte ptr fprod[1], al mov byte ptr fprod[0], 0 sub cx, 8 ; DoOneBits: mov ax, fprod[14] mov bx, fprod[12] mov dx, fprod[10] mov si, fprod[8] mov di, fprod[6] jmp short TestOneBits ; OneBitLoop: shl fprod[0], 1 rcl fprod[2], 1 rcl fprod[4], 1 rcl di, 1 rcl si, 1 rcl dx, 1 rcl bx, 1 rcl ax, 1 dec cx TestOneBits: jcxz StoreProd test ah, 80h jz OneBitLoop ; StoreProd: mov fpacc.mantissa[6], ax mov fpacc.mantissa[4], bx mov fpacc.mantissa[2], dx mov fpacc.mantissa[0], si mov fpacc.exponent, cx or ax, bx or ax, dx or ax, si jnz FMulDone ; ; If underflow occurs, set the result to zero. ; mov fpacc.exponent, ax mov fpacc.sign, al ; FMulDone: pop di pop si pop dx pop cx pop bx pop ax pop ds ret sl_fmul endp assume ds:nothing ; ; ; ; ; Mul64- Multiplies the 8 bytes in fpacc.mant by the 8 bytes in fpop.mant ; and leaves the result in fprod. ; Mul64 proc near assume ds:StdGrp xor ax, ax mov fprod[0], ax mov fprod[2], ax mov fprod[4], ax mov fprod[6], ax mov fprod[8], ax mov fprod[10], ax mov fprod[12], ax mov fprod[14], ax ; ; Computing the following (each character represents 16-bits): ; ; A B C D ; x E F G H ; ------- ; ; Product is computed by: ; ; A B C D ; x E F G H ; ---------- ; HD ; HC0 ; HB00 ; HA000 ; GD0 ; GC00 ; GB000 ; GA0000 ; FD00 ; FC000 ; FB0000 ; FA00000 ; ED000 ; EC0000 ; EB00000 ; + EA000000 ; ---------- ; xxxxxxxx ; ; In the loop below, si indexes through A, B, C, and D above (or E, F, G, ; and H since multiplication is commutative). ; mov si, ax ;Set Index to zero. flp1: mov ax, fpacc.mantissa[si] ;Multiply A, B, C, or D mul fpop.mantissa[0] ; by H. add fprod [si], ax ;Add it into the partial adc fprod+2 [si], dx ; product computed so far. jnc NoCarry0 inc fprod+4 [si] jnz NoCarry0 inc fprod+6 [si] jnz NoCarry0 inc fprod+8 [si] jnz NoCarry0 inc fprod+10 [si] jnz NoCarry0 inc fprod+12 [si] jnz NoCarry0 inc fprod+14 [si] ; NoCarry0: mov ax, fpacc.mantissa[si] ;Multiply A, B, C, or D mul fpop.mantissa[2] ; (selected by SI) by G add fprod+2 [si], ax ; and add it into the adc fprod+4 [si], dx ; partial product. jnc NoCarry1 inc fprod+6 [si] jnz NoCarry1 inc fprod+8 [si] jnz NoCarry1 inc fprod+10 [si] jnz NoCarry1 inc fprod+12 [si] jnz NoCarry1 inc fprod [14] ; NoCarry1: mov ax, fpacc.mantissa [si] ;Multiply A, B, C, or D mul fpop.mantissa [4] ; (SI selects) by F and add add fprod+4 [si], ax ; it into the partial prod. adc fprod+6 [si], dx jnc NoCarry2 inc fprod+8 [si] jnz NoCarry2 inc fprod+10 [si] jnz NoCarry2 inc fprod+12 [si] jnz NoCarry2 inc fprod+14 [si] ; NoCarry2: mov ax, fpacc.mantissa [si] ;Multiply A/B/C/D (selected mul fpop.mantissa [6] ; by SI) by E and add it add fprod+6 [si], ax ; into the partial product. adc fprod+8 [si], dx jnc NoCarry3 inc fprod+10 [si] jnz NoCarry3 inc fprod+12 [si] jnz NoCarry3 inc fprod+14 [si] ; NoCarry3: inc si ;Select next multiplier inc si ; (B, C, or D above). cmp si, 8 ;Repeat for 64 bit x 64 bit jnb QuitMul64 ; multiply. jmp flp1 QuitMul64: ret assume ds:nothing Mul64 endp ; ; ; ; ; ; ; ; ;--------------------------------------------------------------------------- ; Floating Point Division ;--------------------------------------------------------------------------- ; ; ; ; ; Floating point division: Divides fpacc by fpop. ; public sl_fdiv sl_fdiv proc far assume ds:StdGrp push ds push ax push bx push cx push dx push si push di push bp ; mov ax, StdGrp mov ds, ax ; ; See if either operand is zero: ; mov ax, fpacc.mantissa[0] ;No need to check exponent! or ax, fpacc.mantissa[2] or ax, fpacc.mantissa[4] or ax, fpacc.mantissa[6] jz QuoIsZero ; mov ax, fpop.mantissa[0] or ax, fpop.mantissa[2] or ax, fpop.mantissa[4] or ax, fpop.mantissa[6] jnz DenomNotZero ; ; Whoops! Division by zero! Set to largest possible value (+inf) and leave. ; DivOvfl: mov ax, 0ffffh mov fpacc.exponent, ax mov fpacc.mantissa[0], ax mov fpacc.mantissa[2], ax mov fpacc.mantissa[4], ax mov fpacc.mantissa[6], ax mov al, fpop.sign xor fpacc.sign, al ; ; Note: we could also do an INT 0 (div by zero) or floating point exception ; here, if necessary. ; jmp FDivDone ; ; ; If the numerator is zero, the quotient is zero. Handle that here. ; QuoIsZero: xor ax, ax ;Need this! mov fpacc.sign, al mov fpacc.exponent, ax mov fpacc.mantissa[0], ax mov fpacc.mantissa[2], ax mov fpacc.mantissa[4], ax mov fpacc.mantissa[6], ax jmp FDivDone ; ; ; ; If both operands are non-zero, compute the quotient down here. ; DenomNotZero: mov al, fpop.sign ;Compute the new sign. xor fpacc.sign, al ; mov ax, fpop.exponent ;Compute new exponent. sub ax, 7fffh ;Subtract BIAS. mov bx, fpacc.exponent sub bx, 7fffh sub bx, ax ;Compute new exponent jo DivOvfl add bx, 7fffh ;Add in BIAS mov fpacc.exponent, bx ;Save as new exponent. ; ; Okay, compute the quotient of the mantissas down here. ; call Div64 ; ; Normalize the Quotient. ; mov cx, fpacc.exponent jmp short TestNrmDiv ; ; Normalize by shifting 16 bits at a time here. ; NrmDiv1: sub cx, 16 mov ax, fpacc.mantissa[4] mov fpacc.mantissa[6], ax mov ax, fpacc.mantissa[2] mov fpacc.mantissa[4], ax mov ax, fpacc.mantissa[0] mov fpacc.mantissa[2], ax mov fpacc.mantissa[0], 0 TestNrmDiv: cmp cx, 16 jb DoNrmDiv8 mov ax, fpacc.mantissa[6] or ax, ax jz NrmDiv1 ; ; Normalize by shifting eight bits at a time here. ; ; See if we can shift the product a whole byte ; DoNrmDiv8: cmp byte ptr fpacc.mantissa[7], 0 jnz DoOneBitsDiv cmp cx, 8 jb DoOneBitsDiv mov ax, fpacc.mantissa[5] mov fpacc.mantissa[6], ax mov ax, fpacc.mantissa[3] mov fpacc.mantissa[4], ax mov ax, fpacc.mantissa[1] mov fpacc.mantissa[2], ax mov al, byte ptr fpacc.mantissa[0] mov byte ptr fpacc.mantissa[1], al mov byte ptr fpacc.mantissa[0], 0 sub cx, 8 ; DoOneBitsDiv: mov ax, fpacc.mantissa[6] mov bx, fpacc.mantissa[4] mov dx, fpacc.mantissa[2] mov si, fpacc.mantissa[0] jmp short TestOneBitsDiv ; ; One bit at a time normalization here. ; OneBitLoopDiv: shl si, 1 rcl dx, 1 rcl bx, 1 rcl ax, 1 dec cx TestOneBitsDiv: jcxz StoreQuo test ah, 80h jz OneBitLoopDiv ; StoreQuo: mov fpacc.mantissa[6], ax mov fpacc.mantissa[4], bx mov fpacc.mantissa[2], dx mov fpacc.mantissa[0], si mov fpacc.exponent, cx or ax, bx or ax, dx or ax, si jnz FDivDone ; ; If underflow occurs, set the result to zero. ; mov fpacc.exponent, ax mov fpacc.sign, al ; FDivDone: pop bp pop di pop si pop dx pop cx pop bx pop ax pop ds ret sl_fdiv endp assume ds:nothing ; ; ; ; ; Div64- Divides the 64-bit fpacc.mantissa by the 64-bit fpop.mantissa. ; div64 proc near assume ds:StdGrp ; ; ; First, normalize fpop if necessary and possible: ; mov ax, fpop.mantissa[6] mov bx, fpop.mantissa[4] mov cx, fpop.mantissa[2] mov dx, fpop.mantissa[0] mov si, fpacc.exponent jmp short Div16NrmTest ; ; The following loop normalizes fpop 16 bits at a time. ; Div16NrmLp: mov ax, bx mov bx, dx mov cx, dx xor dx, dx add si, 16 Div16NrmTest: cmp si, -16 ja Div16Nrm8 ;Must be unsigned because this or ax, ax ; is bias arithmetic, not jz Div16NrmLp ; two's complement! ; ; ; The following code checks to see if it can normalize by eight bits at ; a time. ; Div16Nrm8: cmp si, -8 ja Div1NrmTest ;Must be unsigned! cmp ah, 0 jnz Div1NrmTest mov ah, al mov al, bh mov bh, bl mov bl, ch mov ch, cl mov cl, dh mov dh, dl mov dl, 0 add si, 8 jmp short Div1NrmTest ; ; Down here we're stuck with the slow task of normalizing by a bit ; at a time. ; Div1NrmLp: shl dx, 1 rcl cx, 1 rcl bx, 1 rcl ax, 1 inc si Div1NrmTest: cmp si, -1 je DivOvfl2 ;Can't do it! test ah, 80h jz Div1NrmLp jmp short DoSlowDiv ; ; If overflow occurs, set FPACC to the maximum possible value and quit. ; DivOvfl2: mov ax, 0ffffh mov fpacc.exponent, ax mov fpacc.mantissa[0], ax mov fpacc.mantissa[2], ax mov fpacc.mantissa[4], ax mov fpacc.mantissa[6], ax jmp QuitDiv ; ; Oh No! A GawdAwful bit-by-bit division routine. Terribly slow! ; Actually, it was sped up a little by checking to see if it could ; shift eight or sixteen bits at a time (because it encounters eight ; or sixteen zeros during the division). ; ; Could possibly speed this up some more by checking for the special ; case of n/16 bits. Haven't tried this idea out though. ; DoSlowDiv: mov fpacc.exponent, si mov si, ax mov di, bx mov fpop.mantissa[2], cx mov fpop.mantissa[0], dx mov ax, fpacc.mantissa[6] mov bx, fpacc.mantissa[4] mov cx, fpacc.mantissa[2] mov dx, fpacc.mantissa[0] mov bp, 64 DivideLoop: cmp bp, 16 jb Test8 or ax, ax jnz Test8 ; ; Do a shift by 16 bits here: ; mov ax, Quotient[4] mov Quotient[6], ax mov ax, Quotient[2] mov Quotient[4], ax mov ax, Quotient[0] mov Quotient[2], ax mov Quotient[0], 0 mov ax, bx mov bx, cx mov cx, dx xor dx, dx sub bp, 16 jnz DivideLoop jmp FinishDivide ; Test8: cmp bp, 8 jb Do1 cmp ah, 0 jnz Do1 ; ; Do a shift by 8 bits here: ; push ax mov ax, Quotient[5] mov Quotient[6], ax mov ax, Quotient[3] mov Quotient[4], ax mov ax, Quotient[1] mov Quotient[2], ax mov al, byte ptr Quotient [0] mov byte ptr Quotient [1], al mov byte ptr Quotient[0], 0 pop ax mov ah, al mov al, bh mov bh, bl mov bl, ch mov ch, cl mov cl, dh mov dh, dl mov dl, 0 sub bp, 8 jz FinishDivide2 jmp DivideLoop FinishDivide2: jmp FinishDivide ; Do1: cmp ax, si jb shift0 ja Shift1 cmp bx, di jb shift0 ja Shift1 cmp cx, fpop.mantissa[2] jb shift0 ja shift1 cmp dx, fpop.mantissa[0] jb shift0 ; ; fpacc.mantiss IS greater than fpop.mantissa, shift a one bit into ; the result here: ; Shift1: stc rcl Quotient[0], 1 rcl Quotient[2], 1 rcl Quotient[4], 1 rcl Quotient[6], 1 sub dx, fpop.mantissa[0] sbb cx, fpop.mantissa[2] sbb bx, di sbb ax, si shl dx, 1 rcl cx, 1 rcl bx, 1 rcl ax, 1 ;Never a carry out. dec bp jnz jDivideLoop jmp FinishDivide ; ; If fpacc.mantissa was less than fpop.mantissa, shift a zero bit into ; the quotient. ; Shift0: shl Quotient[0], 1 rcl Quotient[2], 1 rcl Quotient[4], 1 rcl Quotient[6], 1 shl dx, 1 rcl cx, 1 rcl bx, 1 rcl ax, 1 jc Greater dec bp jnz jDivideLoop jmp FinishDivide jDivideLoop: jmp DivideLoop ; ; If there was a carry out of the shift, we KNOW that fpacc must be ; greater than fpop. Handle that case down here. ; Greater: dec bp jz FinishDivide stc rcl Quotient[0], 1 rcl Quotient[2], 1 rcl Quotient[4], 1 rcl Quotient[6], 1 sub dx, fpop.mantissa[0] sbb cx, fpop.mantissa[2] sbb bx, di sbb ax, si shl dx, 1 rcl cx, 1 rcl bx, 1 rcl ax, 1 jc Greater dec bp jz FinishDivide jmp DivideLoop ; ; Okay, clean everything up down here: ; FinishDivide: mov ax, Quotient[0] mov fpacc.mantissa[0], ax mov ax, Quotient[2] mov fpacc.mantissa[2], ax mov ax, Quotient[4] mov fpacc.mantissa[4], ax mov ax, Quotient[6] mov fpacc.mantissa[6], ax ; QuitDiv: ret assume ds:nothing div64 endp ; ; ; ; ; ;--------------------------------------------------------------------------- ; Floating Point => TEXT (Output) conversion routines. ;--------------------------------------------------------------------------- ; ; ; ; ; Power of ten tables used by the floating point I/O routines. ; ; Format for each entry (13 bytes): ; ; 1st through ; 11th bytes Internal FP format for this particular number. ; ; 12th & ; 13th bytes: Decimal exponent for this value. ; ; ; This first table contains the negative powers of ten as follows: ; ; for n:= 0 to 12 do ; entry [12-n] := 10 ** (-2 ** n) ; entry [13] := 1.0 ; PotTbln dw 9fdeh, 0d2ceh, 4c8h, 0a6ddh, 4ad8h ; 1e-4096 db 0 ; Sign dw -4096 ; Dec Exponent ; dw 2de4h, 3436h, 534fh, 0ceaeh, 656bh ; 1e-2048 db 0 dw -2048 ; dw 0c0beh, 0da57h, 82a5h, 0a2a6h, 72b5h ; 1e-1024 db 0 dw -1024 ; dw 0d21ch, 0db23h, 0ee32h, 9049h, 795ah ; 1e-512 db 0 dw -512 ; dw 193ah, 637ah, 4325h, 0c031h, 7cach ; 1e-256 db 0 dw -256 ; dw 0e4a1h, 64bch, 467ch, 0ddd0h, 7e55h ; 1e-128 db 0 dw -128 ; dw 0e9a5h, 0a539h, 0ea27h, 0a87fh, 7f2ah ; 1e-64 db 0 dw -64 ; dw 94bah, 4539h, 1eadh, 0cfb1h, 7f94h ; 1e-32 db 0 dw -32 ; dw 0e15bh, 0c44dh, 94beh, 0e695h, 7fc9h ; 1e-16 db 0 dw -16 ; dw 0cefdh, 8461h, 7711h, 0abcch, 7fe4h ; 1e-8 db 0 dw -8 ; dw 652ch, 0e219h, 1758h, 0d1b7h, 7ff1h ; 1e-4 db 0 dw -4 ; dw 0d70ah, 70a3h, 0a3dh, 0a3d7h, 7ff8h ; 1e-2 db 0 dw -2 ; Div10Value dw 0cccdh, 0cccch, 0cccch, 0cccch, 7ffbh ; 1e-1 db 0 dw -1 ; dw 0, 0, 0, 8000h, 7fffh ; 1e0 db 0 dw 0 ; ; ; PotTblP- Power of ten table. Holds powers of ten raised to positive ; powers of two; ; ; i.e., x(12-n) = 10 ** (2 ** n) for 0 <= n <= 12. ; x(13) = 1.0 ; x(-1) = 10 ** (2 ** -4096) ; ; There is a -1 entry since it is possible for the algorithm to back up ; before the table. ; dw 979bh, 8a20h, 5202h, 0c460h, 0b525h ; 1e+4096 db 0 dw 4096 ; PotTblP dw 979bh, 8a20h, 5202h, 0c460h, 0b525h ; 1e+4096 db 0 dw 4096 ; dw 5de5h, 0c53dh, 3b5dh, 9e8bh, 09a92h ; 1e+2048 db 0 dw 2048 ; dw 0c17h, 8175h, 7586h, 0c976h, 08d48h ; 1e+1024 db 0 dw 1024 ; dw 91c7h, 0a60eh, 0a0aeh, 0e319h, 086a3h ; 1e+512 db 0 dw 512 ; dw 0de8eh, 9df9h, 0ebfbh, 0aa7eh, 08351h ; 1e+256 db 0 dw 256 ; dw 8ce0h, 80e9h, 47c9h, 93bah, 081a8h ; 1e+128 db 0 dw 128 ; dw 0a6d5h, 0ffcfh, 1f49h, 0c278h, 080d3h ; 1e+64 db 0 dw 64 ; dw 0b59eh, 2b70h, 0ada8h, 9dc5h, 08069h ; 1e+32 db 0 dw 32 ; dw 0, 400h, 0c9bfh, 8e1bh, 08034h ; 1e+16 db 0 dw 16 ; dw 0, 0, 2000h, 0bebch, 08019h ; 1e+8 db 0 dw 8 ; dw 0, 0, 0, 9c40h, 0800ch ; 1e+4 db 0 dw 4 ; dw 0, 0, 0, 0c800h, 08005h ; 1e+2 db 0 dw 2 ; dw 0, 0, 0, 0a000h, 08002h ; 1e+1 db 0 dw 1 ; dw 0, 0, 0, 8000h, 7fffh ; 1e0 db 0 dw 0 ; ; ; ; ; ; ; ; SL_FTOA- Converts extended precision value in FPACC to a decimal ; string. AL contains the field width, AH contains the ; number of positions after the decimal point. The format ; of the converted string is: ; ; sd.e ; ; where "s" is a single character which is either a space ; or "=", "e" is some number of digits which is equal to ; the value passed in AL, and "d" is the number of digits ; given by (AL-AH-2). If the field width is too small, ; this routine creates a string of "#" characters AH long. ; ; ES:DI contains the address where we're supposed to put ; the resulting string. This code assumes that there is ; sufficient memory to hold (AL+1) characters at this address. ; ; ; public sl_ftoa sl_ftoa proc far push di call far ptr sl_ftoa2 pop di ret sl_ftoa endp ; public sl_ftoa2 sl_ftoa2 proc far assume ds:StdGrp ; pushf push ds push ax push bx push cx push dx push si ; cld mov bx, StdGrp mov ds, bx ; ; Save fpacc 'cause it gets munged. ; push fpacc.Mantissa [0] push fpacc.Mantissa [2] push fpacc.Mantissa [4] push fpacc.Mantissa [6] push fpacc.Exponent push word ptr fpacc.Sign ; mov cx, ax ;Save field width/dec pts here. ; call fpdigits ;Convert fpacc to digit string. ; ; Round the string of digits to the number of significant digits we want to ; display for this number: ; mov bx, DecExponent cmp bx, 18 jb PosRS xor bx, bx ;Force to zero if negative or too big. ; PosRS: add bl, ch ;Compute position where we should start adc bh, 0 ; the rounding. inc bx ;Tweak next digit. cmp bx, 18 ;Don't bother rounding if we have jae RoundDone ; more than 18 digits here. ; ; Add 5 to the digit after the last digit we want to print. Then propogate ; any overflow through the remaining digits. ; mov al, DecDigits [bx] add al, 5 mov DecDigits [bx], al cmp al, "9" jbe RoundDone sub DecDigits [bx], 10 RoundLoop: dec bx js FirstDigit inc DecDigits[bx] cmp DecDigits[bx], "9" jbe RoundDone sub DecDigits[bx], 10 jmp RoundLoop ; ; If we hit the first digit in the string, we've got to shift all the ; characters down one position and put a "1" in the first character ; position. ; FirstDigit: mov bx, DecExponent cmp bx, 18 jb FDOkay xor bx, bx ; FDOkay: mov bl, ch mov bh, 0 inc bx FDLp: mov al, byte ptr DecDigits[bx-1] mov DecDigits [bx], al dec bx jnz FDLp mov DecDigits, "1" inc DecExponent ;Cause we just added a digit. ; RoundDone: ; ; See if we're dealing with values greater than one (abs) or between 0 & 1. ; cmp DecExponent, 0 ;Handle positive/negative exponents jge PositiveExp ; separately. ; ; Handle values between 0 & 1 here (negative powers of ten). ; mov dl, ch ;Compute #'s width = DecPlaces+3 add dl, 3 ;Make room for "-0." jc BadFieldWidth cmp dl, 4 jae LengthOk mov dl, 4 ;Minimum string is "-0.0" LengthOK: mov al, ' ' PutSpcs2: cmp dl, cl jae PS2Done stosb inc dl jmp PutSpcs2 ; PS2Done: mov al, DecSign stosb mov al, "0" ;Output "0." before the number. stosb mov al, "." stosb mov ah, 0 ;Used to count output digits lea bx, stdGrp:DecDigits ;Pointer to number string. PutDigits2: inc DecExponent jns PutTheDigit ; ; If the exponent value is still negative, output zeros because we've yet ; to reach the beginning of the number. ; PutZero2: mov al, '0' stosb jmp TestDone2 ; PutTheDigit: cmp ah, 18 ;If more than 18 digits so far, just jae PutZero2 ; output zeros. ; mov al, [bx] inc bx stosb ; TestDone2: inc ah dec ch jnz PutDigits2 mov byte ptr es:[di], 0 jmp ftoaDone ; ; ; Okay, we've got a positive exponent here. First, let's adjust the field ; width value (in CH) so that it includes the sign and possible decimal point. ; PositiveExp: mov dx, DecExponent ;Get actual # of digits to left of "." inc dx ;Allow for sign and the fact that there inc dx ; is always one digit to left of ".". cmp ch, 0 ;# of chars after "." = 0? je NoDecPt add dl, ch ;Add in number of chars after "." adc dh, 0 inc dx ;Make room for "." NoDecPt: ; ; ; Make sure the field width is bigger than the number of decimal places to ; print. ; cmp cl, ch jb BadFieldWidth ; ; ; Okay, now see if the user is trying to print a value which is too large ; to fit in the given field width: ; cmp dh, 0 jne BadFieldWidth ;Sorry, no output >= 256 chars. cmp dl, cl ;Need field width > specified FW? jbe GoodFieldWidth ; ; If we get down here, then we've got a number which will not fit in the ; specified field width. Fill the string with #'s (sorta like FORTRAN). ; BadFieldWidth: mov ch, 0 ;Set CX=field width. mov al, "#" rep stosb mov byte ptr es:[di], 0 jmp ftoaDone ; ; ; Print any necessary spaces in front of the number. ; GoodFieldWidth: call PutSpaces ; ; Output the sign character (" " or "-"): ; mov al, DecSign stosb ; ; Okay, output the digits for this number here. ; mov ah, 0 ;Counts off output characters. lea bx, stdgrp:DecDigits ;Pointer to digit string. mov cl, ch ;CX := # of chars after "." mov ch, 0 ; plus number of characters before add cx, DecExponent ; the ".". inc cx ;Always at least one digit before "." OutputLp: cmp ah, 18 ;Exceeded 18 digits? jae PutZeros mov al, [bx] inc bx jmp PutChar ; PutZeros: mov al, '0' PutChar: stosb cmp DecExponent, 0 jne DontPutPoint mov al, '.' stosb ; DontPutPoint: dec DecExponent inc ah loop OutputLp mov byte ptr es:[di], 0 ;Output the zero byte. ; ftoaDone: pop word ptr fpacc.Sign pop fpacc.Exponent pop fpacc.Mantissa [6] pop fpacc.Mantissa [4] pop fpacc.Mantissa [2] pop fpacc.Mantissa [0] pop si pop dx pop cx pop bx pop ax pop ds popf ret sl_ftoa2 endp ; ; ; ; ; Okay, now we need to insert any necessary leading spaces. We need to ; put (FieldWidth - ActualWidth) spaces before the string of digits. ; PutSpaces proc near cmp dl, cl ;See if print width >= field width jae NoSpaces mov ah, cl sub ah, dl ;Compute # of spaces to print. mov al, ' ' PSLp: stosb dec ah jnz PSLp NoSpaces: ret PutSpaces endp ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; SL_ETOA- Converts value in FPACC to exponential form. AL contains ; the number of print positions. ES:DI points to the array ; which will hold this string (it must be at least AL+1 chars ; long). ; ; The output string takes the format: ; ; {" "|-} [0-9] "." [0-9]* "E" [+|-] [0-9]{2,4} ; ; (The term "[0-9]{2,4}" means either two or four digits) ; ; AL must be at least eight or this code outputs #s. ; public sl_etoa sl_etoa proc far push di call far ptr sl_etoa2 pop di ret sl_etoa endp ; ; public sl_etoa2 sl_etoa2 proc far assume ds:StdGrp ; pushf push ds push ax push bx push cx push si ; cld mov bx, StdGrp mov ds, bx ; push fpacc.Mantissa [0] push fpacc.Mantissa [2] push fpacc.Mantissa [4] push fpacc.Mantissa [6] push fpacc.Exponent push word ptr fpacc.Sign ; call fpdigits ; ; See if we have sufficient room for the number- ; mov ah, 0 mov cx, ax ; ; Okay, take out spots for sign, ".", "E", sign, and at least four exponent ; digits and the exponent's sign: ; Subtract2: sub ax, 8 jc BadEWidth jnz DoTheRound ;Make sure at least 1 digit left! ; BadEWidth: mov ch, 0 mov al, "#" rep stosb mov al, 0 stosb jmp etoaDone ; ; Round the number to the specified number of places. ; DoTheRound: mov ch, al ;# of decimal places is # of posns. mov bl, ch ;Compute position where we should start mov bh, 0 ; the rounding. cmp bx, 18 ;Don't bother rounding if we have jae eRoundDone ; more than 18 digits here. ; ; Add 5 to the digit after the last digit we want to print. Then propogate ; any overflow through the remaining digits. ; mov al, DecDigits [bx] add al, 5 mov DecDigits [bx], al cmp al, "9" jbe eRoundDone sub DecDigits [bx], 10 eRoundLoop: dec bx js eFirstDigit inc DecDigits[bx] cmp DecDigits[bx], "9" jbe eRoundDone sub DecDigits[bx], 10 jmp eRoundLoop ; ; If we hit the first digit in the string, we've got to shift all the ; characters down one position and put a "1" in the first character ; position. ; eFirstDigit: mov bl, ch mov bh, 0 inc bx eFDLp: mov al, byte ptr DecDigits[bx-1] mov DecDigits [bx], al dec bx jnz eFDLp mov DecDigits, "1" inc DecExponent ;Cause we just added a digit. ; eRoundDone: ; ; Okay, output the value here. ; mov cl, ch ;Set CX=Number of output chars mov ch, 0 mov al, DecSign stosb lea si, stdgrp:DecDigits movsb ;Output first char. dec cx ;See if we're done! jz PutExponent ; ; Output the fractional part here ; mov al, "." stosb mov ah, 17 ;Max # of chars to output. PutFractional: cmp ah, 0 jz NoMoreDigs movsb dec ah jmp NextFraction ; ; If we've output more than 18 digits, just output zeros. ; NoMoreDigs: mov al, "0" stosb ; NextFraction: loop PutFractional PutExponent: mov al, "E" stosb mov al, "+" cmp DecExponent, 0 jge NoNegExp mov al, "-" neg DecExponent ; NoNegExp: stosb mov ax, DecExponent cwd ;Sets DX := 0. mov cx, 1000 div cx or al, "0" stosb ;Output 1000's digit xchg ax, dx cwd mov cx, 100 div cx or al, "0" ;Output 100's digit stosb xchg ax, dx cwd mov cx, 10 div cx or al, "0" ;Output 10's digit stosb xchg ax, dx or al, "0" ;Output 1's digit stosb mov byte ptr es:[di], 0 ;Output zero byte. ; etoaDone: pop word ptr fpacc.Sign pop fpacc.Exponent pop fpacc.Mantissa [6] pop fpacc.Mantissa [4] pop fpacc.Mantissa [2] pop fpacc.Mantissa [0] pop si pop cx pop bx pop ax pop ds popf ret sl_etoa2 endp ; ; ; ; ; ; FPDigits- Converts the floating point number in FPACC to a string of ; digits (in DecDigits), an integer exponent value (DecExp), ; and a sign character (DecSign). The decimal point is assumed ; to be between the first and second characters in the string. ; FPDigits proc near assume ds:StdGrp push ds push ax push bx push cx push dx push di push si ; mov ax, seg StdGrp mov ds, ax ; ; First things first, see if this value is zero: ; mov ax, fpacc.Mantissa [0] or ax, fpacc.Mantissa [2] or ax, fpacc.Mantissa [4] or ax, fpacc.Mantissa [6] jnz fpdNotZero ; ; Well, it's zero. Handle this as a special case: ; mov ax, 3030h ;"00" mov word ptr DecDigits[0], ax mov word ptr DecDigits[2], ax mov word ptr DecDigits[4], ax mov word ptr DecDigits[6], ax mov word ptr DecDigits[8], ax mov word ptr DecDigits[10], ax mov word ptr DecDigits[12], ax mov word ptr DecDigits[14], ax mov word ptr DecDigits[16], ax mov word ptr DecDigits[18], ax mov word ptr DecDigits[20], ax mov word ptr DecDigits[22], ax mov DecExponent, 0 mov DecSign, ' ' jmp fpdDone ; ; If the number is not zero, first fix up the sign: ; fpdNotZero: mov DecSign, ' ' ;Assume it's postive cmp fpacc.Sign, 0 jns WasPositive mov DecSign, '-' mov fpacc.Sign, 0 ;Take ABS(fpacc). ; ; This conversion routine is fairly standard. See Neil Graham's ; "Microprocessor Programming for Computer Hobbyists" for the gruesome ; details. Basically, it first gets the number between 1 & 10 by successively ; multiplying (or dividing) by ten. For each multiply by 10 this code ; decrements DecExponent by one. For each division by ten this code ; increments DecExponent by one. Upon getting the value between 1 & 10 ; DecExponent contains the integer equivalent of the exponent. The ; following code does this. ; ; Note: if the value falls between 1 & 10, then the exponent portion of ; fpacc will lie between 7fffh and 8002h. ; WasPositive: mov DecExponent, 0 ;Initialize exponent. ; ; Quick test to see if we're already less than 10. ; WhlBgrThan10: cmp fpacc.Exponent, 8002h ;See if fpacc > 10 jb WhlLessThan1 ja IsGtrThan10 ; ; If the exponent is equal to 8002h, then we could have a number in the ; range 8 <= n < 16. Let's ignore values less than 10. ; cmp byte ptr fpacc.Mantissa [7], 0a0h jb WhlLessThan1 ; ; If it's bigger than ten we could perform successive divisions by ten. ; This, however, would be slow, inaccurate, and disgusting. The following ; loop skips through the positive powers of ten (PotTblP) until it finds ; someone with an exponent *less* than fpacc. Upon finding such a value, ; this code divides fpacc by the corresponding entry in PotTblN. This is ; equivalent to *dividing* by the entry in PotTblP. Note: this code only ; compares exponents. Therefore, it is quite possible that we will divide ; by a number slightly larger than fpacc (since the mantissa of the table ; entry could be larger than the mantissa of fpacc while their exponents ; are equal). This will produce a result slightly less than one. This is ; okay in this case because the code which handles values between 0 & 1 ; follows and will correct this oversight. ; IsGtrThan10: mov bx, -13 ;Index into PotTblP mov ax, fpacc.Exponent WhlBgrLp1: add bx, 13 cmp ax, PotTblP [bx] + 8 ;Compare exponent values. jb WhlBgrLp1 ;Go to next entry if less. ; Okay, we found the first table entry whose exponent is less than or ; equal to the fpacc exponent. Multiply by the corresonding PotTblN ; value here (which simulates a divide). call nTbl2FPOP mov ax, PotTblP [bx] + 11 ;Adjust DecExponent add DecExponent, ax call sl_fMUL ;Divide by appropriate power. mov ax, fpacc.Exponent cmp ax, 8002h ;See if fpacc > 10 ja WhlBgrLp1 jb WhlLessThan1 ; If the exponent is equal to 8002h, then we could have a number in the ; range 8 <= n < 16. Let's ignore values less than 10. cmp byte ptr fpacc.Mantissa [7], 0a0h jae WhlBgrLp1 ; Once we get the number below 10 (or if it was below 10 to begin with, ; drop down here and boost it up to the point where it is >= 1. ; ; This code is similar to the above- It successively multiplies by 10 ; (actually, powers of ten) until the number is in the range 1..10. ; This code is not as sloppy as the code above because we don't have any ; code below this to clean up the sloppiness. Indeed, this code has to ; be careful because it is cleaning up the sloppiness of the code above. ; ; WhlLessThan1: cmp fpacc.Exponent, 7fffh ;See if fpacc < 1 jae NotLessThan1 ; mov bx, -13 ;Index into PotTblN WhlLessLp2: mov ax, fpacc.Exponent WhlLessLp1: add bx, 13 cmp ax, PotTblN [bx] + 8 ;Compare exponent values. ja WhlLessLp1 ;Go to next entry if less. ; ; Okay, we found the first table entry whose exponent is greater than or ; equal to the fpacc exponent. Unlike the code above, we cannot simply ; multiply by the corresponding entry in PotTblP at this point. If the ; exponents were equal, we need to compare the mantissas and make sure we're ; not multiplying by a table entry which is too large. ; jne OkayToMultiply ; ; If the exponents are the same, we need to compare the mantissas. The ; table entry cannot be larger than fpacc; if it is, we'll wind up with ; an endless loop oscillating between a couple of values. ; mov ax, fpacc.Mantissa [6] cmp ax, PotTblN [bx] + 6 ja WhlLessLp2 jb OkayToMultiply mov ax, fpacc.Mantissa [4] cmp ax, PotTblN [bx] + 4 ja WhlLessLp2 jb OkayToMultiply mov ax, fpacc.Mantissa [2] cmp ax, PotTblN [bx] + 2 ja WhlLessLp2 jb OkayToMultiply mov ax, fpacc.Mantissa [0] cmp ax, PotTblN [bx] ja WhlLessLp2 ; ; OkayToMultiply: call pTbl2FPOP mov ax, PotTblN [bx] + 11 ;Adjust DecExponent add DecExponent, ax call sl_fMUL ;Multiply by appropriate power. jmp WhlLessThan1 ;Repeat till in range 1..10. ; ; ; The above code tries to get fpacc in the range 1 <= n < 10. ; However, it doesn't quite accomplish this. In fact, it gets the value ; into the range 1 <= n < 16. This next section checks to see if the value ; is greater than ten. If it is, it does one more division by ten. ; NotLessThan1: cmp fpacc.Exponent, 8002h ;10..15 only if exp = 8002h. jb Not10_15 ; ; For fpacc to be in the range 10..15 the mantissa must be greater than or ; equal to 0A000 0000 0000 0000. ; cmp byte ptr fpacc.Mantissa [7], 0a0h jb Not10_15 ; ; Okay, the mantissa is greater than or equal to ten. Divide by ten once ; more to fix this up. ; lea bx, stdgrp:Div10Value sub bx, offset stdgrp:PotTblN call pTbl2FPOP call sl_fMUL ;Multiply by appropriate power. inc DecExponent ; ; Well, we've managed to compute the decimal exponent value and normalize ; the number to the range 1 <= n < 10. ; ; Make sure the upper four bits contain a BCD value. This may entail ; shifting data to the right. ; Not10_15: mov si, fpacc.Mantissa [0] ;We'll use these a lot, so mov di, fpacc.Mantissa [2] ; put them into registers. mov cx, fpacc.Mantissa [4] mov dx, fpacc.Mantissa [6] SHRLp: cmp fpacc.Exponent, 8002h jae PossiblyRound shr dx, 1 rcr cx, 1 rcr di, 1 rcr si, 1 inc fpacc.Exponent jmp SHRLp ; ; May have to round the number if we wound up with a value between 10..15. ; ; Note: 0.5 e -18 is 7fc5 b8xxxxxxxx... If we adjust this value so that ; the exponent is 7fffh, we keep only the top five bits (10111). The ; following code adds this value (17h) to the mantiss to round as ; appropriate. ; PossiblyRound: add si, 2h jnc ChkTooBig inc di jnz ChkTooBig inc cx jnz ChkTooBig inc dx ; ; If we fall through to this point, it's quite possible that we will produce ; a value greater than or equal to ten. Handle that possibility here. ; ChkTooBig: cmp dh, 0a0h jb NoOvrflw ; ; Well, overflow occurred, clean it up. ; xor ax, ax mov si, ax mov di, ax mov cx, ax mov dx, 1000h inc DecExponent ; ; Finally! We're at the point where we can start stripping off the ; digits from the number ; NoOvrflw: lea bx, stdgrp:DecDigits xor ax, ax ; StripDigits: mov al, dh shr ax, 1 shr ax, 1 shr ax, 1 shr ax, 1 or al, '0' mov [bx], al inc bx cmp bx, offset stdgrp:DecDigits+18 jae fpdDone ; ; Remove the digit we just stripped: ; and dh, 0fh ; ; Multiply the mantissa by ten (using shifts and adds): ; shl si, 1 rcl di, 1 rcl cx, 1 rcl dx, 1 mov fpacc.Mantissa [0], si ;Save *2 mov fpacc.Mantissa [2], di mov fpacc.Mantissa [4], cx mov fpacc.Mantissa [6], dx ; shl si, 1 ;*4 rcl di, 1 rcl cx, 1 rcl dx, 1 ; shl si, 1 ;*8 rcl di, 1 rcl cx, 1 rcl dx, 1 ; add si, fpacc.Mantissa [0] ;*10 adc di, fpacc.Mantissa [2] adc cx, fpacc.Mantissa [4] adc dx, fpacc.Mantissa [6] jmp StripDigits ; fpdDone: pop si pop di pop dx pop cx pop bx pop ax pop ds ret FPDigits endp ; ; ; ; nTbl2FPOP- BX is an index into PotTbln. This routine fetches the entry ; at that index and copies it into FPOP. ; nTbl2FPOP proc near mov ax, PotTbln [bx] + 8 mov fpop.Exponent, ax mov ax, PotTbln [bx] mov fpop.Mantissa [0], ax mov ax, PotTbln [bx] + 2 mov fpop.Mantissa [2], ax mov ax, PotTbln [bx] + 4 mov fpop.Mantissa [4], ax mov ax, PotTbln [bx] + 6 mov fpop.Mantissa [6], ax mov fpop.Sign, 0 ;All entries are positive. ret nTbl2FPOP endp ; ; pTbl2FPOP- Same as above except the data comes from PotTblP. ; pTbl2FPOP proc near mov ax, PotTblp [bx] + 8 cmp ax, 7fffh jne DoPTFPOP sub bx, 13 ;Special case if we hit 1.0 mov ax, PotTblp [bx] + 8 ; DoPTFPOP: mov fpop.Exponent, ax mov ax, PotTblp [bx] mov fpop.Mantissa [0], ax mov ax, PotTblp [bx] + 2 mov fpop.Mantissa [2], ax mov ax, PotTblp [bx] + 4 mov fpop.Mantissa [4], ax mov ax, PotTblp [bx] + 6 mov fpop.Mantissa [6], ax mov fpop.Sign, 0 ;All entries are positive. ret pTbl2FPOP endp ; ; ; ; ; ;---------------------------------------------------------------------------- ; Text => Floating Point (Input) Conversion Routines ;---------------------------------------------------------------------------- ; ; ; ATOF- ES:DI points at a string containing (hopefully) a numeric ; value in floating point format. This routine converts that ; value to a number and puts the result in fpacc. Allowable ; strings are described by the following regular expression: ; ; {" "}* {+ | -} ( ([0-9]+ {"." [0-9]*}) | ("." [0-9]+)} ; {(e | E) {+ | -} [0-9] {[0-9]*}} ; ; "{}" denote optional items. ; "|" denotes OR. ; "()" groups items together. ; ; shl64 macro shl bx, 1 rcl cx, 1 rcl dx, 1 rcl si, 1 endm ; public sl_ATOF sl_ATOF proc far assume ds:StdGrp, es:nothing ; push ds push ax push bx push cx push dx push si push di push bp ; mov ax, StdGrp mov ds, ax ; ; ; First, skip any leading spaces: ; mov ah, " " SkipBlanks: mov al, es:[di] inc di cmp al, ah je SkipBlanks ; ; Check for + or -. ; cmp al, "-" jne TryPlusSign mov fpacc.Sign, 80h jmp EatSignChar ; TryPlusSign: mov fpacc.Sign, 0 ;If not "-", then positive. cmp al, "+" jne NotASign EatSignChar: mov al, es:[di] ;Get char beyond sign inc di ; ; Init some important local vars: ; Note: BP contains the number of significant digits processed thus far. ; NotASign: mov DecExponent, 0 xor bx, bx ;Init 64 bit result. mov cx, bx mov dx, bx mov si, bx mov bp, bx mov ah, bh ; ; First, eliminate any leading zeros (which do not count as significant ; digits): ; Eliminate0s: cmp al, "0" jne EndOfZeros mov al, es:[di] inc di jmp Eliminate0s ; ; When we reach the end of the leading zeros, first check for a decimal ; point. If the number is of the form "0---0.0000" we need to get rid ; of the zeros after the decimal point and not count them as significant ; digits. ; EndOfZeros: cmp al, "." jne WhileDigits ; ; Okay, the number is of the form ".xxxxx". Strip all zeros immediately ; after the decimal point. ; Right0s: mov al, es:[di] inc di cmp al, "0" jne FractionPart dec DecExponent ;Not significant digit, but jmp Right0s ; affects exponent. ; ; ; If the number is of the form "yyy.xxxx" (where y <> 0) then process it ; down here. ; WhileDigits: sub al, "0" cmp al, 10 jae NotADigit ; ; See if we've processed more than 19 sigificant digits: ; cmp bp, 19 ;Too many significant digits? jae DontMergeDig ; ; Multiply value in (si, dx, cx, bx) by ten: ; shl64 mov fpacc.Mantissa [0], bx mov fpacc.Mantissa [2], cx mov fpacc.Mantissa [4], dx mov fpacc.Mantissa [6], si shl64 shl64 add bx, fpacc.Mantissa [0] adc cx, fpacc.Mantissa [2] adc dx, fpacc.Mantissa [4] adc si, fpacc.Mantissa [6] ; ; Add in current digit: ; add bx, ax jnc GetNextDig inc cx jne GetNextDig inc dx jne GetNextDig inc si jmp GetNextDig ; DontMergeDig: inc DecExponent GetNextDig: inc bp ;Yet another significant dig. mov al, es:[di] inc di jmp WhileDigits ; ; ; Check to see if there is a decimal point here: ; NotADigit: cmp al, "."-"0" jne NotADecPt mov al, es:[di] inc di ; ; Okay, process the digits to the right of the decimal point here. ; FractionPart: sub al, "0" cmp al, 10 jae NotADecPt ; ; See if we've processed more than 19 sigificant digits: ; cmp bp, 19 ;Too many significant digits? jae DontMergeDig2 ; ; Multiply value in (si, dx, cx, bx) by ten: ; dec DecExponent ;Raise by a power of ten. shl64 mov fpacc.Mantissa [0], bx mov fpacc.Mantissa [2], cx mov fpacc.Mantissa [4], dx mov fpacc.Mantissa [6], si shl64 shl64 add bx, fpacc.Mantissa [0] adc cx, fpacc.Mantissa [2] adc dx, fpacc.Mantissa [4] adc si, fpacc.Mantissa [6] ; ; Add in current digit: ; add bx, ax jnc DontMergeDig2 inc cx jne DontMergeDig2 inc dx jne DontMergeDig2 inc si ; DontMergeDig2: inc bp ;Yet another significant dig. mov al, es:[di] inc di jmp FractionPart ; ; Process the exponent down here ; NotADecPt: cmp al, "e"-"0" je IsExponent cmp al, "E"-"0" jne NormalizeInput ; ; Okay, we just saw the "E" character, now read in the exponent value ; and add it into DecExponent. ; IsExponent: mov ExpSign, 0 ;Assume positive exponent. mov al, es:[di] inc di cmp al, "+" je EatExpSign cmp al, "-" jne ExpNotNeg mov ExpSign, 1 ;Exponent is negative. EatExpSign: mov al, es:[di] inc di ExpNotNeg: xor bp, bp ExpDigits: sub al, '0' cmp al, 10 jae EndOfExponent shl bp, 1 mov TempExp, bp shl bp, 1 shl bp, 1 add bp, TempExp add bp, ax mov al, es:[di] inc di jmp ExpDigits ; EndOfExponent: cmp ExpSign, 0 je PosExp neg bp PosExp: add DecExponent, bp ; ; Normalize the number here: ; NormalizeInput: mov ax, si ;See if they entered zero. or ax, bx or ax, cx or ax, dx jnz ItsNotZero jmp ItsZero ; ItsNotZero: mov ax, si mov si, 7fffh+63 ;Exponent if already nrm'd. NrmInp16: or ax, ax ;See if we can shift 16 bits. jnz NrmInp8 mov ax, dx mov dx, cx mov cx, bx xor bx, bx sub si, 16 jmp NrmInp16 ; NrmInp8: cmp ah, 0 jne NrmInp1 mov ah, al mov al, dh mov dh, dl mov dl, ch mov ch, cl mov cl, bh mov bh, bl mov bl, 0 sub si, 8 ; NrmInp1: cmp ah, 80h jae NrmDone shl bx, 1 rcl cx, 1 rcl dx, 1 rcl ax, 1 dec si jmp NrmInp1 ; ; Okay, the number is normalized. Now multiply by 10 the number of times ; specified in DecExponent. Obviously, this uses the power of ten tables ; to speed up this operation (and make it more accurate). ; NrmDone: mov fpacc.Exponent, si ;Save away the value so far. mov fpacc.Mantissa [0], bx mov fpacc.Mantissa [2], cx mov fpacc.Mantissa [4], dx mov fpacc.Mantissa [6], ax ; mov bx, -13 ;Index into POT table. mov si, DecExponent or si, si ;See if negative js NegExpLp ; ; Okay, the exponent is positive, handle that down here. ; PosExpLp: add bx, 13 ;Find the 1st power of ten cmp si, PotTblP [bx] + 11 ; in the table which is jb PosExpLp ; just less than this guy. cmp PotTblP [bx] + 8, 7fffh ;Hit 1.0 yet? je MulExpDone ; sub si, PotTblP [bx] + 11 ;Fix for the next time through. call PTbl2FPOP ;Load up current power of ten. call sl_FMUL ;Multiply by this guy. jmp PosExpLp ; ; ; Okay, the exponent is negative, handle that down here. ; NegExpLp: add bx, 13 ;Find the 1st power of ten cmp si, PotTblN [bx] + 11 ; in the table which is jg NegExpLp ; just less than this guy. cmp PotTblN [bx] + 8, 7fffh ;Hit 1.0 yet? je MulExpDone ; sub si, PotTblN [bx] + 11 ;Fix for the next time through. call NTbl2FPOP ;Load up current power of ten. call sl_FMUL ;Multiply by this guy. jmp NegExpLp ; ; If the user entered zero, drop down here and zero out fpacc. ; ItsZero: xor ax, ax mov fpacc.Exponent, ax mov fpacc.Sign, al mov fpacc.Mantissa [0], ax mov fpacc.Mantissa [2], ax mov fpacc.Mantissa [4], ax mov fpacc.Mantissa [6], ax ; ; Round the result to produce a *halfway* decent number ; MulExpDone: cmp fpacc.Exponent, 0ffffh ;Don't round if too big. je atofDone shl byte ptr fpacc.Mantissa, 1 ;Use L.O. bits as guard adc byte ptr fpacc.Mantissa [1], 0 ; bits. jnc atofDone inc fpacc.Mantissa[2] jne atofDone inc fpacc.Mantissa[4] jne atofDone inc fpacc.Mantissa[6] jne atofDone inc fpacc.Exponent ; atofDone: mov byte ptr fpacc.Mantissa, 0 pop bp pop di pop si pop dx pop cx pop bx pop ax pop ds ret sl_ATOF endp ; ; stdlib ends end