关于C#:为什么fmax(a,b)返回较小的(负)零,以及如何彻底解决它?

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

1
2
3
4
5
6
7
8
#include <stdio.h>
#include <math.h>

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

我得到以下结果:

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

fmax手册页中没有记录此(错误)行为。 有合理的解释吗? 并且有一个干净(简洁)的解决方法吗? 另外,如果两个都是-0.0,我想将-0.0作为最大值。


"问题"是a == b。该符号无关紧要,因为尾数(放在一边的符号)纯粹是0。我得到0x80000000 vs 0

因此,fmax只是检查a < bb < a(取决于实现),并且两者均为假,因此答案都是潜在的匹配。

在我的gcc版本中,我在0.0处获得了fmax(0.0,-0.0),但是fmax(-0.0,0.0)-0.0

我尝试一个完整的解决方法,在结果为0的情况下,使用memcmp比较二进制数据。

如建议的更好,使用signbit来测试number是否设置了负位(与值无关):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
#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
"
, a,b, my_fmax(a, b));
    a = 0.0;
    printf("fmax(%f, %f) = %f
"
, a,b, my_fmax(a, b));
    a = b = -0.0;
    printf("fmax(%f, %f) = %f
"
, a,b, my_fmax(a, b));
    a = 1.0;
    printf("fmax(%f, %f) = %f
"
, a,b, my_fmax(a, b));
    a = -1.0;
    printf("fmax(%f, %f) = %f
"
, a,b, my_fmax(a, b));
    b = 0.0;
    printf("fmax(%f, %f) = %f
"
, a,b, my_fmax(a, b));
}

结果(我认为我涵盖了所有情况):

1
2
3
4
5
6
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


来自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.

因此,我认为由您来专门处理返回-0.0的情况。


我担心这是生活。 IEEE754允许返回-0.0或+0.0。

(请原谅我假设您的实现使用该方案作为浮点。)

如果两个值相等,通常会返回第一个参数。这可以构成解决方法的基础,但是严格来说并不是可移植的。

您可以使用C99函数的符号位来区分负数和正数零。


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

fmax()比较值。 +0.0和-0.0具有相同的值。返回ab符合fmax()规范。规范脚注专门针对此问题:

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?

使用signbit()区分+0.0-0.0。其他区分+/- 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

除了带符号的零外,许多浮点实现还允许使用非正规数或非数字(NaN)。在这种情况下,通常的首选操作是返回"正常"数字(如果有)。

对于> < >= <=,如果至少一个操作数为NaN,则结果为false。
a > ba <= b相反。两者都可能是错误的。

将此与OP的零比较目标+0.0击败-0.0进行比较:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#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;
}

或者也许只是检测特殊情况,否则使用fmaxf()-类似于@ Jean-Fran?ois Fabre。注意:使用fmaxf()表示最大float

1
2
3
4
5
6
float fmaxf_sz(float a,float b){
  if(a==0.0 && b==0.0) {
    return signbit(a) ? b : a;
  }
  return fmaxf(a,b);
}

这是fmaxf()的版本,在打破平局的情况下着眼于signbit()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
 #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
"
);
        return 1;
    }
    printf("
"
);
    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
"
,errors);
    }
    return 0;
}

注意1:还请注意,如果使用float,最佳做法是将f放在后缀上,否则它们将被解释为double

我将保留fminfs和类型通用宏作为练习。