diff --git a/search/bisection.go b/search/bisection.go new file mode 100644 index 000000000..78504ceb2 --- /dev/null +++ b/search/bisection.go @@ -0,0 +1,62 @@ +package search + +import ( + "fmt" +) + +// Package search provides functions for searching and root-finding algorithms. +// +// bisection searches for the root of the function f in the interval [a, b]. +// The function f must be continuous and f(a) and f(b) must have different signs. +// This method uses the bisection algorithm to iteratively narrow down the interval +// until the root is found within the specified tolerance or the maximum number of +// iterations is reached. +// +// Parameters: +// - f: A function that takes a float64 and returns a float64. This is the function +// for which we are trying to find the root. +// - a: The start of the interval. +// - b: The end of the interval. +// - tol: The tolerance for the root. The algorithm stops when the interval width +// is less than this value. +// - maxIter: The maximum number of iterations to perform. +// +// Returns: +// - float64: The approximate root of the function. +// - error: An error is returned if f(a) and f(b) do not have different signs or +// if the maximum number of iterations is reached without finding the root. +// +// Complexity: +// - Worst: O(log((b-a)/tol)) +// - Average: O(log((b-a)/tol)) +// - Best: O(1) +// +// Example usage: +// +// root, err := bisection(func(x float64) float64 { return math.Pow(x, 3) - x - 2 }, 1, 2, 1e-6, 1000) +// if err != nil { +// log.Fatal(err) +// } +// fmt.Println(root) // Output: 1.521379 +// +// Reference: +// - https://en.wikipedia.org/wiki/Bisection_method +func bisection(f func(float64) float64, a, b, tol float64, maxIter int) (float64, error) { + if f(a)*f(b) >= 0 { + return 0, fmt.Errorf("f(a) and f(b) must have different signs") + } + + var c float64 + for i := 0; i < maxIter; i++ { + c = (a + b) / 2 + if f(c) == 0 || (b-a)/2 < tol { + return c, nil + } + if f(c)*f(a) < 0 { + b = c + } else { + a = c + } + } + return c, fmt.Errorf("maximum number of iterations reached") +} diff --git a/search/bisection_test.go b/search/bisection_test.go new file mode 100644 index 000000000..558da214a --- /dev/null +++ b/search/bisection_test.go @@ -0,0 +1,74 @@ +package search + +import ( + "math" + "testing" +) + +func TestBisection(t *testing.T) { + tests := []struct { + f func(float64) float64 + a, b float64 + tol float64 + maxIter int + want float64 + wantErr bool + }{ + { + f: func(x float64) float64 { return math.Pow(x, 3) - x - 2 }, + a: 1, + b: 2, + tol: 1e-6, + maxIter: 1000, + want: 1.521380, + wantErr: false, + }, + { + f: func(x float64) float64 { return x*x - 4 }, + a: 0, + b: 3, + tol: 1e-6, + maxIter: 1000, + want: 2, + wantErr: false, + }, + { + f: func(x float64) float64 { return x - 5 }, + a: 0, + b: 10, + tol: 1e-6, + maxIter: 1000, + want: 5, + wantErr: false, + }, + { + f: func(x float64) float64 { return math.Sin(x) }, + a: 3, + b: 4, + tol: 1e-6, + maxIter: 1000, + want: math.Pi, + wantErr: false, + }, + { + f: func(x float64) float64 { return x*x + 1 }, + a: -1, + b: 1, + tol: 1e-6, + maxIter: 1000, + want: 0, + wantErr: true, + }, + } + + for _, tt := range tests { + got, err := bisection(tt.f, tt.a, tt.b, tt.tol, tt.maxIter) + if (err != nil) != tt.wantErr { + t.Errorf("bisection() error = %v, wantErr %v", err, tt.wantErr) + continue + } + if math.Abs(got-tt.want) > tt.tol { + t.Errorf("bisection() = %v, want %v", got, tt.want) + } + } +}