MATH: Functions using plain AppleScript.

A long way has been passed, and AppleScript can already do the math fast enough. I will try to replenish the list of functions in this topic:


set x to pi / 3

my cos(x) -- RESULT: 0.5
my sin(x) -- RESULT: 0.866025
my exp(x) -- RESULT: 2.849654
my ln(x) -- RESULT: 0.046118
my tan(x) -- RESULT: 1.732051
my ctan(x) -- RESULT: 0.57735
my atan(x) -- RESULT: 0.808449
my arccot(x) -- RESULT: 0.762348
my arcsin(-pi / 4) -- RESULT: -0.903339
my arccos(-pi / 4) -- RESULT: 2.474135
my logyX(x / 9, 1 / 3) -- Logarithm of pi/27 with base 1/3 -- RESULT: 1.958022

----------------------- COSINE function ----------------------------------------------------------------
on cos(z as real)
	set theAccuracy to 1.0E-6 --the precision
	set z to z mod (2 * pi)
	set aCos to 1
	set a to 1
	set n to 0
	repeat
		set n to n + 2
		set a to -a * z * z / (n - 1) / n -- new Tailor serie's member
		set aCos to aCos + a
		if a < 0 then
			if -a < theAccuracy then exit repeat
		else
			if a < theAccuracy then exit repeat
		end if
	end repeat
	return aCos div 1 + (round 1000000 * (aCos mod 1)) / 1.0E+6
end cos

------------------------- SINE function --------------------------------------------------------------
on sin(z as real)
	set theAccuracy to 1.0E-6 -- the precision
	set z to z mod (2 * pi)
	set aSin to z
	set a to 1
	set n to 0
	repeat
		set n to n + 2
		set a to -a * z * z / (n - 1) / n
		set adding to a * z / (n + 1) -- new Tailor serie's member
		set aSin to aSin + adding
		if adding < 0 then
			if -adding < theAccuracy then exit repeat
		else
			if adding < theAccuracy then exit repeat
		end if
	end repeat
	return aSin div 1 + (round 1000000 * (aSin mod 1)) / 1.0E+6
end sin

---------------------- EXPONENT function -----------------------------------------------------------------
on exp(z as real)
	set theAccuracy to 1.0E-6 -- the precision
	set aExp to 1
	set a to 1
	set n to 1
	repeat
		set n to n + 1
		set a to a * z / (n - 1)
		set aExp to aExp + a -- add new Tailor serie's member
		if a < 0 then
			if -a < theAccuracy then exit repeat
		else
			if a < theAccuracy then exit repeat
		end if
	end repeat
	return aExp div 1 + (round 1000000 * (aExp mod 1)) / 1.0E+6
end exp

----------------------- NATURAL LOGARITHM function ----------------------------------------------------------------
on ln(z as real) -- limitation: z>0
	if z ≤ 0 then return "The parameter z should be >0"
	set x to (z - 1) / (z + 1) -- convert big number z to number -1<x<1
	set theAccuracy to 1.0E-6 -- the precision 
	set anLn to 2 * x -- first Tailor serie's member
	set a to x -- part of Tailor serie's member
	set n to 1 -- counter of Tailor serie's member
	repeat
		set n to n + 1
		set a to a * x * x -- update part of Tailor serie's member
		set adding to 2 * a / (2 * n - 1) -- new Tailor serie's member
		set anLn to anLn + adding -- update ln's value
		if adding < 0 then -- 
			if -adding < theAccuracy then exit repeat
		else
			if adding < theAccuracy then exit repeat
		end if
	end repeat
	return anLn div 1 + (round 1000000 * (anLn mod 1)) / 1.0E+6
end ln

------------------------- TANGENT function ---------------------------------------------------
on tan(z as real)
	set z to (z + pi / 2) mod pi - pi / 2
	if z = pi / 2 or z = -pi / 2 then return "Infinite Number: ∞"
	set theAccuracy to 1.0E-6 -- the precision
	set aSin to z
	set aCos to 1
	set a to 1
	set aPreviousTan to 0
	set n to 0
	repeat
		set n to n + 2
		set a to -a * z * z / (n - 1) / n
		set aCos to aCos + a -- add new Tailor serie's member
		set aSin to aSin + a * z / (n + 1) -- add new Tailor serie's member
		set atan to aSin / aCos
		if atan > aPreviousTan then
			if atan - aPreviousTan < theAccuracy then exit repeat
		else
			if aPreviousTan - atan < theAccuracy then exit repeat
		end if
		set aPreviousTan to atan
	end repeat
	return atan div 1 + (round 1000000 * (atan mod 1)) / 1.0E+6
end tan

------------------------- COTANGENT function --------------------------------------------
on ctan(z as real)
	set x to (z + pi / 2) mod pi - pi / 2
	if x = -pi or x = pi or x = 0 then return "Infinite Number: ∞"
	set theAccuracy to 1.0E-6 -- the precision
	set aSin to x
	set aCos to 1
	set a to 1
	set aPreviousCotan to 1
	set n to 0
	repeat
		set n to n + 2
		set a to -a * x * x / (n - 1) / n
		set aCos to aCos + a -- add new Tailor serie's member
		set aSin to aSin + a * x / (n + 1) -- add new Tailor serie's member
		set aCotan to aCos / aSin
		if aCotan > aPreviousCotan then
			if aCotan - aPreviousCotan < theAccuracy then exit repeat
		else
			if aPreviousCotan - aCotan < theAccuracy then exit repeat
		end if
		set aPreviousCotan to aCotan
	end repeat
	return aCotan div 1 + (round 1000000 * (aCotan mod 1)) / 1.0E+6
end ctan

------------------------- ARCTANGENT function --------------------------------------------
on atan(z)
	set x to z
	set theAccuracy to 1.0E-6
	set k to 0.57735026919 -- tan(pi/6)
	set k0 to 0.26794919243 -- tan(pi/12)
	set flag to false -- segmentation flag
	
	if z < 0 then set x to -x
	if x > 1 then set x to 1 / x -- limit argument to 0..1
	if x > k0 then set {flag, x} to {true, (x - k) / (1 + k * x)} -- determine segmentation
	
	-- Argument is now < tan(pi/12). Approximate the function
	set arcTang to x
	set dx to x
	set n to 1
	repeat
		set n to n + 1
		set dx to -dx * x * x
		set adding to dx / (2 * n - 1)
		set arcTang to arcTang + adding
		if adding > 0 then
			if adding < theAccuracy / (n - 1) then exit repeat
		else
			if -adding < theAccuracy / (n - 1) then exit repeat
		end if
	end repeat
	
	if flag then set arcTang to arcTang + pi / 6 -- restore offset if needed
	if z > 1 or z < -1 then set arcTang to pi / 2 - arcTang -- restore complement if needed
	if z < 0 then set arcTang to -arcTang -- restore sign if needed
	return arcTang div 1 + (round 1000000 * (arcTang mod 1)) / 1.0E+6
end atan

------------------------- ARCCOTANGENT function --------------------------------------------
on arccot(z)
	set z to 1 / z
	set x to z
	set theAccuracy to 1.0E-6
	set k to 0.57735026919 -- tan(pi/6)
	set k0 to 0.26794919243 -- tan(pi/12)
	set flag to false -- segmentation flag
	
	if z < 0 then set x to -x
	if x > 1 then set x to 1 / x -- limit argument to 0..1
	if x > k0 then set {flag, x} to {true, (x - k) / (1 + k * x)} -- determine segmentation
	
	-- Argument is now < tan(pi/12). Approximate the function
	set arccoTang to x
	set dx to x
	set n to 1
	repeat
		set n to n + 1
		set dx to -dx * x * x
		set adding to dx / (2 * n - 1)
		set arccoTang to arccoTang + adding
		if adding > 0 then
			if adding < theAccuracy / (n - 1) then exit repeat
		else
			if -adding < theAccuracy / (n - 1) then exit repeat
		end if
	end repeat
	
	if flag then set arccoTang to arccoTang + pi / 6 -- restore offset if needed
	if z > 1 or z < -1 then set arccoTang to pi / 2 - arccoTang -- restore complement if needed
	if z < 0 then set arccoTang to -arccoTang -- restore sign if needed
	return arccoTang div 1 + (round 1000000 * (arccoTang mod 1)) / 1.0E+6
end arccot

------------------------- ARCSINE function --------------------------------------------
on arcsin(z)
	if z < -1 or z > 1 then return "Parametr z should be ≥ -1 and ≤ 1"
	if z = -1 then return 1.570796 -- pi/2
	set z to z / (1 + (1 - z * z) ^ 0.5)
	set x to z
	set theAccuracy to 1.0E-6
	set k to 0.57735026919 -- tan(pi/6)
	set k0 to 0.26794919243 -- tan(pi/12)
	set flag to false -- segmentation flag
	
	if z < 0 then set x to -x
	if x > 1 then set x to 1 / x -- limit argument to 0..1
	if x > k0 then set {flag, x} to {true, (x - k) / (1 + k * x)} -- determine segmentation
	
	-- Argument is now < tan(pi/12). Approximate the function
	set arcSine to x
	set dx to x
	set n to 1
	repeat
		set n to n + 1
		set dx to -dx * x * x
		set adding to dx / (2 * n - 1)
		set arcSine to arcSine + adding
		if adding > 0 then
			if adding < theAccuracy / (n - 1) then exit repeat
		else
			if -adding < theAccuracy / (n - 1) then exit repeat
		end if
	end repeat
	
	if flag then set arcSine to arcSine + pi / 6 -- restore offset if needed
	if z > 1 or z < -1 then set arcSine to pi / 2 - arcSine -- restore complement if needed
	if z < 0 then set arcSine to -arcSine -- restore sign if needed
	return 2 * arcSine div 1 + (round 1000000 * (2 * arcSine mod 1)) / 1.0E+6
end arcsin

------------------------- ARCCOSINE function --------------------------------------------
on arccos(z)
	if z < -1 or z > 1 then return "Parametr z should be ≥ -1 and ≤ 1"
	if z = -1 then return -1.570796 -- -pi/2
	set z to (1 - z * z) ^ 0.5 / (1 + z)
	set x to z
	set theAccuracy to 1.0E-6
	set k to 0.57735026919 -- tan(pi/6)
	set k0 to 0.26794919243 -- tan(pi/12)
	set flag to false -- segmentation flag
	
	if z < 0 then set x to -x
	if x > 1 then set x to 1 / x -- limit argument to 0..1
	if x > k0 then set {flag, x} to {true, (x - k) / (1 + k * x)} -- determine segmentation
	
	-- Argument is now < tan(pi/12). Approximate the function
	set arcCosine to x
	set dx to x
	set n to 1
	repeat
		set n to n + 1
		set dx to -dx * x * x
		set adding to dx / (2 * n - 1)
		set arcCosine to arcCosine + adding
		if adding > 0 then
			if adding < theAccuracy / (n - 1) then exit repeat
		else
			if -adding < theAccuracy / (n - 1) then exit repeat
		end if
	end repeat
	
	if flag then set arcCosine to arcCosine + pi / 6 -- restore offset if needed
	if z > 1 or z < -1 then set arcCosine to pi / 2 - arcCosine -- restore complement if needed
	if z < 0 then set arcCosine to -arcCosine -- restore sign if needed
	return 2 * arcCosine div 1 + (round 1000000 * (2 * arcCosine mod 1)) / 1.0E+6
end arccos

-----------------------  LOGARITHM of X with base Y  ------------------------------------------------
on logyX(zx as real, zy as real) -- limitation: zx>0 and zy≠1
	if zx ≤ 0 then return "The parameter zx should be >0"
	if zy ≤ 0 or zy = 1 then return "The base zy should be  >0 and ≠1"
	set x to (zx - 1) / (zx + 1) --convert big number zx to number -1<x<1
	set y to (zy - 1) / (zy + 1) --convert big number zy to number -1<y<1
	set theAccuracy to 1.0E-6 -- the precision 
	set {Ln1, Ln2} to {2 * x, 2 * y} -- first Tailor serie's member
	set anLn to x / y
	set oldLn to anLn
	set {a1, a2} to {x, y} -- parts of Tailor serie's member
	set n to 1 -- counter of Tailor serie's member
	repeat
		set n to n + 1
		set {a1, a2} to {a1 * x * x, a2 * y * y} -- update part of Tailor serie's member
		set {adding1, adding2} to {2 * a1 / (2 * n - 1), 2 * a2 / (2 * n - 1)} -- new Tailor serie's members
		set {Ln1, Ln2} to {Ln1 + adding1, Ln2 + adding2}
		set anLn to Ln1 / Ln2 -- update ln's value
		if anLn > oldLn then -- 
			if anLn - oldLn < 0.1 * theAccuracy then exit repeat
		else
			if oldLn - anLn < 0.1 * theAccuracy then exit repeat
		end if
		set oldLn to anLn
	end repeat
	return anLn div 1 + (round 1000000 * (anLn mod 1)) / 1.0E+6
end logyX

1 Like

UPDATE: added function Logarithm X based Y

1 Like

Been a while since I logged in, so bumping this topic back up because I do love a good series expansion. The problem with a Maclaurin Series expansion when dealing with transcendental functions (such as sin, cos, e, ln, …) is that they require the summing of floating point numbers that end up being subject to huge rounding errors that accumulate with each iteration. This is particularly a problem here, because the handler above seeks a specific “accuracy” independent of the value of x. A Maclaurin series truncated after the term x^(n-1), mathematically, has an absolute error bounded by x^n/n!. Thus, with a small, fixed desired accuracy, as x gets larger, the value of n is going to have to be very large as well, which means: the number of iterations required becomes large; and the floating point precision of the machine or the software or whatever quickly becomes a limiting factor and errors start to accumulate. These errors become unbounded as x grows.

Here’s a table showing values of x=kπ/5 (first column, 0≤k≤100), the output of [u]sin/u as defined above, the true value of [u]sin/u accurate to within 10⁻¹⁵, and the absolute difference between the two values:
[format]0.0000000000 0.000000000000 0 0.0000000000
0.6283185307 0.587785252278 .587785252277944 -0.0000000000
1.2566370614 0.951056516284 .951056516284054 0.0000000000
1.8849555922 0.951056516281 .951056516280900 -0.0000000000
2.5132741229 0.58778525227 .587785252269686 -0.0000000000
3.1415926536 0.000000000000 -.000000000010206 -0.0000000000
3.7699111843 0.000000000000 -.587785252286201 -0.5877852523
4.3982297150 0.000000000000 -.951056516287208 -0.9510565163
5.0265482457 0.000000000000 -.951056516308648 -0.9510565163
5.6548667765 0.000000000000 -.587785252261429 -0.5877852523
6.2831853072 2.03893615832738E-11 .000000000020413 0.0000000000
6.9115038379 0.587785252294 .587785252294459 0.0000000000
7.5398223686 0.95105651629 .951056516290362 0.0000000000
8.1681408993 0.951056516305 .951056516305494 0.0000000000
8.7964594301 0.587785252253 .587785252253171 0.0000000000
9.4247779608 0.000000000000 -.000000000030620 -0.0000000000
10.0530964915 0.000000000000 -.587785252302716 -0.5877852523
10.6814150222 0.000000000000 -.951056516293516 -0.9510565163
11.3097335529 0.000000000000 -.951056516302339 -0.9510565163
11.9380520836 0.000000000000 -.587785252325816 -0.5877852523
12.5663706144 3.83288979950519E-11 .000000000040827 0.0000000000
13.1946891451 0.587785252292 .587785252310974 0.0000000000
13.8230076758 0.951056516284 .951056516296670 0.0000000000
14.4513262065 0.951056516316 .951056516299185 -0.0000000000
15.0796447372 0.587785252293 .587785252317558 0.0000000000
15.7079632679 3.54833389629793E-11 .000000000048966 0.0000000000
16.3362817987 0.000000000000 -.587785252319231 -0.5877852523
16.9646003294 0.000000000000 -.951056516299824 -0.9510565163
17.5929188601 0.000000000000 -.951056516296031 -0.9510565163
18.2212373908 0.000000000000 -.587785252309301 -0.5877852523
18.8495559215 3.47280164084486E-10 -.000000000038759 -0.0000000004
19.4778744523 0.587785243896 .587785252327488 0.0000000084
20.1061929830 0.95105650903 .951056516302978 0.0000000073
20.7345115137 0.951056521218 .951056516292877 -0.0000000049
21.3628300444 0.587785233174 .587785252301043 0.0000000191
21.9911485751 7.02924667711419E-8 .000000000028552 -0.0000000703
22.6194671058 0.000000000000 -.587785252254844 -0.5877852523
23.2477856366 0.000000000000 -.951056516306132 -0.9510565163
23.8761041673 0.000000000000 -.951056516289723 -0.9510565163
24.5044226980 0.000000000000 -.587785252292786 -0.5877852523
25.1327412287 0.000000000000 -.000000000018345 -0.0000000000
25.7610597594 0.587783153173 .587785252263102 0.0000020991
26.3893782902 0.951061327886 .951056516309287 -0.0000048116
27.0176968209 0.951056347512 .951056516286569 0.0000001688
27.6460153516 0.587772317186 .587785252284528 0.0000129351
28.2743338823 8.54012014885689E-6 .000000000008139 -0.0000085401
28.9026524130 0.000000000000 -.587785252271359 -0.5877852523
29.5309709437 0.000000000000 -.951056516281539 -0.9510565163
30.1592894745 0.000000000000 -.951056516283415 -0.9510565163
30.7876080052 0.000000000000 -.587785252276271 -0.5877852523
31.4159265359 0.000000000000 .000000000002067 0.0000000000
32.0442450666 0.587805814225 .587785252279617 -0.0000205619
32.6725635973 0.950347590122 .951056516284693 0.0007089262
33.3008821281 0.950799336863 .951056516280261 0.0002571794
33.9292006588 0.595269847554 .587785252268014 -0.0074845953
34.5575191895 0.005938793299 -.000000000012274 -0.0059387933
35.1858377202 0.000000000000 -.587785252287874 -0.5877852523
35.8141562509 0.000000000000 -.951056516287847 -0.9510565163
36.4424747816 0.000000000000 -.951056516308009 -0.9510565163
37.0707933124 0.000000000000 -.587785252259756 -0.5877852523
37.6991118431 0.051791763587 .000000000022481 -0.0517917636
38.3274303738 0.106449490749 .587785252296131 0.4813357615
38.9557489045 0.841843561757 .951056516291001 0.1092129545
39.5840674352 3.818935840702 .951056516304855 -2.8678793244
40.2123859659 9.763664987574 .587785252332400 -9.1758797352
40.8407044967 82.636861597699 -.000000000032687 -82.6368615977
41.4690230274 372.346171564771 -.587785252304389 -372.9339568171
42.0973415581 1746.36832534517 -.951056516294155 -1747.3193818615
42.7256600888 7987.8413836054 -.951056516301701 -7988.7924401217
43.3539786195 3.5403419011537E+4 -.587785252324143 -35404.0067967893
43.9822971503 1.55108711152585E+5 .000000000042894 -155108.7111525850
44.6106156810 6.66006641759216E+5 .587785252312646 -666006.0539739636
45.2389342117 2.79857057954168E+6 .951056516297309 -2798569.6284851637
45.8672527424 1.15366903147472E+7 .951056516298546 -11536689.3636906836
46.4955712731 4.66381622020993E+7 .587785252315885 -46638161.6143140495
47.1238898038 1.85019522893343E+8 .000000000046898 -185019522.8933430016
47.7522083346 7.20628383191789E+8 -.587785252320904 -720628383.7795742750
48.3805268653 2.75709441350816E+9 -.951056516300463 -2757094414.4592165947
49.0088453960 1.03664360736406E+10 -.951056516295392 -10366436074.5916576385
49.6371639267 3.83210521120268E+10 -.587785252307628 -38321052112.6145858765
50.2654824574 1.39334761842715E+11 -.000000000036691 -139334761842.7149963379
50.8938009882 4.9851203633058E+11 .587785252329161 -498512036329.9922485352
51.5221195189 1.75572681922613E+12 .951056516303617 -1755726819225.1787109375
52.1504380496 6.08932863500673E+12 .951056516292238 -6089328635005.7792968750
52.7787565803 2.08052229787237E+13 .587785252299371 -20805222978723.1132812500
53.4070751110 7.00518804816674E+13 .000000000026485 -70051880481667.3984375000
54.0353936417 2.32520005961312E+14 -.587785252256517 -232520005961312.5937500000
54.6637121725 7.61092395645824E+14 -.951056516306771 -761092395645825.0000000000
55.2920307032 2.45747719370812E+15 -.951056516289084 -2457477193708121.0000000000
55.9203492339 7.82979665837412E+15 -.587785252291113 -7829796658374121.0000000000
56.5486677646 2.4623495850696E+16 -.000000000016278 -24623495850696000.0000000000
57.1769862953 7.64561191516067E+16 .587785252264774 -76456119151606704.0000000000
57.8053048261 2.34454774755079E+17 .951056516309925 -234454774755079008.0000000000
58.4336233568 7.10243501926409E+17 .951056516285930 -710243501926408960.0000000000
59.0619418875 2.12603361000006E+18 .587785252282856 -2126033610000059904.0000000000
59.6902604182 6.29009748565338E+18 .000000000006071 -6290097485653380096.0000000000
60.3185789489 1.83981986059481E+19 -.587785252273032 -18398198605948100608.0000000000
60.9468974796 5.3214120703196E+19 -.951056516282178 -53214120703195996160.0000000000
61.5752160104 1.5223410603455E+20 -.951056516282776 -152234106034550013952.0000000000
62.2035345411 4.30851195989386E+20 -.587785252274598 -430851195989386002432.0000000000[/format]

You’ll probably notice a pattern in the way the error is distributed, which isn’t entirely down to the float point precision of our machines. It’s inherent in the Taylor series expansions themselves, which expand around a given point (in the case above, around x=0, which gives us the Maclaurin Series), close to which the number of terms required for a high degree of accuracy is small (in fact, [u]sin/u≈x for very small x), but as one moves away from the centre of expansion, the error due to truncating a Taylor expansion at the nth term increases, steadily until it erupts, which it has a tendency to do as you get closer to any point at which the series expanded around the point x=a vanishes, i.e. [u]sin/u=0, with the exception of a itself. Hence you see those blocks of lines where the absolute error is 0.0000… in between intervals of π.

This all applies to the other transcendental functions as well. But, besides all of that, Taylor Series are also pretty slow to converge and, as I described, have a bad time around vanishing points of a function.

One method you can use to mitigate all of this is to use the Maclaurin Series—which is accurate for small values of x—and restrict the range within which it operates to those values of x that are close to 0. Since all of the trigonometric functions are cyclic, this is pretty straight forward, since [u]sin/u=(−1)^n∙[u]sin/u. Additionally, you’d use the fact that [u]sin/u=2[u]sin/u∙[u]cos/u, which allows you halve the value of any input passed to your function as much required to make it arbitrarily small (subject to the machine epsilon).

Probably one of the best (i.e. most efficient, fastest, and most accurate) ways is to approximate the trig functions using Chebyshev polynomials. But practically speaking, I think the optimal methods are ones that store pre-calculated values of a function for carefully chosen values of x, then use a minimum number of terms (literally one or two) from a series expansion (Maclaurin is fine) to obtain the in-between values.

PS. I wouldn’t use z as a variable name in this context, as that implies that the handlers accept complex numbers.

1 Like

Thank you for your interest in this topic. Mine code with which you calculated here the sine is taken from another topic. There, Fredrik71 used it in Numbers.app to build a circle. Hardly in Numbers.app are useful argument values outside the range of -2pi- + 2pi. So, there the code is written fast and simply.

In this Code Exchange’s topic, the area of the argument deliberately decreases, as you advise correctly too. But still it was nice to meet another person who is well versed in mathematics.

1 Like