IEEE 浮点数分配了一个位来指示符号,这意味着从技术上讲,您可以使用不同的二进制表示零(+0 和 -0)。有没有我可以做的算术运算,例如在 C 中导致负零浮点值?
这个问题的灵感来自另一个问题,它质疑您是否可以安全地比较0.0f
using ==
,我进一步想知道是否还有其他方法来表示零,这会导致float1 == 0.0f
看似完全相等的值中断。
[编辑] 请不要评论比较浮动是否相等的安全性!我并不是要添加到那个溢出的重复问题中。
IEEE 浮点数分配了一个位来指示符号,这意味着从技术上讲,您可以使用不同的二进制表示零(+0 和 -0)。有没有我可以做的算术运算,例如在 C 中导致负零浮点值?
这个问题的灵感来自另一个问题,它质疑您是否可以安全地比较0.0f
using ==
,我进一步想知道是否还有其他方法来表示零,这会导致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
s 与其他 IEEE 浮点类型一样具有负零,例如double
(在具有 IEEE 浮点的系统上)。Octave中有一个如何创建它们的示例;相同的操作在 C 中起作用。但是,==
运算符将 +0 和 -0 视为相同,因此负零不会破坏这种类型的比较。
是的,您可以有一个 +0 和 -0 并且它们是不同的位模式(应该无法通过相等性测试)。你不应该使用 == 和浮点数,当然不是 IEEE 浮点数。< 或 > 都可以。关于这个主题还有很多其他的 SO 问题和讨论,所以我不会在这里讨论。
这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,它也会失败。
使用浮点数进行相等比较时应小心谨慎。请记住,您正在尝试在二进制系统中表示十进制值。
如果您必须比较浮点值,我建议您使用某种您可以接受的容差,float1 <= toleranceVal && float1 >= toleranceVal2
或者乘以十倍并转换为整数。
if (!(int)(float1 * 10000)) { .. some stuff .. }
-lm 具有 signbit() 函数可用于指示值是否为负(包括 -0)