Home > Mobile >  How to create floating point functions without a coprocessor
How to create floating point functions without a coprocessor

Time:06-12

I've been trying to create a library for the Motorola 68000 (the original, without a floating point coprocessor) that can convert a binary32 floating point value to text that can be displayed on-screen. I was using this website which converts floats to hex to get a breakdown of the logic. Here's my output on that website with the test value 0x3E400000:

0x3e400000
  3      e       4       0      0       0       0        0
0011   1110    0100    0000    0000    0000    0000    0000
0   01111100    10000000000000000000000
sign    exponent    mantissa
 1  124 1.10000000000000000000000 (binary)
 1 *    2^(124 - 127) * 1.5
 1 *    0.125000000 *   1.5

The thing is, this all seems like circular logic. Without a floating point coprocessor how would you calculate and store 2^-3 or 1.5? Wouldn't each of those need to be run through the same routine as your input, and so on?

This is what I have in my assembly code if it helps. If there are instructions you don't recognize, I've created macros to help me out and I've explained them in the comments. Maybe this will make what I'm trying to do clearer.

Float2Text:
    ;input: d0
    ;clobbers d1
    pushRegs d2-d7  ;MOVEM.L D2-D7,-(SP)
    MOVEQ.L #0,D7
    MOVEQ.L #0,D6
    MOVEQ.L #0,D5
    MOVEQ.L #0,D4
    
    
    CLX             ;ANDI #001111,CCR
    MOVE.L D0,D2
    ROXL.L D0
    ROXL.L D7       ;TRANSFER SIGN BIT INTO D7
    
    SWAP D0
    ROR.W #8,D0     ;GET EXPONENT INTO BOTTOM BYTE
    CMP.B #0,D0
    BEQ .isZero
    CMP.B #111111,D0
    BEQ .isInfinity
    
    SUB.B #127,D0
    ;THIS IS THE UNBIASED EXPONENT OF 2
    
    ;NOW WE HAVE THE EXPONENT IN D0, AND THE SIGN BIT IN D7
    ;WE NEED TO SUM UP THE MANTISSA BITS.
    MOVE.B D0,D3
    MOVE.L D2,D0
    AND.L #000000011111111111111111111111,D0     ;CLEAR SIGN AND EXPONENT
    
    
    
.isZero:

.isInfinity:

.done
    popRegs d2-d7   ;MOVEM.L (SP) ,D2-D7
    RTS

CodePudding user response:

For Float2Text:

A floating point number is more accurately described as "base 2 floating point". E.g. the number 1.25 in binary is represented as 1.01b or 101b >> 2.

Your initial goal should be to convert "base 2 floating point" into "base 10 floating point". E.g. for "base 10 floating point" the number 1.25 will be the digits (characters?) 125 with the exponent of -2, like "125 * 10^(-2)". Once you have the number in "base 10 floating point" it becomes relatively easy to print (e.g. just print the digits while inserting the decimal point in the right place).

Start by splitting the "base 2 floating point" number into signed integer "base 2 mantissa" (while taking care of the "implied leading 1" if it's not a sub-normal) and a signed integer "base 2 exponent" (while taking care of the bias).

Next; if the "base 2 exponent" is negative, multiply the original number by 10 while decreasing the "base 10 exponent" to keep track. Note that you can multiply by 10 by multiplying "base 2 mantissa" by 5 and increasing "base 2 exponent". In the same way you can multiply by 100 by multiplying "base 2 mantissa" by 25 and adding 2 to "base 2 exponent"; or multiply by 1000 by multiplying "base 2 mantissa" by 125 and adding 3 to "base 2 exponent", etc. You can speed this up a lot by using the existing "base 2 exponent" to select the right multiplier; e.g. maybe something vaguely like:

    while(base2_exponent < 0) {
        switch(base2_exponent) {
            case -1: 
                base2_mantissa *= 5; base2_exponent -= 1;
                base10_exponent  = 1;
                break;
            case -2: 
                base2_mantissa *= 25; base2_exponent -= 2;
                base10_exponent  = 2;
                break;
            case -3: 
                base2_mantissa *= 125; base2_exponent -= 3;
                base10_exponent  = 3;
                break;
            default: 
                base2_mantissa *= 625; base2_exponent -= 4;
                base10_exponent  = 4;
                break;
            }
        }

This will cause a minor problem - for worst case (very small numbers); the "base 2 mantissa" can grow to become about 160 bits. To deal with this you have to use "big integers" (and you can't just do base2_mantissa *= 625; with a normal integer and will need something more like big_integer_multiply(&base2_mantissa, 625);).

Next; if the "base 2 exponent" is positive; multiply "base 2 mantissa" by 2 and decrease "base 2 exponent" (or in other words, shift "base 2 mantissa" left by the "base 2 exponent"). This has a similar problem - for worst case (very large numbers) the "base 2 mantissa" can grow to become about 160 bits.

At this point, "base 2 exponent" will be zero and can be ignored.

The next step is converting "base 2 mantissa" into a "base 10 mantissa". In theory this shouldn't be that hard - maybe something vaguely like while(base2_mantissa > 0) { *dest = base2_mantissa % 10 '0'; base2_mantissa /= 10; dest--; } except that you'll be using "big integer" (with 160 bit integers) for modulo and division. However; it's better to work from most significant digit to least significant digit. To do this, have a lookup table of powers of 10 (e.g. 1, 10, 100, 1000, 10000, ....), find the largest divisor that's not larger than the mantissa, then do something vaguely like while(base2_mantissa > 0) { divisor = find_largest_divisor_from_table(base2_mantissa); *dest = base2_mantissa / divisor '0'; base2_mantissa %= divisor; dest ; }. This allows you to quit early - e.g. if you only need a maximum of the 10 significant digits then you can use a fixed size buffer of 10 char and stop when you have 10 digits. Of course all of this (the table of divisors, etc) will need to be using "big integer" too.

The final step is printing "base 10 mantissa" while making sure you insert the decimal point character ('.') where "base 10 exponent" says it should be. If you used "most significant digit to least significant digit" in the previous step then this can be combined with the previous step (e.g. print one digit at a time without storing "base 10 mastissa" at all, while inserting the decimal point in the right place).

For other floating point operations:

Negation is trivial (an XOR of the sign bit).

For everything else, do it in 3 phases - split the source values into "signed integer mantissa" and "signed integer exponent" (mostly as described above); do a "middle phase" that depends on what the operation is; then combine the resulting "signed integer mantissa" and "signed integer exponent" back into the right format.

As a general guide (without complications - overflow, underflow, rounding, NaNs, ...) the "middle phases" are:

  • for multiplication: result_mantissa = src1_mantissa * src2_mantissa; result_exponent = src1_exponent src2_exponent - mantissa_size;

  • for division: result_mantissa = (src1_mantissa << mantissa_size) / src2_mantissa; result_exponent = src1_exponent - src2_exponent;

  • for addition: find the value with the most positive exponent; shift the other value's mantissa right while increasing its exponent until the exponents match; result_mantissa = src1_mantissa src2_mantissa; result_exponent = src1_exponent;

  • for subtraction: negate the second value and use addition instead

  • for shift left: if the mantissa is too small (sub-normal) shift it left as much as you can (while making sure it doesn't become too large); then add the remaining shift count to the exponent.

  • for shift right: subtract the shift count from the exponent as much as you can while keeping the exponent from becoming "too negative". If there's any shift count left then shift the mantissa right (forming a sub-normal).

Note that combining the resulting "signed integer mantissa" and "signed integer exponent" back into the right format should include some kind of while(result_mantissa's magnitude is too big) { result_mantissa >> 1; result_exponent ); }; which ends up being a little messy (can/should take into account rounding mode).

  • Related