kanaka
kanaka

Reputation: 73235

Why does fmax(a, b) return the smaller (negative) zero and how to cleanly workaround it?

#include <stdio.h>
#include <math.h>

int main () {
    float a = 0.0, b = -0.0;
    printf("fmax(%f, %f) = %f\n", a, b, fmax(a, b));
}

I get the following result:

gcc f.c -o f -lm
./f
fmax(0.000000, -0.000000) = -0.000000

This (mis)behavior is not documented in the fmax man page. Is there a reasonable explanation for it? And is there a clean (concise) workaround? Also, if both are -0.0, I would like to get -0.0 as the max.

Upvotes: 4

Views: 516

Answers (5)

Persixty
Persixty

Reputation: 8589

Here's a version of fmaxf() that looks at the signbit() in tie-breaking situations.

 #include <stdio.h>
#include <math.h>

float fmaxfs(float a,float b){
    if(a>b){
        return a;
    }
    if(b!=a){
        return b;
    }
    if(signbit(a)==0){
        return a;
    }
    return b;
}

int test(float a,float b,float e){
    float r=fmaxfs(a,b);
    printf("fmaxfs(%f, %f) = %f", a, b, r);
    if(r!=e||signbit(r)!=signbit(e)){
        printf(" ERROR\n");
        return 1;
    }
    printf("\n");
    return 0;
}

int main () {
    int errors=0;
    errors+=test(0.0f,-0.0f,0.0f);
    errors+=test(-0.0f,0.0f,0.0f);
    errors+=test(-0.0f,-0.0f,-0.0f);
    errors+=test(-0.7f,-0.8f,-0.7f);
    errors+=test(987.485f,100.0f,987.485f);
    errors+=test(987.485f,1000000.0f,1000000.0f);
    errors+=test(-987.485f,-100.0f,-100.0f);
    errors+=test(-1.3678f,-19999.6789f,-19999.6789f);

    if(errors>0){
        printf("%d ERRORS\n",errors);
    }
    return 0;
}

NB 1: Also note that if you are using float the best practice is to put f on the suffix or they will be interpreted as double.

I'll leave fminfs and a type generic macro as an exercise.

Upvotes: 0

chux
chux

Reputation: 154601

Why does fmax(a, b) return the smaller (negative) zero

fmax() compares values. +0.0 and -0.0 have the same value. Returning a or b meets the fmax() spec. A spec footnote specifically addressing this:

Ideally, fmax would be sensitive to the sign of zero, for example fmax(−0. 0, +0. 0) would return +0; however, implementation in software might be impractical. C11 #361


how to cleanly workaround it?

Use signbit() to distinguish +0.0 from -0.0. Other ways to distinguish +/-0.0

The signbit macro returns a nonzero value if and only if the sign of its argument value is negative C1dr §7.12.3.6 3

In addition to signed zeros, many floating point implementations allow de-normal or not-a-numbers (NaN). In such cases, the usual preferred action is to return the "normal" number if any.

With > < >= <=, the result is false if at least one of the operands are NaN.
a > b is not the opposite of a <= b. Both could be false.

Combing this with OP's zero compare goal of +0.0 beats -0.0:

#include <math.h>

float fmaxf_sz(float a,float b){
  if(!(a<b)) return b;  // a is known to be less than b, both are normal
  if(!(b<a)) return a;  // b is known to be less than a, both are normal


  if (a == b) {  // a is known to be equal in value to b, both are normal
    return signbit(a) ? b : a;
  }

  // One or both a,b are NaN
  return isfinite(a) ? a : b;
}

Or perhaps simply detect the special condition else use fmaxf() - similar to @Jean-François Fabre. Note: use fmaxf() for maximum float.

float fmaxf_sz(float a,float b){
  if(a==0.0 && b==0.0) {
    return signbit(a) ? b : a;
  }
  return fmaxf(a,b);
}

Upvotes: 1

Jean-Fran&#231;ois Fabre
Jean-Fran&#231;ois Fabre

Reputation: 140316

The "problem" is that a == b. The sign doesn't matter because mantissa (sign put aside) is purely 0. I get 0x80000000 vs 0

So fmax just checks if a < b or b < a (depending on the implementation), and both are false, so either answer is a potential match.

On my gcc version I get fmax(0.0,-0.0) at 0.0, but fmax(-0.0,0.0) is -0.0.

My attempt at a full workaround, using memcmp to compare data binary wise in the case of a 0 result.

Even better as suggested, using signbit which tests if number has negative bit set (regardless of the value):

#include <stdio.h>
#include <math.h>
#include <string.h>

float my_fmax(float a,float b)
{
   float result = fmax(a,b);
   if ((result==0) && (a==b))
   {
       /* equal values and both zero
          the only case of potential wrong selection of the negative 
          value. Only in that case, we tamper with the result of fmax,
          and just return a unless a has negative bit set */

       result = signbit(a) ? b : a;
   }
   return result;
}

int main () {
    float a = -0.0, b = 0.0;

    printf("fmax(%f, %f) = %f\n", a,b, my_fmax(a, b));
    a = 0.0;
    printf("fmax(%f, %f) = %f\n", a,b, my_fmax(a, b));
    a = b = -0.0;
    printf("fmax(%f, %f) = %f\n", a,b, my_fmax(a, b));
    a = 1.0;
    printf("fmax(%f, %f) = %f\n", a,b, my_fmax(a, b));
    a = -1.0;
    printf("fmax(%f, %f) = %f\n", a,b, my_fmax(a, b));
    b = 0.0;
    printf("fmax(%f, %f) = %f\n", a,b, my_fmax(a, b));
}

result (I think I covered all the cases):

fmax(-0.000000, 0.000000) = 0.000000
fmax(0.000000, 0.000000) = 0.000000
fmax(-0.000000, -0.000000) = -0.000000
fmax(1.000000, -0.000000) = 1.000000
fmax(-1.000000, -0.000000) = -0.000000
fmax(-1.000000, 0.000000) = 0.000000

Upvotes: 7

Bathsheba
Bathsheba

Reputation: 234885

It's life I'm afraid. IEEE754 allows either -0.0 or +0.0 to be returned.

(Forgive me for assuming your implementation uses that scheme for floating point.)

More often than not, the first argument will be returned if the two values are equal. That could form the basis of a workaround, but it is not strictly portable.

You can use the C99 function signbit to distinguish a negative from a positive zero.

Upvotes: 2

Stephan Lechner
Stephan Lechner

Reputation: 35164

From fmax cppreference:

This function is not required to be sensitive to the sign of zero, although some implementations additionally enforce that if one argument is +0 and the other is -0, then +0 is returned.

So I suppose it's up to you to specially handle the case that -0.0 is returned.

Upvotes: 3

Related Questions