Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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.

like image 286
kanaka Avatar asked Dec 11 '22 12:12

kanaka


1 Answers

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
like image 124
Jean-François Fabre Avatar answered May 15 '23 20:05

Jean-François Fabre