Day 18: Floating-Point Arithmetic

We have already covered using fixed-point for fractional numbers, but there may be times when you absolutely require true floating-point, so guess what we’re gonna look at now.

OP Registers

A bit of a misnomer, since the OP registers are really sections of RAM. There are six OP RAM registers: OP1 to OP6. They are used mainly for two things:

  • Storing floating-point numbers.
  • Holding variable names.

Each OP register is eleven bytes in length. This is because variable names and floating-point numbers are formatted to be 9 bytes in size. Bytes 10 and 11 are used for extra precision in floating-point numbers when doing math.

Floating-Point Numbers

Well, we are dealing with a calculator after all :).

TI stores floating point numbers according to this structure:

struct FP {
    byte  sign;           // Whether the number is positive or negative
    byte  exponent;       // Locates the decimal point
    byte  significand[7]; // The number itself
    byte  guard[2];       // Guard digits for mathematics
};

The magnitude of every real number except zero can be represented as m × 10exp, where exp is an integer designating the exponent and m is a real number designating the significand such that 1 <= m < 10.

Sign

This byte determines if the number evaluates as positive or negative, and also if it is real or complex. For the uninitiated, a complex number is one of the form a+bi, where i is the square root of -1.

  • %00000000 — Positive and real.
  • %10000000 — Negative and real.
  • %00001100 — Positive and complex.
  • %10001100 — Negative and complex.

Exponent

The exponent field reports the power of ten that the mantissa is to be raised. The number format is not the usual two’s complement, but rather biased to $80. A value of $80 translates as 100. $81 is 101. $7F is 10-1.

Significand

These are the digits of the number. Each nibble specifies one decimal digit, so you can have a floating-point number with 14 digits. The first digit, and only the first digit, is the characteristic (the whole part) with the remainder being the mantissa (the decimal part).

Examples:

  • $00, $9E, $23, $91, $80, $55, $75, $00, $00: 2.391805575 × 1030
  • $80, $AC, $46, $19, $18, $45, $80, $00, $00: -4.61918458 × 1044
  • $80, $77, $75, $16, $99, $60, $94, $17, $87: -7.5169960941787 × 10-7
  • $00, $89, $19, $80, $61, $22, $02, $65, $10: 1980612202.6510

If the number is complex, then this number is the real part (a). The imaginary part (b) is held in the next consecutive OP register, which also has bits 2 and 3 of its sign byte set.

Example:

$0C, $7E, $22, $09, $78, $47, $30, $00, $00
$8C, $7D, $12, $56, $55, $62, $00, $00, $00

0.0220978473 - 0.0012565562i

Loading Floating-Point Numbers

To load floating-point (hereafter, ‘FP’) numbers into the OP registers, you use the Mov9ToOPx system routine.

_Mov9ToOP1: Moves the nine bytes at HL to OP1.
Input
HL
Pointer to start of the nine bytes.
Destroys
all but A

For complex numbers, use _Mov9OP1OP2, which moves the 18 bytes at HL to OP1 and OP2.

_Mov9ToOP2: Same as _Mov9ToOP1, but moves to OP2.

This is what you do:
Example: Store e into OP1.

    LD     HL, exp
    bcall(_Mov9ToOP1)
    RET

exp:       .DB    $00, $80, $27, $18, $28, $18, $28, $45, $94    ;2.7182818284594

FP Math

What can I say…there are a lot of ROM calls for FP math. Here is just a sampling.

All registers are destroyed. Result returned to OP1.

_FPAdd: Adds OP2 to OP1.
_FPDiv: Divides OP1 by OP2.
_FPMult: Multiplies OP1 by OP2.
_FPRecip: Reciprocal of OP1. OP2 = input OP1.
_FPSub: Subtracts OP2 from OP1.
_SqRoot: Square root of OP1.
_Random: Gets a random number. 0.0 > OP1 > 1.0

Manipulating OP Registers

_OPxToOPy: Stores 11 bytes at OPx to OPy.
Destroys
BC DE HL

These combinations are available:

x \ y OP1 OP2 OP3 OP4 OP5 OP6
OP1 x x x x x
OP2 x x x x x
OP3 x x x x
OP4 x x x x x
OP5 x x x x x
OP6 x x x
_OPxExOPy: Swaps 11 bytes at OPx with 11 bytes at OPy.
Destroys
A BC DE HL

These combinations are available:

x \ y OP2 OP3 OP4 OP5 OP6
OP1 x x x x
OP2 x x x
OP5 x

Displaying FP Numbers

_DispOP1A: Displays the floating-point number in OP1 using the small font, formatted using the current FIX setting.
Input
OP1
Number
A
Maximum number of characters (not digits) to display.
Destroys
All
_FormReal: Converts the number in OP1 into a string.
Input
OP1
Number
A
Maximum number of characters (not digits) to display, minimum of six.
Output
BC
Length of string
OP3
Start of string, null-terminated.
Destroys
All

SCI, ENG, and FIX settings affect the string conversion. To ignore all format settings, use FormEReal.

_ConvOP1: Converts the number in OP1 into a two-byte integer.
Input
OP1
Number
Output
DE
Converted number.

Generates an error if the exponent is greater than 3.