如果 x != x 给出相同的结果,为啥 isnan(x) 存在?

Posted

技术标签:

【中文标题】如果 x != x 给出相同的结果,为啥 isnan(x) 存在?【英文标题】:Why does isnan(x) exist if x != x gives the same result?如果 x != x 给出相同的结果,为什么 isnan(x) 存在? 【发布时间】:2021-05-03 19:25:50 【问题描述】:

众所周知,对于任何浮点类型的变量x != x iff(当且仅当)xNaN(不是数字)。或逆版本:x == x iff x 不是 NaN。那么如果使用语言的自然能力可以获得相同的结果,为什么WG14 决定定义isnan(x) (math.h)?动机是什么?更好的代码可读性?

额外问题:isnan(x)x != x 之间是否存在任何功能差异?

【问题讨论】:

我不知道委员会的原因是什么,但对我来说,isnan() 是干净和自我记录的,而x != x,不管它根据 IEEE 的定义多么明确- 754,看起来像是今天可能会起作用但明天不会起作用的杂物。 @JohnKugelman 无法将比较实现为简单的按位比较 - 必须检查“此值是否为 NaN?”该检查 isnan()。如果xNaN,则isnan() 功能是定义x != x 的先决条件。 浮点相等不是独立的处理器指令吗?编译器是否需要多条指令来实现!= @AndrewHenle:x == 0 也不能实现为简单的按位运算(否则无法识别 -0 等于 0),但我们没有 iszero 宏。有一个浮点比较指令可以产生所需的结果。这些都与==isnan 无关,因为它们可以用一条指令实现,也可以根据硬件要求多条指令实现。 Performance. 【参考方案1】:

是否可以使用语言的自然能力获得相同的结果?

如果 x 不是 NaN,C 不指定 x == x。但是,许多实现都是这样做的。 C 不需要遵守IEEE_754。 isnan(x) 定义明确。

isnan(x) 用于可移植代码。


C 在类型的表示(C99 起)有

具有相同对象表示的两个值(NaN 除外)比较相等,但比较相等的值可能具有不同的对象表示。

...但这并没有指定比较 2 个 NAN 的行为。


__STDC_IEC_559__(类似于遵守 IEEE-754)被定义为 1(C 不需要),那么

“如果 x 是 NaN,则表达式 x != x 为真。”

“如果 x 是 NaN,则表达式 x == x 为假。”

__STDC_IEC_559__ 未定义为 1 时,请谨慎假设浮点数学边缘的行为,例如 NAN 相等。


[编辑以解决一些 cmets]

C,在 FP 数学的角落,缺乏 IEEE-754 的细节。 C89 允许 NAN,正如对 IEEE-754 的引用所证明的那样,但缺少 isnan(x)。没有“具有相同对象表示的两个值(NaN 除外)比较相等,......”来指导。当时,未指定 NAN 的x==x。对于 C99,isnan(x) 被定义为明确的 NAN 测试,而不是破坏或使先前的代码无效。在我看来,x==x 仍未针对 NAN 指定,但它通常会导致 falseisnan(x) 还提供代码清晰度。关于 C 和 NAN 的很多内容是 fuzzy:往返有效负载序列信号编码/辨别、NAN 可用性,...


isnan(x)x != x 之间在功能上有什么区别吗?

除了上面讨论的isnan(x)x != x 的定义明确的功能之外,还有一些晦涩难懂的功能:

isnan(x) 计算 x 一次,而 x != x 计算两次。如果xy++ 之类的表达式,则会有所不同。

isnan(x)sematic 类型上运行。当“实现在评估类型中支持 NaN 但在语义类型中不支持”时,这会有所不同。 x != x 对评估类型进行操作。研究FLT_EVAL_METHOD了解更多详情。

【讨论】:

cppreference.com 表示x != x 是另一种测试 NaN 的方法。他们错了吗? @AndrewHenle 从保证中排除某些内容只是将其排除在外。它不保证相反的情况,甚至不保证其他。 @AndrewHenle 这就是你出错的地方。它们仅从保证比较相等的集合中排除。 @AndrewHenle 这句话是关于两个具有相同对象表示的值,不包括那些为 NaN 的值。没有在后面加上关于那些排除值的句子可能会使那些假设一切都被严格定义的人感到困惑,没有空间来适应不同的实现。一种对 C 有点陌生的思维方式。 @AndrewHenle 排除其行为已定义的对象集不提供任何保证。即使这可能会让人感到不满意。【参考方案2】:

如果 x != x(或 x == x)给出相同的结果,为什么 isnan(x) 存在?

因为它们并不总是给出相同的结果。

比如compiling with -funsafe-math-optimizations替换时的GCC

x - x

0.0

即使xNaN,所以 ( x == x ) 也可能为真。

-funsafe-math-optimizations is also enabled if either -fast-math or -Ofast is specified:

...

-ffast-math

设置选项-fno-math-errno、-funsafe-math-optimizations-ffinite-math-only, -fno-rounding-math, -fno-signaling-nans, -fcx-limited-range-fexcess-precision=fast

此选项会导致预处理器宏 __FAST_MATH__ 已定义。

除了-Ofast 之外,任何-O 选项都不会打开此选项,因为 它可能导致依赖于精确的程序的输出不正确 为数学函数执行 IEEE 或 ISO 规则/规范。 但是,它可能会为不需要的程序生成更快的代码 这些规范的保证。

...

-funsafe-math-optimizations

允许对浮点运算进行优化,即 (a) 假设 参数和结果是有效的,并且 (b) 可能违反 IEEE 或 ANSI 标准。在链接时使用时,它可能包括库或启动 更改默认 FPU 控制字或其他类似的文件 优化。

任何-O 选项都不会打开此选项,因为它可能导致 依赖于精确实现的程序的错误输出 数学函数的 IEEE 或 ISO 规则/规范。有可能, 但是,为不需要的程序生成更快的代码 这些规格的保证。启用-fno-signed-zeros-fno-trapping-math-fassociative-math-freciprocal-math

...

因此,在某些情况下,您可能出于性能原因想要使用浮点优化,但仍需要检查 NaN,而这样做的唯一方法是显式检查 isnan() 之类的内容。

另外,C standard states in 6.2.6.1p4:

具有相同对象表示的两个值(NaN 除外)比较相等

实现所需的功能需要能够以某种方式检查NaN,而不是比较对象表示(位)。所以isnan() 功能是实现“x == x is false if x is NaN”的先决条件。

必须有某种类型的功能来检查 NaN 独立于 x == x 否则它只是一个无法实现的无限递归定义。

如果您只需检查NaN 并且不需要浪费CPU 周期来实际进行比较,那么在isnan() 之类的内容中公开该功能可能会提高性能。

【讨论】:

以上是关于如果 x != x 给出相同的结果,为啥 isnan(x) 存在?的主要内容,如果未能解决你的问题,请参考以下文章

这些集合操作是啥,为啥它们会给出不同的结果?

为啥这个涉及 floor 函数的公式没有给出我期望的结果?

为啥这个 Java 代码会编译?

为啥此语句在 java x ^= y ^= x ^= y 中不起作用;

为啥交叉验证结果显示高准确率而存在过度拟合?

为什么这两个代码给出的结果相同?