C 在+;0.0和-0.0给出不同的算术结果?

C 在+;0.0和-0.0给出不同的算术结果?,c,floating-point,C,Floating Point,在C语言中,当支持±0.0时,分配给双精度的-0.0或+0.0通常不会产生算术差异。虽然它们有不同的位模式,但它们在算术上是相等的 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);

在C语言中,当支持
±0.0
时,分配给
双精度
-0.0
+0.0
通常不会产生算术差异。虽然它们有不同的位模式,但它们在算术上是相等的

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
受a的启发,我正在寻找标准C中提供算术上不同结果的更多函数

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

注意:许多函数,如
sin()
,从
f(+0.0)
返回
+0.0
,从
f(-0.0)
返回
-0.0
。但这些并没有提供不同的算术结果。另外,这两个结果不应同时为
NaN

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
}

注:
tgamma(x)
出现在我的GCC4.8.2机器上,但是
=在其他人身上

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
。待定


这是IEEE 754-2008函数rsqrt(将在未来的ISO C标准中)返回的自我回答∞ 在±0上,这是非常令人惊讶的。和
tgamma
也返回±∞ 在±0。使用MPFR时,
MPFR_digamma
返回的是±∞ 在±0上。

我考虑过这个方法,但我不能在周末前检查,所以如果有人愿意,他/她可能会做一些实验,或者告诉我这是胡说八道:

  • 生成-0.0f。通过分配一个微小的负常数,可以静态地生成浮动表示

  • 将该常数指定给volatile double并返回到float

    通过更改位表示2次,我假设 -0.0f的编译器特定标准位表示现在位于 变量编译器在这方面比不上我,因为 其他值可能位于这两个副本之间的volatile变量中

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

  • 如果相等,则指定输入的volitale双变量,然后返回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;

}
typedef联合
{
浮动价值;
/*假设int的位数至少与float的位数相同*/
无符号整数比特;
}tBitAccess;
浮动我的签名F(浮动x)
{
/*假设double具有较小的min和
浮点以外的其他位表示*/
易失性双比特;
tBitAccess tmp;
无符号整数pat0,patX;
如果(x<0.0f)返回-1.0f;
如果(x>0.0f)返回1.0f;
二进制位=(双)(浮点)-DBL_最小值;
tmp.fvalue=(浮点)位;
pat0=tmp.bitpat;
位=(双)x;
tmp.fvalue=(浮点)位;
patX=tmp.bitpat;
返回(patX==pat0)?-1.0f:1.0f;
}
  • 它不是标准函数或运算符,而是一个应区分-0.0和0.0符号的函数
  • 它(主要)基于这样一种假设,即编译器供应商不会因为格式的改变而对-0.0f使用不同的位模式,即使浮点格式允许,并且如果这样,它独立于所选的位模式
  • 对于-0.0f具有精确模式的浮点格式,此函数应安全地执行此操作,而不需要知道该模式中的位顺序
  • 其他假设(关于类型的大小等)可以通过float.h常量上的预编译器开关来处理

编辑:再想一想:如果我们可以强制将值与(0.0 | | |-0.0)进行比较,使之低于最小的可表示的非规范(次正常)浮点数或其负对应值,并且FP格式中的-0.0f(精确)没有第二种模式,我们可以将转换为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;

}
typedef联合
{
浮动价值;
/*假设int的位数至少与float的位数相同*/
无符号整数比特;
}tBitAccess;
浮动我的签名F(浮动x)
{
挥发性tBitAccess tmp;
无符号整数pat0,patX;
如果(x<0.0f)返回-1.0f;
如果(x>0.0f)返回1.0f;
tmp.fvalue=-DBL_MIN;
/*强制某事物比最小次正常值低0.0f
-不确定一个abs()系数是否足够*/
tmp.fvalue=tmp.fvalue*fabsf(tmp.fvalue);
pat0=tmp.bitpat;
tmp.fvalue=x;
tmp.fvalue=tmp.fvalue*fabsf(tmp.fvalue);
patX=tmp.bitpat;
返回(patX==pat0)?-1.0f:1.0f;
}

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

当您建议
atan2
时,我印象深刻,当您添加
sprintf
时,我印象更深刻。从那时起,我就想不出任何进一步的函数来区分+0和-0了。@Pascal Cuoq你的评论激发了这一努力。一些双曲函数,如
csch()
,也可以区分,但它们不会出现在C标准中。除了
1/0
导致坏事情之外,
1/x
似乎