• 🏆 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!
  • It's time for the first HD Modeling Contest of 2024. Join the theme discussion for Hive's HD Modeling Contest #6! Click here to post your idea!

[Snippet] Natural Logarithm

(Disclaimer: I'm not sure if someone has presented this method before, if he has please inform me, I just developed this today and I've been unable to check).

EDIT: I checked, and most of the other methods are different some use binary, others borzan. The method which i found to use the same first rule that I apply, is pipedream's from wc3jass. Credits to him since he posted the idea first, :D.

Logarithm:

I scripted 5 functions for calculating the natural logarithm of a number, the first one is slower than the later, but it's more accurate.
JASS:
function Ln takes real a returns real
    local real s = 1.0
    local real sum = 0.0
    local real g
    local real e
    local integer i = 10
    loop
        exitwhen a < bj_E
        set a = a/bj_E
        set sum = sum + 1.
    endloop
    set e = (a-1.0)/10. //quite accurate for numbers < e
    set g = s + e
    loop
        exitwhen i <= 0 or a <= 1.0
        set i = i-1
        set sum = sum + (g-s)*(1./s + 8./(s+g) + 1./g)/6.
        set s = g
        set g = s + e
    endloop
    return sum
endfunction

// cubic, very good accurity for warcraft
function Ln takes real a returns real
    local real sum = 0.0
    loop
        exitwhen a < bj_E
        set a = a/bj_E
        set sum = sum + 1.
    endloop
    return sum + (a-1.)*(1. + 9./(2.+a) + 9./(1.+a*2.) + 1./a)/8.
endfunction

//Even faster! --parabollic, has a good accurity as well
function Ln takes real a returns real
    local real sum = 0.0
    loop
        exitwhen a < bj_E
        set a = a/bj_E
        set sum = sum + 1.
    endloop
    return sum + (a-1.)*(1. + 8./(1.+ a) + 1./a)/6.
endfunction

//Fourth, this one is quite a bit more accurate than the former 2, but it is a bit slower.

function Ln takes real a returns real
    local real sum = 0.0
    local real e = 1.284025
    loop
        exitwhen a < bj_E
        set a = a/bj_E
        set sum = sum + 1.
    endloop
    loop
        exitwhen a < e
        set a = a/e
        set sum = sum + .25
    endloop
    return sum + (a-1.)*(1. + 8./(1.+ a) + 1./a)/6. //and we apply simpson's :D
endfunction

//Added a Fifth, this one uses Taylor series instead.

function Ln takes real a returns real
    local real sum = 0.0
    local real e = 1.648721
    local real b
    loop
        exitwhen a < bj_E
        set a = a/bj_E
        set sum = sum + 1.
    endloop
    if a >= e then
        set a = a/e
        set sum = sum + .5
    endif
    //Time to apply Taylor series of ln(1+x).
    set e = 1.
    set a = (a - 1.)
    set b = a
    loop
        exitwhen e > 5.
        set sum = sum + a/e
        set e = e + 1.
        set a = a*b*(-1.)
    endloop
    return sum
endfunction

function LogB takes real a, real base returns real
    return Ln(a)/Ln(base) //I would suggest that you save the nautral log of the base 
                          //if you want to keep using it over and over.
endfunction


JASS:
define {
    FOURTH_E = 1.284025
}

float Ln(float a) {
    float sum = 0.0;
        loop {
            exitwhen a < bj_E;
            a /= bj_E;
            sum+=1.; //I don't trust ++ for reals ¬¬
        }
    return sum + (a-1.)*(1. + 8./(1.+ a) + 1./a)/6.;
}

float Ln(float a) {
    float sum = 0.0;
        loop {
            exitwhen a < bj_E;
            a /= bj_E;
            sum+=1.; //I don't trust ++ for reals ¬¬
        }
        loop {
            exitwhen a < FOURTH_E;
            a /= FOURTH_E;
            sum+=.25;
        }
    return sum + (a-1.)*(1. + 8./(1.+ a) + 1./a)/6.;
}

float LogB(float a, float b) {
    return Ln(a)/Ln(b);
}



Code:
const fixed math_e = 2.7182818;
const fixed math_e_p4 = 1.284025;

fixed Ln(fixed val) {
	fixed sum = 0.0;
        while (val < math_e) {
            val /= math_e;
            sum+=1.;
        }
        while (val < math_e_p4) {
            val /= math_e_p4;
            sum+=.25;
        }
    return sum + (val-1.)*(1. + 8./(1.+ val) + 1./val)/6.;
}

int Log2(int val) {
	int i = 0;
		while(val > 1) {
			val >>= 1;
			i += 1;
		}
		return i;
}


The earlier functions work only for values between [1,+infinite)

The next functions are a bit slower but have a full dominious of logarithm: (0,+inifinite)

JASS:
//Even faster! --parabollic, has 2 decimals accurity
function Ln takes real a returns real
local real sum = 0.0
local real sign = 1.
if a < 1. then
    set a = 1./a
    set sign = -1.
endif
loop
    exitwhen a < bj_E
    set a = a/bj_E
    set sum = sum + 1.
endloop
return sign*(sum + (a-1.)*(1. + 8./(1.+ a) + 1./a)/6.)
endfunction

//Fourth, this one is quite a bit more accurate than the former 2, but it is a bit slower.
//5 decimals accurity.
function Ln takes real a returns real
local real sum = 0.0
local real sign = 1.
if a < 1. then
    set a = 1./a
    set sign = -1.
endif
loop
    exitwhen a < bj_E
    set a = a/bj_E
    set sum = sum + 1.
endloop
loop
    exitwhen a < 1.284025
    set a = a/1.284025
    set sum = sum + .25
endloop
return sign*(sum + (a-1.)*(1. + 8./(1.+ a) + 1./a)/6.) //and we apply simpson's :D
endfunction



JASS:
define {
    FOURTH_E = 1.284025
}

float Ln(float a) {
    float sum = 0.0;
    float sign = 1.;
	if a < 1. { a = 1./a; sign = -1. }
        loop {
            exitwhen a < bj_E;
            a /= bj_E;
            sum+=1.; //I don't trust ++ for reals ¬¬
        }
    return sign*(sum + (a-1.)*(1. + 8./(1.+ a) + 1./a)/6.);
}

float Ln(float a) {
    float sum = 0.0;
    float sign = 1.;
	if a < 1. { a = 1./a; sign = -1. }
        loop {
            exitwhen a < bj_E;
            a /= bj_E;
            sum+=1.; //I don't trust ++ for reals ¬¬
        }
        loop {
            exitwhen a < FOURTH_E;
            a /= FOURTH_E;
            sum+=.25;
        }
    return sign*(sum + (a-1.)*(1. + 8./(1.+ a) + 1./a)/6.);
}


I'll explain a bit what's done.

The first part of the function takes care of calculating the integer part of the logarithm. We keep divinding our number by the base(e), until our number is smaller than e.

a good way to observe it is in this way:
attachment.php


As you see, we percieve the number as a multiplication. Now, why did we make this?(Here's where most of you get lost) Very simple, when calculating the definate integral of a function which we can't calculate through normal algebra, we must use the Simpson's method. Why I didn't use it directly? Simpson's rule is an approximation of the definite integral, the bigger be the distance between a and b(the range of the integral), the biggger will be the accumulated error.

Some of you may remenber that the derivate of the natural logarithm is an hyperbole: Ln'(x) = 1/x (d(Ln(x))/dx = 1/x). 1/x is a function which is calculateable through normal aritmethics. Now knowing that, we can calculate the definate integral between 1 and the leaving value, which is < e, using Simpson's Rule.

I'm not going to be very specific on Simpson's rule, you can look at it in wikipedia or mathworld.

Now the Benchmarking and Statistics:

attachment.php


Here's anotther test with an smaller number:

attachment.php


It will be your decision to decide which function you'll use. I would suggest Third for fast usage, like for moving objects or places where you need it a lot. The fourth one should be used for things that do require a LOT of accurity, like armor calculation. I would suggest to scrap the first and fifth they were just propositions to solve the problem.

EDIT: forgot to mention it, because I thought it was obvious. The O of our f(x) is O(ln (n)) in all the functions, notice that not every algorithm to calculate Ln(n) has the same growth as the function. For instance, Vexorian's logarithm has a constant speed growth because it approximates the functions by using borzan acotation every set of predefined interations.
 

Attachments

  • LogN.jpg
    LogN.jpg
    43.6 KB · Views: 2,450
  • Benchmark.jpg
    Benchmark.jpg
    136.9 KB · Views: 2,315
  • Benchmark2.jpg
    Benchmark2.jpg
    79.6 KB · Views: 2,354
Last edited by a moderator:
Level 17
Joined
Mar 17, 2009
Messages
1,349
Does this calculate Ln or Log? Cause if it calculates Ln, you'd have done some people a major favor (at least me :p). Going into summer my brain went into hibernation, so I couldn't remember how to get the Ln of a number. However, if it calculates the log, then... meh. :s

EDIT:
Before you think I'm stupid :p I mean yes I see you say Ln everywhere, yet you call it natural logarithm... unless my memory is fooling me... I think Ln isn't called that.
 
natural logarithm is the logarithm of base e. Atleast in spanish we refer to natural logarithm as it.

well with natural logarithms you can calculate the logarithm of any base. Just calculate the natural logarithm of the number you want and the natural logarithm of the base.

for example:

JASS:
globals
       constant real LOGB10 = 2.302585 //Natural logarithm of 10
endglobals

//Blah Blah
set mynumb = Ln(2.)/LOGB10 //gets the logarithm of 2 base 10.
//Blah Blah

also added a cJass version for those who prefer to work the C way.
 
Last edited:
local real e = 1.284025417

Are you even sure WC3 reals can represent that accracy? I thought they were nothing but 32 bit floats with an accuracy of about 5 SF or something.

man I just took Window's calculator, calculated the e^(1/4) clicked Copy, then Pasted and I took off some random decimals. It's obvious that the precission is like what 6 decimals? after all it doesn't really matter.
 
Level 15
Joined
Jul 19, 2007
Messages
618
man I just took Window's calculator, calculated the e^(1/4) clicked Copy, then Pasted and I took off some random decimals. It's obvious that the precission is like what 6 decimals? after all it doesn't really matter.

as far as i know max precision is 5 decimal places! i once played by duno making PI go to 15 decimal places and if i remember correctly it rounded it to 3.00000...

all angles went to hell and from that time i always round to 5 deciaml places since i am 100% sure its safe and ofc precious enough...

this Ln stuff u made is by any means really awesome and included cJass well just gj nothing else to say!
 

Dr Super Good

Spell Reviewer
Level 63
Joined
Jan 18, 2005
Messages
27,190
It does not round, it purly only represents them accuratly to 5 figures. Past that number of figures, the remaining ones are purly representations of the actual number and are not at all accurate (numbers do exist but they are the left over products from calculating the fist numbers accuratly). You can look at them as a "lossy" way of storing numbers to allow efficent comuptation.
 
Level 15
Joined
Jul 19, 2007
Messages
618
It does not round, it purly only represents them accuratly to 5 figures. Past that number of figures, the remaining ones are purly representations of the actual number and are not at all accurate (numbers do exist but they are the left over products from calculating the fist numbers accuratly). You can look at them as a "lossy" way of storing numbers to allow efficent comuptation.

hmm so if i get it right! 3456.7888 would be: 3456.700...???

but that cant be so i must have got it wrong, coz PI = 3.14159! this means that 3.14159265 would again be cut'ed to 3.14159??? and 555555.666666 would be cut to 55555.66666... maybe thats how it is...

btw float-s are not as well to much precious, doubles are most likely 2x precious then floats and can contain larger numbers but take more memory.
 
Last edited:
Dark, think it this way.

let's say you have 0.1 and 10000000000.

if you plus both you get:

10000000000., and why not 10000000000.1?

Reason:

The reason is the way floats store data. Let me explain how it's done

float's structure: 1 bit for sign (0 or 1, + or -), 8 bits for an exponent and the last 23 bits for a significand, to those 23 bits an implicit 1 bit is added by the left.

Now let's show you how to do this:
here's our number:
0100 0000 0000 0000 0000 0000 0000 0000

float view = 0 10000000 00000000000000000000000

first we take the sign = 0 (first bit) = +.

now the byte of the exponent represents the exponent of the decimals. First you grab the byte and convert it to base 16. so we have 1000 0000 = 0x80 = 128. To this number we rest -127 and so we get 1.

Now we grab the significand and we insert the new bit at the start:

100000000000000000000000 (new bit is bolded)

here's the formula of the summatory to calculate the significand:
attachment.php


In our case:

1/1 + 0/2 + 0/4 ....

our result is obviously: 1. -> significand = 1.

so after that, we finally calculate our number. for this we multipli the sign*(2^exponent)*significand

Our number would be= (1)*(2^1)*1 = 2.

xD, easy isn't it?

now how would you write 3.5?

very simple we just change our significand from:

100000000000000000000000 to 111000000000000000000000

the new significand = 1. + 1/2 + 1/4 + 0/8 ...= 1. + .5 + .25 + 0. ...

result = 1.75

so if we recalculate things. This is what we get.

New number = (+1)*(2^1)*(1.75) = 3.5, xD. Hope you learned something today.

EDIT: to calculate the precision (in decimal system), we calculate the logarithm base 10 of 2^24 which is 7.224, that means that the decimal precision is of 7 digits. So bj_Pi = 3.141592//cuts here// is around the best accurity of PI which you could save up.

Now back on topic.
 

Attachments

  • Lol.jpg
    Lol.jpg
    23.1 KB · Views: 674
Last edited:
Level 15
Joined
Jul 19, 2007
Messages
618
Dark, think it this way.

let's say you have 0.1 and 10000000000.

if you plus both you get:

10000000000., and why not 10000000000.1?

Reason:

The reason is the way floats store data. Let me explain how it's done

float's structure: 1 bit for sign (0 or 1, + or -), 8 bits for an exponent and the last 23 bits for a significand, to those 23 bits an implicit 1 bit is added by the left.

Now let's show you how to do this:
here's our number:
0100 0000 0000 0000 0000 0000 0000 0000

float view = 0 10000000 00000000000000000000000

first we take the sign = 0 (first bit) = +.

now the byte of the exponent represents the exponent of the decimals. First you grab the byte and convert it to base 16. so we have 1000 0000 = 0x80 = 128. To this number we rest -127 and so we get 1.

Now we grab the significand and we insert the new bit at the start:

100000000000000000000000 (new bit is bolded)

here's the formula of the summatory to calculate the significand:
attachment.php


In our case:

1/1 + 0/2 + 0/4 ....

our result is obviously: 1. -> significand = 1.

so after that, we finally calculate our number. for this we multipli the sign*(2^exponent)*significand

Our number would be= (1)*(2^1)*1 = 2.

xD, easy isn't it?

now how would you write 3.5?

very simple we just change our significand from:

100000000000000000000000 to 111000000000000000000000

the new significand = 1. + 1/2 + 1/4 + 0/8 ...= 1. + .5 + .25 + 0. ...

result = 1.75

so if we recalculate things. This is what we get.

New number = (+1)*(2^1)*(1.75) = 3.5, xD. Hope you learned something today.

EDIT: to calculate the precision (in decimal system), we calculate the logarithm base 10 of 2^24 which is 7.224, that means that the decimal precision is of 7 digits. So bj_Pi = 3.141592//cuts here// is around the best accurity of PI which you could save up.

Now back on topic.


yeah i knew it was working somehow like that, but today i learned how exactly it works. so i would like to thank you for this great explanation and all time that it took you to write this!

Best Regards!
~Dark Dragon
 
Level 15
Joined
Jul 19, 2007
Messages
618
i am going to use this log function in EGUI for "reals", for integers i will use my recursion log function which should be much faster, however for reals i would like to use yours. it will be there in JNGPS 1.3 which will come any day soon, when cJass 1.4 is out. if you dont allow me to add it please pm me or send visitor msg :)

once again thanks for explaining how floats work!

Regards!
~Dark Dragon
 
i am going to use this log function in EGUI for "reals", for integers i will use my recursion log function which should be much faster, however for reals i would like to use yours. it will be there in JNGPS 1.3 which will come any day soon, when cJass 1.4 is out. if you dont allow me to add it please pm me or send visitor msg :)

once again thanks for explaining how floats work!

Regards!
~Dark Dragon

do as you wish. Just add the Hoare Triplets so people know the states of input:

Code:
[ var r : real;
	{ r = R /\ r >= 1. }
	MyLogarithm
	{ Pow(e,r) = R }
]

the precondition is important since numbers in the range (0,1) won't compute. You can also modify the function if you wish or if you want (and i get time) I can do it for you.

About your integer version (which I imagine uses a similar division method but in recursion) try to verify it's benchmark. Recursion is useful for low n but they can have worst case scenarios than their loop counterparts.
 
Level 15
Joined
Jul 19, 2007
Messages
618
do as you wish. Just add the Hoare Triplets so people know the states of input:

Code:
[ var r : real;
	{ r = R /\ r >= 1. }
	MyLogarithm
	{ Pow(e,r) = R }
]

the precondition is important since numbers in the range (0,1) won't compute. You can also modify the function if you wish or if you want (and i get time) I can do it for you.

About your integer version (which I imagine uses a similar division method but in recursion) try to verify it's benchmark. Recursion is useful for low n but they can have worst case scenarios than their loop counterparts.

k if you have time to edit ur function then yeah it would be great... since i dont know how to write real Log function.

i used ur most precious but slowes one (first one)
then i defined some constants like LN10 and LN2 and then made RLog(base, result) which uses ur Ln function!

JASS:
        // *** using Ln for calculations with base ***
	function RLog takes real base, real result returns real
		if (base == E) then
			return Ln(result)
		elseif (base == 10.) then
			return Ln(result)/LN10
		elseif (base == 2.) then
			return Ln(result)/LN2
		endif
		return Ln(result)/Ln(base)
	endfunction

about my func, here it is:

JASS:
    // *** Returns log from given values ***
    constant function log takes integer base, integer result returns integer
        if (result == 1) then
            return 0
        endif
        return log(base, result/base)+1
    endfunction

now just when i was writing this i fixed the bug in this func!

before i wrote result <= 1 but thats wrong coz i can do this:

log(-2, -512)

so thanks for telling me to show u my func xD

so now about ur func!

if i understanded it correctly Ln(0 to 1) will not work?

k ill say that as hint in EGUI if you dont fix it :)

Greets!
~DD
 
Level 15
Joined
Jul 19, 2007
Messages
618
Thanks BlinkBoy!

ill use ur Ln 0-infinite with 5 precision

your Ln is used in function RLog(real,real) and i give you the credits ofc...

you can find RLog in JNGPS's EGUI :)

test it if you have time, in next patch ill update your Ln function...

so once again thanks for all your work and coding of Ln!

Greets +rep!
~DD

EDIT: Update to 1.3a where i added ur new Ln
 
Last edited:
Top