IEEE 浮点数有一个指定的位来指示符号,这意味着从技术上讲,您可以使用不同的零二进制表示形式(+0 和 -0)。例如,我可以在 C 中执行算术运算,从而产生负零浮点值吗?
这个问题受到另一个问题的启发,该问题质疑您是否可以使用
0.0f
安全地比较 ==
,我进一步想知道是否有其他方法来表示零,这会导致 float1 == 0.0f
打破看似完全相等的值.
[编辑] 请不要评论比较浮点数是否相等的安全性!我并不是想添加重复的问题。
根据标准,负零是存在的,但它等于正零。对于几乎所有目的,两者的行为方式相同,并且许多人认为否定的存在是实现细节。然而,有些函数的行为完全不同,即除法和
atan2
:
#include <math.h>
#include <stdio.h>
int main() {
double x = 0.0;
double y = -0.0;
printf("%.08f == %.08f: %d\n", x, y, x == y);
printf("%.08f == %.08f: %d\n", 1 / x, 1 / y, 1 / x == 1 / y);
printf("%.08f == %.08f: %d\n", atan2(x, y), atan2(y, y), atan2(x, y) == atan2(y, y));
}
这段代码的结果是:
0.00000000 == -0.00000000: 1
1.#INF0000 == -1.#INF0000: 0
3.14159265 == -3.14159265: 0
这意味着代码可以正确处理某些限制,而不需要显式处理。不确定依赖此功能来获取接近极限的值是否是一个好主意,因为简单的计算错误可能会更改符号并使值远离正确,但如果您避免进行以下计算,您仍然可以利用它:改变标志。
有没有算术运算我 例如在 C 中可以这样做,其结果是 负零浮点数 价值?
当然:
float negativeZero = -10.0e-30f * 10.0e-30f;
乘法的数学精确结果无法表示为浮点值,因此它四舍五入到最接近的可表示值,即
-0.0f
。
负零的语义由 IEEE-754 标准明确定义;它的行为与算术表达式中的零的行为不同的唯一真正可观察的方式是,如果除以它,您将得到不同的无穷大符号。 例如:
1.f / 0.f --> +infinity
1.f / -0.f --> -infinity
使用
-0.f
进行比较、加法和减法的结果与使用 +0.f
得到的结果相同(在默认舍入模式下)。 乘法可以保留零的符号,但如上所述,它通常是不可观察的。
有一些数学库函数,其行为可能会根据零的符号而变化。 例如:
copysignf(1.0f, 0.0f) --> 1.0f
copysignf(1.0f,-0.0f) --> -1.0f
这在复杂函数中比较常见:
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 系统上):
当我编写优化器来简化算术表达式时,这些让我感到惊讶。如果 b 恰好为负数(正确答案为 -0),则将“a = b * 0”优化为“a = 0”将导致错误答案 (+0)。
是的,
float
有负零,就像其他 IEEE 浮点类型,例如 double
(在具有 IEEE 浮点的系统上)。 Octave 中有一个示例 here 介绍了如何创建它们;相同的操作在 C 中也有效。不过,==
运算符将 +0 和 -0 视为相同,因此负零不会破坏这种类型的比较。
使用浮点数进行相等比较时应小心谨慎。 请记住,您正在尝试在二进制系统中表示十进制值。
如果您必须比较浮点值,我建议您使用某种您可以接受的容差
float1 <= toleranceVal && float1 >= toleranceVal2
或乘以某个系数十并转换为整数。
if (!(int)(float1 * 10000)) { .. some stuff .. }
-lm 有signbit()函数可用于指示值是否为负数(包括-0)
是的,您可以有 +0 和 -0,它们是不同的位模式(应该无法通过相等测试)。 你永远不应该将 == 与 float 一起使用,当然更不能使用 IEEE float。 < or > 都很好。 关于这个主题还有很多其他问题和讨论,所以我不会在这里讨论。
这个
float1 == 0.0f
从来都不是一个真正安全的比较。
如果你有类似的东西
float x = 0.0f;
for (int i = 0; i < 10; i++) x += 0.1f;
x -= 1.0f;
assert (x == 0.0f);
即使它看起来应该是 0,它也会失败。