关于语言不可知:浮点数是否为零?(-0f)

Does float have a negative zero? (-0f)

IEEE浮点数有一个位用于指示符号,这意味着您可以在技术上具有零(+0和-0)的不同二进制表示。 我是否可以在C中进行算术运算,从而产生负零浮点值?

这个问题受到另一个问题的启发,这让人怀疑是否可以安全地比较0.0f使用==,我还想知道是否有其他方法来表示零会导致float1 == 0.0f因看似完全相等的值而中断。

[编辑]请不要评价比较花车的平等安全性! 我并没有试图添加那些重复的问题。


根据标准,负零存在,但它等于正零。对于几乎所有目的,两者的行为方式相同,许多人认为负面的存在是一个实现细节。但是,有些函数的行为完全不同,即除法和atan2

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <math.h>
#include <stdio.h>

int main() {
    double x = 0.0;
    double y = -0.0;
    printf("%.08f == %.08f: %d
", x, y, x == y);
    printf("%.08f == %.08f: %d
", 1 / x, 1 / y, 1 / x == 1 / y);
    printf("%.08f == %.08f: %d
", atan2(x, y), atan2(y, y), atan2(x, y) == atan2(y, y));
}

此代码的结果是:

1
2
3
0.00000000 == -0.00000000: 1
1.#INF0000 == -1.#INF0000: 0
3.14159265 == -3.14159265: 0

这意味着代码可以正确处理某些限制,而无需显式处理。不确定依靠此功能接近极限的值是一个好主意,因为简单的计算错误可能会改变符号并使值远远不正确,但如果您避免计算,您仍然可以利用它改变标志。


Is there an arithmetic operation I
could do for example in C which result
in a negative zero floating point
value?

当然:

1
float negativeZero = -10.0e-30f * 10.0e-30f;

乘法的数学精确结果不能表示为浮点值,因此它舍入到最接近的可表示值,即-0.0f

负零的语义由IEEE-754标准明确定义;在算术表达式中,它的行为与零的行为不同的唯一真实可观察方式是,如果除以它,你将得到一个不同的无穷大符号。例如:

1
2
1.f /  0.f --> +infinity
1.f / -0.f --> -infinity

使用-0.f进行比较和加法和减法得到与+0.f(在默认舍入模式下)相同的结果。乘法可以保留零的符号,但如上所述,它通常是不可观察的。

有一些数学库函数的行为可以根据零的符号而变化。例如:

1
2
copysignf(1.0f, 0.0f) -->  1.0f
copysignf(1.0f,-0.0f) --> -1.0f

这在复杂的功能中更常见:

1
2
csqrtf(-1.0f + 0.0f*i) --> 0.0f + 1.0f*i
csqrtf(-1.0f - 0.0f*i) --> 0.0f - 1.0f*i

但是,一般情况下,您不必担心负零。


是零可以签名,但标准要求正负零测试相等


有几个简单的算术运算导致负零回答(至少在我测试它的i386 / x64 / ARMv7 / ARMv8系统上):

  • -1 * 0
  • 0 / -1

当我编写一个优化算术表达式的优化器时,这些让我感到意外。如果b恰好为负(正确答案为-0),则将"a = b * 0"优化为"a = 0"将导致错误答案(+0)。


是的,float确实有负零,但不是,在比较浮点值时你不必担心这一点。

浮点运算被定义为在特殊情况下正常工作。


是的,你可以有+0和-0,这些是不同的位模式(应该通过相等测试失败)。你永远不应该使用== with float,当然不是IEEE float。 <或>很好。关于这个主题还有很多其他的SO问题和讨论,所以我不会在这里讨论。


是的,float s具有负零,就像其他IEEE浮点类型一样,如double(在具有IEEE浮点的系统上)。 Octave中有一个如何创建它们的例子;相同的操作在C中工作。==运算符将+0和-0视为相同,因此负零不会破坏该类型的比较。


-lm有signbit()函数可用于指示值是否为负数(包括-0)


使用浮点数进行相等比较时应谨慎。请记住,您试图在二进制系统中表示十进制值。

将浮点值的相等性检查为0是否安全?

如果你必须比较浮点值,我建议你使用某种你可以接受的容差float1 <= toleranceVal && float1 >= toleranceVal2或乘以某个因子10并作为整数转换。
if (!(int)(float1 * 10000)) { .. some stuff .. }


这个float1 == 0.0f从来都不是一个安全的比较。

如果你有类似的东西

1
2
3
4
float x = 0.0f;
for (int i = 0; i < 10; i++) x += 0.1f;
x -= 1.0f;
assert (x == 0.0f);

它会失败,即使它看起来应该是0。