正面与负面的nans
Posted
技术标签:
【中文标题】正面与负面的nans【英文标题】:Positive vs negative nans 【发布时间】:2014-02-16 11:03:48 【问题描述】:我有一些在 AMD64 Linux 上开发的数字代码(使用 LLVM 3.2)。
我最近用 XCode 将它移植到了 OSX 10.9。它运行良好,但很多单元测试都失败了:似乎在 Linux 上返回 NaN(或 -NaN)的一些计算现在在 OSX 上返回 -NaN(或 NaN)。
我是否可以安全地假设正和负 NaN 是等效的,并调整我的单元测试以接受成功,或者这是出现更严重问题的迹象?
【问题讨论】:
如果您的 NaN 有迹象,则可能已经出现严重问题。这不正常。 ***.com/a/8817304/267482 嗯。我认为符号位总是被忽略;看起来有显示它的系统。那应该没问题。 您的测试首先区分不同的 NaN 有点奇怪。如果x
和/或y
是NaN,则x == y
、x < y
和x > y
都返回false;没有办法通过数值比较来区分 NaN。这表明您的测试要么正在检查代表 NaN 的位,要么正在检查 NaN 的某些转换,例如在 NaN 上使用 printf
生成的字符。在前一种情况下,有人已经决定了哪些位是重要的,你应该明白为什么。在后一种情况下,您依赖于 printf
的实现相关属性。
是的,测试只是区分 printf 的输出。当然我知道我需要了解哪些位是重要的——这就是我问这个问题的原因!
【参考方案1】:
在 IEEE-754 算术中没有“负 NaN”的概念。 NaN 编码仍然有一个符号位,并且存在使用或影响该位的“符号位”操作的概念(copysign、abs 等),但在解释 NaN 编码时它没有任何意义作为一个值。许多打印例程碰巧将该位打印为负号,但它在形式上没有意义,因此标准中没有太多规定其值应该是什么(除了 w.r.t. 上述函数)。
这是 IEEE-754 (2008) 的相关部分:
将支持格式的安静 NaN 转换为外部字符序列应生成语言定义的“nan”或除大小写外等效的序列(例如“NaN”),并带有可选的前置符号. (本标准不解释 NaN 的符号。)
所以你平台的转换函数可能会打印出 NaN 值的“符号”,但它没有任何意义,你不应该出于测试目的考虑它。
编辑得更强大:将含义附加到 NaN 数据的“符号位”几乎总是一个错误。
【讨论】:
但是,第一个系统上的结果是“NaN”,第二个系统上的结果是“-NaN”,第一个系统上的结果是“-NaN”,而第一个系统上的结果是“NaN”第二个表明在早期的浮点运算中发生了一些不同的事情,而不仅仅是最终的printf
。在生成 NaN 之前,可能存在在两个系统上具有不同符号的数值结果。测试程序没有在数值结果(非 NaN 结果)中发现错误的事实可能是因为测试不充分。因此,可能值得研究为什么这些标志不同。
更可能的解释是有一个库函数在一个平台上保留 NaN 的“符号”,但在另一个平台上没有(这很好,因为“符号”没有意义)。这很常见;一个平台有if (isnan(x)) return x
,另一个有if (isnan(x)) return NAN
,或类似的。我想说进一步调查的唯一条件是,如果被测例程只包含基本算术运算(没有库调用)并且在相同的硬件上运行。
@StephenCanon:即使使用相同的硬件,代码也有可能在一种情况下执行a+b
,而在另一种情况下执行b+a
;加法预计始终是可交换的,除非添加正零和负零,或者两个操作数都是 NaN(如果只有一个是 NaN,则结果应该是 NaN 逐字,但如果两者都是 NaN,则标准对结果是否应该保持沉默是第一个、第二个、“最大值”、“按位或”或其他)。
@supercat: 是的(但是 +0 和 -0 的加法 是 可交换的)。
@StephenCanon:根据 WIKI,(+0)+(-0) 产生 (+0),但 (-0)+(+0) 产生 (-0)。恕我直言,这样的规则很愚蠢,因为纯粹为了一个极其狭窄且通常不相关的极端情况而使加法不可交换具有使其几乎在任何地方都不可交换的效果,但这并不像未能定义任何形式的比较那么糟糕可以单独用作等价关系(最好是!((a < b) || (a > b))
,但这很恶心)。【参考方案2】:
这完全取决于你的单元测试正在测试什么。
除非您正在执行的测试实际上是 IEEE754 浮点软件本身或打印它们的 C 运行时代码,否则您很可能能够将它们视为等效的。否则,如果使用您正在测试的代码将它们视为相同,则您应该将它们视为相同。
这是因为测试应该在每一个情况下反映你的实际使用情况。一个(诚然做作的)示例是,如果您正在测试返回双精度的函数doCalc()
。如果它只是这样使用:
x = doCalc()
if x is any sort of Nan:
doSomethingWithNan()
那么您的测试应该将所有NaN
值视为等效。但是,如果你这样使用它:
x = doCalc()
if x is +Nan:
doSomethingForPositive()
else:
if x is -Nan:
doSomethingForNegative()
那么您会希望将它们视为不同的。
同样,如果您的实现在小数位中创建了有用的有效负载(见下文),并且您的实际代码使用,那么单元测试也应该对其进行检查。
由于 NaN 只是指数中的所有 1 位和分数中除所有零位之外的其他东西,因此符号位可能是正的或负的,而小数位可能是宽的各种价值观。但是,它仍然是数据类型表示之外的值或结果,因此,如果您只是期望如此,那么符号或有效负载所包含的内容可能几乎没有什么区别。
在检查NaN
值的文本输出方面,the Wikipedia page on NaN 表示不同的实现可能会给您提供广泛不同的输出,其中:
nan
NaN
NaN%
NAN
NaNQ
NaNS
qNaN
sNaN
1.#SNAN
1.#QNAN
-1.#IND
甚至变体显示对其 NaN 没有影响的不同符号和有效负载:
-NaN
NaN12345
-sNaN12300
-NaN(s1234)
因此,如果您想在单元测试中实现大规模可移植,您会注意到所有输出表示形式都包含字符串 nan
的一些变体。因此,对字符串 nan
或 ind
的值进行不区分大小写的搜索会将它们全部找出来。这可能不适用于所有环境,但覆盖范围非常大。
对于它的价值,C 标准对使用 %f
输出浮点值有这样的说法(%F
使用大写字母):
表示
NaN
的double
参数被转换为[-]nan
或[-]nan(n-char-sequence)
中的一种样式 - 哪种样式以及任何n-char-sequence
的含义是实现定义的。
因此,只需检查该值是否在其中某处有 nan
就足够了。
【讨论】:
不过,这并不能完全回答我的问题 --- 我需要知道 NaN 中的符号位是否重要到足以让我的单元测试注意到。我真的很惊讶 OSX 和 Linux 在这里产生不同的结果:它是相同的处理器和相同的编译器,我认为 IEEE 浮点规范没有给任何回旋余地来产生不同的结果。这是我需要关心的事情吗? @David,正如我所说,这取决于您的测试。除非您专门测试 NaN 的输出,否则几乎可以肯定地假设所有 NaN 都是相同的,因为找到一个的任何代码几乎肯定会以相同的方式对待它们。如果您正在测试一个函数,其调用者会根据符号表现出不同的行为,那么您应该区别对待它们。 但这很不寻常。我会澄清的。 没有有意义的概念,其中一个值可以是 +NaN 或 -NaN,因为 NaN 没有符号;如果意图在 IEEE-754 算法上运行,“将值视为不同”是程序中的一个错误。 @Stephen,虽然 ieee754 可能无法区分,但 C 的 output 允许呈现该信息,因此,如果这是正在检查的内容,则需要允许它。printf
可能出于多种原因将它们明显呈现:因为标准允许它并且行为恰好从实现中自然脱落,因为为格式说明符编写转换器的人question 不知道没有理由区分它们,或者因为它们有一些与其需求相关但超出标准范围的特定用途。 IEEE-754 NaN 的符号位没有语义意义。以上是关于正面与负面的nans的主要内容,如果未能解决你的问题,请参考以下文章