BusinessMath Quarterly Series
11 min read
Part 24 of 12-Week BusinessMath Series
goalSeek() function for root-finding problemsGoalSeekOptimizer class for constrained problemsMany business problems require inverse solving—finding an input that produces a target output:
Manual trial-and-error (guessing values in Excel) is slow, imprecise, and doesn’t scale.
Goal-seeking (also called root-finding) automates the inverse problem. BusinessMath implements Newton-Raphson iteration with numerical differentiation for robust convergence.
Understanding the difference is critical:
| Goal-Seeking | Optimization |
|---|---|
| Find where f(x) = target | Find where f’(x) = 0 |
| Root-finding | Minimize/Maximize |
| Example: Breakeven price | Example: Optimal price |
Uses: goalSeek() | Uses: minimize(), maximize() |
import BusinessMath
import Foundation
// Find x where x² = 4
let result = try goalSeek(
function: { x in x * x },
target: 4.0,
guess: 1.0
)
print(result) // ~2.0
API Signature:
func goalSeek(
function: @escaping (T) -> T,
target: T,
guess: T,
tolerance: T = T(1) / T(1_000_000),
maxIterations: Int = 1000
) throws -> T
Parameters:
function: The function f(x) to solvetarget: The value you want f(x) to equalguess: Initial guess (critical for convergence!)tolerance: Convergence threshold (default: 0.000001)maxIterations: Maximum iterations before giving up (default: 1000)Find the price where profit equals zero:
import BusinessMath
// Profit function with demand elasticity
func profit(price: Double) -> Double {
let quantity = 10_000 - 1_000 * price // Demand curve
let revenue = price * quantity
let fixedCosts = 5_000.0
let variableCost = 4.0
let totalCosts = fixedCosts + variableCost * quantity
return revenue - totalCosts
}
// Find breakeven price (profit = 0)
let breakevenPrice = try goalSeek(
function: profit,
target: 0.0,
guess: 4.0,
tolerance: 0.01
)
print("Breakeven price: \(breakevenPrice.currency(2))")
print("Verification: \(profit(price: breakevenPrice).currency(2))")
Output:
Breakeven price: $5.00
Verification: $0.00
The method: Newton-Raphson typically converges in 5-7 iterations.
Find the sales volume needed to hit a revenue target:
import BusinessMath
let pricePerUnit = 50.0
let targetRevenue = 100_000.0
// Revenue = price × quantity
let requiredQuantity = try goalSeek(
function: { quantity in pricePerUnit * quantity },
target: targetRevenue,
guess: 1_000.0
)
print("Need to sell \(requiredQuantity.number(0)) units")
print("Revenue: \((pricePerUnit * requiredQuantity).currency(0))")
Output:
Need to sell 2,000 units
Revenue: $100,000
IRR is the discount rate where NPV equals zero—a perfect goal-seek problem:
import BusinessMath
import Foundation
let cashFlows = [-1_000.0, 200.0, 300.0, 400.0, 500.0]
func npv(rate: Double) -> Double {
var npv = 0.0
for (t, cf) in cashFlows.enumerated() {
npv += cf / pow(1 + rate, Double(t))
}
return npv
}
// Find rate where NPV = 0
let irr = try goalSeek(
function: npv,
target: 0.0,
guess: 0.10 // Start with 10% guess
)
print("IRR: \(irr.percent(2))")
print("Verification - NPV at IRR: \(npv(rate: irr).currency(2))")
Output:
IRR: 12.83%
Verification - NPV at IRR: $0.00
The insight: This is exactly how BusinessMath’s irr() function works internally.
Solve complex equations numerically:
import BusinessMath
// Solve: e^x - 2x - 3 = 0
let solution = try goalSeek(
function: { x in exp(x) - 2*x - 3 },
target: 0.0,
guess: 1.0
)
print("Solution: x = \(solution.number(6))")
// Verify: Should be ≈ 0
let verify = exp(solution) - 2*solution - 3
print("Verification: \(verify.number(10))")
Output:
Solution: x = 1.923939
Verification: 0.0000000000
Goal-seeking uses Newton-Raphson iteration for root-finding:
x_{n+1} = x_n - (f(x_n) - target) / f'(x_n)
Convergence Properties:
Numerical Differentiation:
Since we don’t have symbolic derivatives, f’(x) is computed using central differences:
f'(x) ≈ (f(x + h) - f(x - h)) / (2h)
Where h is a small step size (default: 0.0001).
Occurs when the derivative f’(x) = 0 (flat function):
do {
// Function with zero derivative at x=0
let result = try goalSeek(
function: { x in x * x * x }, // f'(0) = 0
target: 0.0,
guess: 0.0 // BAD: Starting at stationary point
)
} catch let error as BusinessMathError {
print(error.localizedDescription)
// "Goal-seeking failed: Division by zero encountered"
if let recovery = error.recoverySuggestion {
print("How to fix:\n\(recovery)")
// "Try a different initial guess away from stationary points"
}
}
Solution: Choose a different initial guess away from stationary points.
Occurs when the algorithm doesn’t converge in max iterations:
do {
let result = try goalSeek(
function: { x in sin(x) },
target: 1.5, // BAD: sin(x) never equals 1.5
guess: 0.0
)
} catch let error as BusinessMathError {
print(error.localizedDescription)
// "Goal-seeking did not converge within 1000 iterations"
if let recovery = error.recoverySuggestion {
print("How to fix:\n\(recovery)")
// "Try different initial guess, increase max iterations, or relax tolerance"
}
}
Possible causes:
Solutions:
The initial guess is critical for convergence:
1. Use domain knowledge:
// Breakeven usually between cost and market price
let guess = (costPrice + marketPrice) / 2
2. Try multiple guesses:
let guesses = [5.0, 10.0, 20.0]
for guess in guesses {
if let result = try? goalSeek(function: f, target: target, guess: guess) {
print("Found solution: \(result)")
break
}
}
3. Start near expected solution:
// If last month's breakeven was $10, start there
let guess = lastMonthBreakeven
4. Avoid problematic points:
// Don't start where derivative is zero
let guess = 1.0 // Not 0.0 for f(x) = x²
For more control and constraint support:
import BusinessMath
func profitFunction(price: Double) -> Double {
let quantity = 10_000 - 1_000 * price
let revenue = price * quantity
let fixedCosts = 5_000.0
let variableCost = 4.0
let totalCosts = fixedCosts + variableCost * quantity
return revenue - totalCosts
}
let optimizer = GoalSeekOptimizer(
target: 0.0,
tolerance: 0.0001,
maxIterations: 1000
)
let result = optimizer.optimize(
objective: profitFunction,
constraints: [],
initialGuess: 4.0,
bounds: (lower: 0.0, upper: 100.0)
)
print("Solution: \(result.optimalValue.currency(2))")
print("Converged: \(result.converged)")
print("Iterations: \(result.iterations)")
Output:
Solution: $5.00
Converged: true
Iterations: 6
import BusinessMath
import Foundation
// MARK: - Basic Goal Seek
// Find x where x² = 4
let result = try goalSeek(
function: { x in x * x },
target: 4.0,
guess: 1.0
)
print(result.number()) // ~2.0
// MARK: - Breakeven Analysis
// Find the price where profit = 0
// Profit function with demand elasticity
func profit(price: Double) -> Double {
let quantity = 10_000 - 1_000 * price // Demand curve
let revenue = price * quantity
let fixedCosts = 5_000.0
let variableCost = 4.0
let totalCosts = fixedCosts + variableCost * quantity
return revenue - totalCosts
}
// Find breakeven price (profit = 0)
let breakevenPrice = try goalSeek(
function: profit,
target: 0.0,
guess: 6.0,
tolerance: 0.01
)
print("Breakeven price: \(breakevenPrice.currency(2))")
print("Verification: \(profit(price: breakevenPrice).currency(2))")
// MARK: - Target Revenue
let pricePerUnit = 50.0
let targetRevenue = 100_000.0
// Revenue = price × quantity
let requiredQuantity = try goalSeek(
function: { quantity in pricePerUnit * quantity },
target: targetRevenue,
guess: 1_000.0
)
print("Need to sell \(requiredQuantity.number(0)) units")
print("Revenue: \((pricePerUnit * requiredQuantity).currency(0))")
// MARK: - Internal Rate of Return
let cashFlows = [-1_000.0, 200.0, 300.0, 400.0, 500.0]
func npv(rate: Double) -> Double {
var npv = 0.0
for (t, cf) in cashFlows.enumerated() {
npv += cf / pow(1 + rate, Double(t))
}
return npv
}
// Find rate where NPV = 0
let irr = try goalSeek(
function: npv,
target: 0.0,
guess: 0.10 // Start with 10% guess
)
print("IRR: \(irr.percent(2))")
print("Verification - NPV at IRR: \(npv(rate: irr).currency(2))")
// MARK: - Equation Solving
// Solve: e^x - 2x - 3 = 0
let solution = try goalSeek(
function: { x in exp(x) - 2*x - 3 },
target: 0.0,
guess: 1.0
)
print("Solution: x = \(solution.number(6))")
// Verify: Should be ≈ 0
let verify = exp(solution) - 2*solution - 3
print("Verification: \(verify.number(10))")
// MARK: - Error Handling, Division by Zero
do {
// Function with zero derivative at x=0
let result = try goalSeek(
function: { x in x * x * x }, // f'(0) = 0
target: 0.0,
guess: 0.0 // BAD: Starting at stationary point
)
print(result)
} catch let error as BusinessMathError {
print(error.localizedDescription)
// "Goal-seeking failed: Division by zero encountered"
if let recovery = error.recoverySuggestion {
print("How to fix:\n\(recovery)")
// "Try a different initial guess away from stationary points"
}
}
// MARK: - Error Handling, Failed Convergence
do {
let result = try goalSeek(
function: { x in sin(x) },
target: 1.5, // BAD: sin(x) never equals 1.5
guess: 0.0
)
} catch let error as BusinessMathError {
print(error.localizedDescription)
// "Goal-seeking did not converge within 1000 iterations"
if let recovery = error.recoverySuggestion {
print("How to fix:\n\(recovery)")
// "Try different initial guess, increase max iterations, or relax tolerance"
}
}
// MARK: - Goal Seek Optimizer Class
func profitFunction(price: Double) -> Double {
let quantity = 10_000 - 1_000 * price
let revenue = price * quantity
let fixedCosts = 5_000.0
let variableCost = 4.0
let totalCosts = fixedCosts + variableCost * quantity
return revenue - totalCosts
}
let optimizer_GS = GoalSeekOptimizer(
target: 0.0,
tolerance: 0.0001,
maxIterations: 1000
)
let result_GS = optimizer_GS.optimize(
objective: profitFunction,
constraints: [],
initialGuess: 4.0,
bounds: (lower: 0.0, upper: 100.0)
)
print("Solution: \(result_GS.optimalValue.currency(2))")
print("Converged: \(result_GS.converged)")
print("Iterations: \(result_GS.iterations)")
→ Full API Reference: BusinessMath Docs – 5.3 Core Optimization
Modifications to try:
minimize() on negative profit)CFO use case: “We need to hit $5M EBITDA next quarter. What revenue do we need given our cost structure and operating leverage?”
Goal-seeking automates this calculation instantly.
★ Insight ─────────────────────────────────────
Why Newton-Raphson Converges Quadratically
Newton-Raphson doubles the number of correct digits with each iteration when close to the solution. This is called quadratic convergence.
Example progression (finding √2):
Why? Taylor series analysis shows the error decreases proportional to the square of the previous error: ε_{n+1} ∝ ε_n²
Trade-off: Fast convergence requires good initial guess. Bad guesses may diverge or converge to wrong root.
─────────────────────────────────────────────────
The hardest part was making numerical differentiation robust across all Real types (Float, Double, Decimal, etc.).
Challenge: The step size h for f’(x) ≈ (f(x+h) - f(x-h)) / (2h) must be:
We settled on h = √ε × max(|x|, 1) where ε is machine epsilon. This adapts to:
Result: Goal-seeking works reliably across all numeric types without user tuning.
Related Methodology: Numerical Stability (Week 2) - Covered catastrophic cancellation and condition numbers.
Coming up Thursday: Vector Operations - Understanding the VectorSpace protocol, Vector2D, Vector3D, and VectorN for multivariate optimization.
Series Progress:
Tagged with: businessmath, swift, goal-seeking, root-finding, irr, breakeven, error-handling