+0.0和-0.0上的哪些操作和功能会产生不同的算术结果?

时间:2014-08-15 18:34:16

标签: c floating-point

在C中,当支持±0.0时,分配给-0.0的{​​{1}}或+0.0通常不会产生算术差异。虽然它们具有不同的位模式,但它们在算术上比较相同。

double

受到@Pascal Cuoq comment的启发,我正在寻找标准C中的一些功能,这些功能提供了算术上不同的结果。

注意:许多功能(例如double zp = +0.0; double zn = -0.0; printf("0 == memcmp %d\n", 0 == memcmp(&zn, &zp, sizeof zp));// --> 0 == memcmp 0 printf("== %d\n", zn == zp); // --> == 1 )从sin()返回+0.0,从f(+0.0)返回-0.0。但这些并不能提供不同的算术结果。此外,2个结果不应同时为f(-0.0)

3 个答案:

答案 0 :(得分:16)

有一些标准操作和功能在f(+0.0)f(-0.0)之间形成数字上不同的答案。

不同的舍入模式或其他浮点实现可能会产生不同的结果。

#include <math.h>

double inverse(double x) { return 1/x; }

double atan2m1(double y) { return atan2(y, -1.0); }

double sprintf_d(double x) {
  char buf[20];
  // sprintf(buf, "%+f", x);   Changed to e
  sprintf(buf, "%+e", x);
  return buf[0];  // returns `+` or `-`
}

double copysign_1(double x) { return copysign(1.0, x); }

double signbit_d(double x) {
  int sign = signbit(x);  // my compile returns 0 or INT_MIN
  return sign;
}

double pow_m1(double x) { return pow(x, -1.0); }

void zero_test(const char *name, double (*f)(double)) {
  double fzp = (f)(+0.0);
  double fzn = (f)(-0.0);
  int differ = fzp != fzn;
  if (fzp != fzp && fzn != fzn) differ = 0;  // if both NAN
  printf("%-15s  f(+0):%-+15e %s  f(-0):%-+15e\n", 
      name, fzp, differ ? "!=" : "==", fzn);
}

void zero_tests(void) {
  zero_test("1/x",             inverse);
  zero_test("atan2(x,-1)",     atan2m1);
  zero_test("printf(\"%+e\")", sprintf_d);
  zero_test("copysign(x,1)",   copysign_1);
  zero_test("signbit()",       signbit_d);
  zero_test("pow(x,-odd)",     pow_m1);;  // @Pascal Cuoq
  zero_test("tgamma(x)",       tgamma);  // @vinc17 @Pascal Cuoq
}

Output:
1/x              f(+0):+inf             !=  f(-0):-inf           
atan2(x,-1)      f(+0):+3.141593e+00    !=  f(-0):-3.141593e+00  
printf("%+e")    f(+0):+4.300000e+01    !=  f(-0):+4.500000e+01   
copysign(x,1)    f(+0):+1.000000e+00    !=  f(-0):-1.000000e+00  
signbit()        f(+0):+0.000000e+00    !=  f(-0):-2.147484e+09 
pow(x,-odd)      f(+0):+inf             !=  f(-0):-inf           
tgamma(x)        f(+0):+inf             !=  f(-0):+inf  

注意:
我的gcc 4.8.2计算机上显示tgamma(x) ==,其他人correctly !=

rsqrt(),AKA 1/sqrt()可能是未来的C标准函数。可能/可能也不行。

double zero = +0.0; memcpy(&zero, &x, sizeof x)可以显示x+0.0的位模式不同,但x仍然可以是+0.0。我认为某些FP格式有许多位模式+0.0-0.0。 TBD。

这是https://stackoverflow.com/help/self-answer提供的自我回答。

答案 1 :(得分:5)

IEEE 754-2008函数rsqrt(将在未来的ISO C标准中)在±0上返回±∞,这是非常令人惊讶的。并且tgamma也会在±0上返回±∞。对于MPFR,mpfr_digamma在±0上返回±∞的反向。

答案 2 :(得分:1)

我考虑过这种方法,但我不能在周末之前检查,所以有人可能会对此进行一些实验,如果他/她喜欢,或者只是告诉我这是无稽之谈:

  • 生成-0.0f。应该可以通过指定一个下浮动表示的微小负常数来静态生成。

  • 将此常量分配给volatile double并返回float。

    通过改变位表示2次,我假设了 编译器特定的标准位表示为-0.0f现在在 变量。编译器不能在那里智能化,因为完全 其他值可能在这两个副本之间的易变量中。

  • 将输入与0.0f进行比较。检测我们是否有0.0f / -0.0f案例

  • 如果相等,则分配输入volitale double变量,然后返回float。

    我再次假设它现在具有0.0f

  • 的标准编译器表示
  • 通过联合访问位模式并进行比较,以确定它是否为-0.0f

代码可能类似于:

typedef union
{
  float fvalue;
  /* assuming int has at least the same number of bits as float */
  unsigned int bitpat;
} tBitAccess;

float my_signf(float x)
{
  /* assuming double has smaller min and 
     other bit representation than float */

  volatile double refitbits;
  tBitAccess tmp;
  unsigned int pat0, patX;

  if (x < 0.0f) return -1.0f;
  if (x > 0.0f) return 1.0f;

  refitbits = (double) (float) -DBL_MIN;
  tmp.fvalue = (float) refitbits;
  pat0 = tmp.bitpat;

  refitbits = (double) x; 
  tmp.fvalue = (float) refitbits;
  patX = tmp.bitpat;

  return (patX == pat0)? -1.0f : 1.0f;

}
  • 它不是标准函数或运算符,而是应区分-0.0和0.0符号的函数。
  • 它(主要)假设编译器供应商不使用-0.0f的不同位模式作为格式更改的结果,即使浮点格式允许它,如果这样,它是独立的来自所选的位模式。
  • 对于具有-0.0f精确一种模式的浮点格式,此函数应该在不知道该模式中的位排序的情况下安全地执行该操作。
  • 其他假设(关于类型的大小等)可以使用float.h常量上的预编译器开关来处理。

编辑:第二个想法:如果我们可以强制比较(0.0 || -0.0)低于最小可表示的非正规(次正规)浮点数或其负对应的值,并且没有第二个模式为-0.0在FP格式中f(精确),我们可以将转换放到volatile double。 (但也许保持浮动不稳定,以确保使用去激活的非正规数,编译器不能做任何奇特的技巧,忽略操作,这会进一步减少比较等于0.0的事物的绝对值。)

代码可能如下所示:

typedef union
{
  float fvalue;
  /* assuming int has at least the same number of bits as float */
  unsigned int bitpat;
} tBitAccess;

float my_signf(float x)
{

  volatile tBitAccess tmp;
  unsigned int pat0, patX;

  if (x < 0.0f) return -1.0f;
  if (x > 0.0f) return 1.0f;

  tmp.fvalue = -DBL_MIN;

  /* forcing something compares equal to 0.0f below smallest subnormal 
     - not sure if one abs()-factor is enough */
  tmp.fvalue = tmp.fvalue * fabsf(tmp.fvalue);
  pat0 = tmp.bitpat;

  tmp.fvalue = x; 
  tmp.fvalue = tmp.fvalue * fabsf(tmp.fvalue);
  patX = tmp.bitpat;

  return (patX == pat0)? -1.0f : 1.0f;

}

这可能不适用于花式舍入方法,可以防止从负值舍入到-0.0。