ZX Basic Features

 

The ‘Division Error’

If you run the following program on a standard Spectrum, or on an emulator with the standard ROM, then the results are predictably correct.

 

10 PRINT .25 = 1/4

20 PRINT .9999999948

30 FOR I = 0 TO 1 STEP .25 : PRINT I : NEXT I : PRINT “end”

 

If you run the same program on an emulator using a modified ROM, with the corrected “division error”, then the results are somewhat inaccurate.

 

Normal ZX ROM                                                      Modified ROM

1                                                                                  0

.99999999                                                                 1

0                                                                                  0

0.25                                                                            0.25

0.5                                                                               0.5

0.75                                                                            0.75

1.0                                                                               end

end

Clearly something is amiss.

 

Floating-point Arithmetic

 

The ZX81 and the ZX Spectrum use a similar floating-point number representation and the following applies to both machines.

A four byte, 32-bit mantissa is used with the most significant bit, the implied set bit, being used as a sign bit for the number.

This bit is reset to denote a positive number and left set to denote a negative number.

The byte that precedes the mantissa is the exponent byte. This holds the number of times the imaginary decimal point that precedes the mantissa has to be moved to restore the number from it’s normalized state.

The normal state is when the bits of the mantissa are moved left (or right) so that the most significant bit is set.

A number like a half ( .1 binary ) or three-quarters ( .11 binary) is already normalized and has a plus zero exponent ( $80).

A number like a quarter  ( .01 binary )  is shifted left and the exponent decreased ( $7F ).

A vast range of numbers, of which 1/65536 through 65536 is just a small subset, can be held accurately with a mantissa consisting of four zero bytes. 

In contrast, just as a third cannot be held accurately in the decimal system (giving .33333 recurring), so a tenth cannot be held accurately in a binary mantissa and becomes .00110011 recurring. Only machines like the Jupiter Ace, which use Binary Coded Decimal, can hold a tenth accurately.

 

Table 1.

ROMS

ZX ROM

Div 34th

DEC-TO-FP

BOTH

Internal Storage

.5

7F 7F FF FF FF

80 00 00 00 00

80 00 00 00 00

80 00 00 00 00

½

80 00 00 00 00

80 00 00 00 00

80 00 00 00 00

80 00 00 00 00

.25

7E 7F FF FF FF

7F 00 00 00 01

7E 7F FF FF FF

7F 00 00 00 00

¼

7F 00 00 00 00

7F 00 00 00 00

7F 00 00 00 00

7F 00 00 00 00

.125

7D 7F FF FF FF

7E 00 00 00 01

7D 7F FF FF FF

7E 00 00 00 00

1/8

7E 00 00 00 00

7E 00 00 00 00

7E 00 00 00 00

7E 00 00 00 00

.0625

7C 7F FF FF FF

7D 00 00 00 01

7C 7F FF FF FF

7D 00 00 00 00

1/16

7D 00 00 00 00

7D 00 00 00 00

7D 00 00 00 00

7D 00 00 00 00

.03125

7C 00 00 00 00

7C 00 00 00 01

7C 00 00 00 00

7C 00 00 00 01

1/32

7C 00 00 00 00

7C 00 00 00 00

7C 00 00 00 00

7C 00 00 00 00

.015625

7B 00 00 00 00

7B 00 00 00 01

7B 00 00 00 00

7B 00 00 00 00

1/64

7B 00 00 00 00

7B 00 00 00 00

7B 00 00 00 00

7B 00 00 00 00

.0078125

79 7F FF FF FF

7A 00 00 00 01

7A 00 00 00 00

7A 00 00 00 00

1/128

7A 00 00 00 00

7A 00 00 00 00

7A 00 00 00 00

7A 00 00 00 00

.00390625

79 00 00 00 00

79 00 00 00 01

79 00 00 00 00

79 00 00 00 00

1/256

79 00 00 00 00

79 00 00 00 00

79 00 00 00 00

79 00 00 00 00

.001953125

77 7F FF FF FF

78 00 00 00 01

77 7F FF FF FF

78 00 00 00 00

1/512

78 00 00 00 00

78 00 00 00 00

78 00 00 00 00

78 00 00 00 00

.000976525

76 7F FF FF FF

77 00 00 00 01

76 7F FF FF FF

77 00 00 00 00

1/1024

77 00 00 00 00

77 00 00 00 00

77 00 00 00 00

77 00 00 00 00

Boolean Logic

.5 = 1/2

TRUE

TRUE

TRUE

TRUE

½  = .5

FALSE

TRUE

TRUE

TRUE

.25 = 1/4

TRUE

FALSE

TRUE

TRUE

¼ = .25

FALSE

FALSE

FALSE

TRUE

.125 = 1/8

TRUE

FALSE

TRUE

TRUE

1/8 =.125

FALSE

FALSE

FALSE

TRUE

.0625 = 1/16

TRUE

FALSE

TRUE

TRUE

1/16 = .0625

FALSE

FALSE

FALSE

TRUE

.03125 = 1/32

TRUE

FALSE

TRUE

FALSE

1/32 = .03125

TRUE

FALSE

TRUE

FALSE

.015625 = 1/64

TRUE

FALSE

TRUE

TRUE

1/64 = .015625

TRUE

FALSE

TRUE

TRUE

.0078125 = 1/128

TRUE

FALSE

TRUE

TRUE

1/128 = .0078125

FALSE

FALSE

TRUE

TRUE

.00390625=1/256

TRUE

FALSE

TRUE

TRUE

1/256=.00390625

TRUE

FALSE

TRUE

TRUE

Fractional Loop Steps

.5

CORRECT

CORRECT

CORRECT

CORRECT

.25

CORRECT

WRONG

CORRECT

CORRECT

.125

CORRECT

WRONG

CORRECT

CORRECT

.0625

CORRECT

WRONG

CORRECT

CORRECT

.03125

CORRECT

WRONG

CORRECT

WRONG

.015625

CORRECT

WRONG

CORRECT

CORRECT

.0078125

CORRECT

WRONG

CORRECT

CORRECT

Print values

.123456785

0.12345678

0.12345679

0.12345678

0.12345678

.100000015

0.10000001

0.10000002

0.10000001

0.10000002

.200000015

0.20000002

0.20000002

0.20000002

0.20000002

.876543215

0.87654322

0.87654322

0.87654322

0.87654322

.9999999948

0.99999999

1

0.99999999

0.99999999

Finally

0.1 storage

7D 4C CC CC CC

7D 4C CC CC CD

7D 4C CC CC CC

7D 4C CC CC CD

1/10 storage

7D 4C CC CC CC

7D 4C CC CC CD

7D 4C CC CC CC

7D 4C CC CC CD

1 storage

81 00 00 00 00

81 00 00 00 00

81 00 00 00 00

81 00 00 00 00

2 storage

82 00 00 00 00

82 00 00 00 00

82 00 00 00 00

82 00 00 00 00

5 storage

83 20 00 00 00

83 20 00 00 00

83 20 00 00 00

83 20 00 00 00

10 storage

84 20 00 00 00

84 20 00 00 00

84 20 00 00 00

84 20 00 00 00

 

The first section of the above table shows the internal format of a selection of numbers as stored in the ZX computers with the first column showing results for the standard ROM.

 

The Division Error (credit to Dr. Frank O’Hara)

This error, a failure to round the 34th bit in the division routine, leads to, for example;

0.5 having the floating-point form 7F 7F FF FF FF

but ½ having the floating-point form 80 00 00 00 00.

What the quoted book “Understanding Your Spectrum” fails to point out is that all the other decimal fractions from .25 down are rounded up too much leading to a failure of all comparisons of binary fractions less than a half. Similarly, for-next loops with a fractional step terminate before the limit value and, when printing decimal numbers, the window of accuracy is merely shifted downwards. The figures in the second column have been obtained from a ROM that implements the correction, as suggested, by jumping back further to the label DIV-34TH in the division routine.

 

The DEC-TO-FP routine

The Spectrum and ZX81 can successfully calculate ½ and have an accurate representation of a half in their table of constants so it would be illustrative to see how the evaluation of .5 arrives at a less than accurate answer.

When the ZX81 and Spectrum encounter a decimal digit in a Basic Line or expression then the number is parsed left to right. Digits before the decimal point are converted using routine INT-TO-FP.

If there are no digits preceding the decimal point then a zero is stacked as the starting value.

Then the number 1 is placed in a variable and a loop entered for each digit found.

For the first digit the variable (1) is fetched divided by ten, giving the inaccurate result  .1 (a tenth).

The result is then multiplied by the digit thereby compounding the inaccuracy. A half is calculated as .1 * 5.

On the next pass, the variable is further divided by ten giving one-hundredth for the multiplicand of the second digit.

It occurred to me that, rather than reduce the variable to an inaccurate component,  it would be best to multiply the variable by ten and divide the digit directly by the variable.

So, if the first digit is 5, then fetch the variable 1, multiply by ten and then divide giving 5/10 which leads to the accurate representation of a half. This only requires two bytes to be altered in the ROM as follows.

 

2CDA NXT-DGT-1 RST 0018,GET-CHAR

              CALL 2D22,STK-DIGIT

              JR   C,2CEB,E-FORMAT

              RST  0028,FP-CALC

              DEFB +E0, get-mem-0          ; fetch the variable (initially one)

              DEFB +A4, stk-ten            ;

              DEFB +04, multiply           ; (was +05, div) – now multiply

                                           ; giving 10,100, 1000 …

              DEFB +C0, st-mem-0           ; store the variable for next time through

              DEFB +05, division           ; (was +04, mult) – now division

                                           ; for example 5/10, 9/10.

              DEFB +0F, addition           ; add to running total.

              DEFB +38, end-calc           ; end calculator mode.

              RST  0020,NEXT-CHAR          ; move to next character.

              JR   2CDA,NXT-DGT-1          ; loop back to test for more digits.

 

The results for a ROM featuring this change are shown in the third column. 

 

A ROM featuring both changes

I then decided to produce a ROM featuring both changes. The results were initially very exciting with the internal representation being exact in all cases. I then hit a snag. The number .03125 was stored incorrectly. Furthermore the error was on the wrong side so that comparisons between .03125 and 1/32 didn’t work. This is something that the standard Spectrum performed correctly. The results are shown in the right hand column.

 

Conclusion

In conclusion, it can be seen that a number of modifications can affect the accuracy of ZX floating point division.

Altering the DEC-TO-FP routine to avoid imprecise intermediate values increases the accuracy without any known drawbacks. This particularly applies to the benchmark test ½ =.5.

Other schemes are not without their drawbacks, but, of the options available, the poorest choice is to implement Dr. Frank O’Hara’s division error correction on it’s own.

The original Spectrum ROM was not so bad as an all-rounder and there certainly never was a ‘division error’.  

 

References

The Complete ZX81 ROM Disassembly Part B, Dr. Frank O’Hara, 1982.

Understanding Your Spectrum, Dr. Ian Logan, 1982.

The Complete Spectrum ROM Disassembly, Dr Ian Logan and Dr. Frank O’Hara, 1983

Das Sinclair Spectrum ROM, R. Arenz and M. Görlitz, 1984.

The Pocket Guide to the Sinclair Spectrum, Steven Vickers, 1984.

The ZX Programmer’s Companion, John Grant and Catherine Grant, 1984.

Home