Jump to content
IGNORED

Floating Point Atari BASIC


Recommended Posts

2 hours ago, ClausB said:

When I worked at New Dimensions in Computing in 1981 they asked me to find out why their accounting program was dropping pennies. It was in MS BASIC for CP/M on a Vector Graphic S100 system. The floating point variables were in dollars, so 0.01 dollars was not exact because of the binary representation. I added some rounding to fix it. Also sales tax percentage, 4% or 0.04, was not exact plus there were rounding rules specified in the law.

 

Atari's BCD representation would not have lost pennies, nor would have MS if the variables had been defined in cents instead of dollars.

That's why IBM and other mainframe manufacturers loved them some BCD, as did bean counters everywhere, although scaled 64 bit integer data types like M$'s Currency or bignum-like Decimal fit the bill as well. There are also modern "densely packed decimal" schemes to store three decimal digits in every ten bits to ease the space inefficiency pain.

  • Like 1
Link to comment
Share on other sites

On 6/22/2024 at 11:54 PM, thorfdbg said:

"different" is an euphemism. "Many more" is more appropriate. It has serious rounding issues.

 

Is it because of the BCD or because of the six bytes, like how everyone uses 64 bit double even when 32 bit float would often do

Link to comment
Share on other sites

On 6/24/2024 at 10:32 PM, yetanothertroll said:

 

Is it because of the BCD or because of the six bytes, like how everyone uses 64 bit double even when 32 bit float would often do

Its because it arithmetic to the basis of 100. The rounding loss a number format creates depends on this basis, and larger bases have larger losses. With its base-100 arithmetic, the math pack performs considerably worse than binary implementations. Six bytes for floating point are fine. IEEE single precision only takes four, which is quite sufficient for many elementary needs.

  • Like 2
Link to comment
Share on other sites

3 hours ago, thorfdbg said:

Its because it arithmetic to the basis of 100. The rounding loss a number format creates depends on this basis, and larger bases have larger losses. With its base-100 arithmetic, the math pack performs considerably worse than binary implementations. Six bytes for floating point are fine. IEEE single precision only takes four, which is quite sufficient for many elementary needs.

 

I wonder if Atari went with BCD because early electronic calculators did, and it wouldn't do for a $500-1000 home computer to have a smaller range or show different results for simple calculations than dad's fourbanger

 

http://datamath.org/EarlyArchitecture.htm

Link to comment
Share on other sites

On 6/22/2024 at 5:15 AM, TGB1718 said:

I'm writing a program that will be running on a vanilla 800XL that's extensively using floating point numbers, the

data will be saved to disk. [...]

Depending on what you're doing with the data, this might be of interest to you, especially if you later want to play with 6 000, 6 000 000, or 6 000 000 000+ numbers instead of merely 600 or so

 

https://www.johndcook.com/blog/skewness_kurtosis/

 

Edit: I dun goofed. Even with the above code, expect trouble after processing only 999 999 999 numbers or so even with the OS-B mathpack. There are likely to be other gotchas

Edited by yetanothertroll
Atari floating point, how does it work
Link to comment
Share on other sites

Posted (edited)
6 hours ago, yetanothertroll said:

Edit: I dun goofed. Even with the above code, expect trouble after processing only 999 999 999 numbers or so even with the OS-B mathpack. There are likely to be other gotchas

Yes, I spotted that, I'm only interested in printable numbers without the "E", and this is what I found that

the conversion process does with large numbers.

 

look what happens if you exceed that number of characters (it's not the value, it's the number of characters)

it does exactly the same thing with 9999999.99 and then make it 99999999.99

this is ok

A=1234567.99

?A
1234567.99

 

but add another number and it truncates the last character

A=12345678.99
?A
12345678.9

 

I wasn't aware it did this, it's not a limit of the value, but the floating point package is used

to convert numbers to strings for display, and I think that's where the limit comes from, the output buffer

is only big enough to accommodate 10 characters including the "."

 

Luckily that's good enough for what I'm doing :)

 

EDIT: just checked and the FP package uses BASIC's line buffer which is 128 bytes long at $580 to $5FF

so it's not a buffer limit, just probably the max characters the FP package will process.

 

Edited by TGB1718
Link to comment
Share on other sites

20 hours ago, yetanothertroll said:

 

I wonder if Atari went with BCD because early electronic calculators did, and it wouldn't do for a $500-1000 home computer to have a smaller range or show different results for simple calculations than dad's fourbanger

Atari Basic the math pack is part of was a quick rough job, and given the time it was developed in, it is actually quite good. But, apparently, mathematics was not quite in focus here. Also multiplication and division would have been easier in binary than in BCD.

  • Like 1
Link to comment
Share on other sites

9 hours ago, TGB1718 said:

this is ok

A=1234567.99

?A
1234567.99

 

but add another number and it truncates the last character

A=12345678.99
?A
12345678.9

This is the result of the base 100 computation I was talking about. The mathpack can only adjust/shifts numbers by bytes, and thus pairs of digits. It adds to the rather large rounding loss of the mathpack implementation.

  • Like 1
Link to comment
Share on other sites

9 hours ago, TGB1718 said:

Yes, I spotted that, I'm only interested in printable numbers without the "E", and this is what I found that

the conversion process does with large numbers.

 

look what happens if you exceed that number of characters (it's not the value, it's the number of characters)

it does exactly the same thing with 9999999.99 and then make it 99999999.99

this is ok

A=1234567.99

?A
1234567.99

 

but add another number and it truncates the last character

A=12345678.99
?A
12345678.9

 

I wasn't aware it did this, it's not a limit of the value, but the floating point package is used

to convert numbers to strings for display, and I think that's where the limit comes from, the output buffer

is only big enough to accommodate 10 characters including the "."

 

Luckily that's good enough for what I'm doing :)

 

EDIT: just checked and the FP package uses BASIC's line buffer which is 128 bytes long at $580 to $5FF

so it's not a buffer limit, just probably the max characters the FP package will process.

 

 

Part of it is that I've gotten a little too used to dmsc's FastBasic, which behaves differently than Atari BASIC in some ways. FB seems to be no better than BASIC at converting string to floating point, still losing that pesky tenth digit even when there's room for it in memory, but it can do accurate arithmetic involving the tenth digit in some circumstances BASIC doesn't,

t1.thumb.png.e3502ec21de549edd977f2042fa80b3a.png

 

t2.thumb.png.005cbfde9795f703312a4e75254d9b32.png

t3.thumb.png.f83e0f29642acb6557c571fa24aa171f.png

test.rom test.bas

Link to comment
Share on other sites

59 minutes ago, thorfdbg said:

This is the result of the base 100 computation I was talking about. The mathpack can only adjust/shifts numbers by bytes, and thus pairs of digits. It adds to the rather large rounding loss of the mathpack implementation.

Doesn't multiplying or dividing by 10 require shifting by nybble? There's nybble shifting going on somewhere, somehow. I modified the FastBasic program to divide then multiply by 10,

t4.thumb.png.b2cf30cae054826d001989f775247d16.png


' Program FPTest

 Dim Z%(2)
 
 Do

  Input "Number or Q to quit? "; X$

  If X$ = "Q" Then Exit

  Print X$; " entered"

  Z%(0) = Val(X$)
  Print Z%(0); ": ";
  @FPDump Adr(Z%) + 0

  Z%(1) = Z%(0) / 10.0
  Print Z%(1); ": ";
  @FPDump Adr(Z%) + 6

  Z%(2) = Z%(0) * 10.0
  Print Z%(2); ": ";
  @FPDump Adr(Z%) + 12

 Loop

 Print
 Print "OK"
 Get ZZ

End

Proc FPDump Za

 Data Hex() Byte ROM = "0123456789ABCDEF"

 ' Hex dump

 Print "$";
 For i = 0 to 5
  If i Then Print "'";
  j = Peek(Za + i)
  Print Chr$(Hex(1 + (j / 16)));  ' So which way of doing this is better?
  Print Chr$(Hex(1 + (j & 15)));
 Next i
 Print

 ' Quick and dirty Str$(), sort of

 Print " = ";
 If (Peek(Za) & 128) Then Print "-";
 For i = 1 to 5
  j = Peek(Za + i)
  Print $(&Hex)[1 + (j / 16), 1]; ' I have absolutely no idea whatsoever!
  Print $(&Hex)[1 + (j & 15), 1];
  If i = 1 Then Print ".";
 Next i
 Print " * 100 ^ "; (127 & Peek(Za)) - 64

EndProc

 

test.rom test.bas

Link to comment
Share on other sites

28 minutes ago, phaeron said:

That's the AFP issue I had mentioned earlier. It only shifts in up to 9 digits when converting ATASCII to floating point, even in cases where a 10th digit would fit.

 

 

It's even worse than that. Even FP to ATASCII is goofy in Atari BASIC, even with integer values, while under the hood simple arithmetic appears to work,

 

10 V=VAL("9999999989")
15 W=V:? W;" ";V;" ";ABS(W-V)
20 X=W+1
30 Y=X-W
40 Z=Y-1
45 PRINT W-V;" ";
50 PRINT W;" ";X;" ";Y;" ";Z
60 IF Z<>0 THEN 90
70 W=X
80 GOTO 20
90 END

 

t6.thumb.png.f8cfd229ca2e68d81d233ab806c9e2b3.png

 

EDIT: ...well, up to a point, anyway.

 

My brain hurts :(

 

Edited by yetanothertroll
Specialist: It will have to come out.
Link to comment
Share on other sites

10 hours ago, yetanothertroll said:

Doesn't multiplying or dividing by 10 require shifting by nybble?

It does, but the math pack does not normalize numbers by nibbles, but only by bytes. Normalizing here means that the exponents of two numbers to add or subtract are identical.

  • Like 1
Link to comment
Share on other sites

On 6/28/2024 at 12:19 AM, thorfdbg said:

It does, but the math pack does not normalize numbers by nibbles, but only by bytes. Normalizing here means that the exponents of two numbers to add or subtract are identical.

I'm not sure why the base 100 thing is so bad. I've done base 100 myself when writing my own bignum code in various languages or even base 1'000'000'000 when attempting a bignum package in 8-bit Logo. I think the issues are, five bytes for the mantissa aren't a lot, and Atari went and burned the 0.9 alpha to the production OS ROMs. Maybe the 1.0 would have had ten digit conversion routines and cleaned up the rest, who knows

 

https://atariwiki.org/wiki/Wiki.jsp?page=Atari BASIC

 

Link to comment
Share on other sites

This may be of interest to someone, somewhere. Presented with all my apologies to Knuth and Welford,

 

10 DIM S$(10):S$="9999999994"
20 N0=VAL(S$)+VAL(S$(10))
30 N=N0:RM1=99:REM 10000*RND(0)
40 N1=N:N=N+1:IF (N-N1)<1 THEN 90
50 X=10000*RND(0):RM1=RM1+((X-RM1)/N)
60 PRINT INT(N/10);N-(10*INT(N/10));
70 PRINT " ";N-N0;" ";X;" ";RM1
80 GOTO 40
90 PRINT "MEAN = ";RM1
99 END

 

FBStats10.thumb.png.c1cde3670d58e0df33477bd98fc696e9.png

Link to comment
Share on other sites

On 7/1/2024 at 9:39 PM, yetanothertroll said:

I'm not sure why the base 100 thing is so bad.

The problem is the size of the error in number representations if you have to work with a finite number of digits in a basis b. Thus, for example, it is not too hard to see that the largest relative error for a floating point representation with a finite (fixed) number of digits is b-1, so 1 for binary systems, but 99 for the BCD choice of the math pack.

 

  • Like 1
Link to comment
Share on other sites

Posted (edited)
1 hour ago, thorfdbg said:

The problem is the size of the error in number representations if you have to work with a finite number of digits in a basis b. Thus, for example, it is not too hard to see that the largest relative error for a floating point representation with a finite (fixed) number of digits is b-1, so 1 for binary systems, but 99 for the BCD choice of the math pack.

 

I'm just not seeing egregiously bad results. Sure, sometimes I resort to little tricks like X=X*X:X=X*X:X=X*X instead of X=X^8, but I haven't felt the need to go full Kahan–Babuška to sum an array as of yet. Could things be a little better with a replacement mathpack based on Turbo Pascal's six byte real or a 48-bit MBF? Maybe. Buuut, packed BCD made the conversions from/to human-readable ATASCII much easier and faster, but that sign bit had to go somewhere, and if they went with base 10 instead of 100, that seven bit exponent wouldn't go very far. That really could drive you into strange territories just to find the sum or product of an array, file, or READ/DATA stream

Edited by yetanothertroll
Dang typos
Link to comment
Share on other sites

 

Just now, thorfdbg said:

The issue appears for addition and subtraction, requiring denormalization and normalization.

Yup, and that line buffer could have been used to denormalize into a base 10 format for intermediate results. Maybe one of those third party replacements mathpacks did exactly that

Link to comment
Share on other sites

On 7/3/2024 at 11:12 AM, thorfdbg said:

The issue appears for addition and subtraction, requiring denormalization and normalization.

 

I'm starting to wonder about that. It may not be necessary to denormalize the operands, only to normalize the result. It might not be the fastest way, but it could be done if you had a spare 200+ bytes lying around to hold the intermediate result. I'll try to offer a proof by construction, most likely in FastBasic, when I have a chance

 

Link to comment
Share on other sites

OK, this FastBasic code is kind of a hot mess, but if you really really want to be able to count to 1.23456789E97 by 1.23456789E-97s and actually expect to get to 1.23456789E97, eventually, someday, maybe in a few billion years or so, here's one way you can do it,

 

FBAccum.thumb.png.f28c8ed8dfdc83bdbeda1f1f22835302.png

 


' Program FPAccumulator

 Print Fre(); " ";

 LM = DPeek(82) ' Preserve text window margins
 Poke 82, 0     ' Set left and right margin to max
 Poke 83, 39

 Dim Z%(3)

 Acc$ = Str$(0) ' Just allocate those 256 bytes already
 MSet Adr(Acc$) + 1, 255, 0
 Poke Adr(Acc$), 255 ' or use this as an an overflow sentinel?

 Print Fre()
 
 Do

  Input "Number or Q to quit? "; X$

  If X$ = "Q" Then Exit

  Print X$; " entered"

  Z%(0) = Abs(Val(X$))
  Print Z%(0);
  @FPDump Adr(Z%) + 0

  ' Add "X" to Accumulator

  XExpn = 127 & Peek(Adr(Z%) + 0)

  Carry = 0

  For i = 5 To 1 Step -1

   XSuperD = Peek(Adr(Z%) + 0 + i)
   AccumSD = Peek(Adr(Acc$) + 127 - XExpn + i)

   D1 = (XSuperD & 15) + (AccumSD & 15) + Carry

   If D1 < 10
    Carry = 0
   Else
    Carry = 1
    D1 = D1 - 10
   EndIf

   D2 = (XSuperD / 16) + (AccumSD / 16) + Carry

   If D2 < 10
    Carry = 0
   Else
    Carry = 1
    D2 = D2 - 10
   EndIf

   Poke Adr(Acc$) + 127 - XExpn + i, ((D2 * 16) ! D1)
  Next i

  i = 0

  While Not (Carry = 0) ' NB: No attempt is made to deal with overflow!

   AccumSD = Peek(Adr(Acc$) + 127 - XExpn + i)

   D1 = (AccumSD & 15) + Carry

   If D1 < 10
    Carry = 0
   Else
    Carry = 1
    D1 = D1 - 10
   EndIf

   D2 = (AccumSD / 16) + Carry

   If D2 < 10
    Carry = 0
   Else
    Carry = 1
    D2 = D2 - 10
   EndIf

   Poke Adr(Acc$) + 127 - XExpn + i, ((D2 * 16) ! D1)

   Dec i

  Wend

  ' Show accumulator

  Print "A= ";

  i = 1
  Do
   If Not (i < 256) Then Exit
   If Not (Peek(Adr(Acc$) + i) = 0) Then Exit
   Inc i
  Loop

  j = 255
  Do
   If Not (0 < j) Then Exit
   If Not (Peek(Adr(Acc$) + j) = 0) Then Exit
   Dec j
  Loop

  If Not (i <= j)
   Print "I guess it's zero"
  Else
   AccExp = 128 - i - 64
   For k = i To j
    If (i + 1) = k
     Print ".";
    ElIf (i + 1) < k
     Print "'";
    EndIf
    
    AccumSD = Peek(Adr(Acc$) + k)
    Print AccumSD / 16; AccumSD & 15;

   Next k

   Print "*100^"; AccExp

   ' Export accumulator to Atari FP

   Poke Adr(Z%) + 6, 128 - i ' 0 < i
   Poke Adr(Z%) + 7, Peek(Adr(Acc$) + i + 0)
   Poke Adr(Z%) + 8, Peek(Adr(Acc$) + i + 1)
   Poke Adr(Z%) + 9, Peek(Adr(Acc$) + i + 2)
   Poke Adr(Z%) + 10, Peek(Adr(Acc$) + i + 3)
   Poke Adr(Z%) + 11, Peek(Adr(Acc$) + i + 4)
   . If $50 <= Peek(Adr(Acc$) + i + 5) Then ' Should I try to round up or not?
   Print " = "; Z%(1)
  EndIf

 Loop

 While Key() : Get ZZ : Wend

 Print
 Print "OK"
 Get ZZ

 DPoke 82, LM

End


Proc FPDump Za

 Data Hex() Byte ROM = "0123456789ABCDEF"

 ' Hex dump

 Print ":", "$";
 For i = 0 to 5
  If i Then Print "'";
  j = Peek(Za + i)
  Print Chr$(Hex(1 + (j / 16)));  ' So which way of doing this is better?
  Print $(&Hex)[1 + (j & 15), 1]; ' I have absolutely no idea whatsoever!
 Next i
 Print

 ' Quick and dirty Str$(), sort of

 Print " = ";
 If (Peek(Za) & 128) Then Print "-";
 For i = 1 to 5
  j = Peek(Za + i)
  Print $(&Hex)[1 + (j / 16), 1];
  Print Chr$(Hex(1 + (j & 15)));
  If i = 1 Then Print ".";
 Next i
 Print " * 100 ^ "; (127 & Peek(Za)) - 64

EndProc

 

Link to comment
Share on other sites

  • 1 month later...

I am not a mathematician by any stretch, but my understanding of the anomalies inherent to Atari's floating point spec is indeed due to its base-100 exponents. This much has been discussed here already, but what appears to have escaped mention is that, despite the name, the decimal point isn't floating at all - it's fixed to the right of the first mantissa byte. You can only virtually "float" this decimal by manipulating the exponent value, but because the exponent is base-100, the decimal can only be moved in multiples of 2 positions in either direction. "Ok -=P=-, so what if it's necessary to move the decimal just one position?" I'm glad you asked! The work-around is to shift the mantissa digits rightward to mate it appropriately to the fixed decimal point when necessary, thereby truncating the 10th digit and consequently yielding only 9 digits of precision.

 

Perhaps they should have more appropriately labeled the specification, "floating precision"?

 

-=P=-

Unstretched mathematician.

 

Link to comment
Share on other sites

2 hours ago, -=Psycho=- said:

I am not a mathematician by any stretch, but my understanding of the anomalies inherent to Atari's floating point spec is indeed due to its base-100 exponents. This much has been discussed here already, but what appears to have escaped mention is that, despite the name, the decimal point isn't floating at all - it's fixed to the right of the first mantissa byte. You can only virtually "float" this decimal by manipulating the exponent value, but because the exponent is base-100, the decimal can only be moved in multiples of 2 positions in either direction. "Ok -=P=-, so what if it's necessary to move the decimal just one position?" I'm glad you asked! The work-around is to shift the mantissa digits rightward to mate it appropriately to the fixed decimal point when necessary, thereby truncating the 10th digit and consequently yielding only 9 digits of precision.

 

Perhaps they should have more appropriately labeled the specification, "floating precision"?

 

-=P=-

Unstretched mathematician.

 

Yup, multiplying or dividing by 10 can zap any tenth decimal digit. It's easy to see if you're using a version of Basic that makes it easy to hex-dump floats. It might be best to simply ignore the tenth decimal digit if there is one, like Atari BASIC appears to do, sometimes, when it feels like it

Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Loading...
  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...