I managed to dig into the code (specifically the FOUT
function), and my findings are as follows:
The 6502 Microsoft BASIC interpreter comes in two flavours, which I am going to call 32-bit builds and 40-bit builds; the name comes from the interpreter’s internal floating-point precision. The relevance of this for the number formatting algorithm is that the value of the constant N mentioned below is 6 in 32-bit builds, and 9 in 40-bit builds. Checking whether you’re using a 32-bit or 40-bit build is easy: just ask it to print 106. 32-bit builds will output 1E+06
, while 40-bit builds will output 1000000
. As far as I am aware, most Microsoft BASIC versions are 40-bit builds.
The formatting algorithm itself is as follows:
Output a sign character: ‘-
’ if negative, a space otherwise. The rest of the algorithm operates on the absolute value.
If the number is zero, output ‘0
’ and exit. (I failed at making the interpreter ever produce a negative zero through ordinary arithmetic, although one can be represented in the underlying format, and the sign will be printed.) The rest of the algorithm operates on positive numbers.
If the number is smaller than 1, multiply it by 10N and initialise the order-of-magnitude counter to −N. Otherwise initialise it to zero. (While this may seem otherwise redundant to the next step, I assume this is a speed and/or precision optimisation.)
Adjust the order-of-magnitude counter by repeating the following steps:
- While the number is smaller than 10N − 1 − 0.0501 (*), multiply the number by 10 and decrement the counter.
- While the number is larger than 10N − 0.501 (*), divide the number by 10 and increment the counter.
- If neither, continue to the next point.
Add ½ to the number and compute its floor. The integer obtained will serve as the significand to display.
If the order-of-magnitude counter is in the range [−(N + 1), 1) (in other words, the absolute value of the original number is in the range [0.01, 10N)), output the number in the ordinary decimal format; the order-of-magnitude counter will determine between which digits the decimal point will be placed. Pure integers are output without a decimal point; pure fractions are output without a leading zero (e.g. ½ is .5
).
Otherwise, output the number in the exponential format: one leading digit, followed by an optional decimal point, then at most N − 1 fractional digits, the letter ‘E
’, the sign (‘+
’ or ‘-
’) and two-digit magnitude of the exponent.
(*) Subject to approximation; the actual values are 99 999.9375 and 999 999.4375 for 32-bit builds, or 99 999 999.90625 and 999 999 999.25 for 40-bit builds. The lower bounds are actually off by one bit in the second-to-last place and could have been made tighter: 99 999.953125 and 99 999 999.96875 respectively.
FOUT
and$FOFMT
look relevant.