More about programming arithmetic operations

ZXNet echo conference «code.zx»

From Ivan Roshin To All 12 June 2001

Hello, All! ═══════════════════ part_1 .T ══════════════════ (c) Ivan Roshchin, Moscow Fido: 2:5020/689.53 ZXNet: 500:95/462.53 E-mail: asder_ffc@softhome.net WWW: http://www.ivr.da.ru More about programming arithmetic operations ═══════════════════════ ═══════════════════════ (“Radio amateur. Your computer” 12/2000, 1-4/2001) (Added version) After reading the article [1], I decided to continue on the pages of the magazine "Amateur Radio. Your Computer" a story about programming arithmetic operations in assembler. This is how this one was written article, which discusses some, in my opinion, interesting algorithms and optimization techniques. The article is addressed to primarily for those who write programs in Z80 assembly language, but the described algorithms may be useful regardless of the computer platform and programming language used. So, what happened is in front of you! Addition and subtraction. Comparisons. ──────────────────────────────── It would seem that the simplest arithmetic operations. But even here There are several interesting techniques that allow in some cases optimize the program. When you need to increase or decrease the contents of a register, register pair or memory cell by a small amount, it costs see if it would be more profitable to usethis several INC/DEC (increase/decrement by 1) commands? Let's say we need to increase the value of HL by 3. Option with using the ADD command would be like this: LD DE,3 ;3 bytes, 10 clock cycles ADD HL,DE ;1 byte, 11 clock cycles ───────────────────────────── Total: 4 bytes, 21 clock cycles At the same time, three INC HL instructions will take 3 bytes/18 clock cycles, which is shorter and faster, and does not change the contents of DE. A few more useful tricks (instead of HL there may be another register pair): HL:=HL+256 - INC H HL:=HL-256 - DEC H HL:=HL+255 - INC H: DEC HL HL:=HL-255 - DEC H: INC HL Real example: in HL the file length in bytes is specified, in A it is necessary get its length in sectors (1 sector = 256 bytes): INC H DEC HL LD A,H Another advantage of the INC/DEC commands is that their operand can be in any register, register pair or cell memory addressed (HL), (IX+s), (IY+s). When using the same ADD/SUB commands one of the operands must be in register A or in a register pair HL, IX, IY - which means they can additional commands will be required to send it there, and then - forwarding the result. When using INC/DEC, remember that they changeflags are not the same as ADD/SUB: INC/DEC 8-bit operand has no effect on the CY flag, and the INC/DEC of the 16-bit operand has no effect at all flags! Now let us pay attention to the fact that the calculations are carried out according to modulo 256 for registers and modulo 65536 for register pairs. This can also be used for optimization. It happens that you need to add or subtract some number, but it turns out that some register already has its complement. Then you can replace addition with subtraction of an additional number and vice versa. For example, if you need to add to the accumulative value ra 4, and register B contains 252 (i.e. 256-4), you can save Save 1 byte/3 clock cycles by writing SUB B instead of ADD A,4. Just don't remember that such a replacement affects the value of the CY flag after operations. Subtraction of a 16-bit constant can be replaced by addition with by adding this constant to 65536, thereby gaining 2 bytes/ 8 bars: Was: AND A ;1 byte, 4 clock cycles LD DE,#7352 ;3 bytes, 10 clock cycles SBC HL,DE ;2 bytes, 15 clock cycles ───────────────────────────────── Total: 6 bytes, 29 clock cycles Now: LD DE,-#7352 ;3 bytes, 10 clock cycles ADD HL,DE ;1 byte, 11 clock cycles ───────────────────────────────── Total: 4 bytes, 21 clock cycles Alas, the ADD HL,DE command does not affect the Z flag, which canhave in some cases (for example, when you need to check whether whether the result is zero) is critical. Then you have to use the "slow" SBC command. I note that if exactly the value of the CY flag before subtraction is known, AND A command becomes unnecessary - it is enough only in the case of CY=1 to reduce subtractable constant per unit. ════════════════════════ ════════════════════════ Best regards, Ivan Roshchin.

From Ivan Roshin To All 12 June 2001

Hello, All! ═══════════════════ part_2 .T ══════════════════ When adding or subtracting signed operands, one of of which one is 16-bit and the other is 8-bit, needs to be expanded 8-bit operand to 16-bit. In the Z80 processor, unfortunately, there is no special sign expansion command (unlike, say, processors of the 680X0 family), and you have to do this using several teams. Here is the fastest way (A - convert- required number, HL - result): LD L,A ;low byte will be the same ADD A,A ;added the sign bit to the CY flag SBC A,A ;if the number is negative (sign bit=1), we get ;#FF, for a positive number we get 0 LD H,A ;this will be the high byte Now let's look at a few well-known (and not so well-known) techniques: allowing optimization of various types of comparisons. Instead of the CP 0 instruction (2 bytes/7 clocks) for comparison signed or unsigned numbers with zero are more profitable to use one of the instructions AND A or OR A (1 byte/4 clock cycles), which are exactly the flags CY, Z and S are also set. Generally speaking, after arithmetic and logical operations with accumulator in in most cases (it depends on the specific operation) flags are set according to its contents, so that a special command to check for equality to zero may turn out to be unnecessary. Check if the contents of one of the registers B, C are equal to zero,D,E,H,L, conveniently using a pair of commands INC reg: DEC reg. This equivalent to the commands LD A,reg: AND A, but does not change the value battery Quickly check if the contents of one of the registers A is equal to B,C,D,E,H,L number #FF or 1, you can use, respectively, INC reg or DEC reg commands. The check is performed in the same way for the equality of B,C,D,E,H,L to the number #FE or 2 - for this you will need two commands INC or DEC (for a battery this method is already disadvantageous - the CP command works faster). Please note that the content The register being checked is changed. Let's remember that the Z80 processor has a DJNZ command - it Decrements the contents of register B by one, and if as a result turned out to be non-zero, performs a relative transition along to the given address. This command was originally intended for organization of cycles, but who prevents us from using it for others goals? With its help you can, for example, carry out quick checking register B for inequality with numbers 1, 2 or 3: Transition in case B<>1: DJNZ LABEL Transition in case B<>2: DEC B: DJNZ LABEL Transition in case B<>3: DEC B: DEC B: DJNZ LABEL This method is applicable, however, only when the distance from DJNZ to the jump address does not exceed 128 bytes. Moreover, the meaning register B changes with such a comparison (however, with comparing B with one, this method will remain faster than all otherseven taking into account the subsequent restoration of the value of B by the command INC B). Let us consider another problem that occurs quite often in practice: the accumulator is given a number from 1 to n (say, conditional number of the pressed key), and is required depending on this number to perform certain actions. Here's the simplest one way to do it: CP 1 JR NZ, m1 .......... ;actions when pressing key 1 RET m1 CP 2 JR NZ,m2 .......... ;actions when pressing key 2 RET m2 CP 3 JR NZ,m3 .......... With a little thought, you can find the best option: DEC A JR NZ, m1 .......... ;actions when pressing key 1 RET m1 DEC A JR NZ,m2 .......... ;actions when pressing key 2 RET m2 DEC A JR NZ,m3 .......... But the most elegant way is using DJNZ! LD B,A DJNZ m1 .......... ;actions when pressing key 1 RET m1 DJNZ m2 .......... ;actions when pressing key 2 RET m2 DJNZ m3 .......... Well, let's finish with the DJNZ team and talk about something else. If the battery contains a signed number (-128..127) and you need determine whether it is negative, then instead of this: AND A JP P,M1 ;jump if A<0 It's often better to write it like this: ADD A,A ;sign bit is advanced to CY flag JR C,M1 ════════════════════════ ════════════════════════Best regards, Ivan Roshchin.

From Ivan Roshin To All 12 June 2001

Hello, All! ═══════════════════ part_3 .T ══════════════════ How is this better? Firstly, it’s one byte shorter - after all, we replaced the absolute jump command JP P (3 bytes) with the command relative transition JR C (2 bytes). Alas, in the Z80 command set there is no JR P team, which is why we have to resort to such a replacement. Secondly, JP is executed in 10 clock cycles, and JR is executed in 12 clock cycles with fulfillment of the condition and for 7 - if not fulfilled, and if the condition will not be fulfilled more often than it will be fulfilled, we will win in program execution time (and if it’s the other way around, we’ll lose). Such a replacement is possible if the transition address is distant from JR commands for no more than 128 bytes and the contents of the accumulator (which will be changed by the ADD command) will no longer be needed. If the speed of the program comes first, pay attention attention to whether the replacement will save time. A similar replacement can be made for the condition A>=0. To do this, instead of this fragment: AND A JP M,M1 ;jump if A>=0 write like this: ADD A,A JR NC,M1 If you need to jump depending on the sign of a number, located in some other register, it is convenient to use command BIT 7,reg (2 bytes/8 clock cycles) - in this case, the Z flag will be set depending on the value of the seventh (sign) bit register. For the H register, such a check can be done with the commandADD HL,HL (1 byte/11 clock cycles), with the 7th bit of H moving forward CY flag. It will take longer than using commands BIT 7,H and will also change the contents of HL, but it will one byte shorter. The Z80 processor only has four conditional capabilities. transfer of control after comparing two operands - in depending on whether one of the conditions A=s, A<>s is met, A=s. This is not always enough. If you need to do transition to label M according to the condition "A>s" (for unsigned numbers), then instead of commands CP s JR Z,M1 ;if A=s, then bypass the next command JR NC,M ;transition if A>=s, but we excluded the case A=s M1... in the case when the comparison occurs with a constant, it is more convenient write like this: CP s+1 JR NC,M ;transition if A>=s+1, i.e. if A>s and if the comparison occurs with the value of a register or cell memory, then like this: DEC A CP s JR NC,M ;jump if A-1>=s, i.e. if A>s In the same way, if you need to perform a conditional transition A<=s, then instead of this: CP s JR Z,M ;jump if A=s JR C,M ;jump if A

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.