在 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
。
有一些标准运算和函数在
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提供的自我回答。
IEEE 754-2008 函数
rsqrt
(将成为未来的 ISO C 标准)在 ±0 上返回 ±∞,这非常令人惊讶。并且 tgamma
也会在 ±0 上返回 ±∞。对于 MPFR,mpfr_digamma
在 ±0 上返回 ±∞ 的相反值。
我想这个方法,但周末之前我无法检查,所以有人可能会对此做一些实验,如果他/她喜欢的话,或者只是告诉我这是无稽之谈:
生成-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 没有第二种模式(精确)在 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。
不完全是问题的答案,但了解一下很有用:
刚刚遇到的情况
,如果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
取自这里。