Heyo
So, I spent the last week debugging my binary partitioning algorithm using print and console commands.
I combed through my code hundreds of times but couldn't identify the issues.
Till today.
the issue was with floats.
See,
my code is full of variable comparisions
there were comparisons like
A = 0.29530204755881
B = 0.29533333333333
and i always assumed A == B which isnt the case.
So i started rounding the numbers,
now I am getting
A = 2.34 vs B = 2.35
Having torn most of hair by now, reduced the precision to 1
resulting in comparisons like
A = 0.2
B = 0
I could go all the way to ints but then my output will b wrong anyway due to the low precision.
I am on the verge of comitting sudoku now. or crying in a corner.
Thanks for listening.
PS. how do you guys handle float comparisons in your code? any suggestions?
Dealing with floats
Re: Dealing with floats
I don't understand why your numbers change so much. But, for a "perfect" float comparison, see the C# rules : use a delta :
You can also do this in a different way (if you have many float comparison, it can be helpful) :
Code: Select all
if math.abs(A - B) < 0.001 then ... end
Code: Select all
-- Configure this variable to your needs
-- Use a lower value for more precise comparison
-- Use a higher value for less precise comparison
local EPSILON=0.001
function cmp(A, B)
local delta = A - B
if delta >= -EPSILON and delta <= EPSILON then
return 0
elseif delta > 0 then
return 1
else
return -1
end
end
-- Use it like this :
if cmp(A, B) == 0 then
-- Nearly equal
elseif cmp(A, B) > 0 then
-- A > B
elseif cmp(A, B) < 0 then
-- A < B
end
Founder of NeoShadow Studio. Currently working on the project "Sirami".
github / linkpy
github / linkpy
Re: Dealing with floats
i am currently checking intersection of thousands of lines which in turn require slope comparisons. Out of the thousands of slopes, few always come up wrong no matter what i do. Your delta method looks very simple and nice. Gotta try !
Got any more tricks for floats?
I am not a good programmer and love to learn all such things.
Got any more tricks for floats?
I am not a good programmer and love to learn all such things.
Re: Dealing with floats
You can get the coordinates of where they actually intersect without having to calculate slopes by using cross product ( https://en.wikipedia.org/wiki/Cross_product ). Throw this function two lines in the form L1 = { X1 = 0, Y1 = 0, X2 = 1, Y2 = 1 } etc.
Also, watch out for math.atan2( y, x ) - especially if you are performing parallel checks. You'll definitely want to use the EPSILON trick mentioned by @Linkpy , and also remember that 0, -0, and 2*pi radians is the same.
Code: Select all
GetIntersection = function( L1, L2 )
local d = (L2.Y2 - L2.Y1) * (L1.X2 - L1.X1) - (L2.X2 - L2.X1) * (L1.Y2 - L1.Y1)
if (d == 0) then -- if it's zero, the lines are parallel
return false
end
local n_a = (L2.X2 - L2.X1) * (L1.Y1 - L2.Y1) - (L2.Y2 - L2.Y1) * (L1.X1 - L2.X1)
local n_b = (L1.X2 - L1.X1) * (L1.Y1 - L2.Y1) - (L1.Y2 - L1.Y1) * (L1.X1 - L2.X1)
local ua = n_a / d
local ub = n_b / d
if (ua >= 0 and ua <= 1 and ub >= 0 and ub <= 1) then
local x = L1.X1 + (ua * (L1.X2 - L1.X1))
local y = L1.Y1 + (ua * (L1.Y2 - L1.Y1))
return x, y -- lines intersect at x, y
end
return nil -- lines do not intersect
end
Re: Dealing with floats
I suspect that the problem in this case is caused by the "==" operator.
Don't use that operator when dealing with floating point numbers and you should be fine.
Also, there are simpler ways to find the intersection point between two segments,
take a look at: http://2dengine.com/doc/gs_intersection.html
Don't use that operator when dealing with floating point numbers and you should be fine.
Also, there are simpler ways to find the intersection point between two segments,
take a look at: http://2dengine.com/doc/gs_intersection.html
Re: Dealing with floats
I had the same problem in the same situation as you. Here's the function I ended up using:
Code: Select all
function checkFuzzy( x, y, delta )
return math.abs( x - y ) <= ( delta or .00001 )
end
GitHub | MLib - Math and shape intersections library | Walt - Animation library | Brady - Camera library with parallax scrolling | Vim-love-docs - Help files and syntax coloring for Vim
Re: Dealing with floats
Thanks guys. So far the fuzzy/epsilon/delta method is working.
@ivan, yeah the problem is with using == operators with floats.
I spent over a week doing my incredibly comprehensive line intersection algo, so i'd stick with it a bit longer.
Here is it in all its glory.
Read and be awed. Or laugh at me.
@ivan, yeah the problem is with using == operators with floats.
I spent over a week doing my incredibly comprehensive line intersection algo, so i'd stick with it a bit longer.
Here is it in all its glory.
Read and be awed. Or laugh at me.
Code: Select all
function get_line_intersection (lineA, lineB)
local Ax, Ay, d
-- m1= Am, m2 = Bm
local Am, Ab = lineA.m, lineA.b
local Bm, Bb = lineB.m, lineB.b
-- For this function to work, both lines need at least one defined point in them. This is the testing point Tx, Ty
-- for segments, we can take either of the points as Testing Points
--Lets define testing points for line A
if lineA.seg == true then
Ax = lineA.P1x
Ay = lineA.P1y
else
Ax = lineA.Tx
Ay = lineA.Ty
end
-- And line B
if lineB.seg == true then
Bx = lineB.P1x
By = lineB.P1y
else
Bx = lineB.Tx
By = lineB.Ty
end
-- Finding the min and max of the two points. This is used for checking if a point falls in a line.
local minAx, minAy, maxAx, maxAy, minBx, minBy, maxBx, maxBy
if lineA.seg == true and lineB.seg == true then
minAx = math.min(lineA.P1x, lineA.P2x)
maxAx = math.max(lineA.P1x, lineA.P2x)
minAy = math.min(lineA.P1y, lineA.P2y)
maxAy = math.max(lineA.P1y, lineA.P2y)
minBx = math.min(lineB.P1x, lineB.P2x)
maxBx = math.max(lineB.P1x, lineB.P2x)
minBy = math.min(lineB.P1y, lineB.P2y)
maxBy = math.max(lineB.P1y, lineB.P2y)
elseif lineA.seg == false and lineB.seg == true then
minAx = 0
maxAx = math.huge
minAy = 0
maxAy = math.huge
minBx = math.min(lineB.P1x, lineB.P2x)
maxBx = math.max(lineB.P1x, lineB.P2x)
minBy = math.min(lineB.P1y, lineB.P2y)
maxBy = math.max(lineB.P1y, lineB.P2y)
elseif lineA.seg == true and lineB.seg == false then
minAx = math.min(lineA.P1x, lineA.P2x)
maxAx = math.max(lineA.P1x, lineA.P2x)
minAy = math.min(lineA.P1y, lineA.P2y)
maxAy = math.max(lineA.P1y, lineA.P2y)
minBx = 0
maxBx = math.huge
minBy = 0
maxBy = math.huge
elseif lineA.seg == false and lineB.seg == false then
minAx = 0
maxAx = math.huge
minAy = 0
maxAy = math.huge
minBx = 0
maxBx = math.huge
minBy = 0
maxBy = math.huge
else
print("Some error with the line.seg attribute")
return {intersection = false}
end
-- First lets test for colinear and parallel lines
if Am == Bm or Am == -Bm then
print("Parallel Lines")
-- test for colinear lines
if Am == 0 and Bm == 0 then
print("Scenario : Am = Bm = 0")
-- here both have same y intercept
if Ay == By then
if lineA.seg == true and lineB.seg == true then
-- if both lines are segments, there is a chance they lie side by side
-- check if the points for any line lie between the bounding points of the other line
if (
(minAx <= minBx and minBx <= maxAx)
or (minAx <= maxBx and maxBx <= maxAx)
or (minBx <= minAx and minAx <= maxBx)
or (minBx <= maxAx and maxAx <= maxBx)
) then
print("Colinear Lines")
return {intersection = true, colinear = true}
else
print("Non Colinear Lines")
print("Lines lie Side by Side")
return {intersection = false}
end
else
-- if either of the lines is not a segment, then they will definitely be collinear
print("Colinear Lines")
return {intersection = true, colinear = true}
end
else
print("Non Colinear Lines")
return {intersection = false}
end
elseif (Am == math.huge or Am == -math.huge) and (Bm == math.huge or Bm == -math.huge) then
print("Scenario : Am = Bm = inf")
-- here both have same x intercept
if Ax == Bx then
if lineA.seg == true and lineB.seg == true then
-- if both lines are segments, there is a chance they lie side by side
-- check if the points for any line lie between the bounding points of the other line
if (
(minAy <= minBy and minBy <= maxAy)
or (minAy <= maxBy and maxBy <= maxAy)
or (minBy <= minAy and minAy <= maxBy)
or (minBy <= maxAy and maxAy <= maxBy)
) then
print("Colinear Lines")
return {intersection = true, colinear = true}
else
print("Non Colinear Lines")
print("Lines lie Side by Side")
return {intersection = false}
end
else
-- if either of the lines is not a segment, then they will definitely be collinear
print("Colinear Lines")
return {intersection = true, colinear = true}
end
else
print("Non Colinear Lines")
return {intersection = false}
end
else
print("Scenario : Am = Bm = valid")
print("Am = ", Am)
print("Bm = ", Bm)
print("Ab = ", Ab)
print("Bb = ", Bb)
-- local d = round((math.abs(Bb - Ab)) / (math.sqrt((Bm * Bm) + 1)))
-- print("d = ", d)
if Ab == Bb then
if lineA.seg == true and lineB.seg == true then
-- if both lines are segments, there is a chance they lie side by side
-- check if the points for any line lie between the bounding points of the other line
if (
(minAx <= minBx and minBx <= maxAx)
or (minAx <= maxBx and maxBx <= maxAx)
or (minBx <= minAx and minAx <= maxBx)
or (minBx <= maxAx and maxAx <= maxBx)
)
and
(
(minAy <= minBy and minBy <= maxAy)
or (minAy <= maxBy and maxBy <= maxAy)
or (minBy <= minAy and minAy <= maxBy)
or (minBy <= maxAy and maxAy <= maxBy)
) then
print("Colinear Lines")
return {intersection = true, colinear = true}
else
print("Non Colinear Lines")
print("Lines lie Side by Side")
return {intersection = false}
end
else
-- if either of the lines is not a segment, then they will definitely be collinear
print("Colinear Lines")
return {intersection = true, colinear = true}
end
else
print("Non Colinear Lines")
return {intersection = false}
end
end
else
-- print("Non parallel lines")
-- Am = 0 and Bm = inf
if Am == 0 and (Bm == math.huge or Bm == -math.huge) then
print("Scenario : Am = 0 and Bm = inf")
iy = Ay
ix = Bx
-- print(ix, iy)
-- Am = inf and Bm = 0
elseif (Am == math.huge or Am == -math.huge) and Bm == 0 then
print("Scenario : Am = inf and Bm = 0")
iy = By
ix = Ax
-- print(ix, iy)
-- Am = 0 and Bm = valid
elseif Am == 0 and (Bm ~= math.huge or Bm ~= -math.huge) then
print("Scenario : Am = 0 and Bm = valid")
iy = Ay
ix = (iy - Bb) / Bm
-- print(ix, iy)
-- Am = valid and Bm = 0
elseif (Am ~= math.huge or Am ~= -math.huge) and Bm == 0 then
print("Scenario : Am = valid and Bm = 0")
iy = By
ix = (iy - Ab) / Am
-- print(ix, iy)
-- Am = inf and Bm = valid
elseif (Am == math.huge or Am == -math.huge) and (Bm ~= math.huge or Bm ~= -math.huge) then
print("Scenario : Am = inf and Bm = valid")
ix = Ax
iy = (Bm * ix) + Bb
-- print(ix, iy)
-- Am = valid and Bm = inf
elseif (Am ~= math.huge or Am ~= -math.huge) and (Bm == math.huge or Bm == -math.huge) then
print("Scenario : Am = valid and Bm = inf")
ix = Bx
iy = (Am * ix) + Ab
-- print(ix, iy)
-- Am = valid and Bm = valid
elseif (Am ~= math.huge or Am ~= -math.huge) and (Bm ~= math.huge or Bm ~= -math.huge) then
print("Scenario : Am = valid and Bm = valid")
print("Am = ", Am)
print("Bm = ", Bm)
ix = -( (Bb - Ab)/(Bm - Am))
iy = (Bm * ix) + Bb
-- print(ix, iy)
else
print("WHAT IS THIS I CANT EVEN!")
print("UNHANDLED SCENARIO ALERT!!!")
end
-- Now that we have the Intersection points ix and iy, we need to check if they fall on the lines
-- for lines, the intersection points just being +ve is sufficient
-- for the intersection point to be valid, it should fall on both the lines
-- for the intersection point to be valid, it should fall on both the lines
if (
(minAx <= ix and ix <= maxAx)
and (minBx <= ix and ix <= maxBx)
and (minAy <= iy and iy <= maxAy)
and (minBy <= iy and iy <= maxBy)
) then
print("Found Intersection!")
return {intersection = true, colinear = false, ix = round(ix), iy = round(iy)}
else
-- print("Found NO Intersection!")
return {intersection = false}
end
end
end
Re: Dealing with floats
A quick update,
I went with
instead.
This allows me to tackle numbers like 196 vs 196.3 which wasn't possible with a fixed delta value.
I went with
Code: Select all
function equals( x, y, delta )
local comp = 0
if x >=1 and y >=1 then
delta = 0.01
elseif x >=10 and y >=10 then
delta = 0.1
elseif x>100 and y >= 100 then
delta = 1
end
return math.abs( x - y ) <= ( delta or 0.001 )
end
This allows me to tackle numbers like 196 vs 196.3 which wasn't possible with a fixed delta value.
Re: Dealing with floats
Yeah, the error is going to be in the mantissa. You can use math.frexp to separate the mantissa and compare it. It will save you the range comparisons.
Code: Select all
local mx = math.frexp(x)
local my = math.frexp(y)
return math.abs(mx - my) < 0.01
Re: Dealing with floats
One quick note: in your "equals" code, you never use the comp variable. Just a minor nitpick.
GitHub | MLib - Math and shape intersections library | Walt - Animation library | Brady - Camera library with parallax scrolling | Vim-love-docs - Help files and syntax coloring for Vim
Who is online
Users browsing this forum: No registered users and 8 guests