Investigating Basic floating point accuracy (and finding a bug)

I’ve a feeling that when were looking into Acorn’s Basic 4r32 version, which was faster and more accurate than previous versions, but with a bug in the log and trig routines, Dave @hoglet ran some long 6502 traces which included all the bytes of the two floating point accumulators, which helped pinpoint the place where things went astray.

The Model 100’s interpreter was written when MS were toying with decimal floating point. You will get very different output with that. On WebMSX — a convenient (if not very fast) way to try an interpreter with decimal floats — the PRINT 7E7+1234-7E7 test returns 1234 as expected, and the non-zero values output as the third column of the first program are all of the order of ±1E-13.

2 Likes

Are there any zero values in the third column? That would be the bug.

It is unfortunate - perhaps I should apologise - that this thread is nominally about accuracy but has a major strand which is about a bug.

These are pretty much outdated:
You can simply paste to the emulator using CTRL/Command + V or the Edit menu of the browser, or using the context menu of the emulated screen. (The latter may either directly use the clipboard contents or provide a textarea for input, depending on the browser and the security settings.)

But there’s a difference: This will type the text into the emulated PET, while the link console constructs a link, which will result in the emulator loading the text as a BASIC program.
So, for a short program, pasting will be more convenient, while for longer programs the link console may be still viable. (But, then, it’s probably simpler to just drop a text file onto the emulator.)

Conversely, you may export a link to any BASIC program in memory via the “Export/Utils” menu and its item “BASIC Program as URL”.

I like to edit in a text box!

This program shows, I think, that the problem is not just boolean comparisons, but subtractions too.

110 A=SQR(2)
120 B=SQR(3)
130 C=SQR(6)
140 PRINT A*B-C
145 PRINT C-A*B
150 PRINT A*B-SQR(6)
155 PRINT SQR(6)-A*B
160 PRINT SQR(2)*SQR(3)-C
165 PRINT C-SQR(2)*SQR(3)
170 PRINT SQR(6)-SQR(2)*SQR(3)
180 IF A*B=C THEN PRINT "SAME 1"
190 IF C=A*B THEN PRINT "SAME 2"
200 IF C<>A*B THEN PRINT "DIFFERENT 1"
210 IF A*B<>C THEN PRINT "DIFFERENT 2"
220 IF SQR(6)=SQR(2)*SQR(3) THEN PRINT "SAME 3"
230 IF SQR(6)<>SQR(2)*SQR(3) THEN PRINT "DIFFERENT 3"
240 IF SQR(2)*SQR(3)=SQR(6) THEN PRINT "SAME 4"
250 IF SQR(2)*SQR(3)<>SQR(6) THEN PRINT "DIFFERENT 4"
260 IF SQR(2)*SQR(3)-SQR(6)=0 THEN PRINT "SAME 5"
270 IF SQR(2)*SQR(3)-SQR(6)<>0 THEN PRINT "DIFFERENT 5"

Result:
image

which is to say:

-9.31322575E-10
 0
 0
 0
-9.31322575E-10
 0
 0
DIFFERENT 1
DIFFERENT 2
DIFFERENT 3
DIFFERENT 4
SAME 5

Well, nobody ever claimed that Commodore BASIC was a particularly good one… :wink:

1 Like

No zero values are printed.

I know that NoLand has already described the mechanics of the bug, but to me, MS 6502 BASIC printing 0 is indicating a number too small to be significant rather than the Ultimate Big Nada of mathematical zero. There are so many conventions in representing binary floating point to humans that illogical situations will happen.

Yes, a sensible way to think about it may be as the tiny mathematical Voodoo that is epsilon.
Is it an insignificant amount or anything to write home about (where “home” is a variable or the screen)?
Having said that, it’s still somewhat absurd to have a check for equality return false and then providing a delta of zero.

1 Like

At this point, I’m somewhat relieved not to see “SAME 1” and “DIFFERENT 1” at the same time. :wink:

You’re not the only person to suggest this, but I really don’t see it. Floating point is there to deliver large and small quantities, and if we subtract two similar numbers, we should get the difference. The only time we won’t is if we underflow towards zero, but that would involve much smaller numbers.

Unsurprisingly, perhaps, this bug and several others have previously been found, and fixed - at least, fixed in the modern-era EhBasic which is derived from MSBasic. Thanks to @hoglet for pointing me in the right direction, and JGHarston for his archive, Klaus for keeping a repository and Bruce (dclxvi) for investigations, elaborations, and fixes, on the 6502 forum!

Quoting Bruce from that thread:

Applesoft BASIC has several well-known bugs. I recently reviewed EhBASIC 2.22 to see which of these bugs also apply to EhBASIC, and there are some that do.

In EhBASIC, the bug can be demonstrated by the fact that these two lines output different values:

A= 16908289:PRINT A/20
A=1*16908289:PRINT A/20

As indeed they do in PET Basic:

A=  16908289:PRINT A/20
 845414.45

READY.

A=1*16908289:PRINT A/20
 845414.425

READY.

Bruce even suggests a simple length-preserving local fix:

The fix is simple: just add a SEC before jumping to LAB_2569. In fact, the LSR ORA #$80 at LAB_2627 can be replaced by SEC ROR, so you can get the byte back without affecting the cycle count for the nonzero case.

Edit: here’s the offending line in Microsoft’s original source code.

Edit: in case anyone wants to dig into the 6809 Extended Basic, Disk Basic Unravelled might contain the disassembly you need. Or possibly see about page 71 of Color Basic Unravelled II where the multiplication doesn’t even have the zero-byte shortcut.

1 Like

Now I / we’ve just to find out, where this is in the PET ROM…

Procedures to experiment with temporal fixes would be, for whoever wants to give this a try:

  1. hex-dump the ROM in question in the emulator or, alternatively,
    download files from zimmers.net – see links below,
  2. fix ist up (e.g., copy/drop it here, edit, and export it)
  3. drop the resulting .bin file on the screen of the emulator to install it.

(Note, ROM-files must have extension “.bin” or “.rom” and the size must be a multiple of 0x800, i.e, 2K. You will have to provide either a start address or a socket designator, see below. ROM files are usually found with either in the filename.)

ROM chips / socket designators on the PET 2001 are:

A) Original PET 2001, 2K Chips

  • H1 … C000–C7FF
  • H2 … D000–D7FF
  • H3 … E000–E7FF
  • H4 … F000–F7FF
  • H5 … C800–CFFF
  • H6 … D800–DFFF
  • H7 … F800–FFFF

B) PET 2001N, 4K Chips

  • D3 … 9000–9FFF (empty / option ROM)
  • D4 … A000–AFFF (empty / option ROM)
  • D5 … B000–BFFF (used for BASIC 4 only)
  • D6 … C000–CFFF
  • D7 … D000–DFFF
  • D8 … E000–E7FF *
  • D9 … F000–FFFF

*) E800–EFFF: I/O space

Where, ROMs used in “2.0”, AKA “New ROM” config. (which is the default):

  • C000–CFFF901465-01.bin” BASIC
  • D000–DFFF901465-02.bin” BASIC
  • E000–E7FF “901447-24.bin” Kernal (OS)
  • F000–FFFF “901465-03.bin” Editor (graphics keyboard, 60Hz, 40 cols, no CRTC)

(Links are to respective files at zimmers net.)

So it should be in the C000…DFFF range… :wink:

PS: There is no known ROM Listing for BASIC 2, but there is a reverse engineered source for BASIC 1, 2, and 4 for an unknown assembler here: http://www.zimmers.net/anonftp/pub/cbm/src/pet/basic.zip

Ah, nice that it’s as easy as drag-dropping a *.bin file into your web-based emulator. I can confirm that Bruce’s length-preserving patch fixes the bug!

image

which is

LIST

 110 A=SQR(2)
 120 B=SQR(3)
 130 C=SQR(6)
 140 PRINT A*B-C
 145 PRINT C-A*B
 900 A= 16908289:PRINT A/20
 990 A=1*16908289:PRINT A/20
READY.
RUN
 0
 0
 845414.45
 845414.45

READY.

I patched using xxd and sed - there might be other ways!

xxd -p basic-2-d000.901465-02.bin |\
  sed '/a890/s/dad0034c8fd84a0980a8/dad004384c8fd8386aa8/' |\
  xxd -p -r > basic-2-d000.901465-02-patched.bin

I located the errant code by looking for the byte sequence a8 90 which, as it happens, appears only once. That’s TAY BCC.

I used the easy6502 assembler to assemble the patch - it’s a habit, as your assembler would surely have done just as well.

Edit: aha, but no, in fact the patched code still misbehaves with other examples, such as

 10 FORI=2 TO 9
 20 FOR J=2 TO I
 30 IF 0+SQR(J*I)=0+SQR(J)*SQR(I) GOTO 40
 35 PRINT I,J,SQR(J*I)-SQR(J)*SQR(I)
 40 NEXT
 50 NEXT

which should never print a zero in the third column but still does, a bit less often than the unpatched version

RUN
 2         2        -1.04409992E-09
 3         3         0
 5         2        -1.02591002E-09
 5         5         0
 6         2        -1.55705493E-09
 6         5         0
 7         2        -1.49884727E-09
 7         5         0
 7         7        -2.73576006E-09
 8         2        -2.08819984E-09
 8         5         0
 8         8        -4.17639967E-09
 9         6        -2.61934474E-09
 9         8        -4.75847628E-09
 9         9        -4.8748916E-09
1 Like

Don’t mind, there is no aspiration to replace better tools – which there are, without question. :slight_smile:

I think, this is due to that ZEROISE routine, which will be applied, when printing a value.
(I still think that the “A <> B while A - B = 0” contradiction is owed to this.)

I’m failing to find a ZEROISE routine - pointer please!

I’m perpetually disappointed that more systems - ancient or modern - don’t incorporate the concept of ε in comparing real numbers. You can get into all sorts of knots (like this entire thread) if you don’t accept that floating point has holes and imperfections.

As my old numerical methods prof warned us in advance of any homework: “You’ll have to give me a very good reason not to mark differences smaller than 1E-6 as errors.”

2 Likes

(Just to note: I’m completely supportive of the idea that floating point is counterintuitive in some ways, has interesting and unexpected corner cases, offers things which are not quite mathematically ideal numbers, and that one should be very careful in comparing or subtracting nearly equal quantities… but on the other hand, I claim that the bug-hunting part of this thread is not about those kinds of considerations.)

Here you are, from the aforementioned “Programming the PET/CBM” by Raeto West, p.446:

There are also other routines involved, see (ibidem, p. 414) here:

Notably, we learn, FAC#1 should be “zeroised” on copy/transfer to FAC#2, which somewhat undermines my theory, but only for the rounding byte, which is in a separate zero page address. But there’s also this routine, which is referred to as “less elegant”:

Note on BASIC versions: This is somewhat convoluted, and you’ll find diverging numbering schemes. The story is that there was a pre-production version, which probably never was delivered with any PET sold. This had several bugs, which leads to the first official version, which is, depending on how you look at it, either version 1 or already version 2. This was then replaced by the next version featuring such fancy and unheard of things like a working disk and IEEE bus implementation, which is also the the basis for the VIC, C64, etc, which both show “BASIC 2.0” in the prompt. This is sometimes referred to as BASIC 2, but also, maybe even more often, as BASIC 3. Indeed, the few sources, there are, show an internal versioning like 3.x. (I think, this is a source for the editor only.) Official Commodore communications refer to this as BASIC Level II, but, admittedly, I’ve seen this applied to the upgrade ROM, as well. Back in the day, the two versions were known as “Old ROM” and “New ROM”, until the next BASIC version came, on which everybody agrees upon that it’s BASIC 4. (BASIC 4 also includes a patched ROM, but this is generally ignored and nobody suggests this to be BASIC 5, which is probably disappointing.)

Notably, BASIC 2 (the “New ROM” version) and BASIC 4 share most, if not all zero page addresses, but not the ROM addresses, with BASIC 4 occupying extra address space from 0xB000–0xBFFF. (In contrast to BASIC 1, or is it 2?) In order to keep things sporty, there isn’t a universal nomenclature for ROM routines or zero page addresses, either.

Here, in Programming the PET/CBM, “BASIC 1” refers to the “Old ROM”, “BASIC 2” to the “New ROM”, and BASIC 4 is unambigously BASIC 4.
(Sorry for the lengthy note, but in PET-context, you’ve to explain yourself and/or your sources. :slight_smile: )

PS: “Programming the PET/CBM” is available on archive org: Programming The PET CBM (197x)(West, Raeto) : Free Download, Borrow, and Streaming : Internet Archive
(There is no publication date, but, provided it includes BASIC 4, it’s earliest 1980.)

Another great book on all things PET 2001 is “The PET Revealed” by Nick Hampshire (published in Somerset! – and, for this, we even have a publication date, 1st ed. Oct. 1979, 2nd ed. Jan. 1980, there was certainly some demand for publications like this): http://www.1000bit.it/support/manuali/commodore/pet/The_Pet_Revealed.pdf

PPS: As you may guess, I love how obscure things are, regarding the PET.

Arn’t you glad you never had ‘Balance your check book’ program in BASIC.
Is the bug also in a PDP8 basic?