Sign safer on this way ?

Started by
9 comments, last by alvaro 8 years, 5 months ago

Hi,

Is it really safer to use :


inline int sign(float a)
{
  return (a > 0.0f) - (a < 0.0f);
}

Than :


inline int sign(float a)
{
  return (a > 0.0f) ? 1 : (a < 0.0f) ? -1 : 0;
}

Thanks

Advertisement
I'm not sure why you think one or the other would be "safer", but the first snippet is less troublesome for the optimizer. It's slightly less explicit, but I would still use it because it's apparent from the function's name what is happening, and it's not difficult to grok either. It's something that can easily be templatized to work with any type, too: http://stackoverflow.com/a/4609795/572743

They are both working correctly, and insofar they are equally "safe". Integral promotion makes sure that the first snippet works (it is undefined what the value of true is, but it is defined that conversion to integer will yield 1), and the second snippet has an explicit ternary operation attached.

The compiler will most likely convert the first snippet into a single floating point comparison against zero, followed by a conditional move on an integer register.

The second snippet is harder, but will likely (assuming a no-suck optimizer) result in the same. It's much more hassle for the optimizer, though.
Define safer..?
The first is equivalent to:
inline int sign(float a)
{
  return (a > 0.0f ? 1 : 0) - (a < 0.0f ? 1 : 0);
}
Venturing off-ttopic, is there a reason to have three results? Often it's simpler to say that zero is positive by convention.
Depending on the platform, it may also be advisable not to mix float and int types:
inline float sign(float a)
{
  return (a >= 0.0f) ? 1.0f : 0.0f;
}

Venturing off-ttopic, is there a reason to have three results? Often it's simpler to say that zero is positive by convention.


As someone with a mathematical background I would find that extremely vexing and force me to curse the author for at least twenty minutes. If you call it like the popular function then let it do what the popular function does. If you want something different, have the courtesy to call it differently.

Talking about safety, wouldn't it make sense to use an epsilon instead of 0.0f, as is done with most floating point math?


const float EPSILON = 0.00001f; // pass in to the function if this needs to be different throughout the program

inline int sign(float a)
{
  return (a > EPSILON) - (a < EPSILON);
}

Unless you explicitely want 0.00000002 to be positive and not 0 in your whole application.

Talking about safety, wouldn't it make sense to use an epsilon instead of 0.0f,

0.00001f is many million trillion trillion times bigger than the smallest positive float - that's a large bracket to round to zero without a specific computer-science or maths reason :D

Different algorithms would likely want different biases (often none at all), so I'd not force such a feature across the board. Let each algorithm decide whether it needs to crush such rounding errors.

As someone with a mathematical background I would find that extremely vexing and force me to curse the author for at least twenty minutes. If you call it like the popular function then let it do what the popular function does. If you want something different, have the courtesy to call it differently.

Hah, as a graphics/engine programmer I just don't want the codegen to be crap. e.g. the sign function in HLSL covers these 3 cases, but should almost never be used as there's usually more optimal algorithms than ones that use that function.
I'll remeber to call my function sign_bit if I ever write that "optimized" version :wink:

I'll remeber to call my function sign_bit if I ever write that "optimized" version wink.png


That would be acceptable and would limit my amount of cursing down to the baseline.


I'll remeber to call my function sign_bit if I ever write that "optimized" version

http://en.cppreference.com/w/cpp/numeric/math/signbit

Talking about safety, wouldn't it make sense to use an epsilon instead of 0.0f, as is done with most floating point math?


const float EPSILON = 0.00001f; // pass in to the function if this needs to be different throughout the program

inline int sign(float a)
{
  return (a > EPSILON) - (a < EPSILON);
}
Unless you explicitely want 0.00000002 to be positive and not 0 in your whole application.


Bad, bad, BAD idea. This kind of thinking was implemented in some of the code I work with and it has proven so problematic that I couldn't help myself and I down-voted your post. This seems to come from a vague understanding of how floating-point numbers work, where some people imagine them as being imprecise or having error, noise or fuzziness. Floating-point numbers have well-defined values and questions like "is this number more than zero" can be answered unambiguously. The chosen epsilon invariably ends up being inadequate in some future situation and it creates all kinds of problems. You don't need epsilons to handle floating-point numbers.

I have a little more sympathy towards Hodgman's point of view. The vast majority of the time, my programs only need to check inequality between two floating-point numbers, in a way where in case of equality either result is acceptable; life is much simpler if you stay away from equality testing.

However, there are enough situations where I want equality to return 0 that I really wouldn't want sign to return something else at 0. The main example that comes to mind is finding the rank of a float x among a list of floats y[]. You can implement this by adding up sign(x-y). Often you will be interested in the rank of one of the elements in y[], and then you expect the sign to return 0 for this rank to behave sensibly.
It seems some people may have forgotten -- or perhaps never knew -- that in floating point math zero has a sign.

You can have both +0.0 and -0.0, and it does make a difference in certain floating point operations. It can trigger the difference between positive or negative infinity, which is about as dramatic a difference as floating point can get.

I agree that the sign of a floating point number is a binary result; either a positive sign or a negative sign, so boolean is a better return type... or an enum with two choices if you prefer.

If you have it, use std::signbit() to do it. If you don't have it and can't get a newer compiler, you'll need some implementation-specific work to properly cast the floating point value and test the high order bit, or other implementation-specific operation.

This topic is closed to new replies.

Advertisement