Quadratic Equation Solver

Hello.

This script solves quadratic equations, also those with imaginary roots. You may get the coeffecients, and the roots that solves the equation pasted to the clipboard.


property scriptTitle : "Quadratic Equations Solver"
-- Copyright 2013-2015 McUsr: you may use it as you see fit but you must keep this notice.
property cliptextIcon : a reference to file ((path to library folder from system domain as text) & "CoreServices:CoreTypes.bundle:Contents:Resources:ClippingText.icns")
property Debug : false
property listSep : missing value
property basicMessage : missing value


if listSep is missing value then
	set listSep to listSeparator()
	set basicMessage to "Input the coeffecients a " & listSep & "b " & listSep & "c," & return & "in order to solve the equation:" & return & return & "Æ’(x)=ax²+bx+c=0"
end if

set {theMessage, isWarning, l} to {basicMessage, false, {}}
repeat while true
	set l to _LIST_INPUT(theMessage, l as text, isWarning, scriptTitle, false, "regular")
	if length of l is not 3 then
		set isWarning to true
		set theMessage to "I need exactly three coeffecients, that is numbers!" & return & return & theMessage
	else
		set {a, b, c} to l
		exit repeat
	end if
end repeat
# needs three terms
if |abs|(a) < 1.0E-15 then
	if |abs|(b) < 1.0E-15 then
		if |abs|(c) < 1.0E-15 then
			set status to "L ∈ R"
		else
			set status to "L ∈ Ø"
		end if
	else
		set x1 to (-c / b)
		if x1 < 0 then
		else
			set status to "x₁ and x₂ = " & x1
		end if
	end if
else
	set x1 to (b ^ 2) - 4 * a * c
	# removed bug, thanks to Adam Bell
	if x1 ≥ 0 then
		if b < 0 then
			set x1 to -b + (x1 ^ 0.5)
		else
			set x1 to -b - (x1 ^ 0.5)
		end if
		set x1 to -x1 / 2 / a
		set x2 to c / a / x1
		
		set status to "x₁ = " & x1 & return & "x₂ = " & x2
		
	else
		set x2 to ((-x1) ^ 0.5) / 2 / a
		set x1 to -b / 2 / a
		if x2 < 0 then set x2 to -x2
		set status to "" & x1 & " ± ί * " & x2
	end if
end if



tell application (path to frontmost application as text)
	if status is in {"L ∈ R", "L ∈ Ø"} then
		display dialog return & status & return with title scriptTitle buttons {"Quit", "Ok"} cancel button 1 default button 2 with icon cliptextIcon giving up after 300
	else
		set btn to button returned of (display dialog return & status & return with title scriptTitle buttons {"Quit", "Clipboard", "Ok"} cancel button 1 default button 3 with icon cliptextIcon giving up after 300)
		if btn = "Clipboard" then set the clipboard to ("a = " & item 1 of l & listSep & " b = " & item 2 of l & listSep & " c = " & item 3 of l & return & "Result: " & status)
	end if
end tell


on listSeparator()
	if text 2 of ((1 / 2) as text) is "," then
		return ";	"
	else
		return ","
	end if
end listSeparator


on _LIST_INPUT(txt, defaAns, isWarning, scriptName, stayAlive, listType)
	-- The core of the handler originally written by Nigel Garvey.
	-- Parameters:
	--			txt:			Text:		The message that is shown in the input dialog.
	--			defaAns:		Text: 	The default value that is show in the input field.
	--			isWarning:	Boolean: If set to true, then a caution icon is shown, not 
	--										a note icon which is default.
	--			ScriptName:	Text:		Dialog box title, not necessarily a script name.
	--			stayAlive:	Boolean: If true the script returns missing value, otherwise
	--										dies.
	--			listType:	Word:		Word can be: "Regular", "PositiveDate", "EveryDate"
	--										or "Triplet","Hours".		
	-- Returns: 
	--			A list with values on success.
	--			Missing value if the user canceled and stayAlive is true.
	--			{""} if the input was bad somehow.
	--			Dies directly if the user cancelled and the stayalive was false.
	--
	--	If you have this handler in a tell block, then you want to wrap
	--	That tell block into a try block to "smooth over" the error number -128 call.
	local inpt, btnList, isNegative, astid, outpt, digits, param, listSep
	-- Thanks to Yvan Koenig for tips on increasing robustness!
	if listType is not in {"Regular", "PositiveDate", "EveryDate", "Triplet", "Hours"} then ¬
		error "Bad value for listType" number 4015
	set inpt to missing value
	# Configures list separator based on decimal separator: ";" <-- "," and "," <-- "."
	# A list separator always works, as do spaces between elements.
	# A "-" works for dates , when dates are restricted to positive dates.
	# A "." works for separatring elements of dates and times too.
	# A "/" works for separating dates.
	# A ":" works for separating hours.
	# A  list of regular numbers can only be separated with space and the designated list separator.
	# Counting of the elements happens in the caller of this handler.
	
	if text 2 of ((1 / 2) as text) is "," then
		set listSep to ";"
	else
		set listSep to ","
	end if
	# Appropriate buttons for whether we shall stay alive or not.
	if stayAlive then
		set btnList to {"Cancel", "Ok"}
	else
		set btnList to {"Quit", "Ok"}
	end if
	
	with timeout of (10 * minutes) seconds
		tell application (path to frontmost application as text)
			if isWarning then
				try
					set inpt to text returned of (display dialog txt default answer defaAns with title scriptName buttons btnList with icon caution cancel button 1 default button 2)
				end try
			else
				try
					set inpt to text returned of (display dialog txt default answer defaAns with title scriptName buttons btnList with icon note cancel button 1 default button 2)
				end try
			end if
		end tell
	end timeout
	if inpt is missing value then
		if not my Debug and not stayAlive then -- Need to stay alive here, if we are entering the second date.
			error number -128 -- User hit cancel.
		else
			return missing value -- Thanks to Yvan Koenig.
		end if
	end if
	if inpt is "" then return {}
	
	if listType is "Triplet" then
		ignoring white space
			set isNegative to (inpt begins with "-")
		end ignoring
	else if listType is "EveryDate" then
		set isNegative to (inpt contains "-")
	else
		set isNegative to false
	end if
	log "" & inpt
	set astid to AppleScript's text item delimiters
	set titems to {"-", listSep, space}
	if listType is not in {"PositiveDate", "Triplet"} then set titems to rest of titems
	if listType is in {"PositiveDate", "EveryDate"} then set titems to titems & {".", "/"}
	if listType is in {"Triplet", "Hours"} then set end of titems to ":"
	
	set AppleScript's text item delimiters to titems
	if listType is in {"Triplet", "EveryDate"} then
		set outpt to inpt's words
	else
		set outpt to inpt's text items
	end if
	#	set AppleScript's text item delimiters to " "
	#	set inpt to inpt as text
	set AppleScript's text item delimiters to astid
	
	set reslist to {}
	#	set outpt to inpt's words
	set digits to "1234567890"
	repeat with i from 1 to (count outpt)
		set param to item i of outpt
		if param is not "" and (character 1 of param is in digits or character 1 of param is "-") then
			try -- Thanks to Yvan Koenig.
				set param to param as number
			on error
				if listType is not in {"PositiveDate", "EveryDate"} then
					# Can't have decimal fraction anyway
					try
						set AppleScript's text item delimiters to "."
						set param to text items of param
						set AppleScript's text item delimiters to ","
						set param to param as text
						set AppleScript's text item delimiters to astid
						set param to param as number
					on error
						return {""} -- this must be resolved by caller
					end try
				else
					return {""}
				end if
			end try
			if (isNegative) then # This works only for triplets
				if listType is in {"EveryDate", "Triplet"} then set param to -param
			end if
			set end of reslist to param
		end if
	end repeat
	
	return reslist
end _LIST_INPUT

on |abs|(i)
	-- absolute value, faster than from an Osax.
	return i * (-1 + 2 * ((i > 0) as integer))
	-- if i > 0  then we add two, and multiply with 1
	-- if i < 0 then we add zero and multiply with -1
end |abs|

Hi, McUsrII

Three small errors: “if (abs a) < 1.0E-15 then” and those following for “b” and “c” should be of the form:

if abs(a) < 1.0E-15 then

Otherwise, abs is not recognized as a function.

Hello.

I am sorry for not stating that I used Satimage.osax, because there, abs work like a verb. However, I found another bug, which is now removed, I had taken the square root of a coeffecient, when I should have squared it. :confused:

If you have something that uses abs() instead of Satimage’s abs, then you are of course right.

Thanks for reporting the bug Adam, I really just made it, and didn’t test it extensively enough to reveal it. So it could have taken several days -or longer, before I would have discovered it!

Hi McUsrII.

Your posted script currently contains two copies of the code. Both copies display the wrong signs in the solutions.

Here’s my basic effort:

on solveQuad(a, b, c)
	-- ax2 + bx + c = 0. Find the two possible values of x.
	
	if (a = 0) then -- Not quadratic. Return two results anyway.
		set x to {c / -b, c / -b}
	else -- Quadratic.
		set s to b / a / 2
		try
			set r to (s ^ 2 - c / a) ^ 0.5
			set x to {-r - s, r - s}
		on error -- Square root calculation too large.
			set x to {false, false}
		end try
	end if
	
	return x
end solveQuad

set {a, b, c} to {2, 4, -4}
solveQuad(a, b, c)

-- Result test. Both items here should be 0.0 or as near as dammit.
-- tell result to return {a * ((item 1) ^ 2) + b * (item 1) + c, a * ((item 2) ^ 2) + b * (item 2) + c}

Edit: Catches added for a = 0 and for square root calculation crashes.

Hello.

I had returned an equation with the roots, that was rather confusing, I have now changed it to just deliver the roots, instead of delivering the roots with their value multiplicated with −1-

The roots I got out by running your example is −2.732050807569 and 0.732050807569. Those are returned directly now instead of being returned as (X + 2.732050807569) ( X - 0.732050807569). They both ( the real roots), gives the result of zero when fed to this handler together with the coeffecients.

on f(a, b, c, x)
	set tmp to (a * (x ^ 2) + (b * x) + c)
	if (abs tmp) < 1.0E-12 then return 0
# Satimage osax for abs
	return tmp
end f

Edit

There may have been some mess with the sign of the roots, in all fairness, it should be ok now.

Hello.

I have updated the script in post #1 with an |abs| handler of my own, so that the script is independent of any osax.

Edit

I found it easier to read the result, with the “human readable” sign of the roots presented, so I changed that for the case when the roots are real and two different ones.

This is now a work in progress, so I won’t bump it further before I’m done.