Reputation: 73235
#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
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
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 examplefmax(−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
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
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
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