Printing a floating point number
Posted: Mon May 22, 2017 5:21 pm
Dear Zeddy Fans,
Since debut of the last ROM improvements I dealt with the number printing on ZX81. I had several ideas and now I introduce 2 usable of them: a traditional and a dirty solution (the latter is my favourite).
The ZX81 BASIC Programming manual (chapter 27) "describes" the storage of the ZX81 number format only in few words: 5 bytes...
byte 1: the exponent - its value is 128 more than the real value (+128 offset)
(1 means 2^-127, 128 means 2^0 and 255 means 2^+127)
if its value is zero, then the whole number is zero (the next 4 bytes are also 0)
byte 2: the MSB of the mantissa - bit 7 is the signum bit
..
byte 5: the LSB of the mantissa.
The number is in normalized form (it is normalized to zero: zero is before the "binary point"), 0.5 =< mantissa value <1 -> the most significant bit is always 1, so it is not stored, this bit holds the sign of the number (0/1 -> +/-).
Let's begin with my favourite, the dirty solution:
The idea was to divide the full scale to 16 ranges, and store 16 base numbers in 10 digit BCD form, which represents the value of the least significant bit of the first number in the range. The ranges (and the BCD numbers also) are selected by the upper 4 bits of the binary exponent: $0x - $Fx.
At the start of the conversion the printable digits are '$00,$00,$00,$00,$00' (10 digits in 5 bytes). The chosen base number is the first value for the factor wich represents the value of a binary mantissa bit. If the mantissa bit is set then this factor will be added to the BCD number. Then the factor will be doubled to represent the value of the next mantissa bit, and so on... (similar to manual counting)
For example the mantissa is 170 (b10101010). The factor (the base number) is 1000000000 E0.
bin. man. the factor the value
bit0 = 0 1.000000000 E0 0.000000000 E0
bit1 = 1 2.000000000 E0 2.000000000 E0
bit2 = 0 4.000000000 E0 2.000000000 E0
bit3 = 1 8.000000000 E0 1.000000000 E1
bit4 = 0 1.600000000 E1 1.000000000 E1
bit5 = 1 3.200000000 E1 4.200000000 E1
bit6 = 0 6.400000000 E1 4.200000000 E1
bit7 = 1 1.280000000 E2 1.700000000 E2
This algorithm is very fast (see BNR16 on pict.1) but requires much more bytes of the code space than available. So I made 2 shorter variants also, using only one (BNR1) and two (BNR2) base numbers. Both are much faster than the original ZX81 solution but still not the real thing - the 'BNR2' was not short enough, and the 'BNR1' was not fast enough in the range of everyday use.
The traditional solution is based on conversion to scientific form (i.e. 12345 -> 1.2345 e4). The integer part of the normalized number gives the first printable digit, and the rest come from the multiplication (by ten) of the fractional part: 'MT10x'. I was not happy with the results. The next idea was to replace multiplication by addition: 10x = 2x + 8x, where the "multiply by 2" is a simple "increment by 1" of the binary exponent! This little "trick" made it much faster, but was not fast and short enough.
The next step was to rewrite the E-TO-FP routine using the new "multiply by 10" solution, and the calculator-calls were replaced with direct ROM-calls. These modifications made the algorithm much faster and it can fit into the ROM!!! (see pict.2 SG81/E_MT)
The "Base Number" solution (I did not want to let go) does not use the E-TO-FP routine. What uses this subroutine at runtime? It is solely the "VAL" function, if you enter text that represents a number in scientific format. This is not too common - I belive - so I changed the E-TO-FP to a shorter (slower) one to fit my dirty favourite into the ROM. The results are convincing (see pict.2 SG81/E_BN)
I ran another comparison in the 0..65535 (word sized integer) range, where the Speccy/SPONZY solution is very strong because of its "2 in 1" number format (see CHAPTER 24 of the manual)
The SG81/E_BN is fast enough in this case too.
Both variants have same print-out section, which is different (in 2 points) than the original:
- the first printed character is always a number (i.e. you see 0.01234 instead of .01234)
- if the number is less than 0.0001 or greater than 99999999, then it will show in normalized (scientific) format.
If you like any of these number printing solutions, then pls vote: Which should be the next improved ROM (SG81_E.ROM)
Thanks,
Zsolt
ps: During testing of the 'MT' solution I found a little bug in the FP-TO-BC routine.
If the number was less than zero (negative) but greater than -0.5, then it returned '-0'
Solved - both ROM-variants contain the improved routine.
Since debut of the last ROM improvements I dealt with the number printing on ZX81. I had several ideas and now I introduce 2 usable of them: a traditional and a dirty solution (the latter is my favourite).
The ZX81 BASIC Programming manual (chapter 27) "describes" the storage of the ZX81 number format only in few words: 5 bytes...
byte 1: the exponent - its value is 128 more than the real value (+128 offset)
(1 means 2^-127, 128 means 2^0 and 255 means 2^+127)
if its value is zero, then the whole number is zero (the next 4 bytes are also 0)
byte 2: the MSB of the mantissa - bit 7 is the signum bit
..
byte 5: the LSB of the mantissa.
The number is in normalized form (it is normalized to zero: zero is before the "binary point"), 0.5 =< mantissa value <1 -> the most significant bit is always 1, so it is not stored, this bit holds the sign of the number (0/1 -> +/-).
Let's begin with my favourite, the dirty solution:
The idea was to divide the full scale to 16 ranges, and store 16 base numbers in 10 digit BCD form, which represents the value of the least significant bit of the first number in the range. The ranges (and the BCD numbers also) are selected by the upper 4 bits of the binary exponent: $0x - $Fx.
At the start of the conversion the printable digits are '$00,$00,$00,$00,$00' (10 digits in 5 bytes). The chosen base number is the first value for the factor wich represents the value of a binary mantissa bit. If the mantissa bit is set then this factor will be added to the BCD number. Then the factor will be doubled to represent the value of the next mantissa bit, and so on... (similar to manual counting)
For example the mantissa is 170 (b10101010). The factor (the base number) is 1000000000 E0.
bin. man. the factor the value
bit0 = 0 1.000000000 E0 0.000000000 E0
bit1 = 1 2.000000000 E0 2.000000000 E0
bit2 = 0 4.000000000 E0 2.000000000 E0
bit3 = 1 8.000000000 E0 1.000000000 E1
bit4 = 0 1.600000000 E1 1.000000000 E1
bit5 = 1 3.200000000 E1 4.200000000 E1
bit6 = 0 6.400000000 E1 4.200000000 E1
bit7 = 1 1.280000000 E2 1.700000000 E2
This algorithm is very fast (see BNR16 on pict.1) but requires much more bytes of the code space than available. So I made 2 shorter variants also, using only one (BNR1) and two (BNR2) base numbers. Both are much faster than the original ZX81 solution but still not the real thing - the 'BNR2' was not short enough, and the 'BNR1' was not fast enough in the range of everyday use.
The traditional solution is based on conversion to scientific form (i.e. 12345 -> 1.2345 e4). The integer part of the normalized number gives the first printable digit, and the rest come from the multiplication (by ten) of the fractional part: 'MT10x'. I was not happy with the results. The next idea was to replace multiplication by addition: 10x = 2x + 8x, where the "multiply by 2" is a simple "increment by 1" of the binary exponent! This little "trick" made it much faster, but was not fast and short enough.
The next step was to rewrite the E-TO-FP routine using the new "multiply by 10" solution, and the calculator-calls were replaced with direct ROM-calls. These modifications made the algorithm much faster and it can fit into the ROM!!! (see pict.2 SG81/E_MT)
The "Base Number" solution (I did not want to let go) does not use the E-TO-FP routine. What uses this subroutine at runtime? It is solely the "VAL" function, if you enter text that represents a number in scientific format. This is not too common - I belive - so I changed the E-TO-FP to a shorter (slower) one to fit my dirty favourite into the ROM. The results are convincing (see pict.2 SG81/E_BN)
I ran another comparison in the 0..65535 (word sized integer) range, where the Speccy/SPONZY solution is very strong because of its "2 in 1" number format (see CHAPTER 24 of the manual)
The SG81/E_BN is fast enough in this case too.
Both variants have same print-out section, which is different (in 2 points) than the original:
- the first printed character is always a number (i.e. you see 0.01234 instead of .01234)
- if the number is less than 0.0001 or greater than 99999999, then it will show in normalized (scientific) format.
If you like any of these number printing solutions, then pls vote: Which should be the next improved ROM (SG81_E.ROM)
Thanks,
Zsolt
ps: During testing of the 'MT' solution I found a little bug in the FP-TO-BC routine.
If the number was less than zero (negative) but greater than -0.5, then it returned '-0'
Solved - both ROM-variants contain the improved routine.