Rectangle class width/height problems because of floating point errors

Hi,

I'm trying to write a game and for that I created a class to represent a rectangle for bounding boxes and similiar things.
I already had tons of problems with floating point numbers and more problems just keep on popping up...
This time it's the following:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
void RectEx::MoveBy(float x, float y)
{
	float oldWidth = right - left;
	float oldHeight = bottom - top;

	left += x;
	top += y; // 0.0 + 92.0000153 = 92.0000153
	right += x;
	bottom += y; // 240.0 + 92.0000153 = 332.0    HERE is the problem...

	// This should not be different, since moving the rectangle should not change its size...
	// But because of floating point inaccuracy it does...
	float newWidth = right - left;
	float newHeight = bottom - top;

	if(newWidth != oldWidth || newHeight != oldHeight)
	{
		bool headache = true;
	}
}


After it gets rounded down it's a whole pixel difference and because of that I get flickering and collision bugs.

Any ideas how I can fix this? I don't know...
How can I store a rectangle with floating point numbers so that the width and height always stay constant when changing the coordinates?
Is it even possible or do I need to make it super inconvenient with coordinates as int and "overflow" as floats or something like that?
Someone else may have a better solution, but from the numbers you've given it doesn't look like you really need floating point precision. Is there any reason you couldn't just use ints? If you need precision to two decimal places, you could multiple all your numbers by 100, store them as ints, and only convert them back to floats by dividing by 100 if you need to.
The reason I use floating points for the position is because I want to be able to move it maybe 0.17 pixels per update, so after 6 updates it moved 1 pixel. It should be able to move "between" pixels, so that if its x position is 13.84f, it means that its currently at pixel 13, and 84% done to moving to pixel 14. If it were stored as ints then I would somehow keep track of how many updates are left before it should be moved by 1 pixel. And that would be pretty annoying, when I want to be able to just write "rect.MoveBy(0.01f, 0.0f)" instead of specifying how many frames it should take to move it by 1.
To keep everything smooth and to make vector math work, position, velocity etc need to be floats.
The numbers in my example are when the player starts moving down because of gravity.
After one frame its at 0.0000153 then gets bigger and bigger to make the character descend faster and faster. But sometimes the numbers don't add up correctly and the resulting height changes.
You could use doubles to get more digits of precision. 0.0000153 is at the limit of float precision. That's why when you add a larger number the farthest right digit gets cut off.
It looks like you are mixing integer and floating-point numbers. You could store the important values (x and y coordinates) as a floating-point type, and only convert (with appropriate rounding) to integer prior to drawing the image. Height and width would be integers.
Last edited on
Why don't you just store your rectangle as a point with width and height rather than 2 points?

1
2
3
4
5
6
7
8
9
10
class Rect
{
    Vec2 position;
    float width, height; // won't change when you do a move now...

    Vec2 TopLeft() const { return position; }
    Vec2 BotRight() const { return position + Vec2(width, height); } 


};
Last edited on
I just rewrote my code to use width and height instead of right and bottom.
It seems to work now, the only downside is that I can't use rect.right anymore and had to write accessor methods for everything and replace every (Rect).right with (Rect).GetRight() and so on.
So annoying... :(
Other parts where I had rect.right += x didn't work anymore so I had to rewrite that also.
So for now what I do is, I convert MyRect into a RECT (which is just a struct with left, top, right, bottom as ints) using conversion operator, increase right by a certain amount and then convert it back into a MyRect using a constructor of MyRect.
Looks really ugly and is probably redunant, should probably write a fews methods for increasing a certain side only like ModifyRightSideBy(float x) but right now for testing this should be enough.
Wished there was a more elegant solution but at least it works now (or so it seems).

So for now what I do is, I convert MyRect into a RECT (which is just a struct with left, top, right, bottom as ints) using conversion operator, increase right by a certain amount and then convert it back into a MyRect using a constructor of MyRect.


rect.width += x;

There's an equivalent way to do the same... That is assuming you just wanted to extend the width.
In that case, yes, another bad example from me :)
rect.left += x;
That would also move the "right side" (which doesn't exist anymore because it gets calculated from left + width) which it shouldn't.
rect.left += x;
rect.width -= x;
Should be correct, but less intuitive and double the amount of lines! ;)
Should be correct, but less intuitive and double the amount of lines! ;)

That doesn't really matter, that extra line causes no more performance loss and are you really going to argue about being intuitive about a piece of code that's as simplistic as that?

If you want to argue that, translating the rectangle takes 2 extra lanes since you aren't using a some sort of Vector2 class, which would actually be intuitive for you to use.
I just rewrote my code to use width and height instead of right and bottom.
It seems to work now, the only downside is that I can't use rect.right anymore and had to write accessor methods for everything and replace every (Rect).right with (Rect).GetRight() and so on.
So annoying... :(

At risk of sounding glib, I'd say you should treat this as a useful lesson in:

a) Why it's important to encapsulate the implementation of your class, behind an API

b) The important difference between designing a sensible API, vs just writing a load of accessor functions for your data members.
Last edited on
Topic archived. No new replies allowed.