Dealing with floats

General discussion about LÖVE, Lua, game development, puns, and unicorns.
User avatar
Rishavs
Party member
Posts: 103
Joined: Sat Oct 17, 2009 5:29 am
Contact:

Dealing with floats

Post by Rishavs »

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. :cry:

PS. how do you guys handle float comparisons in your code? any suggestions?
User avatar
Linkpy
Party member
Posts: 102
Joined: Fri Aug 29, 2014 6:05 pm
Location: France
Contact:

Re: Dealing with floats

Post by Linkpy »

I don't understand why your numbers change so much. But, for a "perfect" float comparison, see the C# rules : use a delta :

Code: Select all

if math.abs(A - B) < 0.001 then ... end
You can also do this in a different way (if you have many float comparison, it can be helpful) :

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
User avatar
Rishavs
Party member
Posts: 103
Joined: Sat Oct 17, 2009 5:29 am
Contact:

Re: Dealing with floats

Post by Rishavs »

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.
User avatar
deströyer
Prole
Posts: 32
Joined: Thu Jun 27, 2013 7:59 pm
Contact:

Re: Dealing with floats

Post by deströyer »

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.

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
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.
User avatar
ivan
Party member
Posts: 1915
Joined: Fri Mar 07, 2008 1:39 pm
Contact:

Re: Dealing with floats

Post by ivan »

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
davisdude
Party member
Posts: 1154
Joined: Sun Apr 28, 2013 3:29 am
Location: North Carolina

Re: Dealing with floats

Post by davisdude »

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
User avatar
Rishavs
Party member
Posts: 103
Joined: Sat Oct 17, 2009 5:29 am
Contact:

Re: Dealing with floats

Post by Rishavs »

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. :D

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

User avatar
Rishavs
Party member
Posts: 103
Joined: Sat Oct 17, 2009 5:29 am
Contact:

Re: Dealing with floats

Post by Rishavs »

A quick update,

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
instead.

This allows me to tackle numbers like 196 vs 196.3 which wasn't possible with a fixed delta value.
User avatar
pgimeno
Party member
Posts: 3641
Joined: Sun Oct 18, 2015 2:58 pm

Re: Dealing with floats

Post by pgimeno »

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
davisdude
Party member
Posts: 1154
Joined: Sun Apr 28, 2013 3:29 am
Location: North Carolina

Re: Dealing with floats

Post by davisdude »

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
Post Reply

Who is online

Users browsing this forum: YaCy [Bot] and 2 guests