A different approach to calculating the decimal logarithm

Calculating the decimal logarithm usually involves dividing its natural logarithm by the natural logarithm of 10.
But for very small or very large numbers, calculating their natural logarithm, by Taylor series or convergence iteration, can require a very large number of loops (up to hundreds), which is not very efficient.

For the decimal logarithm, another approach is to consider that any number can be written as the multiplication of its integer exponent of 10 and its reduction to a real number between 1 and 10.
This means that the tedious calculation of the logarithm can be reduced to that of a real number less than 10, which can be very fast.
The routine therefore consists in:

  1. Write the number in Applescript’s scientific notation
  2. Retrieve the significant real number between 1 and 10 (the future mantissa)
  3. Retrieve the exponent of 10 (the future characteristic)
  4. Find a first value as close as possible to the mantissa
  5. Converge this value to Applescript’s calculation limit, using the “mean value theorem”.
  6. Return the decimal logarithm equal to the sum {characteristic + mantissa}.

In this way, no matter whether the number is a real number in E+135 or E-49, the total calculation loops required is no different than for an integer like 100001, or 2, or 6185, i.e. at most 4 loops in this convergence algorithm.

The calculator’s validity range is from 9.88E-324 through 1.0E+308.


on log10(Nb)
	(* If needed enforces scientific notation to calculate only the mantissa of a number's decimal logarithm.
	 The convergence algorithm applies the “mean value theorem”*)
	try -- checking the validity range and converting to scientific notation
		set Nb to Nb as real
		if Nb ≤ 0 then error
	on error
		display alert "Error: The given value " & (Nb as string) & "\nis not a real number > 0"
		return
	end try
	
	if Nb = 1 then
		return 0
	else if Nb = 10 then
		return 1
		(* If needed force the number to a scientific notation by introducing an exponential factor of 9 *)
	else if (Nb as string) contains "E" then -- scientific notation exists
		set coeff to 0
	else if Nb < 1 then -- need to create pseudo number small enough for Applescript scientific notation
		set coeff to -1
	else
		set coeff to 1 -- need to create pseudo number big enough for Applescript scientific notation
	end if
	set {NbCar} to {Nb * ((1.0E+9) ^ coeff)} -- the number in its scientific notation
	
	set E to offset of "E" in (NbCar as string) -- for splitting fractional part and exponential part of the number
	set x to (text 1 thru (E - 1) of (NbCar as string)) as real -- retrieving Nb's significant figures (for log mantissa calculation))
	set charact to text (E + 1) thru -1 of (NbCar as string)
	set charact to (charact as integer) - (9 * coeff) -- back to true Nb's exponent of ten (the log10's  'characteristic') 
	
	######  calculation of the mantissa of Nb in the domain [1<x<10] #######
	-- first approximated value of the mantissa, using a polynomial function emulating log10(x) curve on [1<x<10]
	set Y to (x ^ 0.194) - (x ^ -0.248)
	repeat -- convergence loops using the “mean value theorem”
		log Y
		set Yexpon to 10 ^ Y --evaluates the value of the "approximate logarithm" by its reciprocal function
		set TgtY to 0.4343 / Yexpon -- Slope of finite increase of {∆Y/∆x} of log10(x) (i. e. value of tangent of "base 10 logarithm function" at the calculated point)
		set delta to (x - Yexpon) -- Deviation of calculated value comparing to the given value
		set tempY to Y + (delta * TgtY) -- Reducing the gap towards the target value
		if tempY as string = Y as string then exit repeat -- checking if convergence of mantissa dead-ended
		set Y to tempY -- value for next convergence loop
	end repeat
	return (charact + Y) -- log decimal = characteristic + mantissa
end log10

Edited a useless line in the script.

Here is the script I use to do logs of any base.

on logb(base as integer, x as real)
	local y, c, n, tmp
	set y to 0
	set c to 1
	repeat
		set n to base ^ y
		if n > x then
			if (n - x) < 2.0E-13 then exit repeat
			set y to tmp
			set c to c / 2
			set y to y + c
		else if n = x then
			exit repeat
		else
			set tmp to y
			set y to y + c
			if y = tmp then exit repeat
		end if
	end repeat
	return y
end logb

Yes, I know, you’ve already posted it, and it can be useful, but in some ways it shows its limitations:
Convergence is slow when numbers are big
for example let’s say x = (4.289E+5 * pi) ^ 2 / 3
it will need 88 loops to return “11.781890583147”

More, it fails with error “tmp” variable is not defined for
x = (4.289E-5 * pi) ^ 2 / 3
(the result should be “-8.218109416853”)

Maybe you can fix it?

Regards,

Weird,
it doesn’t fail for me.
BTW, How can it fail and return a result.
I’ll check it out

I checked it out.
11.781890583147 is the correct result.
10 ^ 11.781890583147 = 6.05188383406439E+11 = (4.289E+5 * pi) ^ 2 / 3

What am I not getting?

Excuse-me, but you read to fast my answer:
I gave two examples with the slight difference between + and - before the exponent

yes your script gave the right result for
x = (4.289E+5 * pi) ^ 2 / 3 i.e. “E PLUS 5”

But it failed for
x = (4.289E-5 * pi) ^ 2 / 3 i.e. “E MINUS 5”
(x= 6.051883834064E-9)

Did you try with this second number ?

Hello,

Obviously this algorithm can’t manage numbers like 0<x<1
I suggest a little change which seems to fix the problem :

on logb(base as integer, x as real)
	set inverseX to 1
	if x < 1 then
		set inverseX to -1
		set x to 1 / x
	end if
	local y, c, n, tmp
	set y to 0
	set c to 1
	set q to 0
	repeat
		set q to q + 1
		log q
		set n to base ^ y
		if n > x then
			if (n - x) < 2.0E-13 then exit repeat
			set y to tmp
			set c to c / 2
			set y to y + c
		else if n = x then
			exit repeat
		else
			set tmp to y
			set y to y + c
			if y = tmp then exit repeat
		end if
	end repeat
	return (y * inverseX) as real
end logb

#########
With regards

That is awesome! Thanks!

BTW I ran your script vs the one you just fixed for me thru Script Geek. Yours is close to 7.5 times slower,
so that fact that mine takes around 84 loops to converge is still better than converting numbers to strings.

Hi!

Hummm :melting_face:

Focused on decimal logarithm we didn’t see a mistake for bases that are not integer but real numbers, like neperian 2.71828… , for which the script returns a wrong logarithm,
so in the handler, defining the parameters must be:

on logb(base as real, x as real)

and with that, now your script returns the correct value for any base and the most important for Natural Logarithm for any x > 0 in the limits of Applescript capabilities. :smile:

Hi here is a log10 version.

on log10(x)
	set logtable to {0.1505, 0.3891, 0.5396, 0.6505, 0.7386, 0.8116, 0.8741, 0.9287, 0.9771}
	if x ≤ 0 then return "ERROR"
	if x = 1 then return 0
	if x ≤ 1.0E+9 and x > 1 then
		set exp to -9.0
		set x to 1.0E+9 * x
	else if x < 1 and x ≥ 1.0E-4 then
		set exp to 4.0
		set x to 1.0E-4 * x
	else
		set exp to 0
	end if
	set x_string to x as text
	set off to (offset of "^" in x_string) + 1
	set exp to exp + (text off thru -1 of x_string) as real
	if x_string starts with "1.0×" then return exp
	set a to (text 1 thru (off - 5) of x_string) as real
	set xn to item ((character 1 of x_string) as integer) of logtable
	repeat with i from 1 to 5
		log xn
		set xn to xn + 0.434294481903 * (10 ^ (-xn) * a - 1)
	end repeat
	return exp + xn
end log10
´´´

Hi,

Interesting approach, unfortunately due to Applescript and System localisation behavior, this algorithm fails in countries like mine where numeric separator is the comma (,) and not full stop (.)
So, I can’t even test it.
Sorry…

strange I also have this locale, but does not effect AppleScript…
for max portability I set all my application so that “.” is use as decimal separator. Anyway here’s a version that should work also if “,” is use as decimal separator.

on log10(x)
	set logtable to {151, 389, 540, 651, 739, 812, 874, 929, 977}
	if x ≤ 0 then return "ERROR"
	if x = 1 then return 0
	if x ≤ 10 ^ 9 and x > 1 then
		set exp to -9
		set x to 10 ^ 9 * x
	else if x < 1 and x ≥ 10 ^ (-4) then
		set exp to 4
		set x to 10 ^ (-4) * x
	else
		set exp to 0
	end if
	set x_string to x as text
	set off to (offset of "^" in x_string) + 1
	set exp to exp + (text off thru -1 of x_string) as real
	if x_string starts with "1.0×" or x_string starts with "1,0×" then return exp
	set a to (text 1 thru (off - 5) of x_string) as real
	set xn to (item ((character 1 of x_string) as integer) of logtable) / 1000
	if (pi as text) contains "," then
		set ln10_Inv to "0,4342944819032518276511289189166051" as real
	else
		set ln10_Inv to "0.4342944819032518276511289189166051" as real
	end if
	repeat with i from 1 to 5
		set xn to xn + ln10_Inv * (10 ^ (-xn) * a - 1)
	end repeat
	return exp + xn
end log10
´´´
BR

to get any base:

on logB(b as real, a as real) -- find x=b^a
	if b ≤ 0 then return "ERROR"
	return log10(a) / (log10(b))
end logB

´´´

Well, I can’t imagine that you posted a script without having thoroughly tested it.
For my own, this time I configured my System (High Sierra) to English and decimal separator to “.”.
So it seems weird, but one more time your last script that I copied/pasted in Script Editor (I don’t use Script Debugger) fails immediately at this line

set a to (text 1 thru (off - 5) of x_string) as real

with error
«“Can’t make "2.5E" into type real.” number -1700 from “2.5E” to real »

So this is the deadlock I’m facing with when trying to run your script.
BTW no matters the value given to x > 0, the error message is always the same at the same place.

Joseph25b,
interesting, when I coerce real to text I get a different format than you
e.g.

log (1.234567E+10 as text)

generate this: (1.234567×10^+10)

what do you get?

BR

Hello,
With Script Editor Version 2.10 (194) AppleScript 2.7 - High Sierra

I get exactly
«1,234567E+10»
(because of my French System, but 1.234567E+10 with full stop settings)

And as far as I know I never got such (1.234567×10^+10) when dealing with scientific notation in AppleScript.

Weird indeed…

(1.234567×10^+10) as string returns an error at compilation

(1.234567 * (10 ^ 10)) as string) is accepted
and its result is 1,234567E+10

Which makes sens on my Mac.
I can’t figure out what’s causing the difference between our scripting environment…

Hi,
this version should work but I can’t test you setup since I couldn’t either figure out where to set the different notations.

on log10(x)
	set logtable to {151, 389, 540, 651, 739, 812, 874, 929, 977}
	if x ≤ 0 then return "ERROR"
	if x = 1 then return 0
	if x ≤ 10 ^ 9 and x > 1 then
		set exp to -9
		set x to 10 ^ 9 * x
	else if x < 1 and x ≥ 10 ^ (-4) then
		set exp to 4
		set x to 10 ^ (-4) * x
	else
		set exp to 0
	end if
	set x_string to x as text
	set off to (offset of "^" in x_string)
	if off > 0 then
		set exp to exp + (text (off + 1) thru -1 of x_string) as real
		set a to (text 1 thru (off - 4) of x_string) as real
	else
		set off to (offset of "E")
		set exp to exp + (text (off + 1) thru -1 of x_string) as real
		set a to (text 1 thru (off - 1) of x_string) as real
	end if
	if text 1 thru 4 of x_string is in {"1.0×", "1,0×", "1.0E", "1,0E"} then return exp
	set xn to (item ((character 1 of x_string) as integer) of logtable) / 1000
	if (pi as text) contains "," then
		set ln10_Inv to "0,4342944819032518276511289189166051" as real
	else
		set ln10_Inv to "0.4342944819032518276511289189166051" as real
	end if
	repeat with i from 1 to 5
		set xn to xn + ln10_Inv * (10 ^ (-xn) * a - 1)
	end repeat
	return exp + xn
end log10

Hi !

I do appreciate your efforts to make your script run on “any Mac configuration”
its still fails on my Mac, but this is not the important thing right now (I could easily adapt your script to make it works on my H. Sierra config).
Among the yet 237 viewers of this topic I’d like to know how many tried to run your script and their answer for whom it ran and form whom it failed because of the notation of exponential real numbers.
BTW, you didn’t gave your scripting environment, it could be a thing to look at, considering my “old fashion System” on my old iMac !
Anyway, as far as I know and remember* “1.234 x 10^3” nether had been an allowed syntax for exponential notation in Applescript, while the caret “^” is of course the power indicator in arithmetic operators.
* I had been scripting AppleScript since version 1.0 while still scripting with HyperCard…

The only, but reliable, old source I can give to my help is this official page of Apples’s developpers
https://developer.apple.com/library/archive/documentation/LanguagesUtilities/Conceptual/MacAutomationScriptingGuide/ManipulateNumbers.html
with paragraph beginning by
« Converting a Long Number to a String
In AppleScript, long numeric values are displayed in scientific notation. For example, 1234000000 is displayed by a script as 1.234E+9.» etc.

Are things changing, I’d sure be glad to append it to my knowledge and not been stuck one future day with my “old fashioned syntax” scripts!

With regards,

Hi,
maybe things are changing. for sure properties are no longer stored, and thus any configuration needs to be stored in a file. I have AppleScript 2.8, and I’m not sure how to change this.
anyway the below natural log is quite fast and has an error of less than 10^(-12)
br

on log10(x)
	return ln(x) * 4342.944819032518 / 10000
end log10

on ln(x)
	set logtable to {{1.0, 0.0}, {1.18, 165.514438477573}, {1.36, 307.484699747961}, {1.54, 431.782416425538}, {1.72, 542.324290825362}, {1.9, 641.853886172395}, {2.08, 732.367893713226}, {2.26, 815.364813284194}, {2.44, 891.99803930511}, {2.62, 963.174317773006}, {2.8, 1029.619417181158}}
	set ln10 to 2302.585092994046 / 1000
	set e_1 to 2718.281828459045 / 1000
	set e_2 to 7389.05609893065 / 1000
	if x ≤ 0 then return "ERROR"
	if x = 1 then return 0
	if x < 1 then return (-1) * (ln(1 / x))
	if x ≤ 10 ^ 9 and x > 1 then
		set exp10 to -9
		set x to 10 ^ 9 * x
	else
		set exp10 to 0
	end if
	set text item delimiters to {"E", "×10^"}
	set {x, tmp} to text items of (x as text)
	set exp10 to exp10 + tmp
	set text item delimiters to ""
	if x > e_2 then
		set exp to 2
		set x to x / e_2
	else if x > e_1 then
		set exp to 1
		set x to x / e_1
	else
		set exp to 0
	end if
	set {a, f_x} to item ((((x * 100 - 91)) / 18 + 1) div 1) of logtable
	set x_a to x - a
	set nextterm to 1 / a * x_a
	set f_x to f_x / 1000 + nextterm
	repeat with i from 2 to 10
		set nextterm to (-1) * nextterm / i * (i - 1) / a * x_a
		set f_x to f_x + nextterm
	end repeat
	return f_x + exp + ln10 * exp10
end ln

Great Job ! Your script runs as fast and precise as AppleScript can make it.
Unless I’m mistaken, your script applies an idea put forward by Richard Feynman, which your algorithm implements with great efficiency.

However, there’s still this syntax quirk, which you’ve skilfully worked around, but which remains unexplained.
I had a friend test your very first script under Ventura 13.3 and Applescript 2.8 and on his machine (M1 processor), the same error is still generated. An error that doesn’t come from the decimal separator (as I first thought), but from this “unknown” syntax of scientific notation to text.
The mystery remains! :laughing: