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:
Write the number in Applescript’s scientific notation
Retrieve the significant real number between 1 and 10 (the future mantissa)
Retrieve the exponent of 10 (the future characteristic)
Find a first value as close as possible to the mantissa
Converge this value to Applescript’s calculation limit, using the “mean value theorem”.
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
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”)
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
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.
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.
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
´´´
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
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.
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
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…
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!