+0.0 和 -0.0 上的哪些运算和函数会给出不同的算术结果?

问题描述 投票:0回答:4

在 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

c floating-point
4个回答
23
投票

有一些标准运算和函数在

f(+0.0)
f(-0.0)
之间形成数字上不同的答案。

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

#include <math.h>
#include <stdio.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
#if __STDC_VERSION__ >= 202310  // C23
  zero_test("rsqrt(x)",        rsqrt);
#endif
}

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(x)
,又名
1/sqrt(x)
添加到 C23。 (未经测试)。

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提供的自我回答。


7
投票

IEEE 754-2008 函数

rsqrt
(将成为未来的 ISO C 标准)在 ±0 上返回 ±∞,这非常令人惊讶。并且
tgamma
也会在 ±0 上返回 ±∞。对于 MPFR,
mpfr_digamma
在 ±0 上返回 ±∞ 的相反值。


1
投票

我想这个方法,但周末之前我无法检查,所以有人可能会对此做一些实验,如果他/她喜欢的话,或者只是告诉我这是无稽之谈:

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

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

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

  • 将输入与 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.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。


1
投票

不完全是问题的答案,但了解一下很有用:

刚刚遇到的情况

a - b = c => b = a - c
,如果
a
0.0
b
-0.0
,则不成立。我们有
0.0 - (-0.0) = 0.0 => b = 0.0 - 0.0 = 0.0
。标志丢失了。
-0.0
未恢复。

取自这里

© www.soinside.com 2019 - 2024. All rights reserved.