From f698bae1dfd194d83bfdaaa99b4a58a3b2f46b98 Mon Sep 17 00:00:00 2001 From: Ben Cragg Date: Wed, 10 Apr 2019 12:47:07 +0100 Subject: [PATCH 1/2] Added floating point round error correction --- geometry.go | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/geometry.go b/geometry.go index 8ccab8d..42fe018 100644 --- a/geometry.go +++ b/geometry.go @@ -49,6 +49,33 @@ func V(x, y float64) Vec { return Vec{x, y} } +// nearlyEqual compares two float64s and returns whether they are equal, accounting for rounding errors.At worst, the +// result is correct to 7 significant digits. +func nearlyEqual(a, b float64) bool { + epsilon := 0.000001 + + if a == b { + return true + } + + diff := math.Abs(a - b) + + if a == 0.0 || b == 0.0 || diff < math.SmallestNonzeroFloat64 { + return diff < (epsilon * math.SmallestNonzeroFloat64) + } + + absA := math.Abs(a) + absB := math.Abs(b) + + return diff/math.Min(absA+absB, math.MaxFloat64) < epsilon +} + +// Eq will compare two vectors and return whether they are equal accounting for rounding errors. At worst, the result +// is correct to 7 significant digits. +func (u Vec) Eq(v Vec) bool { + return nearlyEqual(u.X, v.X) && nearlyEqual(u.Y, v.Y) +} + // Unit returns a vector of length 1 facing the given angle. func Unit(angle float64) Vec { return Vec{1, 0}.Rotated(angle) @@ -275,7 +302,7 @@ func (l Line) Closest(v Vec) Vec { // Contains returns whether the provided Vec lies on the line. func (l Line) Contains(v Vec) bool { - return l.Closest(v) == v + return l.Closest(v).Eq(v) } // Formula will return the values that represent the line in the formula: y = mx + b From 96e0a8f3bfa328c855349867a16c4a1fff869301 Mon Sep 17 00:00:00 2001 From: Ben Cragg Date: Wed, 10 Apr 2019 12:47:21 +0100 Subject: [PATCH 2/2] Added test cases --- geometry_test.go | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/geometry_test.go b/geometry_test.go index 2f214a7..32bd9f6 100644 --- a/geometry_test.go +++ b/geometry_test.go @@ -1181,6 +1181,19 @@ func TestLine_Intersect(t *testing.T) { args: args{k: pixel.L(pixel.V(0, 1), pixel.V(10, 11))}, want: pixel.ZV, want1: false, + }, { + name: "Lines intersect", + fields: fields{A: pixel.V(600, 600), B: pixel.V(925, 150)}, + args: args{k: pixel.L(pixel.V(740, 255), pixel.V(925, 255))}, + want: pixel.V(849.1666666666666, 255), + want1: true, + }, + { + name: "Lines intersect", + fields: fields{A: pixel.V(600, 600), B: pixel.V(925, 150)}, + args: args{k: pixel.L(pixel.V(740, 255), pixel.V(925, 255.0001))}, + want: pixel.V(849.1666240490657, 255.000059008986), + want1: true, }, } for _, tt := range tests {