【问题标题】:Why does fmax(a, b) return the smaller (negative) zero and how to cleanly workaround it?为什么 fmax(a, b) 返回较小的(负)零以及如何干净地解决它?
【发布时间】:2017-04-05 20:25:35
【问题描述】:
#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));
}

我得到以下结果:

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

fmax 手册页中没有记录这种(错误)行为。有没有合理的解释?是否有一个干净(简洁)的解决方法?另外,如果两者都是-0.0,我希望得到-0.0作为最大值。

【问题讨论】:

  • 无法使用 gcc 4.9.4 进行复制。我得到 0.000000。您是否尝试过交换 ab 值?交换值给了我-0.0000000 BTW。
  • 在 gcc 5.4.0 上也没有复制。
  • +0 和 -0 在 IEEE-754 中比较相等,所以这个实现可能只是选择一个任意参数作为其输出。
  • 我使用的是 gcc 5.4.0(64 位)。
  • MSVC 报告0.000000 无论如何。

标签: c math floating-point


【解决方案1】:

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

所以fmax 只是检查a &lt; bb &lt; a(取决于实现),两者都是假的,所以任何一个答案都是潜在的匹配。

在我的 gcc 版本中,我在 0.0 得到 fmax(0.0,-0.0),但 fmax(-0.0,0.0)-0.0

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

按照建议更好,使用signbit 测试数字是否设置了负位(无论值如何):

#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));
}

结果(我想我涵盖了所有案例):

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

【讨论】:

  • 这当然是我更喜欢的方式:如果参数相等,则返回第一个参数的约定。很好的解释。
  • 这适用于我发布的特定案例,但如果它们都是 -0.0,我也希望返回 -0.0。
  • 好用c标准库函数signbit来区分。从 C99 开始有效。
  • 使用@Bathsheba 的建议进行简化,我会将其标记为正确。
  • @Bathsheba 完成了,signbit 更简单,你说得对。并感谢您的支持。我尽量不让支持者失望:)
【解决方案2】:

来自fmax cppreference

这个函数不需要对零的符号敏感, 尽管某些实现另外强制执行,如果一个 参数为+0,另一个为-0,则返回+0。

所以我想你应该专门处理返回-0.0的情况。

【讨论】:

    【解决方案3】:

    这是我害怕的生活。 IEEE754 允许返回 -0.0 或 +0.0。

    (请原谅我假设您的实现使用该方案进行浮点运算。)

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

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

    【讨论】:

      【解决方案4】:

      为什么 fmax(a, b) 返回较小的(负)零

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

      理想情况下,fmax 将对零符号敏感,例如 fmax(−0. 0, +0. 0) 将返回 +0;然而,在软件中实现可能是不切实际的。 C11 #361


      如何彻底解决它?

      使用signbit() 区分+0.0-0.0Other ways to distinguish +/-0.0

      signbit 宏返回非零值当且仅当其参数值的符号为负 C1dr §7.12.3.6 3

      除了有符号零之外,许多浮点实现还允许 de-normalnot-a-numbers (NaN)。在这种情况下,通常首选的操作是返回“正常”数字(如果有)。

      对于&gt; &lt; &gt;= &lt;=,如果至少有一个操作数是NaN,则结果为假。
      a &gt; ba &lt;= b相反。两者都可能是假的。

      将此与 OP 的 +0.0 比 -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;
      }
      

      或者也许只是检测特殊条件,否则使用fmaxf() - 类似于@Jean-François Fabre。注意:使用fmaxf() 最大为float

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

      【讨论】:

        【解决方案5】:

        这是fmaxf() 的一个版本,它在平局的情况下查看signbit()

         #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;
        }
        

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

        我将留下fminfs 和一个类型泛型宏作为练习。

        【讨论】:

        • bnot-a-number 时,此代码返回bfmaxf(1.0, 0.0/0.0) 返回 1.0。也许对 OP 来说不是问题。
        猜你喜欢
        • 2021-03-16
        • 2013-10-07
        • 2023-02-22
        • 2020-01-20
        • 1970-01-01
        • 2012-05-20
        • 1970-01-01
        • 2020-06-14
        • 2011-10-08
        相关资源
        最近更新 更多