165 lines
4.3 KiB
Go
165 lines
4.3 KiB
Go
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"math"
|
|
)
|
|
|
|
// specification for an integration
|
|
type spec struct {
|
|
lower, upper float64 // bounds for integration
|
|
n int // number of parts
|
|
exact float64 // expected answer
|
|
fs string // mathematical description of function
|
|
f func(float64) float64 // function to integrate
|
|
}
|
|
|
|
// test cases per task description
|
|
var data = []spec{
|
|
spec{0, 1, 100, .25, "x^3", func(x float64) float64 { return x * x * x }},
|
|
spec{1, 100, 1000, float64(math.Log(100)), "1/x",
|
|
func(x float64) float64 { return 1 / x }},
|
|
spec{0, 5000, 5e5, 12.5e6, "x", func(x float64) float64 { return x }},
|
|
spec{0, 6000, 6e6, 18e6, "x", func(x float64) float64 { return x }},
|
|
}
|
|
|
|
// object for associating a printable function name with an integration method
|
|
type method struct {
|
|
name string
|
|
integrate func(spec) float64
|
|
}
|
|
|
|
// integration methods implemented per task description
|
|
var methods = []method{
|
|
method{"Rectangular (left) ", rectLeft},
|
|
method{"Rectangular (right) ", rectRight},
|
|
method{"Rectangular (midpoint)", rectMid},
|
|
method{"Trapezium ", trap},
|
|
method{"Simpson's ", simpson},
|
|
}
|
|
|
|
func rectLeft(t spec) float64 {
|
|
var a adder
|
|
r := t.upper - t.lower
|
|
nf := float64(t.n)
|
|
x0 := t.lower
|
|
for i := 0; i < t.n; i++ {
|
|
x1 := t.lower + float64(i+1)*r/nf
|
|
// x1-x0 better than r/nf.
|
|
// (with r/nf, the represenation error accumulates)
|
|
a.add(t.f(x0) * (x1 - x0))
|
|
x0 = x1
|
|
}
|
|
return a.total()
|
|
}
|
|
|
|
func rectRight(t spec) float64 {
|
|
var a adder
|
|
r := t.upper - t.lower
|
|
nf := float64(t.n)
|
|
x0 := t.lower
|
|
for i := 0; i < t.n; i++ {
|
|
x1 := t.lower + float64(i+1)*r/nf
|
|
a.add(t.f(x1) * (x1 - x0))
|
|
x0 = x1
|
|
}
|
|
return a.total()
|
|
}
|
|
|
|
func rectMid(t spec) float64 {
|
|
var a adder
|
|
r := t.upper - t.lower
|
|
nf := float64(t.n)
|
|
// there's a tiny gloss in the x1-x0 trick here. the correct way
|
|
// would be to compute x's at division boundaries, but we don't need
|
|
// those x's for anything else. (the function is evaluated on x's
|
|
// at division midpoints rather than division boundaries.) so, we
|
|
// reuse the midpoint x's, knowing that they will average out just
|
|
// as well. we just need one extra point, so we use lower-.5.
|
|
x0 := t.lower - .5*r/nf
|
|
for i := 0; i < t.n; i++ {
|
|
x1 := t.lower + (float64(i)+.5)*r/nf
|
|
a.add(t.f(x1) * (x1 - x0))
|
|
x0 = x1
|
|
}
|
|
return a.total()
|
|
}
|
|
|
|
func trap(t spec) float64 {
|
|
var a adder
|
|
r := t.upper - t.lower
|
|
nf := float64(t.n)
|
|
x0 := t.lower
|
|
f0 := t.f(x0)
|
|
for i := 0; i < t.n; i++ {
|
|
x1 := t.lower + float64(i+1)*r/nf
|
|
f1 := t.f(x1)
|
|
a.add((f0 + f1) * .5 * (x1 - x0))
|
|
x0, f0 = x1, f1
|
|
}
|
|
return a.total()
|
|
}
|
|
|
|
func simpson(t spec) float64 {
|
|
var a adder
|
|
r := t.upper - t.lower
|
|
nf := float64(t.n)
|
|
// similar to the rectangle midpoint logic explained above,
|
|
// we play a little loose with the values used for dx and dx0.
|
|
dx0 := r / nf
|
|
a.add(t.f(t.lower) * dx0)
|
|
a.add(t.f(t.lower+dx0*.5) * dx0 * 4)
|
|
x0 := t.lower + dx0
|
|
for i := 1; i < t.n; i++ {
|
|
x1 := t.lower + float64(i+1)*r/nf
|
|
xmid := (x0 + x1) * .5
|
|
dx := x1 - x0
|
|
a.add(t.f(x0) * dx * 2)
|
|
a.add(t.f(xmid) * dx * 4)
|
|
x0 = x1
|
|
}
|
|
a.add(t.f(t.upper) * dx0)
|
|
return a.total() / 6
|
|
}
|
|
|
|
func sum(v []float64) float64 {
|
|
var a adder
|
|
for _, e := range v {
|
|
a.add(e)
|
|
}
|
|
return a.total()
|
|
}
|
|
|
|
type adder struct {
|
|
sum, e float64
|
|
}
|
|
|
|
func (a *adder) total() float64 {
|
|
return a.sum + a.e
|
|
}
|
|
|
|
func (a *adder) add(x float64) {
|
|
sum := a.sum + x
|
|
e := sum - a.sum
|
|
a.e += a.sum - (sum - e) + (x - e)
|
|
a.sum = sum
|
|
}
|
|
|
|
func main() {
|
|
for _, t := range data {
|
|
fmt.Println("Test case: f(x) =", t.fs)
|
|
fmt.Println("Integration from", t.lower, "to", t.upper,
|
|
"in", t.n, "parts")
|
|
fmt.Printf("Exact result %.7e Error\n", t.exact)
|
|
for _, m := range methods {
|
|
a := m.integrate(t)
|
|
e := a - t.exact
|
|
if e < 0 {
|
|
e = -e
|
|
}
|
|
fmt.Printf("%s %.7e %.7e\n", m.name, a, e)
|
|
}
|
|
fmt.Println("")
|
|
}
|
|
}
|