Advanced Techniques

SHORTCUTS TO "FLOATING" IN MACHINE LANGUAGE

by Chris Brenner

Reprinted from Commodore World Issue #5



The machine language programmer is often faced with the prospect of using floating point math. This can take time and will ultimately add to the size of the program being developed. In some cases, a problem which seems to require floating point math can be solved by using integer math. Take the example of calculating the percentage of a variable. This can be handled with integer math simply by multiplying the variable by the percent value, and then dividing that result by one hundred. There are, however, many situations where integer math cannot be used efficiently. This leaves us with a couple of options. We can develop our own floating point routines, a laborious process, or we can use the floating point routines built in to the BASIC ROM. This is a desirable approach since not only will this keep the size of the code down, but it will take less time to develop a program using this method.

The BASIC ROM in the C-64 contains a fairly complete library of floating point routines. There are, however, some pitfalls when trying to access these routines directly from machine language. In this article we will look at some of the more common floating point routines, and things to watch for when calling these routines from a machine language program.

Before we get started, a quick overview is in order. There are two areas in zero page memory defined as floating point registers. These are known as floating point accumulator 1 and floating point accumulator 2. Throughout this article, these will be referred to as register 1 and register 2. Both of these registers are comprised of a one byte exponent followed by a four byte mantissa followed by a one byte sign. Most of the floating point math routines use these two registers, and return the result of the operation in register 1. Almost all of the conversion routines use register 1. There are only a couple that deal with register 2.

Change Is Inevitable
The first thing we'll look at is how to put floating point values into the registers. Let's start with integer conversion. There are a number of ways to convert an integer to floating point. The first method we'll examine is the conversion of an eight bit integer. This is very easy, we just load the A register with the number to be converted, and then call the conversion routine. The floating point value is placed into register 1.

ByteToFloat    =    $BC3C
               lda  #$07
               jsr  ByteToFloat
               rts
There is a similar routine available to convert a sixteen bit integer to floating point. This routine uses the A and Y registers to pass the sixteen bit number. As before, the floating point value is placed into register 1.
WordToFloat    =    $B391
Value          =    $1234
               ldy  #?
               lda  #?
               jsr  WordToFloat
               rts
Here's where things can get tricky. Both of the previous routines treat the integers as signed numbers. If we pass an integer that we are treating as an unsigned number, and it just so happens that the high bit is set, the conversion routine will happily place a negative floating point value into register 1. This can produce unexpected results, not to mention the urge to throw the computer through the window because the program doesn't work right. The solution is to convert the integer as an unsigned number. This requires a little more work, but can save us the trouble of tracking down the source of the problem, not to mention a costly window replacement. In this example, we'll convert the unsigned value $9FFF to floating point. This method requires the unsigned sixteen bit value to first be written into the first two bytes of the mantissa for register 1. Note that the high byte is stored before the low byte. This differs from the standard 6502 little endian byte ordering. Next, the X register must be loaded with the exponent for the sixteen bit number. Setting the exponent to #$90 places the binary point to the right of the least significant bit of this integer, making it a whole number. And finally, the carry must be set so that our integer will be treated as an unsigned number.
UWordToFloat   =    $BC49
Mantissa1      =    $62
Value          =    $9FFF
               lda  #?
               sta  Mantissa1
               lda  #?
               sta  Mantissa1+1
               ldx  #$90
               sec
               jsr  UWordToFloat
               rts
Telling It Like It Is
So far we've looked at how to convert an eight or sixteen bit integer to floating point. Now, let's put this knowledge to practical use. One of the most common problems facing the machine language programmer is turning a binary value into a decimal ASCII string. Fortunately, we have some floating point routines at our disposal to make this an easy process. The only problem facing us here is that these routines trash the floating point registers. We will deal with this later. This example converts the hexadecimal number $64 to the ASCII string 100 and prints it to the screen. The Kernal CHROUT routine is then used to print a carriage return.
ByteToFloat    =    $BC3C
FloatPrint     =    $BDD7
CHROUT         =    $FFD2
               lda  #$64
               jsr  ByteToFloat
               jsr  FloatPrint
               lda  #$0D
               jsr  CHROUT
               rts
We probably won't always want to print the result to the screen, so there's another routine available which places the string in memory at $0100.
ByteToFloat    =    $BC3C
FloatToString  =    $BDDD
               lda  #$64
               jsr  ByteToFloat
               jsr  FloatToString
               rts
Up until now we have been dealing with whole numbers only. Of course, this will not always be the case. As you may have guessed, there is a way to convert an ASCII string to floating point. This null terminated string can be a fractional number, and can even be in scientific notation. The StringToFloat routine uses the CHRGET routine to parse the ASCII string. We must do some preliminary work, which involves setting the CHRGOT pointer to the first character in our string, and then entering at CHRGOT to retrieve the first character. As usual, the floating point value is placed into register 1.
CHRGOT         =    $79
StringToFloat  =    $BCF3
               lda  #?
               sta  CHRGOT+1
               lda  #?
               sta  CHRGOT+2
               jsr  CHRGOT
               jsr  StringToFloat
               rts
String         .byte "37.321",0
Save It For A Rainy Day
There are times when we will want to save the contents of a floating point register, so that we can use that number later on. A good example is with the previously described binary to ASCII conversion routines. Here we can see how to save register 1 to memory.
Float1ToMem    =    $BBD4
               ldx  #?
               ldy  #?
               jsr  Float1ToMem
               rts
Save1          .byte  0,0,0,0,0,0,0,0
There is no routine available to transfer register 2 to memory. If we want to save register 2, we must first copy it's contents to register 1 and then transfer register 1 to memory.
Float2ToFloat1 =    $BBFC
Float1ToMem    =    $BBD4
               jsr  Float2ToFloat1
               ldx  #?
               ldy  #?
               jsr  Float1ToMem
               rts
Save2          .byte 0,0,0,0,0,0,0,0
When it comes time to restore the floating point registers, we can transfer these saved values back from memory. This time we have a separate routine for register 2.
MemToFloat1    =    $BBA2
MemToFloat2    =    $BA8C
               lda  #?
               ldy  #?
               jsr  MemToFloat1
               lda  #?
               ldy  #?
               jsr  MemToFloat2
               rts
The Way We Were
Eventually, we will want to put a floating point number back into integer form so that we may deal with it in machine language. Depending upon the situation, we may also wish to round this value to the nearest whole number. The integer is returned in the mantissa of register 1. Remember though, the floating point mantissa uses big endian byte ordering. We will have to scan the four bytes of the mantissa from the highest address down to obtain the correct value.
RoundFloat     =    $BE2F
FloatToInt     =    $BC9B
               jsr  RoundFloat
               jsr  FloatToInt
               rts
Cool And Calculating
Putting numbers in the floating point registers is pretty easy, but what about performing floating point math. As it turns out, this is also easy. The floating point math routines use both floating point registers as arguments for the function, and require that the A register contain the current value held in the exponent of register 1. The trick here is to use the floating point registers in the proper order. No need to worry about this for addition or multiplication due to the associative nature of these two operations. Subtraction and division, however, do make a difference. Let's first look at an example of floating point addition. The following code performs the operation 3+2 and places the result into register 1.
Float1Exp      =    $61
ByteToFloat    =    $BC3C
Float1ToFloat2 =    $BC0C
FloatAdd       =    $B86A
               lda  #$03
               jsr  ByteToFloat
               jsr  Float1ToFloat2
               lda  #$02
               jsr  ByteToFloat
               lda  Float1Exp
               jsr  FloatAdd
               rts
Floating point subtraction is handled pretty much the same way as floating point addition. The only difference between the two is the order in which the operation occurs. Register 1 is always subtracted from register 2. In this example, we can see how to code the statement 3-2.
Float1Exp      =    $61
ByteToFloat    =    $BC3C
Float1ToFloat2 =    $BC0C
FloatSub       =    $B853
               lda  #$03
               jsr  ByteToFloat
               jsr  Float1ToFloat2
               lda  #$02
               jsr  ByteToFloat
               lda  Float1Exp
               jsr  FloatSub
               rts
Well that was fun, let's do some more. How about a simple electronics problem. Let's calculate the DC current for a 5 volt drop across a 4.7K resistor. The formula is I=E/R where E is the DC voltage, R is the resistance, and I is the current.
Float1Exp      =    $61
ByteToFloat    =    $BC3C
WordToFloat    =    $B391
Float1ToFloat2 =    $BC0C
FloatDiv       =    $BB12
E              =    5
R              =    4700
               lda  #E
               jsr  ByteToFloat
               jsr  Float1ToFloat2
               ldy  #?
               lda  #?
               jsr  WordToFloat
               lda  Float1Exp
               jsr  FloatDiv
At this point, register 1 contains the calculated current flow through the resistor, which turns out to be 1.06382979E-03 amperes. We can turn this into something a bit more readable by converting to milliamperes. This is accomplished by multiplying by 1000, after which register 1 contains the calculated current flow in the form 1.06382979 milliamperes.
FloatMult      =    $BA2B
Multiplier     =    1000
               jsr  Float1ToFloat2
               ldy  #?
               lda  #?
               jsr  WordToFloat
               lda  Float1Exp
               jsr  FloatMult
               rts
That covers the four basic arithmetic operations. In this final example, we will take a look at how to perform exponentiation. As with subtraction and division, we need to make sure we use the floating point registers in the proper order. The following code will perform the operation 3^2.
Float1Exp      =    $61
ByteToFloat    =    $BC3C
Float1ToFloat2 =    $BC0C
FloatExpon     =    $BF7B
               lda  #$03
               jsr  ByteToFloat
               jsr  Float1ToFloat2
               lda  #$02
               jsr  ByteToFloat
               lda  Float1Exp
               jsr  FloatExpon
               rts
The End Of The Rainbow
We've covered only a small part of what's available; there's much more to be explored. None the less, these routines will be a welcome addition to any machine language programmer's collection.


[Sample Issue] [CMDRKEY.com Home]


Copyright © 2002 Click Here Software Co.
Comments and questions regarding this site
should be directed to support@cmdrkey.com