From 54ff33c552437d4c0ba5c6f3a5c53c81e4a1bea3 Mon Sep 17 00:00:00 2001 From: danhale-git <36298392+danhale-git@users.noreply.github.com> Date: Fri, 17 Jul 2020 23:50:07 +0100 Subject: [PATCH] Benchmark d2math (#595) * Vector and tests reviewed. * Tests and benchmarks for d2math.math. Also docs/comments. --- d2common/d2math/d2vector/doc.go | 5 ++ d2common/d2math/d2vector/vector.go | 17 ++-- d2common/d2math/d2vector/vector_test.go | 2 +- d2common/d2math/doc.go | 5 ++ d2common/d2math/math.go | 13 ++- d2common/d2math/math_test.go | 114 ++++++++++++++++++++++-- 6 files changed, 130 insertions(+), 26 deletions(-) create mode 100644 d2common/d2math/d2vector/doc.go diff --git a/d2common/d2math/d2vector/doc.go b/d2common/d2math/d2vector/doc.go new file mode 100644 index 00000000..351ba24a --- /dev/null +++ b/d2common/d2math/d2vector/doc.go @@ -0,0 +1,5 @@ +// Package d2vector provides an implementation of a 2D Euclidean vector using float64 to store the two values. +/* +Vector uses d2math.Epsilon for approximate equality and comparison. Note: SetLength and Rotate do not (per the unit +tests) return exact values but ones within Epsilon range of the expected value.*/ +package d2vector diff --git a/d2common/d2math/d2vector/vector.go b/d2common/d2math/d2vector/vector.go index e350aac5..526dfe6e 100644 --- a/d2common/d2math/d2vector/vector.go +++ b/d2common/d2math/d2vector/vector.go @@ -1,4 +1,3 @@ -// Package d2vector provides an implementation of a 2D Euclidean vector using float64 to store the two values. package d2vector import ( @@ -38,15 +37,14 @@ func (v *Vector) Equals(o Vector) bool { // EqualsApprox returns true if the values of this Vector are approximately equal to those of the given Vector. If the // difference between either of the value pairs is smaller than d2math.Epsilon, they will be considered equal. func (v *Vector) EqualsApprox(o Vector) bool { - x, y := v.CompareApprox(o) - return x == 0 && y == 0 + return d2math.EqualsApprox(v.x, o.x) && d2math.EqualsApprox(v.y, o.y) } // CompareApprox returns 2 ints describing the difference between the vectors. If the difference between either of the // value pairs is smaller than d2math.Epsilon, they will be considered equal. func (v *Vector) CompareApprox(o Vector) (x, y int) { - return d2math.CompareFloat64Fuzzy(v.x, o.x), - d2math.CompareFloat64Fuzzy(v.y, o.y) + return d2math.CompareApprox(v.x, o.x), + d2math.CompareApprox(v.y, o.y) } // IsZero returns true if this vector's values are both exactly zero. @@ -86,8 +84,8 @@ func (v *Vector) Floor() *Vector { // Clamp limits the values of v to those of a and b. If the values of v are between those of a and b they will be // unchanged. func (v *Vector) Clamp(a, b *Vector) *Vector { - v.x = d2math.ClampFloat64(v.x, a.x, b.x) - v.y = d2math.ClampFloat64(v.y, a.y, b.y) + v.x = d2math.Clamp(v.x, a.x, b.x) + v.y = d2math.Clamp(v.y, a.y, b.y) return v } @@ -245,7 +243,7 @@ func (v *Vector) Angle(o Vector) float64 { to.Normalize() denominator := math.Sqrt(from.Length() * to.Length()) - dotClamped := d2math.ClampFloat64(from.Dot(&to)/denominator, -1, 1) + dotClamped := d2math.Clamp(from.Dot(&to)/denominator, -1, 1) return math.Acos(dotClamped) } @@ -276,8 +274,7 @@ func (v *Vector) Reflect(normal Vector) *Vector { return v } -// ReflectSurface does the same thing as Reflect, except the given vector describes, -// the surface line, not it's normal. +// ReflectSurface does the same thing as Reflect, except the given vector describes the surface line, not it's normal. func (v *Vector) ReflectSurface(surface Vector) *Vector { v.Reflect(surface).Negate() diff --git a/d2common/d2math/d2vector/vector_test.go b/d2common/d2math/d2vector/vector_test.go index 76280a19..eaba7f0c 100644 --- a/d2common/d2math/d2vector/vector_test.go +++ b/d2common/d2math/d2vector/vector_test.go @@ -602,7 +602,7 @@ func BenchmarkVector_Normalize(b *testing.B) { v := NewVector(1, 1) for n := 0; n < b.N; n++ { - v.Normalize() + outFloat = v.Normalize() } } diff --git a/d2common/d2math/doc.go b/d2common/d2math/doc.go index 6a758ac1..52baa0ff 100644 --- a/d2common/d2math/doc.go +++ b/d2common/d2math/doc.go @@ -1,2 +1,7 @@ // Package d2math provides mathematical functions not included in Golang's standard math library. +/* +The decimal numeric type used is float64. + +Math also dictates the threshold for approximate equality (d2math.Epsilon). This is currently used both for moving +entities and for approximate floating point equality in vector functions. See d2vector.*/ package d2math diff --git a/d2common/d2math/math.go b/d2common/d2math/math.go index f5e42f29..1082445e 100644 --- a/d2common/d2math/math.go +++ b/d2common/d2math/math.go @@ -17,10 +17,9 @@ func EqualsApprox(a, b float64) bool { return Abs(a-b) < Epsilon } -// CompareFloat64Fuzzy returns an integer between -1 and 1 describing -// the comparison of floats a and b. 0 will be returned if the -// absolute difference between a and b is less than Epsilon. -func CompareFloat64Fuzzy(a, b float64) int { +// CompareApprox returns an integer between -1 and 1 describing the comparison of floats a and b. 0 will be returned if +// the absolute difference between a and b is less than Epsilon. +func CompareApprox(a, b float64) int { delta := a - b if Abs(delta) < Epsilon { @@ -37,14 +36,14 @@ func CompareFloat64Fuzzy(a, b float64) int { // Abs returns the absolute value of a. It is a less CPU intensive version of the standard library math.Abs(). func Abs(a float64) float64 { if a < 0 { - return a * -1 + return -a } return a } -// ClampFloat64 returns a clamped to min and max. -func ClampFloat64(a, min, max float64) float64 { +// Clamp returns a clamped to min and max. +func Clamp(a, min, max float64) float64 { if a > max { return max } else if a < min { diff --git a/d2common/d2math/math_test.go b/d2common/d2math/math_test.go index e0d0b772..7438b7bc 100644 --- a/d2common/d2math/math_test.go +++ b/d2common/d2math/math_test.go @@ -4,6 +4,15 @@ import ( "testing" ) +//nolint:gochecknoglobals // These variables are assigned to in benchmark functions to avoid compiler optimisations +// lowering the runtime of the benchmark. See: https://dave.cheney.net/2013/06/30/how-to-write-benchmarks-in-go (A note +// on compiler optimisations) +var ( + outFloat float64 + outBool bool + outInt int +) + func TestEqualsApprox(t *testing.T) { subEpsilon := Epsilon / 3 @@ -22,12 +31,21 @@ func TestEqualsApprox(t *testing.T) { } } -func TestCompareFloat64Fuzzy(t *testing.T) { +func BenchmarkEqualsApprox(b *testing.B) { + x := 1.0 + y := 2.0 + + for n := 0; n < b.N; n++ { + outBool = EqualsApprox(x, y) + } +} + +func TestCompareApprox(t *testing.T) { subEpsilon := Epsilon / 3 want := 0 a, b := 1+subEpsilon, 1.0 - got := CompareFloat64Fuzzy(a, b) + got := CompareApprox(a, b) if got != want { t.Errorf("compare %.2f and %.2f: wanted %d: got %d", a, b, want, got) @@ -35,7 +53,7 @@ func TestCompareFloat64Fuzzy(t *testing.T) { want = 1 a, b = 2, 1.0 - got = CompareFloat64Fuzzy(a, b) + got = CompareApprox(a, b) if got != want { t.Errorf("compare %.2f and %.2f: wanted %d: got %d", a, b, want, got) @@ -43,17 +61,52 @@ func TestCompareFloat64Fuzzy(t *testing.T) { want = -1 a, b = -2, 1.0 - got = CompareFloat64Fuzzy(a, b) + got = CompareApprox(a, b) if got != want { t.Errorf("compare %.2f and %.2f: wanted %d: got %d", a, b, want, got) } } -func TestClampFloat64(t *testing.T) { +func BenchmarkCompareApprox(b *testing.B) { + x := 1.0 + y := 2.0 + + for n := 0; n < b.N; n++ { + outInt = CompareApprox(x, y) + } +} + +func TestAbs(t *testing.T) { + want := 1.0 + x := -1.0 + got := Abs(x) + + if got != want { + t.Errorf("absolute value of %.2f: want %.2f: got %.2f", x, want, got) + } + + want = 1.0 + x = 1.0 + got = Abs(x) + + if got != want { + t.Errorf("absolute value of %.2f: want %.2f: got %.2f", x, want, got) + } +} + +func BenchmarkAbs(b *testing.B) { + x := -1.0 + + for n := 0; n < b.N; n++ { + outFloat = Abs(x) + } +} + +func TestClamp(t *testing.T) { want := 0.5 a := 0.5 - got := ClampFloat64(a, 0, 1) + got := Clamp(a, 0, 1) if got != want { t.Errorf("clamped %.2f between 0 and 1: wanted %.2f: got %.2f", a, want, got) @@ -61,7 +114,7 @@ func TestClampFloat64(t *testing.T) { want = 0.0 a = -1.0 - got = ClampFloat64(a, 0, 1) + got = Clamp(a, 0, 1) if got != want { t.Errorf("clamped %.2f between 0 and 1: wanted %.2f: got %.2f", a, want, got) @@ -69,13 +122,21 @@ func TestClampFloat64(t *testing.T) { want = 1.0 a = 2.0 - got = ClampFloat64(a, 0, 1) + got = Clamp(a, 0, 1) if got != want { t.Errorf("clamped %.2f between 0 and 1: wanted %.2f: got %.2f", a, want, got) } } +func BenchmarkClamp(b *testing.B) { + f := 0.5 + + for n := 0; n < b.N; n++ { + outFloat = Clamp(f, 0, 1) + } +} + func TestSign(t *testing.T) { want := 1 a := 0.5 @@ -102,6 +163,14 @@ func TestSign(t *testing.T) { } } +func BenchmarkSign(b *testing.B) { + f := 0.5 + + for n := 0; n < b.N; n++ { + outInt = Sign(f) + } +} + func TestLerp(t *testing.T) { want := 3.0 x := 0.3 @@ -116,6 +185,16 @@ func TestLerp(t *testing.T) { } } +func BenchmarkLerp(b *testing.B) { + x := 1.0 + y := 1000.0 + interp := 1.01 + + for n := 0; n < b.N; n++ { + outFloat = Lerp(x, y, interp) + } +} + func TestUnlerp(t *testing.T) { want := 0.3 x := 3.0 @@ -130,6 +209,16 @@ func TestUnlerp(t *testing.T) { } } +func BenchmarkUnlerp(b *testing.B) { + x := 1.0 + y := 2.0 + lerp := 1.5 + + for n := 0; n < b.N; n++ { + outFloat = Unlerp(x, y, lerp) + } +} + func TestWrapInt(t *testing.T) { want := 50 a, b := 1050, 100 @@ -165,3 +254,12 @@ func TestWrapInt(t *testing.T) { t.Errorf(d, a, b, want, got) } } + +func BenchmarkWrapInt(b *testing.B) { + x := 10 + y := 2 + + for n := 0; n < b.N; n++ { + outInt = WrapInt(x, y) + } +}