什么操作和+0.0和-0.0的function给出不同的算术结果?
在C中,当支持±0.0
时,分配给double
-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
通过@Pascal Cuoq的https://stackoverflow.com/a/25312364/2410359中的评论鼓舞,我在标准C中寻找几个提供算术上不同结果的函数。
注意:许多函数,如sin()
,从f(+0.0)
返回+0.0
,从f(-0.0)
返回f(-0.0)
。 但是这些不提供不同的算术结果。 此外,2个结果不应该都是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 %sf(-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/xf(+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)
double zero = +0.0; memcpy(&zero, &x, sizeof x)
可以显示x
是与+0.0
不同的位模式,但x
仍然可以是+0.0
。 我认为一些FP格式有许多位模式是+0.0
和-0.0
。 TBD。
IEEE 754-2008函数rsqrt
(将在未来的ISO C标准中)在±0上返回±∞,这是相当令人惊讶的。 而且tgamma
也在±0上返回±∞。 用MPFR, mpfr_digamma
在±0上返回±∞的相反值。
我想到这个方法,但是我不能在周末之前检查一下,所以如果他/她愿意的话,也许有人会做一些实验,或者只是告诉我这是无稽之谈:
-
生成一个-0.0f。 应该可以通过分配一个微小的负常数来静态生成,这个常数会使浮点数下溢。
-
把这个常量赋值给一个volatile double,然后返回float。
通过改变位表示2次,我假定-0.0f的编译器特定标准位表示现在在variables中。 编译器不能智取我,因为这两个副本之间的variablesvariables可能完全是其他值。
-
将input与0.0f进行比较。 检测是否有0.0f / -0.0f的情况
-
如果相等,则分配input的volitale doublevariables,然后返回到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确切的一个模式的浮点格式,该函数应该安全地执行该技巧,而不知道该模式中的比特sorting。
- 其他假设(关于types的大小等)可以通过float.h常量上的预编译器开关来处理。
编辑:第二个想法:如果我们可以强制比最小可表示的反常规(低于正常)浮点数或其负对应的值低于(0.0 || -0.0),并且没有-0.0f的第二模式)在FP格式中,我们可以把这个转换放到volatile中。 (但是也许要保持浮点数不稳定,以确保在停用非规范化的情况下,编译器不能做任何奇怪的技巧,忽略操作,这会进一步减less比较等于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。