Benchmark d2math (#595)

* Vector and tests reviewed.

* Tests and benchmarks for d2math.math.

Also docs/comments.
This commit is contained in:
danhale-git 2020-07-17 23:50:07 +01:00 committed by GitHub
parent dcb0c087b9
commit 54ff33c552
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 130 additions and 26 deletions

View File

@ -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

View File

@ -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()

View File

@ -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()
}
}

View File

@ -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

View File

@ -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 {

View File

@ -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)
}
}