Double, Double, Trouble

By Eric

While procrastinating (avoiding writing this blog entry for as long as possible), I debugged an interesting problem.  This gives me something to talk about here.  What follows might be simple or obvious, but I find that considering tiny details very carefully is a good way to improve the quality of the code I write.  Consider the following code snippets
 
sphere* make_sphere( double radius, double x, double y, double z);

void do_something( /* ... */ )
{

    // ...

    sphere* my_sphere = make_sphere(10,0,0,1);
}

and

class position
{
    // ...
public:
    position( double x, double y, double z);
    //...
};

sphere* make_sphere( position const& p, double radius);

void do_something( /* ... */ )
{

    // ...
    position center( 0,0,1);
    sphere* my_sphere = make_sphere();
}
With the second version of the code, you actually need to have a class structure defining your objects (which requires more code), but strong type checking can help you.  There is also an annoyance with the second version of the code that you may have to write code converting between various types of geometric operators.  This (having well thought out basic types for mathematics) is one area where CGM does particularly well.
 
The actual bug I looked at was closely related (class names changed to protect the guilty).
class nifty_curve_calculator
{
// ...
public:
    nifty_curve_calculator( double convergence_epsilon, double fitol_desired, ...);
    //..
};
In nifty_curve_calculator, exact points on a curve are calculated to convergence_epsilon.  The nifty_curve_calculator then concatenates a bunch of exact points on the curve into a bspline fit for the curve.  The fitol is the requested distance of the bspline from the exact curve being calculated.  The two tolerances mean completely different things, but the compiler will happily accept code which switches the two tolerances.  In the case I looked at today, the two parameters were swapped which resulted in code that worked most of the time, but caused a hang with more poorly behaved geometry.  We should expect that convergence_epsilon is a lot smaller (10^3 times smaller or more) than the fitol_desired.
 
There is a whole constellation of bugs like this that can be avoided by making a careful object model.  A simple way to improve type checkability is to avoid argument lists where convertable types are right next to each other.  Avoiding void* arguments like the plague also fits into this line of design improvement.  An additional help is to only require arguments in a constructor which are absolutely mandatory and use get/set methods to control the other parameters.  
 
One area where I run into problems with this is writing code (e.g., for MESH_MANAGERS) where large objects are stored using arrays of indices into other arrays.  If everything has type int (or size_t if that is how you roll), then compiler type checking doesn't help much.  Pointers are slightly better for this, but then you get into ownership issues.  I really wish you could do typedefs that aren't convertable to each other but have the same operations as integers.
 
Does you have any suggestions or comments for improving type checking in geometric code?

Thanks for the input, we will

Thanks for the input, we will definitely take it into consideration.

I was curious if you ever

I was curious if you ever thought of changing the
structure of your site? Its very well written; I love
what youve got to say. But maybe you could a little more in the way of content so people could
connect with it better. Youve got an awful lot of text for only having one or two pictures.
Maybe you could space it out better?

A couple of added ramblings:

A couple of added ramblings: I made the constructor explicit so you cannot accidentally convert a raw type into strong type. I also ended up making the data member private and added get/set methods for it. The whole point of the added type safety is to prevent me from accidentally converting things that aren't the same.

I should mention I have already used this in some prototype code this week and it seems to work really well.

thanks again. It's a great idea.

Eric

Thanks. That is a really good

Thanks. That is a really good idea!

As I mentioned in the post, this comes up in meshes a lot, where you have a big array of vertices, and another big array of triangles (where a triangle is a block of three consecutive vertex indices). Code often needs to refer to both triangle and vertex indices in a relatively small space. Something like this trick allows type checking so you can have lots of kinds of "int"s if you need them.

Examining the code, it looks like (1) there is no run time penalty for doing this as long as you have a decent compiler, and (2) you could use "int"s rather than the enums for the value types if you don't know before hand how many different types of integers you will need. Maybe some sort of template/macro scheme could be used for incrementing valueType field.

best regards

Eric Zenk

Just an idea how to get type

Just an idea how to get type checking:

enum valueType{NUMERATOR,DENOMINATOR};

template<valueType NAME,class T>
struct strongTypedValue
{
strongTypedValue(T aValue) {mValue = aValue;};
operator T(){return mValue;}
T mValue;
};

typedef strongTypedValue<NUMERATOR,double> numerator;
typedef strongTypedValue<DENOMINATOR,double> denominator;

double Divide(numerator aNumerator,denominator aDenominator)
{
double lResult = aNumerator / aDenominator;
return lResult;
}

numerator lNumerator(12.) ;
denominator lDenominator(34.) ;

double lResult = Divide(lNumerator,lDenominator); // compiles
lResult = Divide(lDenominator,lNumerator); // won't compile

Maybe you like it...

Post new comment

The content of this field is kept private and will not be shown publicly.
  • Allowed HTML tags: <a> <em> <strong> <cite> <code> <ul> <ol> <li> <dl> <dt> <dd>
  • Lines and paragraphs break automatically.
  • Images can be added to this post.

More information about formatting options

Twitter Facebook LinkedIn YouTube RSS