Hi, i'm writing (in fact wrote) a complex number library with masm.
It uses five global temporary variables: zt1, zt2, .. zt5:complex
The type complex is defined as
complex STRUCT

r REAL10 ?
i REAL10 ?
complex ends


And macros:
fldt MACRO value

fld tbyte ptr [value]
ENDM

fstpt MACRO value
fstp tbyte ptr [value]
endm


CMul & CDiv follows



CMul proc z1:complex,z2:complex,zret:complex
fldt z1.r
fldt z2.r
fmulp st(1),st
fldt z1.i
fldt z2.i
fmulp st(1),st
fsubp st(1),st
fstpt zret.r

fldt z1.r
fldt z2.i
fmulp st(1),st
fldt z1.i
fldt z2.r
fmulp st(1),st
faddp st(1),st
fstpt zret.i

ret
CMul endp

CDiv proc z1:complex, z2:complex, zret:complex
fldt z2.i
fchs
fstpt z2.i
---> invoke CMul,z1,z2,zt1
fldt z2.i
fchs
fstpt z2.i
fldt z2.i
fmul st,st
fldt z2.r
fmul st,st
faddp st(1),st

---> fldt zt1.r
fdiv st,st(1)
fstpt zret.r
---> fldt zt1.i
fdiv st,st(1)
fstpt zt1.i
ret
CDiv endp


Now here is my question:
CMul works ok
in CDiv invoke CMul returns true
But in fldt zt1.x it loades every time zero.

To test i used:
(pseudocode)

z1.r=3
z1.i=0
z2.r=4
z2.i=0
z3 is empty
invoke CDiv,z1,z2,z3

again, CMul's result is 12.0 but when accessing from CDiv(fldt zt1.r) it's 0.0 :mad:
Algo's should be true, i converted them from VB which i coded a complex number lib. before.
And i'm very new to masm, is there a hot spot to make this work? or sthg different?
Posted on 2003-07-10 10:09:49 by inFinie
Your mistake is simple: confusing "call by reference" and "call by value". When you call CMul, zt1 is passed by value, i.e. CMul receives a copy of zt1 on stack. When you thought you returned the computed result in zt1, you actually saved the result on the stack without modifying zt1.

One solution is: Revise your CMul (and CDiv) to use "call by reference" for the return value struct.
Posted on 2003-07-10 14:00:12 by Starless
like what?
an example please?
Posted on 2003-07-10 15:15:00 by inFinie
In Visual BASIC, when you pass a variable to a function, the function works directly on that variable. Instead, in ASM, you pass a COPY of that variable, located in the stack. So if you want you function to change the variable itself, you must pass a pointer to it...



Sample proc lpvar:DWORD
mov eax,lpvar
mov [eax],value
ret
Sample endp


And called like this:

invoke Sample,addr myvar

So "Sample" takes the address in memory of "myvar" variable from "lpvar" in the stack, and uses it as a pointer. "myvar" can be located elsewhere, not necessarily on the stack.
Posted on 2003-07-11 10:59:33 by QvasiModo
I tried and tried and know frustrated :mad:
Source is attached, it is semi-working with OllyDbg i can see that right operations are done, but it does not return by value. I couldn't make it work. Any help is appreciated.
By the way, i use RadASM:
Posted on 2003-07-16 11:08:05 by inFinie
I dont have your FPU.INC / LIB ?? So i couldnt compile it.

But anyways, i looked at your source and make my changes to it. In short your getting into trouble using the Mul proc. Way to messy using temp complex storage areas just to call a short proc. Instead i suggest just "code on through" ;)

This is what i have done. I didnt test it, but it should work. I made efforst to comment it as well. Basically im calculating the factor 1/(A^2 + B^2) of the divisor. Stacking it twice, and then running through a "backwards" multiplication (as defined by your Mul proc). Since you need to change the sign, i just changed the add to subtract and vice versa on the latter parts. This way you dont need any temp workings.. and is not much longer than what you had to start ;)

CDiv proc z1:complex, z2:complex, zret:complex


;z1 = (A + jB) z2 = (C + jD) zret = (E + jF)

; Makes 1/( A^2 + B^2) on stack 0, and 1 positions (Stacked Factor)
fld1
fldt z2.i
fld st(0) ; st(0) = st(1) = z2.i ; st(2) = 1.0
fmulp st(1), st ; st(0) = Imag^2 ; st(1) = 1.0
fldt z2.r
fld st(0)
fmulp st(1), st ; st(0) = Real^2 ; st(1) = imag^2 ; st(2) = 1.0
fadd ; st(0) = Real^2 + Imag^2 ; st(1) = 1.0
fdiv ; st(0) = 1/R2 + I2
fld st(0) ; st(1) = st(0) = 1/(r2.r^2 + r2.i^2)

; Makes (A*C + B*D)*stack'd factor = E
fldt z1.r ;
fldt z2.r
fmulp st(1),st
fldt z1.i
fldt z2.i
fmulp st(1),st
faddp st(1),st
fmul ; Uses the first stacked factor
fstpt zret.r

; Makes (C*B - D*A)*Stacked factor = F
fldt z1.i
fldt z2.r
fmulp st(1),st
fldt z1.r
fldt z2.i
fmulp st(1),st
fsubp st(1),st
fmul ; Uses the second stacke factor
fstpt zret.i

ret
CDiv endp


Hope you like...
:alright:
NaN
Posted on 2003-07-16 18:40:04 by NaN
By the way, i suggest you expand your structure definition to include weather or not the data in the structure is in rectangular or polar forms.
complex STRUCT

r REAL10 ?
i REAL10 ?
Polar dd ?
complex ends


This way a programmer can test the data, and see if its rectangular or not (complex.Polar = FALSE/TRUE )

:NaN:
Posted on 2003-07-16 18:46:43 by NaN
FPULIB is the masm32 fpulib, it is attached and should be in your masm dir.

Your code works correctly, but it has the same problem. Doesn't save the answer in z3:mad:
If this problem won't be eliminated then non of the Trig funcs and derivatives won't work and that's too bad.
NaN:Thanks for your help.
And you commented the FPU code it's really hard work.

Instead of expanding the struct, i may use another struct pcomplex as polar just to avoid checking if Polar is equal to ...

And two procs, polar to rect, rect to polar seems to me more handy
Posted on 2003-07-17 04:39:41 by inFinie
I had a cursory look at your source code. You have done a tremendous amount of work up to now. However, you will have to concentrate on understanding the purpose and meaning of passed parameters to procedures before your code can work properly. For example:
CMov proc z1:complex, z2:complex


fldt z2.r
fstpt z1.r
fldt z2.i
fstpt z1.i
ret

CMov endp

That procedure reserves space on the stack for two complex STRUC. It then loads whatever value it finds on the stack at the z2.r location (as a REAL10 according to your fldt macro) and stores it on the stack at the z1.r location (as a REAL10 according to your fstpt macro). Same with the .i values.

You then destroy the stack frame when you exit that procedure, effectively "destroying" the stored values. Nothing has been stored in the data section.

That procedure for example would probably work correctly as a MACRO instead of a procedure if you must insist using STRUC syntax. It can work as a procedure if you pass the addresses of the structures but you would not be able to use the struc syntax in references to struc items.
CMov proc lpz1:DWORD,lpz2:DWORD

mov eax,lpz2
mov edx,lpz1
fldt eax ;the R item from z2
fstpt edx ;the R item to z1
fldt eax+10 ;the I item from z2
fstpt edx+10 ;the I item to z1
ret
CMov endp

Similarly, all your procedures reserve space on the stack where you store the results of your computations, and then destroy those results as you return from the procedures.

Your project is interesting. Additional help is definitly available.

BTW the second parameter for the FpuFLtoA function should be the number of decimal places required in the result. the 80 which you used would be replaced by the default maximum of 15 for the scientific notation.
Then, in your CtoStr procedure, the address passed to the FpuFLtoA function would be the address of the space reserved on the stack for a complex struc which may only contain garbage at that point. Probably the same with your use of other Fpulib functions.

Raymond
Posted on 2003-07-17 12:10:25 by Raymond

That procedure for example would probably work correctly as a MACRO instead of a procedure if you must insist using STRUC syntax. It can work as a procedure if you pass the addresses of the structures but you would not be able to use the struc syntax in references to struc items.

I don't understand... wouldn't this work?


CMov proc lpz1:DWORD,lpz2:DWORD
mov eax,lpz2
mov edx,lpz1
assume eax:ptr complex
assume edx:ptr complex
fldt eax.R
fstpt edx.R
fldt eax.I
fstpt edx.I
assume eax:nothing
assume edx:nothing
ret
CMov endp
Posted on 2003-07-17 13:28:36 by QvasiModo
I strongly believe in the KISS principle.

Why complicate simple things? The STRUC in this particular case simply contains two REAL10 variables. (Using a STRUC in this case could be assimilated to the use of a macro to declare a variable with two REAL10 components).

Whether you refer to those components as var and var+10 instead of var.r and var.i would perform exactly the same function (without any significant difference in the total amount of typing). Therefore no need for "ASSUMEs" and "UNASSUMEs" throughout the source code with always the possibility of forgetting one. (Plus all that useless extra typing for no constructive purpose.)

Raymond
Posted on 2003-07-17 15:16:45 by Raymond
I disagree. There *is* a good reason to use pointers to structs: if you try to access them calculating offsets, and later you decide to change the struct members, you'll have to change several parts of your source code... with always the possibility of forgetting one :grin:

Besides, there is a way to use pointers to structures without assumes:

(SOME_STRUCT ptr ).structMember

You have to type a lot more, but I think it's a good programming practice.
Posted on 2003-07-17 16:07:03 by QvasiModo
QvasiModo is right here. You can even go "Mov eax, Complex.i" and EAX will hold 4!

However, Raymond obviously spotted what i overlooked. (Feel dumb for missing that). the params should clearly be pointers if he expects a return.

InFnie, your arguments are "in only" arguments. Meaning you can pass in data to a function, but you can not pass data back. This is because for the following function


CMov PROC r1:COMPLEX, r2:COMPLEX
[... stuff here .. ]
ret
CMov ENDP

And a program calles it like so:
.data

r1 COMPLEX <>
r2 COMPLEX <>

.code
mov r1.r, 4
mov r1.i, 3
mov r2.r, 40
mov r2.i, 30
invoke CMov, r1, r2

The compiled program would end up as:


mov r1.r, 4
mov r1.i, 3
mov r2.r, 40
mov r2.i, 30
push r2.i
push r2.r
push r1.i
push r2.r
call CMove
add esp, 16
The fact that the stack is cleaned up by adding 16 to it (4 * 4byte pushes) means the 'returned' data your trying to pass back gets thrown in the garbage the moment it returns from the call. THIS is considered "passing by value" or "in only" function calling.

To get around this, you can pass "in only" a pointer. And modify your function to use the pointer to get more data in, or write data out. This style of argument is considered "passing by reference", since the pointer is a reference in memory as to where you can find and write back information to.

In your case something like this will work:
CMov proc z1:DWORD, z2:DWORD  ; Note all pointers are 32 bits long


; Get data pointed to in memory
mov edx, z1
mov eax, [edx].Complex.r

; Set data pointed to in memory
mov edx, z3
mov [edx].Complex.r, eax

ret
CMov endp


And your program would set up for the function like so:
.data

r1 COMPLEX <>
r2 COMPLEX <>

.code
mov r1.r, 4
mov r1.i, 3
mov r2.r, 40
mov r2.i, 30
invoke CMov, addr r1, addr r2


To help understand, when compiled your source would turn into the following before calling CMov:
   mov r1.r, 4

mov r1.i, 3
mov r2.r, 40
mov r2.i, 30
lea eax, r2
push eax
lea eax, r1
push eax
call CMove
add esp, 8


We dont care if the pointer gets trashed afterwards, because the function has already gone directly to the memory at "r1" and "r2" and did its business! This is the essence of pass by reference. Pass by value is good too, since you dont need to fuss with pointers, but you can only expect data back in a register (eax, ebx, etc). Pass by reference allows for both in this case ;)

Lastly, there is nothing stopping you from mixing the two. Ie, one argument takes a pointer for returning information, and the next takes a value for "in only" processing. Most API's are this way, like GetWindowText. It takes "in only" the window handle, and then asks for a memory pointer, so it can "write out" the text found in the window to you buffer (which you pointed to).

I hope i have expanded upon this enough for you to understand.
:alright:
NaN
Posted on 2003-07-17 18:02:12 by NaN
QvasiModo,
Please don't take me wrong. I do agree there is a place for strucs.
However, for this specific example, there is a set of variables containing only two members each which are not bound to change - ever. It could easily be programmed without the use of strucs and potentially avoid a lot of confusion.

inFinie,
I had tried to run your program and was getting only the ERROR string as a result. That may be why I assumed (wrongly) that you were feeding garbage to the FpuFLtoA function.

I fiddled a bit with your code and got the CAdd, CSub and CMul procs to work properly and realized that your use of the FpuFLtoA function is correct (except for the specified number of decimal places).

As a side note, you must initialize your buffer with a 0 byte before you concatinate strings into it. Secondly, you should not automaticly insert a "+" sign if the imaginary portion is negative. Finally, the size of the edit control for the result in the dialog box is insufficient for 15 decimal places.

I tried to fiddle with the code for the CDiv proc but something seems wrong. You don't seem to store the imaginary result as in the other procs.

Here's the fiddling I did with the test proc and the CAdd, CSub and CMul procs.
CTest proc

LOCAL z1:complex,z2:complex,z3:complex
invoke GetDlgItemText,hWnd,1001,ADDR buf,256
invoke FpuAtoFL, ADDR buf, ADDR z1.r, DEST_MEM
invoke GetDlgItemText,hWnd,1002,addr buf,256
invoke FpuAtoFL, ADDR buf, ADDR z1.i, DEST_MEM
invoke GetDlgItemText,hWnd,1003,addr buf,256
invoke FpuAtoFL, ADDR buf, ADDR z2.r, DEST_MEM
invoke GetDlgItemText,hWnd,1004,addr buf,256
invoke FpuAtoFL, ADDR buf, ADDR z2.i, DEST_MEM
invoke CAdd, z1, z2, addr z3
invoke CtoStr,z3, addr buf
invoke SetDlgItemText,hWnd,1005,addr buf
ret
CTest endp

CAdd proc z1:complex,z2:complex,zret:DWORD
mov eax,zret
fldt z1.r
fldt z2.r
faddp st(1),st
fstpt eax

fldt z1.i
fldt z2.i
faddp st(1),st
fstpt eax+10
ret
CAdd endp

CSub proc z1:complex, z2:complex,zret:DWORD
mov eax,zret
fldt z1.r
fldt z2.r
fsubp st(1),st
fstpt eax

fldt z1.i
fldt z2.i
fsubp st(1),st
fstpt eax+10
ret
CSub endp

CMul proc z1:complex,z2:complex,zret:DWORD
mov eax,zret
fldt z1.r
fldt z2.r
fmulp st(1),st
fldt z1.i
fldt z2.i
fmulp st(1),st
fsubp st(1),st
fstpt eax

fldt z1.r
fldt z2.i
fmulp st(1),st
fldt z1.i
fldt z2.r
fmulp st(1),st
faddp st(1),st
fstpt eax+10
ret
CMul endp

Raymond
Posted on 2003-07-17 21:02:29 by Raymond
Thank you for your help. It works now! I'll make the other proc's work soon.
The 80 char in FPU funcs.: I used it to see what is there after 15~16 chars
CtoStr: It is programmed not to put '+' before '-' but it didn't worked, (early stages). I thought it is wrongly programmed, but it is from 'referencing'


Is 15 decimal places is what can i get for high precision?(in FPU not mmx,sse... i don't know them, yet)
Posted on 2003-07-18 02:19:56 by inFinie
OK, i have finished doing it (zret to eax and add mov eax,zret)
It's attached
But CRecip is not working i.e. not returning i see when i debug it does computations truly. It seems like the same problem. Again thanks to all.
Note that CLog is very lousy.

Edit: oops, i forgot to attach:grin: Hey can't i attach to this post? It seems not.
Posted on 2003-07-18 02:52:16 by inFinie
Complexis:eek:
Posted on 2003-07-18 02:59:18 by inFinie
The 80-bit extended precision (REAL10) format of the FPU data registers has a 64-bit mantissa which is equivalent to 18-19 significant decimal digits. That is the maximum precision available from normal use of the FPU.

The packed Binary Coded Decimal (BCD) format used by the FPU to help convert to/from decimal/binary has a maximum of 18 decimal digits, the least significant one not always rounded. The FpuFLtoA function was limited to 16 decimal digits for simplicity (1 integer digit + 15 decimal places maximum for the scientific notation).

Raymond
Posted on 2003-07-18 08:12:50 by Raymond

However, Raymond obviously spotted what i overlooked. (Feel dumb for missing that). the params should clearly be pointers if he expects a return.


Great, I've posted before to tell the same thing to inFinie, and then I forget it myself. Embarrasing. :o
I'll just crawl back under my rock now, never mind me....
:stupid:
Posted on 2003-07-18 09:19:57 by QvasiModo
inFinie, re your latest Complexis,

Whenever you pass parameters, you must always ask yourself if it is the address of the data which is needed or the actual value of that data. This may get confusing when that data IS an address such as in your CRecip proc calling the CDiv proc. In that case, you don't want to pass the address of that address (although it could be handled correctly with the proper code).

It may have been less confusing if your original approach had been to pass only addresses to the various procedures instead of copying the actual data over and over and over again on the stack.

As for the display of the result, you don't have to rezero the entire buffer. All you have to do is rezero the first byte before the concatination as follows:
      mov   eax,s1

mov byte ptr [eax],0

You will also have to adjust the jumps (and add one) correctly to display the proper sign. You should also allow the display of a +0i in the complex answer.

Raymond

BTW, is one of the ultimate purposes of this library related to fractals?
Posted on 2003-07-18 14:34:47 by Raymond