• 🏆 Texturing Contest #33 is OPEN! Contestants must re-texture a SD unit model found in-game (Warcraft 3 Classic), recreating the unit into a peaceful NPC version. 🔗Click here to enter!

[Snippet] Complex Numbers

Level 18
Joined
Sep 14, 2012
Messages
3,413
If it can be used by someone I share it :
JASS:
library Complex/*
************************************************************************************
*       Description
*       -------------------------
*
*           This is a library that allows you to use complex number to do some gemotry
*           or some special calculus.
*
*
*       Credits to :
*       -------------------------
*           - looking_for_help for the InternLn function :)
*
*
*       Fields
*       -------------------------
*
*           struct Complex
*               real re
*               real im
*
*
*       Methods
*       -------------------------
*           
*           static method create takes nothing returns thistype
*           static method createPolar takes real abs, real phase returns thistype
*
*           method destroy takes nothing returns nothing
*
*
*           method add takes thistype z returns thistype
*               ~ It returns this+z
*
*           method sub takes thistype z returns thistype
*               ~ It returns this-z
*
*           method mul takes thistype z returns thistype
*               ~ It returns this*z
*
*           method div takes thistype z returns thistype
*               ~ It returns this/z
*
*           method conj takes nothing returns thistype
*               ~ It returns the conjugate of this
*
*           method operator abs takes nothing returns real
*               ~ It returns abs(this)
*
*           method operator abs= takes real value returns nothing
*               ~ Change the abs of the complex of the number to value
*
*           method operator arg takes nothing returns real
*               ~ It returns arg(this)
*
*           method operator arg= takes real value returns nothing
*               ~ Change the argument of the complex of the number to value
*
*           method toString takes nothing returns string
*               ~ It returns "a+bi"
*
*           method toStringPolar takes nothing returns string
*               ~ It returns "abs*e^(arg*i)"
*
*           method ln takes nothing returns thistype
*               ~ It returns ln(this)
*
*           method exp takes nothing returns thistype
*               ~ It returns e^this
*
*           method pow takes thistype a returns thistype
*               ~ It returns this^a
*
*           method sqrt takes nothing returns thistype
*               ~ It returns sqrt(this)
*
*           method equals takes thistype z returns boolean
*               ~ It returns if this==z or not
************************************************************************/
    globals
        private constant real E = 2.718282
        private constant real PI = 3.141592
        private constant real INV_E = 1/E
        
        //Change the maximum number of instance of complex number
        private constant integer MAX_NUMBER = 8190
    endglobals

    struct Complex [MAX_NUMBER]
        real re
        real im
    
        static method create takes real r, real i returns thistype
            local thistype this = thistype.allocate()
            set this.re = r
            set this.im = i
            return this
        endmethod
        
        static method createPolar takes real abs, real phase returns thistype
            if (abs < 0) then
                debug call BJDebugMsg("thistype : Creating a Complex Number with abs<0, crashing the thread!")
                return 1/0
            endif
            return create(abs*Cos(phase), abs*Sin(phase))
        endmethod
        
        //Thanks to looking_for_help !
        private static method internLn takes real r returns real
            local real sum = 0.0
            local real sign = 1.0
            if r < 1.0 then
                set r = 1.0/r
                set sign = -1.0
            endif
            loop
                exitwhen r < E
                set r = r*INV_E
                set sum = sum + 1.0
            endloop
            loop
                exitwhen r < 1.2840254
                set r = r*0.778808
                set sum = sum + 0.25
            endloop

            return sign*(sum + 0.125*(r - 1.0)*(1 + 9.0/(2.0 + r) + 4.5/(0.5 + r) + 1.0/r))
        endmethod
    
    
        method add takes thistype z returns thistype
            return create(this.re + z.re, this.im + z.im)
        endmethod
        
        method sub takes thistype z returns thistype
            return create(this.re - z.re, this.im - z.im)
        endmethod
        
        method mul takes thistype z returns thistype
            return create(this.re*z.re - this.im*z.im, this.re*z.im + this.im*z.re)
        endmethod
        
        method conj takes nothing returns thistype
            return create(this.re, -this.im)
        endmethod
        
        method div takes thistype z returns thistype
            local real dv = z.re*z.re + z.im*z.im
            if (dv!=0) then
                return create((this.re*z.re + this.im*z.im)/dv, (this.im*z.re - this.re*z.im)/dv)
            endif
            debug call BJDebugMsg("thistype Error : Divide by 0!! Crashing thread")
            return 1/0
        endmethod
        
        method operator abs takes nothing returns real
            return SquareRoot(this.re*this.re + this.im*this.im)
        endmethod
        
        method operator arg takes nothing returns real
            return Atan2(this.im, this.re)
        endmethod
        
        method operator abs= takes real value returns nothing
            local real angle = Atan2(this.im, this.re)
            set this.re = value*Cos(angle)
            set this.im = value*Sin(angle)
        endmethod

        method operator arg= takes real value returns nothing
            local real abs = SquareRoot(this.re*this.re + this.im*this.im)
            set this.re = abs*Cos(value)
            set this.im = abs*Sin(value)
        endmethod
        
        method toString takes nothing returns string
            if (this.im >= 0) then
                return R2S(this.re)+"+"+R2S(this.im)+"i"
            endif
            return R2S(this.re)+R2S(this.im)+"i"
        endmethod
        
        method toStringPolar takes nothing returns string
            return R2S(this.abs)+"*e^("+R2S(this.arg)+"*i)"
        endmethod
        
        method ln takes nothing returns thistype
            if this.re == 0 and this.im == 0 then
                debug call BJDebugMsg("thistype Error : Attempt to do something bad like ln(0) !! Crashing thread")
                return 1/0
            endif
            return create(internLn(this.abs), this.arg)
        endmethod
        
        method exp takes nothing returns thistype
            return create(Pow(E, this.re)*Cos(this.im), Pow(E, this.re)*Sin(this.im))
        endmethod
        
        method pow takes thistype z returns thistype
            return this.ln().mul(z).exp()
        endmethod
        
        method sqrt takes nothing returns thistype
            local thistype c = thistype.allocate()
            if (this.im == 0) then
                if (this.re > 0) then
                    set c.re = SquareRoot(this.re)
                    set c.im = 0
                else
                    set c.re = 0
                    set c.im = SquareRoot(-this.re)
                endif
            else
                set c.re = SquareRoot((this.re + this.abs)/2)
                set c.im = SquareRoot((this.abs - this.re)/2)
                if this.im < 0 then
                    set c.im = -c.im
                endif
            endif
            return c
        endmethod
        
        method equals takes thistype z returns boolean
            return this.re==z.re and this.im==z.im
        endmethod
    endstruct
endlibrary

Feel free to use it if you want to do complex geometry in your map or something.
You can use it for example to create some fractals like Mandelbrot's or Julia's.
 
Last edited:

Dr Super Good

Spell Reviewer
Level 63
Joined
Jan 18, 2005
Messages
27,177
Complex numbers could probably be implemented as some kind of vector and matrix system. after all, they basically act like a 2D vector with x being real and y being imaginary.

This also raises an interesting argument of what about multiple dimensions of complexity? I mean in theory you could have a constant like i/j that acts exactly like i/j with the real values but also with i/j since it is perpendicular to both. However this is getting kind of off topic.

You are missing important operators. Such as the exponential of a complex number (a rotational operation), conversion to polar form (the angle). Simple mathematical operators between complex numbers (+, -, /, * etc) are also missing.
 
Level 18
Joined
Sep 14, 2012
Messages
3,413
Ah yes sorry xD
I did this in english class when I was bored but I forgot many operators you're right :)
(Besides I searched what could be useful xD)

I might do quaternions too later (but dunno if someone will use it a day ^^)

Anyway thanks for the replay DSG it is honor for me that you came comment this !
 

Dr Super Good

Spell Reviewer
Level 63
Joined
Jan 18, 2005
Messages
27,177
JASS:
        //It will put the result on the first complex
         static method mul takes thistype z, thistype z2 returns nothing
             local real temp = z.re*z2.im + z.im*z2.re
             set z.re = z.re*z2.re - z.im*z2.im
             set z.im = temp
         endmethod
Why is a local declared when the value produced is only ever used once?
 

Dr Super Good

Spell Reviewer
Level 63
Joined
Jan 18, 2005
Messages
27,177
Oh yeh intermediate values, did not see that one. I have been using matlab too much recently... In any case "nim" would probably be a better name for it than temp since it is shorter and more readable (new im).

You are still lacking a natural exponential operation that takes a complex number and returns a complex number.
 

Dr Super Good

Spell Reviewer
Level 63
Joined
Jan 18, 2005
Messages
27,177
I don't see how to programm this well to create a good API for natural exponential :/
Because it is only : abs(z)*e^(i*angle(z)) :/
Not entirely true.

z = e^ln(z)
as such
abs(z) * e ^ (i * angle(z)) = e ^ ln(abs(z)) * e ^ (i * angle(z))

Which if you simplify... because e ^ a * e ^ b = e ^ (a + b)
= e ^ (ln(abs(z)) + i * angle(z))

ln(abs(z)) + i * angle(z) is a complex number with both a real part (ln(abs(z))) and imaginary part (angle(z)).

As such polar form can be represented as a single complex number instead of 2 separate angle and magnitude values. This complex number could then be represented as another polar form complex number and repeated to form an infinitely deep series. No idea why one would ever do that though.

You may be thinking "How is this possible?". Well here is the proof that what I said makes total sense. Putting (ln(10) + i * pi) through the natural exponential function yields -10 as it is a magnitude of 10 at 180 degrees.

As such you need a natural exponential function that takes a complex number and returns a complex number.
 
Level 22
Joined
Sep 24, 2005
Messages
4,821
122581-albums6788-picture76682.png
Trolling on your own resource?
 
Level 23
Joined
Apr 16, 2012
Messages
4,041
JASS:
            if z2.abs == 0 then
                call BJDebugMsg("Complex library -> Attempt to divide by 0 !! Crashing the thread")
                return 0
            endif

I would prefer:

JASS:
            if z2.abs == 0 then
                debug call BJDebugMsg("thistype: Attempt to divide by 0 !! Crashing the thread")
                return 1/0
            endif

also you are saying crashing the thread, but you only return, if you wanted to crash the thread, you actually have to divide by 0, or run infinite loop until OP limit is hit

Also thistype, no matter if in string or not, will always result in the name of the struct being used(if it is private, it will also include the Private part of the name)
 

Dr Super Good

Spell Reviewer
Level 63
Joined
Jan 18, 2005
Messages
27,177
Probably to compensate for the limited accuracy of floating points so you do not get strange results such as converting integer to real causing an error of multiple units.

In SC2 they use fixed point and clearly enforce its bounds. This results in the inability to convert large integer numbers to fixed point as it results in overflow of the most significant bits.
 
Level 6
Joined
Jul 30, 2013
Messages
282
It does slow things down as reals that long are treated as strings.

Are you for real?
some reference you want to share with us maybe, i would be really intrigued to learn why they would do this :goblin_jawdrop: ..

why oh why would one need to treat a float as a string is beyond me. there are so many bits for the significant digits (and this qty is fixed) that any relative error would be completely negligible unless you do something really stupid (i absolutely horribly abuse reals in the Fortress survival map and have the errors pile up during the entire game instance life time.. and they barely show themselves at all.
 

Dr Super Good

Spell Reviewer
Level 63
Joined
Jan 18, 2005
Messages
27,177
that any relative error would be completely negligible
Um no...
Seeing how an integer already uses all 32 bits for unique values, logically if you convert a large one into a float 32 bit float you will have to get error (error larger than 1 unit). Obviously the string is used to mitigate this error, as illogical as it sounds.

The question is... Why not used a dynamic fixed point? That would surely be more efficient.
 
Level 6
Joined
Jul 30, 2013
Messages
282
Um no...
Seeing how an integer already uses all 32 bits for unique values, logically if you convert a large one into a float 32 bit float you will have to get error (error larger than 1 unit). Obviously the string is used to mitigate this error, as illogical as it sounds.

The question is... Why not used a dynamic fixed point? That would surely be more efficient.
hmm..
IEEE single precision floating point has 24 bits for the fraction so a 24 bit int should at least fit in..
And if you really ran out of precision you could just use a double, all 32bit ints would have a unique representation then..

can anybody actually prove that JASS reals will indeed be treated as strings from some point on?
 

Dr Super Good

Spell Reviewer
Level 63
Joined
Jan 18, 2005
Messages
27,177
And if you really ran out of precision you could just use a double, all 32bit ints would have a unique representation then..
Not a viable solution as double precision floating points heavily load pipelines and take over twice as long to compute. Although it is highly likely to still be faster than any string based methods but might be slower than long fixed point or dynamic fixedpoint.
 
Level 6
Joined
Jul 30, 2013
Messages
282
... you don't interpret JASS on a GPU... do you? :O

Anyway ... it was claimed JASS handles long reals as strings. No reference of any kind was given. Does anybody have an authoritative source they can reference that explicitly states "Warcraft 3 handles large reals as strings" so that i can treat this as anything other than speculation..


Also.. if they really do treat large reals as strings..
That makes you wonder: what do they treat "normal" reals as..
fixed point arithmetic? (i can't see why they'd bother optimize such cheap things when everything else in JASSis so darn expensive.. but well maybe they did?)
 
Level 23
Joined
Apr 16, 2012
Messages
4,041
... you don't interpret JASS on a GPU... do you? :O

Anyway ... it was claimed JASS handles long reals as strings. No reference of any kind was given. Does anybody have an authoritative source they can reference that explicitly states "Warcraft 3 handles large reals as strings" so that i can treat this as anything other than speculation..


Also.. if they really do treat large reals as strings..
That makes you wonder: what do they treat "normal" reals as..
fixed point arithmetic? (i can't see why they'd bother optimize such cheap things when everything else in JASSis so darn expensive.. but well maybe they did?)

tbh, there is almost no source for most of the things found

Most of the thing that are discovered are either tested, or reverse engineered

But it is intresting claim nevertheless
 
Level 14
Joined
Dec 12, 2012
Messages
1,007
The R2S removes the special properties. In fact setting it to a variable alone should remove the properties. But it's when the real is inlined and you're doing something like math that it gets bad.

What "special properties"? Its not possible that too long reals would be treated as strings neither does it make any sense. Wc3 reals are 32 bit floats which you can easily verify by taking a look at its accuracy. Passing a string variable to a function that expects a float would cause an immediate crash of the game.

If it would be like that, why does the accuracy not increase? With a string you would have (practically) infinite accuracy.

This:

JASS:
private static method onInit takes nothing returns nothing
	local integer i = 0
	local real r1
	loop
		set i = i + 1
		call BJDebugMsg(I2S(i))
		set r1 = 1.5555555555555555555555555555555555555555*1.5555555555555555555555555555555555555555
	endloop
endmethod

has exactly the same maximum OP-count as this

JASS:
private static method onInit takes nothing returns nothing
	local integer i = 0
	local real r1
	loop
		set i = i + 1
		call BJDebugMsg(I2S(i))
		set r1 = 1.5*1.5
	endloop
endmethod

-> urban myth.
 
Last edited:
Level 14
Joined
Dec 12, 2012
Messages
1,007
Maybe I sound terribly boring or mad but I don't see what's the link with my resource :O !

Well, the link is that you use this in your resource:

private constant real e = 2.71828182845904523536028747135266249775724700000

This is bad and you shouldn't do that. However, the explanation why it is bad was incorrect. Its not slowing down your resource, but just making the variable to overflow and therefore store an undefined value.

As already mentioned, Wc3 reals are 32 bit floats. This datatype has seven significant digits, which means that if you put in more, it will overflow. Try this:

JASS:
private static method onInit takes nothing returns nothing
	local real e = 2.71828182845904523536028747135266249775724700000		
	call BJDebugMsg(R2S(e*e))
endmethod

For me, it outputs 4.00, while the correct result should be about 7.38.

Thats the reason why I use in my Maths library the constant real E = 2.718282 (seven digits). It is a bit more accurate than the native Jass constant for e, bj_E (six digits), thats why I included that constant in the Maths library. Higher accuracy is not possible with 32 bit floating numbers.

If you want to use a custom constant for e, use that one. Don't use any higher "accuracy", because it will make your variable overflow.
 
Top