comparisons to floating point variables

Development directions, tasks, and features being actively implemented or pursued by the development team.
Post Reply
safemode
Developer
Developer
Posts: 2150
Joined: Mon Apr 23, 2007 1:17 am
Location: Pennsylvania
Contact:

comparisons to floating point variables

Post by safemode »

This deals with double and float types.

In many places we do comparisons with floats/doubles directly. This is a problem because there is always some amount of rounding error associated with these datatypes, that's why we have an epsilon or required precision macro (or should have one).

for instance in unit_collide.cpp line around 440

>if (c<0||a==0)

c and a are doubles. The problem is the a == 0. I dont believe we can ever guarantee that a doesn't equal 0.0000001 when it should be 0 on some machines. We should send any direct comparisons through a macro that basically has a set precision value and returns if we match within that precision or not.

That's a pretty standard way to compare doubles and floats, yet we dont seem to do it.
Ed Sweetman endorses this message.
ace123
Lead Network Developer
Lead Network Developer
Posts: 2560
Joined: Sun Jan 12, 2003 9:13 am
Location: Palo Alto CA
Contact:

Post by ace123 »

I believe the ==0 and <0 are checking specifically whether a divide by zero will return a NaN or if it is taking a sqrt of a positive number.
safemode
Developer
Developer
Posts: 2150
Joined: Mon Apr 23, 2007 1:17 am
Location: Pennsylvania
Contact:

Post by safemode »

perhaps, but anytime you try to see if a double or float is equal to a number, you have to work in your accepted precision, because anything except very specific numbers gets rounded with some degree of error. So checking for 0 to indicate something with a float after some operation, wont necessarily work all the time.

I'm just saying, i haven't seen any type of floating point macro that handles such a comparison and we do make them in other places too.
Ed Sweetman endorses this message.
klauss
Elite
Elite
Posts: 7243
Joined: Mon Apr 18, 2005 2:40 pm
Location: LS87, Buenos Aires, República Argentina

Post by klauss »

Safemode: don't ever try macros. They would bloat generated code and make it (even) slower. Rather, if you want to compare floats against doubles, make sure you compare using target precision.

Ie: if you're going to divide by b like this:

Code: Select all

float a;
double b;
float x=a/b;
You want to make sure that ((float)b) != 0.

That's quite faster and safer and... more readable.
Oíd mortales, el grito sagrado...
Call me "Menes, lord of Cats"
Wing Commander Universe
safemode
Developer
Developer
Posts: 2150
Joined: Mon Apr 23, 2007 1:17 am
Location: Pennsylvania
Contact:

Post by safemode »

that works for literals, but what about instances where we compare against another float/double?

we'd need a function that we can say a > target - precision && a < target + precision
Ed Sweetman endorses this message.
klauss
Elite
Elite
Posts: 7243
Joined: Mon Apr 18, 2005 2:40 pm
Location: LS87, Buenos Aires, República Argentina

Post by klauss »

Why wouldn't (float)a == (float)b work?
If you really need a specific precision that's not float's epsilon (FLT_EPSILON), then I guess "fabsf(a-b) < precision" would work, but just in that very specific case.

Anyway, if you want macros, try:

Code: Select all

#define FCMP(a,b,op) ((float)(a) op (float)(b))
#define FEQ(a,b,precision) (fabsf((a)-(b)) < (precision))
#define FEQ(a,b) FEQ(a,b,==)
#define FNZERO(a) FCMP((a),0,!=)
BTW: I never tried parameterizing operators... does it work? ;)
Oíd mortales, el grito sagrado...
Call me "Menes, lord of Cats"
Wing Commander Universe
ilm
Insys Pilot
Insys Pilot
Posts: 2
Joined: Tue Jul 10, 2007 5:10 pm

Post by ilm »

safemode wrote:that works for literals, but what about instances where we compare against another float/double?

we'd need a function that we can say a > target - precision && a < target + precision
can't you write it like:
abs(a-target) < epsilon
and have epsilon a constant (FLT_EPSILON)

Edit: right that's in the macro's in the previous post. But is too hard to just use that directly instead of macro's ?
jackS
Minister of Information
Minister of Information
Posts: 1895
Joined: Fri Jan 31, 2003 9:40 pm
Location: The land of tenure (and diaper changes)

Post by jackS »

To be more precise, basing a decision upon direct equality testing between two floats/doubles isn't really the issue. The problem is basing a decision upon direct equality testing of the results of two different sets of operations upon floats/doubles without using a precision bound. Comparing non-mutable quantities will alway work just fine, so constants and externally defined values aren't too worrisome, but order-of-operations influences rounding decisions, so values generated upon two different paths may not be exactly the same, even in systems that perform rounding in a fashion actually compliant with the IEEE754 floating point specifications.

However, I would be very surprised if there were frequent places in the code where such equality checks were occurring, excepting checks against 0 (which are probably all fine, because the whole point of those tends to be to catch the exceptional cases where the value actually is 0 and then do something different).

As for greater-than and less-than comparisons, given the underlying looseness and non-mission-critical (in the traditional "lives depend on your software" sense) nature of the modeling we're doing (our time slices are fairly large), I am doubtful that any measurable impact on object level simulation accuracy would be gained from adding operations to check against fudge factors.
safemode
Developer
Developer
Posts: 2150
Joined: Mon Apr 23, 2007 1:17 am
Location: Pennsylvania
Contact:

Post by safemode »

while debugging VS for floating and double inconsistencies, i've come across many places where we use QVector's, which are internally double, with floats. Both taking the values of QVector's data members, or setting them.

I'm not sure how that affects gameplay, but anything that i come across that takes a value from QVector, i'm making a double. I assume if you're reading a double, you are storing to a double. Otherwise you would use regular Vector's, not QVector's.


btw, our Vector class is retarded. Why wouldn't we just make it a templated Vector class? That makes magnitudes more sense than the preprocessor garbage we currently do with it.
Ed Sweetman endorses this message.
chuck_starchaser
Elite
Elite
Posts: 8014
Joined: Fri Sep 05, 2003 4:03 am
Location: Montreal
Contact:

Post by chuck_starchaser »

jackS wrote:... excepting checks against 0 (which are probably all fine, because the whole point of those tends to be to catch the exceptional cases where the value actually is 0 and then do something different).
Even if doing so were 100% safe, floating type equality comparisons are highly deprecated, and for good reasons. First of all is the principle of code being self-documenting.

There was a time when the fashion was to write copious amounts of comments. But then some gurus began to cast doubt on comments: They argued, quite rightly, that comments tend to lose syncronization with the code which the comment on, as the code is updated; --since keeping comments up to date relies on programmer discipline. So, they advocated, instead, to make sure that, as much as possible, the intentions in code be made obvious from the code itself. And this is today's wisdom.

Equality comparisons between floats is one way to trample this self-documentation aspect. A programmer looking at "if( foo == bar ) ..." has a right --and even a responsibility-- to assume that a and b are anything BUT floating point types. Specially when, as I've seen in many cases in vs code, the rhs of the comparison doesn't even look like an fp, such as,

Code: Select all

if( fuel == 0 ) ...
... where the only way to know that fuel is a fp type is to look up its declaration.

So, a major improvement would be to change such statements to read

Code: Select all

if( fuel == 0.0f ) ...
or

Code: Select all

if( fuel == 0.0d ) ...
so that fuel's type is obvious.

But even so, the obvious meaning of such a conditional would be "if the gas tank is empty ..."; but what I've seen in vs code is that often such obvious meanings are NOT the real meanings; but, say, it might mean "if this type of unit doesn't use fuel becaus it's not self-propelled ...".

This is a totally imaginary example because I don't remember what the actual instances were, but I do remember encountering unintuitive true/false conditions being encoded as "special values" of fp variables.

One good book, Code Complete --I forget the author's name now-- dedicate a whole sub-chapter to advocating the erradication of this malpractice of encoding special conditions as special values of variables; --and any variables, not just fp ones; as doing so makes code many times harder to understand.

When a variable must be accompained by special conditions, such as fuel not being warranted, the right thing to do is create a struct, like

Code: Select all

struct fuel
{
   bool is_warranted;
   float litres;
};
Post Reply