Boy, I sure opened a can of worms by suggesting the Wozniak & Rankin routines, didn't I? Okay, here we go...
X1 is the exponent of FP1
M1 is the mantissa of FP1
M1+0 is the high byte of the mantissa
M1+1 is the middle byte of the mantissa
M1+2 is the low byte of the mantissa
Likewise for X2 and M2.
The high byte of the mantissa (M1+0):
00.XXXXXX represents an unnormalized positive mantissa
01.XXXXXX represents a normalized positive mantissa
10.XXXXXX represents a normalized negative mantissa
11.XXXXXX represents an unnormalized negative mantissa
(I'll get to the Exponent = -128 part later.)
So, what does unnormalized mean? All floating point representations are similar to scientific notation, including this representation. In scientific notation, a number is in the following form:
mantissa * 10 ^ exponent
The exponent is an integer. The mantissa starts with a non-zero digit, then the decimal point, then the rest of the digits. For example, 120 is:
1.20 * 10 ^ 2
If we could ignore the non-zero starting digit rule, 120 can be written as:
0.12 * 10 ^ 3
but that is not in scientific notation. 1.20 * 10 ^ 2 is normalized, 0.12 * 10 ^ 3 is unnormalized. Normalization is the process of converting an unnormalized to a normalized number, by shifting the decimal point and adjusting the exponent.
There is an exception to the non-zero starting digit rule: when the number is zero, the mantissa must be zero, so the starting digit must be zero.
For an N-digit mantissa (N=3 in the two examples above), we can express the mantisssa using an integer, M, as shown. The number is:
Code:
M
---------- * 10 ^ exponent
10 ^ (N-1)
where 0 <= M < 10 ^ N. Note that the mantissa is unnormalized when 1 <= M < 10 ^ (N-1).
The FP representation is similar, but uses base 2 rather than base 10. If we treat X1 as an unsigned integer that ranges from 0 to 255 ($00 to $FF) and M1 as a 24-bit twos complement integer that ranges from -8388608 to 8388607, where -8388608 to -1 are $800000 to $FFFFFF and 0 to 8388607 are $000000 to $7FFFFF, then the number represented by FP1 is:
Code:
M1
------ * 2 ^ (X1 - 128)
2 ^ 22
which is the same as:
M1 * 2 ^ (X1 - 150)
Example #1: X1 = $81 and M1 = $600000
$81 = 129 and $600000 = 6291456, so FP1 is 6291456 * 2 ^ (129 - 150) = 3
Example #2: X1 = $81 and M1 = $A00000
$81 = 129 and $A00000 = -6291456, so FP1 is -6291456 * 2 ^ (129 - 150) = -3
The mantissa is normalized when the upper two bits (bits 7 and 6) of the high byte (M1+0) are the same. When the high bit (i.e. bit 7) of the high byte is 0, the mantissa is positive, and when the high bit is 1, the mantissa is negative. One way to see normalization in action is to use the FLOAT routine.
To float 10000 ($2710):
store $27 at M1+0
store $10 at M1+1
store $00 at M1+2 (required by the FLOAT routine)
After a JSR FLOAT:
X1 is $8D (141) and M1 is $4E2000 (5120000)
and sure enough:
5120000 * 2 ^ (141 - 150) = 10000
To float -10000 ($D8F0):
store $D8 at M1+0
store $F0 at M1+1
store $00 at M1+2
After a JSR FLOAT:
X1 is $8D (141) and M1 is $B1E000 (-5120000)
and sure enough:
-5120000 * 2 ^ (141 - 150) = -10000
Note that when zero ($0000) is floated, after a JSR FLOAT, X1 is $00 and M1 is $000000. It doesn't really matter what the exponent is when the mantissa is zero, but the normalization loop at NORM1 shifts until the upper two bits of the high byte of the mantissa are unequal or X1 is zero. Since the former will never occur when the mantissa is zero, it exits once the latter case is true. Like scientific notation, the mantissa zero is an exceptional case.
The largest (i.e. most negative) normalized negative mantissa is $800000 (-2^23).
The smallest (i.e. closest to zero) normalized negative mantissa is $BFFFFF (-2^22 - 1)
The smallest (i.e. closest to zero) normalized positive mantissa is $400000 (2^22).
The largest (i.e. most positive) normalized positive mantissa is $7FFFFF (2^23 - 1).
The minimum exponent is $00 (0)
The maximum exponent is $FF (255)
So, a normalized negative number may range from:
-2^23 * 2 ^ (255 - 150) = -2^128
to:
(-2^22 - 1) * 2 ^ (0 - 150) = -2^-128 - 2^-150
A normalized positive number may range from:
2^22 * 2 ^ (0 - 150) = 2^-128
to:
(2^23 - 1) * 2 ^ (255 - 150) = 2^128 - 2^105
The largest negative mantissa is the largest normalized negative mantissa, and the largest positive mantissa is the largest normalized positive mantissa, so larger numbers are not representable. However, smaller numbers are representable, using unnormalized mantissa, specifically:
The smallest negative mantissa is $FFFFFF (-1)
The smallest positive mantissa is $000001 (1)
So, the smallest negative number is:
-1 * 2 ^ (0-150) = -2^-150
The smallest positive number is:
1 * 2 ^ (0-150) = 2^-150
So, a negative number may range from -2^128 to -2^-150.
A positive number may range from 2^-150 to 2^128 - 2^105.
The routines that return a floating point number (FADD, FCOMPL, FDIV, FMUL, FLOAT, FSUB, and NORM) will return an unnormalized number if the number is too close to zero to be represented as a normalized number. Note that when an unnormalized number is returned the exponent (i.e. X1) will be -128 ($00). (Bet you thought I forgot about the Exponent = -128!)
Here are three examples where the high byte of the mantissa returned is either 00.XXXXXX or 11.XXXXXX. Note that in all three cases, the inputs are normalized mantissa. I should also point out that I have carefully chosen these examples so that there is no swapping or shifting either before the mantissa are added, and after the manitissa are added, M1 (the mantissa of the result) is unchanged, so you can more easily follow along in the code.
Example #1: -3 + 3 = 0
Upon entry:
X1 = $81, M1 = $A00000
X2 = $81, M2 = $600000
JSR FADD returns with:
X1 = $00, M1 = $000000
Example #2: -2^-127 + 2^-128 = -2^128
Upon entry:
X1 = $00, M1 = $800000
X2 = $00, M2 = $400000
JSR FADD returns with:
X1 = $00, M1 = $C00000
Example #3: -1.25 * 2^-128 + 1.5 * 2^-128 = 2^-130
Upon entry:
X1 = $00, M1 = $B00000
X2 = $00, M2 = $600000
JSR FADD returns with:
X1 = $00, M1 = $100000
All clear now?
One other thing. The subtraction performed by FSUB is actually FP1 = FP2 - FP1. The documentation in the wozfp3.txt file is incorrect (there are a lot of errors in the documentation, in fact).
By the way, the Apple II had two BASICs. The first was known as Integer BASIC (it's "official" name was Apple BASIC) and was written by Steve Wozniak himself. The second, and more common, BASIC was known as Applesoft and was written by Microsoft (Apple contributed some Apple II-specific code, like graphics commands).
Applesoft was supplied (in ROM) starting with the II+ (the second machine in the Apple II line). Applesoft used floating point math (there were integer variables, but it converted them to FP to do math on them). EhBASIC is another example of a BASIC based on what might be called the "Microsoft model". EhBASIC uses 3-byte mantissa and Applesoft used 4-byte manitissa, but their FP representation is the same otherwise. In fact, many of EhBASIC's routines are similar if not identical to Applesoft's.
Integer BASIC, as you might guess from its name, used only integer math. It never used the floating point routines (known as the FP package) described in the wozfp3.txt file. You could access these via BASIC using POKE and CALL commands, but this was inconvenient to say the least. It was possible to write special routines to interface between BASIC and the FP package, and this was in fact done. (Until the II+, Integer BASIC was the only BASIC there was for the Apple II.) So there isn't an easy, built-in way to enter a FP number such as 0.125 directly (but you could easily use the FLOAT routine to float an integer) or print FP numbers from BASIC.
Incidentally, Integer BASIC was located at $E000-$F424 in ROM. There was a mini-assembler at $F500-$F63C. If you're paying careful attention, you'll notice that the FP package was located at $F425-$F4FB and $F63D-$F65D, and SWEET16 (also described in the Repository) was located at $F689-$F7FC, which is partly why they start at such strange addresses. (The $F800-$FFFF space contained I/O routines, the monitor program, and some other routines.) Integer BASIC, the FP package, the mini-assembler, and SWEET16 were all basically independent of one another, since none of them called routines in any of the others (all except the FP package used $F800-$FFFF routines, though).
Applesoft took up the entire $E000-$F7FF ROM space (and $D000-$DFFF also), so the mini-assembler, the FP-package, and SWEET16 had to be removed, unfortunately.