为了账号安全,请及时绑定邮箱和手机立即绑定

请问+0.0和-0.0上的哪些操作和函数给出不同的算术结果?

/ 猿问

请问+0.0和-0.0上的哪些操作和函数给出不同的算术结果?

C++
九州编程 2019-12-27 23:18:39

在C中,何时±0.0支持-0.0或+0.0分配给double通常不会产生算术差异。尽管它们具有不同的位模式,但它们在算术上的比较是相等的。


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

受到@Pascal Cuoq注释的启发,我正在寻找标准C中的其他一些函数,这些函数提供算术上不同的结果。


注意:许多函数(例如sin())+0.0从f(+0.0)和-0.0从中返回f(-0.0)。但是这些没有提供不同的算术结果。同样,这2个结果不应该同时为NaN。


查看完整描述

3 回答

?
qq_遁去的一_1

有迹象表明,形成数字之间不同的答案,一些标准的操作和功能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  

注:

tgamma(x)想出==我的gcc 4.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。待定。


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



查看完整回答
反对 2019-12-28
?
皈依舞

IEEE 754-2008函数rsqrt(将在将来的ISO C标准中使用)在±0上返回±∞,这非常令人惊讶。并tgamma在±0上返回±∞。使用MPFR,mpfr_digamma在±0上返回与±∞相反的值。


查看完整回答
反对 2019-12-28
?
呼如林

我考虑过这种方法,但是我无法在周末之前检查一下,因此如果有人愿意,可以有人对此进行一些实验,或者只是告诉我这是胡说八道:


生成-0.0f。通过分配一个微小的负常量来产生浮点表示形式,应该可以静态生成。


将此常量分配给易失性double,然后返回float。


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


将输入与0.0f进行比较。检测我们是否有0.0f / -0.0f的情况


如果相等,则分配输入的volitale double变量,然后返回浮点数。


我再次假设它现在具有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格式更改的结果,并且如果这保持不变,则它独立于所选择的格式位模式。

对于具有-0.0f精确模式的浮点格式,此功能应该可以安全地完成操作,而无需了解该模式中的位顺序。

可以使用float.h常量上的预编译器开关处理其他假设(有关类型的大小等)。

编辑:再想一想:如果我们可以将与(0.0 || -0.0)相比的值强制低于最小可表示的非正态(subnormal)浮点数或其负数,并且-0.0f没有第二个模式(精确)(以FP格式),我们可以将强制类型转换为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舍入的奇异舍入方法。




查看完整回答
反对 2019-12-28

添加回答

回复

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信