4

Normally, when we want to test fractional numbers for equality, we do so with some amount of uncertainty, because of approximate nature of IEEE754.

if (fabs(one_float - other_float) < SUFFICIENTLY_SMALL) {
    consider them equal;
}

Other approach might be to cast the floats to integers of specific magnitude, and compare resulting integers instead.

if ((uint)one_float == (uint)other_float) {
    consider them equal;
}

But consider the situation where our floats never undergo any arithmetics, and the only thing we do with them is assignment.

//  Might be called at any moment
void resize(float new_width, float new_height)
{
    if (current_width == new_width && current_height == new_height) {
        return;
    }

    accomodate framebuffers;
    update uniforms;
    do stuff;

    current_width = new_width;
    current_height = new_height;
}

In the example above, we have an event handler that may fire up spontaneously, and we want to reallocate resources only if real resize occurred. We might take the usual path with approximate comparison, but that seems like a waste, because that handler will fire up pretty often. As far as I know, floats are assigned just as everything else, using memory move; and equality operator performs just the memory comparison. So it looks like we are in a safe harbour. When checked on x86 and ARM, this assumption holds, but I just wanted to be sure. Maybe it's backed by some specs?

So, is there something that might change floats when only assigning them? Is the described approach viable?

Pop Eye
  • 43
  • 5
  • 1
    Yes, *at the moment you assign them*. The value you specify may not be accurately represented, so the value has already changed. – Weather Vane Apr 03 '16 at 06:39
  • It might be OK on some machines but it is still risky. It may work differently on different machines. – Hun Apr 03 '16 at 06:39
  • 1
    The new sizes comes from somewhere, how do you know there is no small error in representartion? *Never check floating point for equality*. – Weather Vane Apr 03 '16 at 06:59
  • 1
    "As far as I know, floats are assigned just as everything else, using memory move; and equality operator performs just the memory comparison". This is not true for NaN values. [Merely loading](https://stackoverflow.com/questions/25050133/are-the-bit-patterns-of-nans-really-hardware-dependent#comment38981088_25051746) certain NaN values into FP registers can cause them to change into different NaNs. And NaNs compare as inequal to themselves. There is also special handling for zero, as positive zero compares as equal to negative zero, although they have different bit patterns. – Boann Apr 03 '16 at 07:19
  • 1
    There is a typo in your second equality operator in the if clause; should be "==". – ssd Apr 03 '16 at 07:55
  • @Boann, I did not know that loading could affect actual bits. Is that true for any float value or only for NaNs? – Pop Eye Apr 03 '16 at 09:27
  • "equality operator performs just the memory comparison" must have overlooked this. My Clang still generates `ucomiss`. Perhaps I should correct the wording. – Pop Eye Apr 03 '16 at 10:12
  • 1
    @PopEye I'm fairly sure it can only happen for NaNs. – Boann Apr 03 '16 at 14:38

3 Answers3

2

A mass of "should"s follows.

I don't think there's anything that says an assignment from float to float (current_width = new_width) couldn't alter the value, but I would be surprised if such a thing existed. There should be no reason to make assignment between variables of the same type anything else than a direct copy.

If the incoming new_width and new_height keep their values until they change, then this comparison should not have any issues. But if they are calculated before every call they might change their value, depending on how the calculation is done. So it's not only this function that needs to be checked.

The C 2011 standard says that calculations may use bigger precision than the format you assign to, but nothing specific about assigning a variable to another. So the only imprecision should be in the calculation stage. The "simple assignment" part (6.5.16.1) says:

In simple assignment (=), the value of the right operand is converted to the type of the assignment expression and replaces the value stored in the object designated by the left operand.

So if the types already match, there should be no need for conversion.

So, simply put: if you don't recalculate the incoming value on every call the comparison for equality should hold true. But is there really a case where your framebuffers are sized as floats and not integers?

Sami Kuhmonen
  • 30,146
  • 9
  • 61
  • 74
  • Good points. So, if I understand correctly, it is only safe if we trust the upstream framework to supply us bitwise equivalents each time. We also have to be sure that we do not load our values to fp registers for comparison. As for me, that pretty much build up to use the usual comparison method. Sigh. – Pop Eye Apr 03 '16 at 08:54
  • Regarding float framebuffers. Yes, they are int-sized, yet those floats are supplied by the environment in the event object. – Pop Eye Apr 03 '16 at 09:06
1

In the case you mentioned it is safe to use == to compare because of the following lines at the end of the function:

current_width = new_width;
current_height = new_height;

any change to new_width or new_height will fail the if statement and you get the behavior you wanted.

abs() function usually is used when there is one variable float which is assigned dynamically in the program and one constant float which you want to use as a reference. Something like:

bool isEqual(float first, float second)
{
   if(fabs(first-second)<0.0001)
      return true;
   return false;
}

int main()
{
   float x = (float) 1 / 3;
   if(isEqual(x,0.3333))
      printf("It is equal\n");
   else
      printf("It is not equal\n");
}
Pooya
  • 6,083
  • 3
  • 23
  • 43
  • 1
    Please bear in mind that `0.3333` is `double` not `float`, so there is another conversion happening which will result in more loss of precision. – Weather Vane Apr 03 '16 at 07:06
  • @WeatherVane that is correct and that's why abs function is needed to check that, with a tweak on the precision to reach the desired functionality – Pooya Apr 03 '16 at 07:10
  • No, that is not why `abs` is needed. In any case it should be `fabs`. – Weather Vane Apr 03 '16 at 07:14
  • @WeatherVane, I used `abs` because it is a pseudocode. Fixed anyway not to confuse anyone. – Pop Eye Apr 03 '16 at 09:02
-1

Have a look here for how to compare two floats.

https://randomascii.wordpress.com/2012/02/25/comparing-floating-point-numbers-2012-edition/

Find the test with "ULP, he said nervously". You can compare the floats as ints but have a look at how it's been done in the article ie (I hope I'm not breaching any licensing here, if so please delete the code that follows it's not mine)

bool AlmostEqualUlps(float A, float B, int maxUlpsDiff){
    Float_t uA(A);
    Float_t uB(B);
    // Different signs means they do not match.
    if (uA.Negative() != uB.Negative()) {
        // Check for equality to make sure +0==-0
        if (A == B) {
            return true;
        }
        return false;
    }

    // Find the difference in ULPs.
    int ulpsDiff = abs(uA.i - uB.i);
    if (ulpsDiff <= maxUlpsDiff) {
        return true;
    }
    return false;
}

Please read the rest of that article, it's really good.

Harry
  • 11,298
  • 1
  • 29
  • 43
  • 1
    "Different signs means they do not match" is untrue. They may both be very close to, but not exactly 0, with different sign. Checking the absolute difference works here: there is no special case. – Weather Vane Apr 03 '16 at 06:53