From
Ivan Roshin
→
To
All
12 June 2001
Hello, All!
═══════════════════ part_4 .T ══════════════════
By the way, usually the jump command is issued immediately
after comparison. But there may be other teams between them if they
do not affect the value of the flag used during the transition - this
sometimes allows you to shorten the program. Here's a specific example -
the last few commands from the table construction procedure
squares (see section "Multiplication"):
INC E ;if received E=0, flag Z will be set
ADD HL,DE ;this command does not affect the Z flag
JR NZ,M1 ;transition depending on the value of the Z flag
Multiplication
─────────
Let's first consider multiplying the accumulator by a small
a constant when the result of the operation fits in the same
battery As noted in [1], this can be conveniently done using
combinations of multiplications by two and additions. I want to appeal to you
Please note that this can usually be done in several
ways. Let us need to multiply the accumulator, for example, by
six:
First method: 6A=2(2A+A)
LD B,A
ADD A,A
ADD A,B
ADD A,A
Second method: 6A=2A+4A
ADD A,A
LD B,A
ADD A,A
ADD A,B
It would seem that both options are exactly the same, both in terms of
the number of bytes occupied, and by clock cycles. By themselves - yes.
But let’s imagine a situation where the multiplicand before the beginningoperation is loaded into register A from some other register
(for example, from the H register). Then the first method can be optimized
say: ADD A,A: ADD A,H: ADD A,A - and we will win 1 byte/4 clock cycles,
and besides, we won’t use register B.
It happens that the constant by which you want to multiply
battery value, “inconvenient” for the application described above
way. Of course, you can always use a universal
multiplication subroutine. But there are faster or shorter ones
methods - I’ll tell you about them.
The fastest way is using a table starting-
from an address that is a multiple of 256. In this case, the multiplication is performed
in just 18 cycles:
LD H,TABL/256
LD L,A
LD A,(HL)
If you need to perform several multiplications in a row, the command
LD H,TABL/256 is needed only before the first multiplication, and each
the following will be executed in 11 clock cycles.
A slow but space-saving way to multiply -
simple addition in a loop:
LD B,A
XOR A
M1 ADD A, constant
DJNZ M1
The drawback is immediately visible - if the battery was zero, then
the loop will be executed 256 times. We will still get the correct result,
but how much time will be lost! Of course you canintroduce an additional check for null, but this will increase the length
programs.
LD C,A
LD B,constant-1
M1 ADD A,C
DJNZ M1
And this option, occupying the same six bytes, provides
the same execution time for any values of A at the input.
True, this uses an additional register.
Let's move on to multiplying arbitrary numbers. How to do it
quickly?
In [4] a procedure is given for multiplying two 8-bit numbers "in
column" - almost the same as described in [1], but all cycles in
are revealed to her. This results in a significant gain in
speed - the procedure is completed in 218-275 cycles at
43 bytes long.
And even faster? The first thing that comes to mind is to use
multiplication table. True, it turns out that she is something too much
large: when both factors are 8-bit, the table must have
65536 two-byte products, which is 128 kilobytes!
What if we reduce the bit depth of the factors? With their total
With a length of 14 bits, the table size will be reduced to 32 kilobytes. Below
an example of performing multiplication is given for the case when the first
the factor is 6-bit, the second is 8-bit, and the table starts with
addresses #8000. To achieve the highest performance table
is arranged in such a way that the junior ones are located at addresses #8000-#BFFF
bytes of products, and at addresses #C000-#FFFF - high bytes.Input: H - first factor (0-63),
L - second factor (0-255).
Output: DE - product.
Length: 6 bytes.
Time: 30 bars.
SET 7,H ;HL := 10xxxxxx yyyyyyyy
LD E,(HL); read the low byte of the product
SET 6,H ;HL := 11xxxxxx yyyyyyyy
LD D,(HL); read the high byte of the product
════════════════════════ ════════════════════════
Best regards, Ivan Roshchin.
From
Ivan Roshin
→
To
All
12 June 2001
Hello, All!
═══════════════════ part_5 .T ══════════════════
The advantage of this method is that it is the fastest
(and if the total length of the factors does not exceed 8 bits, it
it can be even faster - after all, the product fits in a byte).
If the total length of the factors is more than 14 bits, you can
sacrifice accuracy for speed, discarding their minor
ranks.
The disadvantage is also obvious - a huge table (however, with
decreasing the total length of the factors, it can be much
less). But if there is not enough memory or if you need it exactly
multiplying large numbers, this method will not work.
More economical in terms of memory footprint and almost
The logarithmic method of multiplication is just as fast,
based on the following formula (x, y are factors, A is
base of logarithms):
(log x + log y)
xy = A
To multiply 8-bit numbers you will need two tables,
located in memory one after another, starting from a multiple of 256
addresses. The first, 256 bytes long, contains logarithms of numbers from 1
up to 255, multiplied by some coefficient k (so that k *
log 255 = 127 - this is necessary for the addition result to fit into
byte). The second, 512 bytes long, contains the results of constructing
bases of logarithms A to powers from 0 to 254/k: in the first
half of the table - the low bytes of the results, in the second -elder. The base of logarithms can be any, tables from this
will not change. So, in the table calculation program given below
BASIC uses natural logarithms.
10 CLEAR 32767
20 LET K=127/LN 255
30 FOR I=1 TO 255
40 POKE (32768+I),INT (.5+K*LN I)
50 NEXT I
60 FOR I=0 TO 254
70 LET E=INT (.5+EXP(I/K))
80 LET H=INT (E/256): LET L=E-H*256
90 POKE (32768+256+I),L: POKE (32768+512+I),H
100 NEXT I
110 RANDOMIZE USR 15619: REM: SAVE "TABL_LOG" CODE
32768.256+512
Example of performing multiplication:
Input: B - first factor, L - second factor.
Output: DE - product.
Length: 10 bytes.
Time: 51 bars.
LD H,TABL/256
LD A,(HL)
LD L,B
ADD A,(HL)
INC H
LD L,A
LD E,(HL)
INC H
LD D,(HL)
This method has its drawbacks: firstly, it is not suitable
for the case when one of the factors is equal to zero, and secondly,
it is not absolutely accurate: the maximum relative
the error is 4.4%. However, the accuracy can be improved by
increasing the size of the tables.
In [20] you can find procedures for fast logarithmic
multiplying 8-bit operands (both signed and unsigned),
calculating the most significant byte of the 16-bit product. It is also indicated there
a way to solve the zero factor problem that does not require
additional checks. Also there is a procedure, quicklycalculating x*sin(y) - it may be useful, for example,
when working with three-dimensional graphics.
Now let's look at a way to accurately and quickly
multiplication using a table of squares using formulas taken from
[2]:
xy = 1/2 [(x+y)^2 - x^2 - y^2]
or
xy = 1/4 [(x+y)^2 - (x-y)^2]
The calculation for the first of them involves one addition, three
table accesses, two subtractions, one right shift; by
the second is addition, two subtractions, two table references and
two shifts to the right.
To get rid of division by 2 in the first formula and from
division by 4 in the second, it is enough to store it in a table of squares
already divided by 2 or 4 values. Multiplication will be
faster, but will become inaccurate, especially at small
values of the factors.
The second one is better suited for implementation in Z80 assembler
formula, since access to the table is performed comparatively
slowly, and there is one less request.
Below is a software implementation of the second formula for
multiplying 8-bit numbers whose sum does not exceed 256.
A table must be placed from the address TABL_SQR, a multiple of 256
squares of numbers from 0 to 255 (low bytes first, then
elders).
════════════════════════ ════════════════════════
Best regards, Ivan Roshchin.
From
Ivan Roshin
→
To
All
12 June 2001
Hello, All!
═══════════════════ part_6 .T ══════════════════
Input: D - first factor, E - second factor.
Output: DE - product.
Length: 28 bytes - the procedure itself + 512 bytes - the table of squares.
Time: 125 or 128 bars - depending on which one
the factor is greater than the second or vice versa (if in advance
it is known that one is always larger than the other, the procedure can be
simplify).
LD H,TABL_SQR/256
LD B,H
LD A,D
ADD A,E
LD C,A ;C=x+y
LD A,D
SUB E
JR NC,M1
N.E.G.
M1 LD L,A ;L=│x-y│
LD A,(BC)
SUB (HL)
LD E,A
INC H
INC B
LD A,(BC)
SBC (HL) ;CY is reset
RRCA
RR E ;in CY advanced 0!
RRCA
RR E
LD D,A ;DE=x*y
RET
In [3] a procedure based on a similar principle is given
multiplying two 8-bit numbers, free from the "sum" constraint
factors are less than 256", but it is much longer, and
executed more slowly: in 141-207 cycles.
As for 8-bit signed numbers (-128..127), then
a similar procedure for their multiplication can be found in [10]. Ibid.
a procedure for fast inexact multiplication with
using the formula xy=(y+(x/2-y/2))^2-(x/2-y/2)^2.
From the above formulas it is clear that for multiplying numbers
with bit depth n, a table of squares containing 2^n is requiredvalues, each of which takes n/4 bytes. For 8-bit
numbers, the table size is 512 bytes, and for 16-bit numbers it is narrower
65536*4=262144 bytes! Such a table is obviously not in memory
will fit. How can this be? In this case one can imagine
the product of numbers with bit depth n as a sum of products
numbers of bit size n/2. Suppose we need to multiply two
16-bit numbers x and y. Let's break them down into parts: x1 - eldest
part x, x2 - minor; y1 and y2 - similar. We get:
xy = (256x1+x2)(256y1+y2) = 65536x1x2+256(x1y2+x2y1)+x2y2
If you have to sacrifice accuracy to increase speed,
the last term can be neglected: it affects the result
insignificant. Then multiplying two 16-bit numbers
will be reduced to the addition of three products of 8-bit numbers.
Formation of a table of squares of numbers from 0 to 255 is necessary
mine for multiplication is a separate interesting question. It is possible in advance
calculate the table in BASIC and include it in the program, but this
very wasteful. It is much more profitable to create a table directly
exactly in assembler. At the same time, one should strive to ensure that
the procedure for its creation was as short as possible. Her working hours are not
so important, because usually it is called only once in all time
operation of the program (by the way, then it is not at all necessary to register
a separate procedure and calling it using CALL is more profitabledirectly insert it into the program text, saving on this
four bytes).
When creating a table, the following property is used:
the square of the number n is equal to the sum of n consecutive odd numbers
numbers: so, for example, 4^2 = 1+3+5+7 = 16. Thus,
adding another odd number, we get another
square value.
[9] provides a 16-byte program fragment that is very fast
creating a table of squares, but at the same time manipulating
tions with a stack, the table turns out to be inverted, and double-byte
the values of the squares are placed in it in a row, while for
fast access to the table it is necessary that the low bytes
squares were in the first 256 bytes of the table, and the most significant ones were in
next 256.
In [3], a 20-byte procedure is given that creates both
just the table you need. After some thought, I shortened
its by two more bytes (and for a 20-byte procedure two bytes are
a lot!). In addition, my version of the procedure, unlike
original, does not use the BC register pair. So here it is:
XOR A
LD H,A
LD L,A
LD E,A
M1 LD D,TABL_SQR/256
EX DE,HL
LD(HL),E
INC H
LD(HL),D
EX DE,HL
LD D,A ;=LD D,0
ADD HL,DE
INC E
ADD HL,DE ;flag Z is not affected
JR NZ,M1
RET
By the way, such a table is sometimes needed not only for multiplication andsquaring, but also (as we will see later) for
quickly finding the square root, as well as for approximate
calculations of sine and cosine.
════════════════════════ ════════════════════════
Best regards, Ivan Roshchin.
From
Ivan Roshin
→
To
All
12 June 2001
Hello, All!
═══════════════════ part_7 .T ══════════════════
Let's now talk about another way to quickly multiply,
described in [2]. This rather exotic method is based on
a special form of representing numbers - recording in remainders.
Suppose we need to perform calculations with numbers in the range
from 0 to N-1. Let us choose several relatively prime numbers s1, s2,...,
sm in such a way that their product is not less than N:
s1*s2*...*sm >= N.
Then each number can be uniquely written as remainders of
divisions by s1, s2,..., sm. Let's look at this with an example: let
N=30, and 2, 3 are chosen as coprime numbers s1, s2,...
and 5 (in this case, the condition s1*s2*s3>=N, as we see, is satisfied).
Write all 30 numbers in remainders divided by 2, 3 and 5
is given below. The first digit in the entry is the remainder of division by 5,
the second - by 3, the third - by 2 (however, you could also choose
any other recording order).
┌───────┬─────────────────── ╥───────┬───────────────────┐
│ Number │ Entry in balances ║ Number │ Entry in balances │
├───────┼─────────────────── ╫───────┼───────────────────┤
│ 0 │ 000 ║ 15 │ 001 │
│ 1 │ 111 ║ 16 │ 110 │
│ 2 │ 220 ║ 17 │ 221 │
│ 3 │ 301 ║ 18 │ 300 ││ 4 │ 410 ║ 19 │ 411 │
│ 5 │ 021 ║ 20 │ 020 │
│ 6 │ 100 ║ 21 │ 101 │
│ 7 │ 211 ║ 22 │ 210 │
│ 8 │ 320 ║ 23 │ 321 │
│ 9 │ 401 ║ 24 │ 400 │
│ 10 │ 010 ║ 25 │ 011 │
│ 11 │ 121 ║ 26 │ 120 │
│ 12 │ 200 ║ 27 │ 201 │
│ 13 │ 311 ║ 28 │ 310 │
│ 14 │ 420 ║ 29 │ 421 │
└───────┴─────────────────── ╨───────┴───────────────────┘
An important advantage of this method of representing numbers
is that addition, subtraction and multiplication turn out to be
simple bitwise operations. To add (subtract,
multiply) two numbers, just add (subtract, multiply)
values in each of the digits modulo this digit. Let
For example, we need to multiply 9 by 3:
9 leftovers = 401
3 leftovers = 301
We perform multiplication for each digit:
4*3=12, take modulo 5 - we get 2
0*0=0
1*1=1
We received the value 201, look at the table - as expected,this corresponds to the number 27. As you can see, everything is quite simple.
To work with numbers in the range 0-255, it is convenient to select
three prime numbers 2, 11 and 13. Why did I choose these three?
numbers? Number 2 - because the operation of multiplying remainders 0, 1 by
module 2 can be carried out directly with the AND command, but
numbers 11 and 13 are chosen as the smallest for which the product
all three numbers are greater than 256 (2*11*13=286).
The following procedure serves to introduce
number in the remainder. To increase its speed, it uses
two 256-byte tables located in memory one after the other
and starting with the TMOD address, a multiple of 256. In the first table
the remainders from dividing the numbers 0-255 by 11 are given, in the second -
remainders from division by 13. These tables are formed quite simply:
the first is cyclically filled with the numbers 0,1,2,...,10, and the second -
numbers 0,1,2,...,12.
Input: A - the number to be converted.
Output: A,D,E - remainders from dividing this number by 2, 11, 13.
Length: 9 bytes - the procedure itself + 512 bytes - tables.
Time: 46 bars.
LD H,TMOD/256 ;high byte of the address of the first table
LD L,A ;the number serves as an index in the table
LD D,(HL) ;D - remainder of division by 11
INC H ;move to second table
LD E,(HL);E - remainder of division by 13
AND 1 ;A - remainder of division by 2
RET ;outputThe following procedure performs the inverse task: restoring
Divides the number A by the remainders of division by 2, 11 and 13 (we denote
their A mod 2, A mod 11 and A mod 13). For this purpose it is used
512-byte table TABL_CR, starting at a multiple of 256 address,
in which by displacement (A mod 2)*256+(A mod 11)*16+(A mod 13)
is the number A. To quickly multiply by 16 use
table TABL_M16, also starting with a multiple of 256 address.
Input: B,L,A - remainders from division by 2, 11, 13.
Output: A - number.
Length: 10 bytes - the procedure itself + 768 bytes - tables.
Time: 50 ticks.
LD H,TABL_M16/256 ;high byte of table address TABL_M16
ADD A,(HL) ;in (HL) is (A mod 11)*16
LD L,A ;L:=(A mod 11)*16+(A mod 13)
LD A,TABL_CR/256 ;add to the high byte of the address
ADD A,B ;table TABL_CR value (A mod 2)
LD H,A ;now HL is the address in the table
LD A,(HL) ;take the number from the table
RET ;output
Well, here’s what all this was started for - the procedure
multiplying two numbers with remainders. From an address divisible by 256
there is the previously discussed table TABL_M16; from the address
256 more, there is a table for quick multiplication
numbers modulo 11, in which at offsets 16x+y there are
values (x*y) mod 11; from an address that is another 256 bytes larger,There is a similar table for multiplication modulo 13.
════════════════════════ ════════════════════════
Best regards, Ivan Roshchin.
From
Ivan Roshin
→
To
All
12 June 2001
Hello, All!
═══════════════════ part_8 .T ══════════════════
Input: B,D,E - remainders from dividing the 1st factor by 2, 11, 13;
A,C,L - remainders from dividing the 2nd factor by 2, 11, 13.
Output: B,D,E - remainders from dividing the product by 2, 11, 13.
Length: 17 bytes - the procedure itself + 768 bytes - tables.
Time: 85 bars.
AND B ;calculated the remainder of the product divided by 2
LD B,A ;and placed in B
LD H,TABL_M16/256
LD A,E
OR(HL)
LD E,A ;E now contains division remainders
;factors by 13 (packed form)
LD L,D
LD A,C
OR(HL)
LD L,A; similarly, in L - remainders from division by 11
;in packaged form
INC H
LD D,(HL);multiplied the remainders by 11 using
;tables
LD L,E
INC H
LD E,(HL); we do the same with remainders from division by 13
RET ;output
To perform unit multiplication it seems like this
the method is not suitable: for converting numbers to remainders and vice versa
It will take longer than the multiplication itself. But if necessary
perform a long chain of calculations, transforming the result
Only in the end, the time savings can be quite significant.
Division
───────
Let's start by dividing by powers of two (2, 4, 8...). In [1]
It was mentioned that such division is convenient to perform using the commandslogical shift: when the dividend is in the accumulator -
SRL A; when the dividend is in the register pair HL - SRL H:
RR L, and so on. In many cases, however, these methods can be
significantly optimized. Due to what?
There are four rotation commands in the Z80 command system (i.e.
cyclic shift) of the number located in the accumulator: RLCA,
RLA, RRCA and RRA. These commands perform left/right rotation,
simple or via the CY flag. Their peculiarity is that they
occupy one byte of memory and are executed in four clock cycles, while
while all other register shift and rotation commands (in
including SRL) occupy two bytes of memory and are executed in
eight bars. So, in many cases it is possible to use
these quick and short commands instead of slow and long ones, for
which results in a gain in volume and/or speed
programs.
Let the dividend be in the accumulator. To divide by two
the SRL A instruction is used (2 bytes/8 clock cycles), and in general
you won't save anything here. But if it is known that the CY flag is cleared,
it is enough to use the RRA command (1 byte/4 clock cycles). If
the state of CY is not known, but we know that the dividend is even, we can
use the RRCA instruction for division (the same 1 byte/4 clock cycles).
Never put several SRL A commands in a row so that
dividing the accumulator value by 4, 8, 16... is not profitable!To divide by 4, 8, and 16, use the RRCA commands, and to divide by
at 32, 64 and 128, move the battery in the opposite direction
RLCA commands, since the total number of shifts will be
less. Then use the AND command to clear the most significant bits of the result.
Example: dividing the accumulator by four.
Was: SRL A ;2 bytes, 8 clock cycles
SRL A ;2 bytes, 8 clock cycles
─────────────────────────────────
Total: 4 bytes, 16 clock cycles
Now: RRCA ;1 byte, 4 clock cycles
RRCA ;1 byte, 4 clock cycles
AND %00111111 ;2 bytes, 7 clock cycles
─────────────────────────────────
Total: 4 bytes, 15 clock cycles
In this example, the gain was only one clock cycle. But when
dividing by 8, 16, etc. we will gain both in the length of the program and in
speed of its implementation. If you need to do more than three
divisions in a row, you can write the second operand of the command in advance
AND into a register and then save 1 byte/3 clock cycles on each AND.
Well, if it is known that the division will be an entire division, the AND command in general
not needed.
Let the dividend be not in the accumulator, but in some
another register or memory location. Sometimes it pays first
send it to the accumulator, perform division, and then
send the result back.
Example: Dividing the E register by 32.Was: SRL E ;2 bytes, 8 clock cycles
SRL E ;2 bytes, 8 clock cycles
SRL E ;2 bytes, 8 clock cycles
SRL E ;2 bytes, 8 clock cycles
SRL E ;2 bytes, 8 clock cycles
─────────────────────────────────
Total: 10 bytes, 40 clock cycles
Now: LD A,E ;1 byte, 4 clock cycles
RLCA ;1 byte, 4 clock cycles
RLCA ;1 byte, 4 clock cycles
RLCA ;1 byte, 4 clock cycles
AND %00000111 ;2 bytes, 7 clock cycles
LD E,A ;1 byte, 4 clock cycles
─────────────────────────────────
Total: 7 bytes, 27 clock cycles
However, if to divide an 8-bit operand into two instructions
rotation or shift - no competition, then division by four
can be done faster using the table method.
════════════════════════ ════════════════════════
Best regards, Ivan Roshchin.
From
Ivan Roshin
→
To
All
12 June 2001
Hello, All!
═══════════════════ part_9 .T ══════════════════
Let us now consider a possible optimization option for the case
when the dividend is located in a register pair. When divided by
8, 16, 32... it’s advantageous to do this: send the younger one first
byte of the dividend into the accumulator, and then shift the high byte
by the SRL team, and the younger one by the RRA fast team. After graduation
division, the low byte is sent back from the accumulator.
Example: dividing HL by 8.
Was: SRL H ;2 bytes, 8 clock cycles
RR L ;2 bytes, 8 clock cycles
SRL H ;2 bytes, 8 clock cycles
RR L ;2 bytes, 8 clock cycles
SRL H ;2 bytes, 8 clock cycles
RR L ;2 bytes, 8 clock cycles
──────────────────────────
Total: 12 bytes, 48 clock cycles
Now: LD A,L ;1 byte, 4 clock cycles
SRL H ;2 bytes, 8 clock cycles
RRA ;1 byte, 4 clock cycles
SRL H ;2 bytes, 8 clock cycles
RRA ;1 byte, 4 clock cycles
SRL H ;2 bytes, 8 clock cycles
RRA ;1 byte, 4 clock cycles
LD L,A ;1 byte, 4 clock cycles
──────────────────────────
Total: 11 bytes, 44 clock cycles
To find the remainder of division by 2^n, it is enough to leavewith the AND command the low n bits of the dividend. For example, the remainder of
dividing the accumulator by 8 (8=2^3) is calculated using the AND %111 command.
A few words about signed division by powers of two. For
dividing an 8-bit operand uses the arithmetic instruction
shift right SRA, and adapt quick rotation commands here to
Unfortunately, it won't work (although if the sign of the operand is known...).
For a 16-bit operand you can use the same trick
optimizations as with unsigned division (see example above),
only instead of the SRL command there will be SRA.
By the way, about shifts. Let's say we need to split unsigned
the number located in the memory cell at address (IX+7) by two, and
then load the result into the accumulator. You can do this:
SRL (IX+7) ;4 bytes, 23 clock cycles
LD A,(IX+7) ;3 bytes, 19 clock cycles
─────────────────────────────────
Total: 7 bytes, 42 clock cycles
But it is much more profitable to do this with one command:
SRL A,(IX+7) ;4 bytes, 23 clock cycles
“I don’t remember such a team,” someone will say, and there will be
is right in its own way. Indeed, in the description of the Z80 command system you
you won’t find it: it’s not documented. Below is the complete
list of undocumented commands that allow you to perform a shift
or rotation of a number in a memory cell addressed by (IX+s) or (IY+s),
and then load the result into one of the registers A, B, C, D, E, H, L.┌────────────────┬─────────────────────┐
│ Мнемоника │ Машинные коды │
├────────────────┼─────────────────────┤
│ RLC reg,(IX+s) │ #DD #CB s %00000reg │
│ RLC reg,(IY+s) │ #FD #CB s %00000reg │
│ RRC reg,(IX+s) │ #DD #CB s %00001reg │
│ RRC reg,(IY+s) │ #FD #CB s %00001reg │
│ RL reg,(IX+s) │ #DD #CB s %00010reg │
│ RL reg,(IY+s) │ #FD #CB s %00010reg │
│ RR reg,(IX+s) │ #DD #CB s %00011reg │
│ RR reg,(IY+s) │ #FD #CB s %00011reg │
│ SLA reg,(IX+s) │ #DD #CB s %00100reg │
│ SLA reg,(IY+s) │ #FD #CB s %00100reg │
│ SRA reg,(IX+s) │ #DD #CB s %00101reg │
│ SRA reg,(IY+s) │ #FD #CB s %00101reg │
│ SLI reg,(IX+s) │ #DD #CB s %00110reg │
│ SLI reg,(IY+s) │ #FD #CB s %00110reg │
│ SRL reg,(IX+s) │ #DD #CB s %00111reg │
│ SRL reg,(IY+s) │ #FD #CB s %00111reg │
└────────────────┴─────────────────────┘
Все указанные в таблице команды выполняются за 23 такта. В
машинных кодах вместо reg подставляются три бита 111,000,001,
010,011,100 и 101 соответственно для регистров A,B,C,D,E,H,L.
Если же подставить три бита 110 - получится обычная, документи-
рованная разновидность команды. Как это можно объяснить? Приexecuting a command to shift or rotate the contents of a memory cell
Z80 reads a byte from there into some register, performs the necessary
action (shift or rotation), and then writes the result to that
same memory cell. In this case, the result remains in the register. Yes
here, three bits 110 correspond to the special “service” one,
register of the Z80 hidden from the user, which is why it seems to us
that the command acts on a number in memory without changing
register contents. Well, if as an “official”
use one of the normal registers, as a side effect
commands will change the contents of this register.
Why did I indicate machine command codes in the table? The point is
that most assemblers (if not all) do not understand such
mnemonic, and you have to write the desired command using
DB directives (define byte). Here, for example, is how it will be written
already mentioned command SRL A,(IX+7):
DB #DD,#CB,7,%00111111
When disassembling, the STS debugger monitor displays
mnemonics for these undocumented commands, however, does not allow
introduce them.
Yes, you probably noticed an unfamiliar mnemonic in the table -
SLI. This command itself is undocumented and
performs a left shift, writing 1 to the least significant bit (otherwise
speaking, multiplies the operand by two while adding
units). Sometimes it can be useful. SLI mnemonicsupported by most assemblers and the STS debugger.
════════════════════════ ════════════════════════
Best regards, Ivan Roshchin.
From
Ivan Roshin
→
To
All
12 June 2001
Hello, All!
═══════════════════ part_10 .T ══════════════════
Let's now talk about quickly dividing the battery into
a constant that is not a power of two. Here's one way
such a division, which I learned about from the DEMO.DESIGN echo conference.
Let's say we need to divide by three. Approximately
we can assume that three is 8/3 (in fact, 8/3 = 2.66...).
Then A/3 ~= 3A/8, and the division will look like this:
LD B,A
ADD A,A
ADD A,B
RRCA
RRCA
RRCA
AND %00011111
31 cycles - faster than the universal subroutine, but
but less accurate. However, the relative error in this case
is constant, and it can be reduced by taking a more accurate approximation -
let's say 3 ~= 16/5.
If the goal is to complete the division as quickly as possible, and the flow rate
memory is not so important, you can use the tabular method. He
allows division by any constant using
256-byte table located from an address that is a multiple of 256. When
this, if you place the high byte of the table address in advance in
register H, division will be carried out in just 11 clock cycles:
LD L,A
LD A,(HL)
Similarly for the case when the dividend is in another
register (for example in C):
LD L,C
LD C,(HL)
Let's move on to dividing arbitrary numbers. Various procedures
divisions “in a column” have already been given in [1]. Faster thandescribed there, the procedure for dividing 16-bit numbers you can
found in [22] (the quotient and remainder are calculated, the maximum time
execution - 840 cycles excluding RET; if the rest is not needed -
818 cycles).
What about faster ways? Compared to multiplication, there are no
so much (at least that’s how it worked out for me)
impression). Here I will briefly talk about three of them.
The first is division according to a table in which there are already
pre-calculated x/y division results for all possible
x and y. It is implemented similarly to multiplication by table (see.
previous section). The advantages and disadvantages are the same.
The second is replacing division by multiplying by one taken from the table
reciprocal: x/y = x * 1/y. From the precision of the reciprocal
The accuracy of division will also depend. By the way, this way
were used in ancient Babylon (beginning of the 2nd millennium BC).
If the amount of free memory is limited and the values in the table
cannot be represented with the necessary accuracy, one can use
the method of iterative calculation of the inverse considered in [2]
quantities. Let there be some initial approximation (say,
taken from the table): k1 ~= 1/y. Then the next approximation will be
like this: k2 = k1(2-y*k1), and so on. If relative
the approximation error is quite small compared to unity,
then at each iteration the number of correct signs will bedouble: so if 32 bit precision is required,
and the first approximation is found from a table with 8 correct binary
signs, then only two iterations are required.
There are also more complex iteration formulas. Below are
two of them: each iteration according to the first formula triples
the number of correct signs, and according to the second formula - quadruples.
k = k [3(1-y*k ) + (y*k )^2]
i+1 i i i
k = k [6(4-y*k ) + (y*k )^2(4-y*k ) - 20]
i+1 i i i i
By the way, the previously discussed method of approximate division by
a constant is nothing more than multiplication by the reciprocal value,
only the size of this reciprocal is limited to a few
bits.
The third method is logarithmic division, based on
the following formula (A is the base of the system of logarithms):
(log x - log y)
x/y = A
An example of a procedure that performs such a division can be
found in [10].
As we can see, the use of logarithms allows us to significantly
simplify and speed up calculations due to the fact that multiplication and
division is reduced to addition and subtraction, and exponentiation
and root extraction (see next section) - to multiplication and
division.
One of the greats said: “The invention of logarithms made it possible
in a matter of days, perform the calculations that were previouslywere done for months." Well, in our time, logarithms allow
perform the calculations that were done before in a hundred clock cycles
for a thousand.
════════════════════════ ════════════════════════
Best regards, Ivan Roshchin.
From
Ivan Roshin
→
To
All
12 June 2001
Hello, All!
═══════════════════ part_11 .T ══════════════════
Square and other roots. Raising to any degree.
─────────────────────────── ───────────────────────────
In [1] a method was described for calculating the integer part
square root by sequential subtraction of odd
numbers and the iterative method of calculating the square root.
What else can you add to this?
The classic algorithm for calculating the square root "in
column" is described in detail in [2].
A procedure that calculates the square root using a modified
Newton’s algorithm for 1500-2500 cycles can be found in [3].
A fairly fast procedure for calculating the square root
given in [14]. Its operating time (excluding the RET command) is from
294 to 318 clock cycles with argument value 0-255 and from 581 to 629
clock cycles with the argument value 256-65535.
This is not the limit: using the previously described in section
"Multiplication" table of squares of numbers from 0 to 255, you can calculate
the root value is on average 1.3 times faster. Here's the relevant one
algorithm (x - function argument, sq_tab - table of squares):
1. i:=#80, n:=#40.
2. If x < sq_tab(i), then i:=i-n, otherwise i:=i+n.
3. n:=[n/2]; if n<>0, then go to point 2.
4. If x < sq_tab(i), then i:=i-1.
5. i - the desired value of the root.
Here we take advantage of the fact that the function y=x^2 is monotonicallyincreases as the argument changes from zero to infinity, and
This means that the table of squares is ordered by
ascending array of numbers.
Here is the text of the procedure:
Input: DE - argument.
Output: L is the integer part of the square root.
Length: 56 bytes - the procedure itself + 512 bytes - the table of squares.
Time: on average 470 clock cycles if the argument values are uniform
distributed in the range 0-65535.
LD B,#40 ;in register B we place n (#40),
LD HL,SQ_TAB+#180 ;and in the L register - i (#80)
;Compare x and sq_tab(i), starting from the highest byte, because
;it is more likely that the values will be different in the older
;byte than in the younger one:
M4 LD A,D
CP(HL)
JR C,M2 ;probabilities of fulfillment and non-fulfillment
;the conditions are the same - we set JR (on average
;(7+12)/2 = 9.5 bars)
JP NZ,M1; and here the condition will be almost fulfilled
;always - set JP (10 bars)
;If the high bytes are equal, compare the low bytes:
DEC H
LD A,E
CP(HL)
INC H ;INC does not affect the CY flag
JR C,M2 ;probabilities are the same - set JR
;x >= sq_tab(i):
M1 LD A,L ;i:=i+n
ADD A,B
LD L,A
SRL B ;n:=[n/2]
JP NZ,M4 ;the condition will be satisfied in six cases;out of seven - put JP
LD A,D ;fourth point of the algorithm -
CP (HL); compare high bytes...
JR C,M5
RET NZ
LD A,E ;...and younger ones.
DEC H
CP(HL)
RET NC
M5 DEC L ;i:=i-1
RET
;x < sq_tab(i):
M2 LD A,L ;i:=i-n
SUB B
LD L,A
;The following is similar to what has already been discussed...
SRL B ;n:=[n/2]
JP NZ,M4
;Here you could put the jump command (JP) on the same
;a fragment of the program encountered above, but we would have lost on
;this command is ten bars...
LD A,D
CP(HL)
JR C,M3
RET NZ
LD A,E
DEC H
CP(HL)
RET NC
M3 DEC L
RET
════════════════════════ ════════════════════════
Best regards, Ivan Roshchin.
From
Ivan Roshin
→
To
All
12 June 2001
Hello, All!
═══════════════════ part_12 .T ══════════════════
In [22], procedures for extracting the root from a 16-bit
and 24-bit operands without using tables. Maximum
their operating time is 470 and 1026 cycles, respectively (excluding
RET).
A very fast extraction method is described in detail in [21].
root based on the following formula: if x is represented in
form m*2^n (where n is even), then sqr(x)=sqr(m)*2^(n/2). B
the procedure given there for extracting the root from a 16-bit
operand all calculations are performed using common tables
volume of 3 KB, and is spent on this, excluding the RET command, in total
76 bars! It also shows how to reduce computation time further.
by 3 clock cycles due to an increase in space for tables by 2 KB.
Well, the fastest way, of course, is to take the value of the root
from pre-calculated and placed in additional
memory ZX Spectrum 128 full 64 KB square table
roots. Let the values of roots for numbers be stored in 4 banks of RAM
0-#3FFF, #4000-#7FFF, #8000-#BFFF and #C000-#FFFF. Then to
take the root value from the table, you can use this
program fragment:
Input: DE - argument.
Output: A is the integer part of the square root.
Length: 12 bytes - the fragment itself, 256 bytes - the tab_bank table
(to determine the number from the two most significant bits of the argument,
which must be output to port #7FFD to connect the bankRAM in which the root value for a given
argument), 256 bytes - table for quick setting to 1
two most significant bits of the argument (this table should go
immediately after the previous one, and they must begin with addresses,
multiples of 256), 64 KB - table of roots.
Time: 58 bars.
LD BC,#7FFD
LD H,tab_bank/256 ;Determined by the two most significant bits DE
LD L,D ;RAM bank where the value is located
LD A,(HL) ;root.
OUT (C),A ;Connected the desired bank.
INC H ;Set the most significant 2 bits DE
LD D,(HL) ;in 1 to get the desired address.
LD A,(DE) ;Read the value of the root.
An iterative method for finding the root of any
degrees, and as an example the procedure for calculating
cube root.
And finally, the root of any degree can be calculated with
using logarithms, using the following formula (A - base
logarithms):
n -- (logx/n)
/x = A
Now a few words about exponentiation. If the degree
whole, then you can use the information taken from [8]. There
two methods of exponentiation are described: the first is based on
sequential multiplication (for example, x^4=x*x*x*x), and the second,
faster, uses expansion of the exponent intofactors that are multiples of two (for example, x^5=((x^2)^2)*x). Ibid.
You can also find the texts of the relevant procedures.
If the degree is not an integer, the following formula will be convenient
(again using logarithms):
n(logx*n)
x = A
If the speed of calculations using this formula seems to you
insufficient, you can use a table with already calculated
in advance with the values of x^n for all possible x and n - if only for
she had enough memory.
Trigonometry
─────────────
In [1] it is proposed to calculate sine and cosine
use a table of sines of angles from 0 to 90 in hundredths, with
in steps of one degree. But for fast calculations (for example, with
implementation of any graphic effect) are usually used
a slightly different table - 256 bytes. It presents
sine or cosine values in 128ths (signed) for angles
from 0 to 2*Pi in steps of 2*Pi/256. (Please note: in the last
element of the table stores the value of the function not for the angle 2*Pi, but
for angle 2*Pi*255/256; angle 2*Pi is the same as angle 0, and
the function value for it is in the first element of the table.)
Why is using such a table more convenient? Meaning
angles are usually stored in an 8-bit variable: angles from 0 to 2*Pi*
255/256 correspond to variable values from 0 to 255. In this case
to calculate sine or cosine it is enough to usethe value of the variable as an index in the table. When
cyclic change of angle value is not necessary
tracking situations when it leaves the range of 0-2*Pi: like this
how calculations are carried out modulo 256, angle value
automatically converted to this range. Yes and meanings
functions are presented in the table a little more accurately (in 128th fractions,
and not in hundredths).
It is especially convenient to work with a table if it starts with
address that is a multiple of 256 - then the angle value will be both
the value of the low byte of the address where the sine or
cosine of this angle.
Since the sine graph differs from the cosine graph only
shift by a quarter of the period, programs usually use one
and the same table for calculating both sine and cosine. For example,
if the table shows the values of the sine, then to calculate
cosine, you only need to add the number 256/4=64 to the index (and
this is instead of using cumbersome reduction formulas - appreciate it
convenience!).
════════════════════════ ════════════════════════
Best regards, Ivan Roshchin.
From
Ivan Roshin
→
To
All
12 June 2001
Hello, All!
═══════════════════ part_13 .T ══════════════════
When the task is to reduce the size of the program (for example,
when writing intro), makes sense instead of immediate
storing a table, create it in assembler. Program fragment,
creating a table should be as short as possible (and certainly
case, shorter than the table itself), and its operating time is long
does not matter - after all, the table needs to be created only once.
In [15], two procedures for generating a table are given
sines, and when calling them it is possible to set the desired
amplitude and offset along the y axis (i.e. actually creating a table
function values y=a*sin(x)+b). The first procedure takes only
39 bytes (+6 bytes for setting input parameters), but it works
indecently long - about fifteen seconds. What to do, this is
fee for using the ROM calculator. Second procedure -
much faster, but also longer: 119 bytes (+6 bytes per
setting parameters). Of these, 64 bytes are occupied by the table, corresponding
the current quarter of the sine period. As we can see, in reality the sine is not
is calculated - hence the high speed of operation.
A shorter procedure that creates a table of sines without
using the ROM calculator can be found in [16]. Its length is
75 bytes, of which 31 bytes (and not 64, as in the above
procedure!) takes up information about a quarter of the sine period(increment packed using RLE method).
The resulting table has some special features:
news. In fact, it contains the values of the function y=│sin x│,
so you will have to take into account the sign yourself when calculating (for
for the first half-cycle - “+”, for the second - “-”). This allowed
improve the accuracy of sine values: they are presented in 256
fractions, not 128s.
An interesting procedure for constructing a table of cosines is given
in [10]. Let's look at the graph of the function y=cos(x) (Fig. 1). It is clear that
its part lying below the x-axis is shaped like a parabola, and
the part lying above the x-axis is easily obtained by symmetrical
reflections. Parabolic interpolation and is used when
creating a table.
┌────────────────────────────┐
│ There should be a picture here │
│ from the file 'pic_1.scr' │
│ │
│ │
│ │
│ │
│ │
│ │
└────────────────────────────┘
Fig. 1
Of course, this approximation is not absolutely accurate.
(Fig. 2), but for graphic effects the accuracy is quite sufficient.
But the length of the procedure is only 43 bytes! And with the help of a simpleimprovements, it becomes possible to obtain 16-bit values.
┌────────────────────────────┐
│ There should be a picture here │
│ from the file 'pic_2.scr' │
│ │
│ │
│ │
│ │
│ │
│ │
└────────────────────────────┘
Fig. 2
It was not easy to shorten this procedure even by a byte. But to me
managed, using another method of calculating squares, to reduce
its length is three whole bytes! At the same time, the construction time
tables decreased by 3.3 times. Moreover, we managed to do without
using an undocumented SLI command, which may have
crucial when you are writing a program for
processor compatible with the Z80 only according to documented
teams.
So here's an optimized version:
GEN_COS LD HL,#FF01 ;127^2+#C000
LD D,H
LD E,L
LD BC,COS_TABL+#40
PUSH BC
EXX
POP DE
LD BC,COS_TABL+#C0
LD H,B
LD L,C
GEN_LOOP DEC E
DEC L
EXX
LD A,L
ADD A,ALD A,H
ADC A,A
INC. E
INC. E
ADD HL,DE
INC. E
INC. E
ADD HL,DE
LD (BC),A
INC C
EXX
LD (HL),A
CPL
LD (DE),A
LD (BC),A
INC C
JR NZ,GEN_LOOP
RET
═════════════════════ ═════════════════════
С уважением, Иван Рощин.
From
Ivan Roshin
→
To
All
12 June 2001
Hello, All!
═══════════════════ part_14 .T ══════════════════
And if you insert this procedure into the program directly,
without CALL/RET (as is most often done to reduce size),
the winnings will be already five bytes! Indeed, when discarding
RET command we will get a 39-byte program fragment, and in
original procedure RET (conditional) is in the middle, and
you will have to replace it with the conditional JR - the result will be
44-byte fragment.
By the way, if you do not calculate the values of the squares, but take them from
existing table, the length of the procedure can be reduced from 40 to
33 bytes (see below). So, if in your program for some
goals, a table of squares is formed, keep this in mind
possibility of its use.
GEN_COS_2 LD HL,SQ_TAB+#1FE
LD BC,COS_TABL+#40
LD D,B
LD E,C
GEN_LOOP DEC E
LD A,(HL)
SCF
RRA
RES 7,C
LD(BC),A
SET 7,E
LD(DE),A
CPL
SET 7,C
LD(BC),A
RES 7,E
LD(DE),A
DEC L
DEC L
DEC L
DEC L
INC C
JR NZ,GEN_LOOP
RET
An original way to approximate construction of a table of sines
described in [19]. Its essence is this: the next meaningcalculated by adding some value to the previous value
increment, which, in turn, gradually decreases according to
a certain law. The fragment of the program given in [19]
constructing in this way a 128-byte unsigned table of the first
half-cycle of a sine, takes only 24 bytes! What about the similarity?
for a real sine - look at Fig. 3. The solid line shows
graph of the function y=sin x, dots - generated approximate ones
values (scaled accordingly). As you can see, the similarities
very big!
┌────────────────────────────┐
│ There should be a picture here │
│ from the file 'pic_3.scr' │
│ │
│ │
│ │
│ │
│ │
│ │
└────────────────────────────┘
Fig. 3
What is the procedure for accurately constructing a table of sines/
cosines is the shortest? Here is the procedure I wrote
record-breaking length: only 36 bytes. She uses a calculator
The ROM runs for quite a long time, about twelve seconds.
However, this is less than the execution time of the procedure given
in [15].C_TO_STACK EQU #2D29 ;Pushes the number from register C onto the stack
;calculator
FROM_STACK EQU #2DD5 ;removes a number from the stack and places it in A
;Calculator commands used:
multiply EQU #04 ;multiply
add_ EQU #0F ;addition
sin EQU #1F ;sine
cos EQU #20 ;cosine
stk_data EQU #34 ;push number onto stack
end_calc EQU #38 ;exit calculator
stk_one EQU #A1 ;push one onto stack
LD BC,TABL
LOOP PUSH BC
CALL C_TO_STACK
;Calculation
;int ((1+sin((2*Pi/255)*COUNTER))*127)
RST #28
DB stk_data ;2*Pi/255
DB #EB,#49,#D9,#B4,#56
DB multiply
DB sin ;or cos
DB stk_one
DB add_
DB stk_data ;127
DB #40,#B0,#00,#7F
DB multiply
DB end_calc
CALL FROM_STACK
POP B.C.
SUB #7F
LD(BC),A
INC C
JR NZ,LOOP
RET
════════════════════════ ════════════════════════
Best regards, Ivan Roshchin.
From
Ivan Roshin
→
To
All
12 June 2001
Hello, All!
═══════════════════ part_15 .T ══════════════════
And now - several possibilities for further optimization
this procedure.
If, before executing it, it turns out that in the register
the BC pair contains an address suitable for placing the table
(the low byte must be zero), then the command LD BC,TABL,
Naturally, you can remove and save three bytes. If
a suitable address is in HL or DE, the savings will not be
so big - only two bytes. It would seem, what difference does it make?
Which register pair should I use in the procedure - BC, DE or HL?
But in the ROM at address C_TO_STACK there is a command LD A,C, and behind
Due to this, using the BC register pair is more profitable. If
If we rewrite the program to use DE or HL, then
instead of one CALL C_TO_STACK command you will have to write two -
let's say LD A,E: CALL C_TO_STACK+1, which will take a byte more.
If the table is located at address #7F00, then after the command
PUSH BC add another LD A,B: CALL C_TO_STACK+1 (entering the number
#7F on the calculator stack), and the line marked with a comment
“127” and remove the next one after it; replace the SUB #7F command with
SUB B - the result is a savings of two bytes!
If the table is located at address #8100, you can replace
command SUB #7F to ADD B, and the length of the procedure will decrease
per byte.If you don't care about the 1/128 error on negative
function values, you can remove the commands stk_one, add_ and SUB #7F,
and instead write the following after CALL FROM_STACK: JR Z,$+3:
CPL. This saves one byte.
Finally, if you are satisfied with the table with the function values in
range not from -1 to 1, but from 0 to 2 (each value
is an unsigned fixed-point number:
the most significant bit is the integer part, the least significant bits are the fractional part in 128s
fractions), then the SUB #7F command can be removed completely, and the procedure
will be two bytes shorter. The table generated in this way
can be used to implement such graphic effects
(say, "plasma"), which only require that the values in
it varied from 0 to 255 along a sinusoid.
Floating point calculations
─────────────────────────────
It is often possible to transform the algorithm so that the calculations with
floating point are reduced to integer ones, which run much faster
faster. But it still happens that without floating point calculations
can't get by...
If you need to optimize a program in terms of volume (say,
you decided to write 512-bytes intro), don't neglect
the ability to use a ROM calculator. Description of it
functions can be found, for example, in [17]. If for you more
performance is important, I can recommend a library of procedures
for working with 32-bit real numbers, given in[13]. It implements the most common operations:
addition, subtraction, multiplication, division, conversion to int format and
back, selecting the whole part, calculating sine and cosine,
squaring.
Number input/output
────────────────
[11] provides a procedure for quickly printing 32-bit integers.
numbers in decimal form (written in 1983 by Tim Paterson from
Microsoft). In [12] you can find the one used when entering such
numbers from the keyboard conversion procedure from ASCII codes.
A useful library of routines for printing numbers in various
number systems is given in [18].
To input/output numbers (including real ones), you can
use ROM procedures. This is discussed, for example,
in [17].
I came across something interesting in the ZX.SPECTRUM echo conference
message (its author is Ilya Aniskovets), which describes
program fragment that converts a hexadecimal digit
(0,1,...,F) to the corresponding ASCII code. This piece is so
original and small that I couldn’t resist not to put
him here. Try in your spare time to figure out why it
works.
Input: the number in the battery is #00-#0F.
Output: in the battery the corresponding ASCII code is: "0" - "F".
Length: 5 bytes.
Time: 18 bars.
CP#0A
SBC A,#69
DAA
════════════════════════ ════════════════════════Best regards, Ivan Roshchin.
From
Ivan Roshin
→
To
All
12 June 2001
Hello, All!
═══════════════════ part_16 .T ══════════════════
In the ninth issue of the electronic magazine "Adventurer" there was
my fragment for solving the inverse problem has been published
(convert ASCII code to number). Its text is given below.
Input: in the accumulator the symbol code is “0..9”, “a..f” or “A..F”.
Output: in the battery - the corresponding number: #00-#0F.
Length: 10 bytes.
Time: 35 or 37 bars.
RES 5,A ;converted "a..f" to "A..F", and if in
;A was a number - its code will decrease by #20
BIT 6,A ;check: number or letter?
JR NZ,$+4 ;if it’s a letter, skip the next command
ADD A,#27 ;if it’s a number, add #27
SUB #37 ;in A the number is from #37 to #46, and after
;subtractions will just get #00-#0F
Then it seemed to me that with a length of ten bytes such
a fragment can be implemented in several ways, but
it is no longer possible to reduce its length. But it turned out that it is possible! From
ninth issue of the electronic magazine "DEJA VU" I learned that
Max/CBX/BDA managed to reduce it by another two bytes!
Input: in the accumulator the symbol code is “0..9”, “a..f” or “A..F”.
Output: in the battery - the corresponding number: #00-#0F.
Length: 8 bytes.
Time: 26 or 28 bars.
AND #1F
SUB #10
JR NC,$+4
ADD A,#19I tried to shorten this fragment even further with
DAA commands (as in the example above), but, unfortunately,
failed to do this...
What else can you advise about I/O based on
your own experience? If you need to quickly type some numbers,
store them in a form already prepared for output. So, for example,
done in my BestView program: when moving the file panel
right/left (and this shift, to be smooth, must fit
at 1/50 second) when file parameters are displayed (start
address, length, etc.), they are stored in memory in text
form. If they had to be converted every time, no
any smoothness of scrolling would be out of the question. Exactly the same
managed to achieve smooth scrolling when viewing
hex dump file: before viewing it in advance
converted to text format.
And one more important question: quite often in the program
There is a need to display a message like “N files marked”.
So, in this case it is necessary to take into account the case and number of the output
noun! Unfortunately, this is often forgotten, and
the user has to read ugly messages like
"21 files marked"...
For the "bourgeoisie" everything is simple: print "file" when the file
one, and "files" when there are several of them. And it’s still sometimes
you come across the universal "selected N file(s)" - extra bytesThey’re saving money on this, or something... In the Russian language the situation is more complicated,
but not by much. I'll try to explain this with an example.
Suppose we need to display the inscription “N files”. To do this first
we output the number N itself, and then apply the following rules in
order until one of them fits: if the last
two digits of N are 11,12,...,19, or the last digit of N is 0.5,
6,7,8,9, then we display “files”; if the last digit of N is 1, then
output "file"; finally, if the last digit of N is 2,3,4, then
output "file". Not so difficult, right?
════════════════════════ ════════════════════════
Best regards, Ivan Roshchin.
From
Ivan Roshin
→
To
All
12 June 2001
Hello, All!
═══════════════════ part_17 .T ══════════════════
General questions
─────────────
Here I will try to talk about a number of issues that
will undoubtedly arise in your mind when writing a program that performs
arithmetic calculations, especially if she is required
maximum speed or minimum volume.
Which form of representing numbers should I choose? Reply to this
question depends on the range of quantities your
program, on the required accuracy, as well as on the speed
what operations need to be increased. Numbers can be
represented as integers (signed or unsigned), in format with
fixed or floating point, logarithmic,
in leftovers, in ASCII codes, in BCD form... It is even possible that you
come up with your own, most suitable for a specific task
form of representing numbers.
In what ways to perform certain arithmetic
operations? It depends on the chosen form of representation of numbers,
depends on how you want to optimize the program (by speed or
by volume), on the required accuracy of calculations (so, sometimes
one has to prefer the slow but accurate method to the fast but
inexact), and, finally, from the calculated expressions themselves (if
it is known, for example, that half of all operations are
multiplication, then it is this that needs to be optimized).
How to convert calculated expressions to the mostoptimal view? To answer this question, it is necessary
know the speed of performing various operations. Then you can
say, reduce the number of "slow" operations due to
increasing the number of "fast" ones so that the total execution time
programs will decrease. If you need to optimize the program for
volume, you can reduce the total number of operations, or
get rid of some type of operation (say, division -
then the division subroutine will become unnecessary and the volume of the program
will decrease). Please also note that if any
values are calculated only for their subsequent comparison, often
calculations can be greatly simplified: so, if you need to compare
distances between points on a plane, much more convenient
compare the squares of these distances, because there's no need to
perform the root extraction operation.
All these questions are interconnected, and indicate any
the procedure for solving them is very difficult - it all depends on
specific situation. For maximum optimization of the program,
you'll likely have to consider many different
options and choose the best one.
Additional information
─────────────────────────
Here you will find a list of sources I used from
a brief description of their contents. Basically these are different
articles published in Spectrum electronic newspapers and
magazines. You can try to find them on the Internet at anylarge Spectrum website - www.zx.ru, zx.da.ru (in the section
"press"), www.chaosite.com. You can view them on PC using
any emulator that can be found there.
I also recommend studying DEMO.DESIGN FAQ OnLine,
posted on the Internet at http://www.enlight.ru/demo/
faq. There is a section "Mathematics" with descriptions of various
algorithms.
Well, if you are interested in the most optimal procedures for
Z80, you may find the following information useful:
Faster^TNL announced the creation of the "High Technologies" website
(presumable address http://HighTech.da.ru), where they will be,
among other things, the source texts of various procedures are posted,
optimized for length and speed.
So here's what I used when writing this article:
1. V. Ilyichev. "Programming arithmetic operations on
assembler". "Radio amateur. Your computer" 6-9/2000.
2. M.A. Kartsev. "Arithmetic of Digital Machines". Moscow, publishing house
"Science", 1969.
Various ways of representing numbers and algorithms are considered
perform addition, subtraction, multiplication, division, extraction
roots, converting numbers from one number system to another and
other operations.
Do not be confused by the year of publication of this book - arithmetic is not
getting old!
3. Card!nal/PGC/BD. “I want to code!” DEJA VU #5.
The multiplication procedure 8*8->16 (this is how I will denotewidth of operands and results; they are meant whole
and unsigned, unless explicitly stated otherwise) using
tables of squares. Procedure for calculating square root
sqr(16)->16 by modified Newton's algorithm.
4. PSV/Dream Team. "Optimization of programs by execution time."
MIRACLE #3.
Multiplication procedure 8*8->16 "in a column" with open
cycles.
5. M. Spitsyn. "Assembler for dummies." ZX FORMAT #2.
The simplest methods are described in an accessible form
perform basic arithmetic operations.
════════════════════════ ════════════════════════
Best regards, Ivan Roshchin.
From
Ivan Roshin
→
To
All
12 June 2001
Hello, All!
═══════════════════ part_18 .T ══════════════════
6. Creator product. "About coding for beginners." ZX FORMAT #7.
Library of procedures: division 16/16->16, squaring
16^2->16, multiplying 16*16->16 and 16*8->16, extracting square
root sqr(16)->16, factorial calculation (8!->16), raising to
degree (16^8->16), conversion of radians<->degrees, calculation of sine
and cosine.
7. GreenFort. "Fast calculations in assembly language." ZX FORMAT #7.
Division 8/8->8+8 (quotient and remainder); 8/8->8.8 (whole
part, fractional part); 24/24->24. Multiplication 8*8->16, 24*24->24.
8. GreenFort. "Arithmetic II". ZX FORMAT #8.
Raising to any power and extracting any root -
algorithms and examples of procedures.
9.ra!d. "Generator of a table of squares." DEMO OR DIE #2.
16-byte program fragment that quickly builds a table
squares of numbers from 0 to 255.
10. Dark/X-Trade, STS/VolgaSoft. "Spectrum+3D #1". SPECTRUM
EXPERT #1.
Normal multiplication 8*8->16, 16*16->32. Fast multiplication with
using a table of squares (signed) 8*8->16; 8*8->8 (inaccurate,
the result is the most significant byte of the 16-bit product). Normal
division 8/8->8+8, 15/8->8+8, 16/16->16+16, 31/16->16+16.
Logarithmic division 8/8->8.8; the same, but with scaling
and when the dividend is signed: 12/12->8.8 (the quotient is also signed).
Generation of tables in assembler y=2^(x/256)-1, y=log2(x),y~=cos x.
11. STARSOFT. "HELPFUL SUBROUTINES". VOYAGER #3.
Fast printing of a 32-bit number in decimal form. Multiplication
32*16->32, 16*16->32. Division 32/16->32, 16/16->32.
12. Maximum/INTEGER. "LONG??? What is this?" ADVENTURER #8.
Working with 32-bit numbers: adding, subtracting, printing and
conversion from ASCII codes.
13. Maximum/INTEGER. "Working with IEEE numbers." ADVENTURER #10.
Library of procedures for working with real 32-bit
numbers.
14. SerzhSoft. "A few words about the painful issues." ADVENTURER #9.
Fast procedure for extracting the square root sqr(16)->16.
15. SerzhSoft. "It's like a sinus to play around." ADVENTURER #9.
Two procedures for generating a table y=a*sin(x)+b in assembler.
16.ALK/XTM. "How to code optimally." BornDead #0A.
The procedure for generating the table y=│sin x│.
17. Steve Cramer. "Operating system ZX Spectrum. Manual
hacker." Island, Moscow, 1994.
Among other things, the calculator commands and
ROM procedures for input/output of numbers.
18. S. Kolotov. "Printing numbers in different number systems."
DEJA VU #4.
A library of procedures that allow you to print numbers of different
ranges in decimal, hexadecimal, binary and settable
user of number systems. Additionally provided
printing in the Roman numeral system.
19. Cryss/Razzlers. "Secrets of DAINGY". SCENERGY #2.A very short procedure for generating a table y~=sin x.
20. Baze/3SC. "Fast exponential multiply". SCENERGY #2.
Procedures for fast logarithmic multiplication 8*8 -> 8
(result is the most significant byte of the 16-bit product) for
unsigned (0..255) and signed (-127..127) operands.
Procedure for quickly calculating x*sin(y).
21. Baze/3SC. "Fast 16-bit square root". SCENERGY #2.
Quick square root procedure: sqr(16)->8.
22. Aleksey Malov. "Mathematical procedures". Letter to
conference CODE.ZX from 4 Apr 2000. (I read this letter
in the REAL.SPECCY conference, where he was quoted on March 31, 2001
Mikhail Zharov.)
Division: 16/16->16+16 (quotient and remainder), 16/16->16.
Extracting square roots: sqr(16)->8, sqr(24)->16.
════════════════════════ ════════════════════════
Best regards, Ivan Roshchin.