5

I'm failing to find a concise document that describes the formatting used by (6502) MS BASIC when converting internal floating-point numbers to text for display in PRINT, STR$, etc.

I know it has a leading - or space for +, so for testing, I punted and used sprintf(str, "% -f", a);, but that has some rather common edge cases and now I'd like to implement the canonical solution.

3

1 Answer 1

8

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:

  1. Output a sign character: ‘-’ if negative, a space otherwise. The rest of the algorithm operates on the absolute value.

  2. 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.

  3. 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.)

  4. Adjust the order-of-magnitude counter by repeating the following steps:

    1. While the number is smaller than 10N − 1 − 0.0501 (*), multiply the number by 10 and decrement the counter.
    2. While the number is larger than 10N − 0.501 (*), divide the number by 10 and increment the counter.
    3. If neither, continue to the next point.
  5. Add ½ to the number and compute its floor. The integer obtained will serve as the significand to display.

  6. 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).

  7. 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.

7
  • 1
    If a USR() function call returns a non-normalized number, and one tries to print it, the print output will sometimes have leading zeroes. I suspect that's probably influenced by the exact choice of algorithm used for output.
    – supercat
    Commented May 3, 2023 at 14:57
  • Hmm, do you know of a reliable way to reproduce? Commented May 3, 2023 at 20:10
  • Sure. Use emulator at scullinsteel.com/apple2 and after hitting the reset key, use CALL -151 to enter the monitor. Then type A:4C 00 03 to set the USR vector and 300:A9 00 85 9E 60 to create a simple function that will clear the most significant byte of the floating-point accumulator. Then control-C to exit to basic. PRINT USR(65281) should output the value of the bottom byte of 0xFF01, but with extra leading zeroes.
    – supercat
    Commented May 3, 2023 at 22:23
  • 1
    @supercat Is AppleSoft Basic a reliable indicator of the way Microsoft Basic works generally? Would you get the same results with PET Basic?
    – JeremyP
    Commented May 4, 2023 at 7:43
  • @JeremyP - Commodor and AppleSoft BASIC both share the same underlying code with the fewest possible changes needed to get it running. It's possible one of those changes has to do with USR, but I suspect not. This is an interesting twist, but luckily one I don't need to deal with as my USR always returns 0. Commented May 4, 2023 at 11:23

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .