IEEE浮点数有一个位用于指示符号,这意味着您可以在技术上具有零(+0和-0)的不同二进制表示。 我是否可以在C中进行算术运算,从而产生负零浮点值?
这个问题受到另一个问题的启发,这让人怀疑是否可以安全地比较0.0f使用==,我还想知道是否有其他方法来表示零会导致float1 == 0.0f因看似完全相等的值而中断。
[编辑]请不要评价比较花车的平等安全性! 我并没有试图添加那些重复的问题。
-
更有趣的是NaN。 NaN比较不等于所有值,包括它自己!
-
"在C中我是否可以进行算术运算,导致零浮点值为负值?" - 可能C是个坏例子,因为C不保证其浮点类型是IEEE浮点数。 就C标准而言,没有可靠的方法来保证产生负零,但是有些操作允许实现产生负零。 实现还有一种方法可以宣传其浮动实际上是IEEE浮点数,在这种情况下游戏开启。
-
请提供"另一个问题是否可以安全地比较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 |
但是,一般情况下,您不必担心负零。
-
如果表达式float negativeZero = -10.0e-30f * 10.0e-30f;舍入为0.0f而不是-0.0f,那不是更好。 如果正负零都相同,为什么不消除这种模糊性并保持正零? 另一种获得负零的简单方法:float neg=-0.00000000000000000001; printf("%f",neg);
是零可以签名,但标准要求正负零测试相等
有几个简单的算术运算导致负零回答(至少在我测试它的i386 / x64 / ARMv7 / ARMv8系统上):
当我编写一个优化算术表达式的优化器时,这些让我感到意外。如果b恰好为负(正确答案为-0),则将"a = b * 0"优化为"a = 0"将导致错误答案(+0)。
是的,float确实有负零,但不是,在比较浮点值时你不必担心这一点。
浮点运算被定义为在特殊情况下正常工作。
-
许多编译器将FP值上的==转换为内存比较。
-
@David:如果他们没有考虑负零,那么应该将其报告为编译器供应商的错误。
-
@Stephen取决于我们谈论的语言。有些人没有对标准提出任何要求,有些语言不是基于标准!
-
@David:当然,但这些语言不算数。 = P
-
@Stephen哈哈!他们指望我 - 我用这样的语言写了一个FE代码(非常成功,被所有主要石油公司及其承包商使用)!你可怜我了吗?!!
-
@David:fortran标准体通常采取你做任何最快的事情(这种观点不利于严格的工程,但这似乎并没有阻止许多人使用它,遗憾的是)。在现代硬件上,浮点比较通常与编码比较一样快或更快,因此一个好的现代fortran编译器应该使用硬件FP比较,而不是提出你建议的问题(这不是说他们过去没有这样做过)。
-
@Stephen这不是Fortran!如果我们使用Fortran,我们就不会成功! f2c头文件说得最好,"/ * f2c.h - 标准Fortran到C头文件/ / * barf [ba:rf] 2."他建议使用FORTRAN,每个人都被禁止。" - 来自The Shogakukan DICTIONARY OF新英语(第二版)* /"不,我不关心Fortran,尽管John Backus是我的史上最佳英雄。
-
@David:我很震惊地发现有人正在进行有限元建模而不使用fortran,所以我承认我的好奇心:你使用哪种语言/编译器不尊重IEEE-754?
-
@Stephen Object Pascal,Delphi要具体。这个与0比较的问题是我唯一知道的不是IEEE754的问题,因为当然FP值直接映射到硬件。这对我们来说永远不是问题,因为我们只在少数几个地方测试0的相等性,然后只采用优化的分支来保存计算,因此它不会影响正确性。我们使用Delphi的原因是因为我们喜欢我们的软件可用!我们厌恶Fortran充满了狂热的激情!
-
@David:很有意思。我仍然认为这种行为值得向您的编译器供应商提出投诉,特别是如果他们在其他方面设法符合IEEE-754标准。德尔福采取这种方法有一些奇怪的历史原因吗?
-
@Stephen我不知道背后的原因。我知道那天早些时候Borland从未专注于FP codegen。优化器对FP代码不执行任何操作。如果你回去足够远,那么Turbo Pascal就可以在没有协处理器的机器上运行(记住它们!)我怀疑用它们提高它会产生影响。我甚至可以让他们修复像Tanh(x)因为使用天真的Sinh / Cosh公式而失败的大abs(x)等大错误!我主要是通过挂掉他们的RTL函数来解决他们的错误。
是的,你可以有+0和-0,这些是不同的位模式(应该通过相等测试失败)。你永远不应该使用== with float,当然不是IEEE float。 <或>很好。关于这个主题还有很多其他的SO问题和讨论,所以我不会在这里讨论。
-
+0和-1?你并不是说我想
-
nope,意思是+0和-0,将编辑
-
-1平等比比较比特更进一步。事实上,-0和+0被定义为相等。
-
公平地说,快速示例程序确认gcc在-0和+0之间产生相等。并不意味着你不能拥有-0,原始问题,并不意味着使用与float相等的比较是个好主意。
-
你有一个参考,其中-0和+0被定义为相等?谢谢。
-
虽然为了比较目的,负零和正零通常被认为是相等的,但是一些编程语言关系运算符和类似结构可能或者确实将它们视为不同的。
-
(除零负零和正零应视为相等)
-
我不知道我的规范副本在哪里,我看到它引用了它们被定义为相等。 我们都知道,如果任何处理器或系统(处理器+编译器+操作系统)实际上符合IEEE 754标准,我很少知道,所以即使有一个特殊情况,我仍然建议不要对浮点数的等号充满信心。 有用。 对于原始海报,是+和 - 零在标准中定义为+和 - 无穷大。 并且有时您的数学将导致负零。 (+/-无穷大很容易来)
是的,float s具有负零,就像其他IEEE浮点类型一样,如double(在具有IEEE浮点的系统上)。 Octave中有一个如何创建它们的例子;相同的操作在C中工作。==运算符将+0和-0视为相同,因此负零不会破坏该类型的比较。
-
你引用的Octave ==运算符是什么?
-
@David:C ==,但Octave也应该这样做。
-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。