[MATH] Getting the ln(x) given x

Now, this may seem really simple at first, the ln(x) given x so math.log(x) and done, sadly no.

What I’m asking for is a function for getting the ln(x), currently I’ve tried 2 methods, 1 of which is far more accurate than the other, but still has a small margin of error.


Summation Solution

The first uses a summation (here’s the function I’ve made for that, disregard the path),

note: calc.math.e = e (2.71828182845904523536)

function calc.series.summation.new(s,n,body,...)
	local self = {}
			
	self.s = s -- initial step
	self.n = n -- step amount = n-s
	self.body = body -- body function to occur to i,... within every step
	self.args = {...}
	self.p = 0
			
	function self:update()
		local p = 0
		for i=self.s,self.n,1 do
			p = p + self.body(i,unpack(self.args))
		end
		self.p = p
	end
	self:update()
			
	function self:setargs(...)
		self.args = {...}
	end
			
	function self:setbody(newbody)
		self.body = newbody
	end
			
	return self
end

Then, the function for getting the ln of a given x is as follows,

function calc.math.ln(x)
	local summation = calc.series.summation.new(1,100000,function(n) return ((1/n)*(((x-1)/x)^n)) end)
	summation:update()
	return summation.p
end

This is great and all, but once you increase to the higher values of x (like e^20 being x), it doesn’t work and it gives a value which is wrong by a large degree. The solution to this would be changing the 100000 in the summation to a higher number, but this will just create a loop being called 100000+ times (which the 100000 is already alot). The solution for this was to create a whole new function for integrals.


Integral Solution

The function for integrals is as follows,

function calc.series.integral.new(a,dx,fx,s,fp) -- work in progress // DO NOT USE
	local self = {}
		
	self.fx = fx -- f(x) = y = fx
	self.fp = fp or function(p) return p end
	self.dx = dx or 1/100
	self.a = a
	self.s = s or 1
	self.p = 0
		
	function self:update()
		local p = 0
		for x=self.s,self.a,dx do
			p = p + self.fx(x,dx)
		end
		self.p = self.fp(p)
	end
	self:update()
		
	function self:setargs(...)
		self.args = {...}
	end
		
	function self:setbody(newbody)
		self.body = newbody
	end
		
	return self
end

Now, using this we can create a function for the ln(a) which is as follows,

function calc.math.ln(a)
	local es = 0
	repeat es = es+1 until (calc.math.e^es)>a;
	if a == calc.math.e^(es-1) then -- incase it is e, don't overshoot based on algorithm
		return es-1
	end
	local integral = calc.series.integral.new(
		a, -- a
		(calc.math.e^(es))/1500, -- step
		function(x,dx) return ((1/x)*dx) end, -- f(x) = y
		calc.math.e^(es-1), -- starting step
		function(p) return p+(es-1) end -- f(p) = integral.p given p
	)
	integral:update()
	return integral.p
end

The only problem with this is that it has to use a loop of 1500 and then fetches a really close value for the ln(a) with a margin of error of 0.002 (which can be reduced by changing the step/dx size) along with the fact that it can lag the first time you use it.


With all the above issues, comments and functions being stated, is there a better way to be doing this? Or are these the best methods to use?

Thank you for reading this far, if you have any questions as to how anything works, or have any comments or replies, please respond below. I will try to reply as quickly as possible.

Thanks,
Scar

1 Like

Not exactly sure what you’re on about since math.log returns the natural log of its input.

1 Like

As far as I can quickly see a possible issue you encounter is floating point inaccuracy, you are dividing and multiplying floating point numbers a lot and it is their nature in implementation to get a cumulative error to stack up, especially when recursing values. Perhaps a less abstract description of what you try to achieve in the end can help find a solution that does not rely on these calculations as a whole.

1 Like

Can you please explain in more detail how math.log isn’t giving you a natural logarithm?

2 Likes

y = math.log(x)
x = math.exp(1)^y

No clue why you’re trying to solve something so simple with a Riemann sum or whatever you’re doing.

2 Likes

math.log is the natural log
To get the value of e do math.exp(e)
To change log base there’s always
eq0050P
So if you want to go to log10 from loge it’s log10 x / log10 e

1 Like

math.log10(x) is also a thing, FYI.

2 Likes

Good point. The change of base formula is still useful though, just not for this circumstance.

1 Like

The end goal of this is to create a function to give us the log natural of x, as you probably know. To do this, I am in effect using the following integration:

and then adding f by getting how many times e can be multiplied by itself to be less than a. (es-1 in my code)
I will try reducing unnecessary dividing/multiplying for the integration.


Mostly this is just for fun, so although math.log gives the natural log, I’m trying to create my own function to do the same.

I should rephrase it, math.log(x) does give the natural logarithm, but what I’m doing is creating my own function to do so (mostly for the fun of it), then asking if there are any better methods.

The point of it is to not use the lua/roblox integrated math functions, but to create my own function for it, for the fun of it.

I’m currently using the change of base formula for logs in my logbx(b,x) function where loga is the log natural, but what I am doing here is creating my own “math.log” function.

Try using Kahan summation to reduce fp errors accumulating due to repeated addition.

Update: Doesn’t seem to work with your summation solution.

1 Like

The only problem with this is that it has to use a loop of 1500 and then fetches a really close value for the ln(a) with a margin of error of 0.002 (which can be reduced by changing the step/dx size) along with the fact that it can lag the first time you use it.

What rediscovered here is that step-wise integration is a terrible way to try to approximate numerical values, because as you increase the number of steps of your approximation you only increase the precision of your result linearly with the amount of numbers you’re crunching. Not only that, but the floating point error introduced also goes up linearly with the amount of steps you use.

This is great and all, but once you increase to the higher values of x (like e^20 being x), it doesn’t work and it gives a value which is wrong by a large degree.

Your error here is not applying some simplifying identities first before you apply your series. You should always ask if mathematics can solve part of the problem before you throw the rest of it at a computer:

ln(3,457,398,457,345) = ln(3.457398457345) + ln(1,000,000,000,000)
                      = ln(3.457398457345) + ln(10^12)
                      = ln(3.457398457345) + 12 * ln(10)
                      = ln(3.457398457345) + 12 * 2.3025... (hard-codable constant)

(Base 10 used for demonstration purposes, you probably want to use a base 2 reduction instead as far as actually implementing this for obvious reasons)

Now, ln(3.457…) is much easier for whatever series approximation you’re using to deal with, because series approximations tend to be more accurate around x=0 or x=1, that’s probably why you were getting so much error with large values for your series approximation.

4 Likes

Using this, I have created a new function which is far cleaner and far more accurate (with a margin of error compared to the roblox/lua math.log being 0). This margin of error is far better than what any other function could have accomplished. Thank you so much for reminding me of this.


The function I have created for this is as follows (utilizing previous stated functions for summation, etc):

local function rootsubx(g,r) -- gets the root base r of g
	local roots = 0
	repeat 
		if r^(roots+1) <= g then
			roots = roots+1
		end
	until r^(roots+1) > g
	return roots
end
	
local function ln0(x) -- gets the log natural of a small number (x < e^5)
	local summation = calc.series.summation.new(1,20000,function(n) return ((1/n)*(((x-1)/x)^n)) end)
	summation:update()
	return summation.p
end
	
function calc.math.ln(x) -- gets the log natural of any number
	local root2 = rootsubx(x,2)
	local xlessroot = x/(2^root2)
	return ln0(xlessroot)+root2*0.69314718055995
end

AxisAngles would be proud.

Until he tells you how your implementation could be 10x better.

/joke

1 Like

After applying this manner of factoring, the standard approach would be to use as many terms of the Maclaurin Series expansion of either ln(1+x) or ln(1-x) as is required for the precision you need. i.e. x - math.pow(x,2)/2 + math.pow(x,3)/3 - math.pow(x,4)/4 … for as many terms as you need. Instead of using math.pow, you could also just initialize a variable to x, and multiply it by the original x each iteration of the loop since you only need integer powers of x. I don’t know off the top of my head if the result would be any different in terms of floating point accuracy.

I’m no expert, but a binary search is always fun:

(I realise there are several problems with this).

local epsilon = 0.0001
local e = math.exp(1)

function ln(x, i, j)
	i = i or -1e6
	j = j or 1e6
	local k = (i + j) * 0.5
	local v = e ^ k
	if math.abs(v - x) <= epsilon then
		return k
	elseif v > x then
		return ln(x, i, k)
	elseif v < x then
		return ln(x, k, j)
	end
end
1 Like