使用IEEE754浮点NaN(非数字)作为未设置的值是否是一个好主意?

13

在非数学意义下未定义的值中,使用IEEE754浮点NaN(非数字)是一个好主意吗?

在我们的情况下,这些值尚未设置,因为它们尚未从其他设备接收到。上下文是使用IEC1131 REAL32值的嵌入式系统。编辑:编程语言是C,因此我们最可能使用NAN和isnanf(x),这些都来自于C99。虽然我们可能需要一些额外的技巧才能将它们放入我们的操作系统兼容性层。

编程语言中的默认值似乎是使用正零来初始化浮点变量,其内部表示全部为零。对于我们来说,这是无法使用的,因为0是有效值的范围之内。

似乎使用NaN是一个干净的解决方案,但也许这会带来更多麻烦,我们应该选择其他值吗?


我不会C,但在.NET 1.1推出可空类型之前,许多人都使用最小值(int.MinValue)等东西。问题是您必须在各处考虑它,并确保永远不要使用MinValue。也许在C中存在类似的东西? - RichardOD
1
我问了一个类似但不完全相同的问题,也许答案对你有帮助。http://stackoverflow.com/questions/787828/nan-as-a-special-argument - quinmars
9个回答

11

刚注意到这个问题。

这是IEEE 754委员会考虑使用NaN的用途之一(我曾是委员会成员)。NaN在算术中的传播规则使其非常有吸引力,因为如果您有一个经过一系列计算得出的结果,其中涉及一些初始化数据,您不会将结果误认为是有效结果。它还可以使通过计算回溯查找使用初始化数据的位置更加简单明了。

尽管如此,还有一些陷阱是超出754委员会控制范围之外的:正如其他人所指出的,不是所有硬件都能快速支持NaN值,这可能导致性能风险。幸运的是,在性能关键的环境中,很少需要对初始化数据进行大量操作。


这个方案被接受了,因为我们在这种情况下使用NaN来表示未定义的值,尽管结果比预期更麻烦。这主要是因为我们的工具和系统缺乏对NaN的支持或存在错误,所以我们不得不绕过这些问题。 - starblue

4
NaN(Not a Number)是一个合理的选项,用于表示“无值”(例如,D编程语言将其用于未初始化的值),但是由于涉及它们的任何比较都将返回false,因此您可能会遇到一些意外情况:
- 如果DEFAULT_VALUE是NaN,则if (result == DEFAULT_VALUE)将不按预期工作。
- 如果您不小心使用它们,它们还可能在范围检查方面引起问题。考虑以下函数:
bool isOutsideRange(double x, double minValue, double maxValue) { return x < minValue || x > maxValue; }
如果x为NaN,则此函数将错误地报告x在minValue和maxValue之间。
如果您只想要一个用户测试的特殊值,我建议使用正无穷或负无穷而不是NaN,因为它没有相同的陷阱。当您不想依赖调用者检查该值时,可以使用NaN,例如,当您不想要求调用者检查该值时非常方便。
[编辑:我最初在上面打错了“涉及它们的任何比较都将返回true”的字,这不是我的意思,也是错误的,除了NaN!= NaN是true,其他所有比较都是false]

哪种语言使用这些比较规则?也许是D语言。但至少C和C++不会以这种方式处理NaN。所有的排序比较都将为false。x == NaN对于任何x,包括NaN,都是false。 - Igor Krivokon
1
不,你的函数只报告它没有超出范围。它既不在内部也不在外部,这可能会让那些天真地使用浮点数的人感到困惑。 - starblue
@Igor:我们在这里说的是同样的事情。如果x是NaN,isOutsideRange将返回false,这意味着它在范围内,但实际上并不是。 - jskinner
@jskinner 不,它并不意味着它在范围内。本质上,NaN 就是不存在的。 - starblue
2
@starblue: 我意识到了。'isOutsideRange'是一个在面对NaN输入时定义不清的函数示例:NaN既不在范围内也不在范围外,因此返回布尔值是不合适的。这只是一个例子,说明当引入NaN时,表面上看起来很好的东西实际上是不正确的。 - jskinner
显示剩余2条评论

4

我认为这通常是一个不好的想法。需要记住的一件事是,大多数CPU处理NaN比“普通”浮点数慢得多。而且在通常的设置中很难保证你永远不会遇到NaN。我的数值计算经验表明,它经常带来比它所值得的更多的麻烦。

正确的解决方案是避免在浮点数中编码“缺失值”,而是用另一种方式来表示它。但这并不总是实际可行的,这取决于你的代码库。


3

我曾在类似的情况下使用 NaN,原因是默认的初始化值 0 也是有效值。到目前为止,NaN 的效果还不错。

顺便说一句,为什么默认的初始化值通常是 0 而不是 NaN(例如在 Java 原始类型中)是个好问题。它难道不可以是 42 或其他值吗?我想知道零的理论基础是什么。


1
我认为使用0的原因是,无论类型如何,内存都会初始化为零字节,例如在C语言的BSS段中。 - starblue
是的,可能就是这样。但既然语言/编译器设计者已经付出了初始化内存的努力,那么初始化为任意值(而不仅仅是零)几乎同样容易,零只是其中的一些位 :-) - Joonas Pulakka
3
@mad-j:你想要使用相同的位模式初始化所有内存。因此它不能是42,因为这样你通常必须对两个相邻的short进行不同处理,而与int的处理不同。这只剩下了0和-1。但0xffffffff作为浮点数不等于-1,所以你会有一个不一致性。差别不大,但我认为0可能是最好的选择。此外,某些硬件可以高效地一次性将整个物理内存块清零,这也值得考虑。 - Steve Jessop

3

当心 NaN(非数字)……如果你不小心,它们会像野火一样蔓延。

对于浮点数来说,它们是完全有效的值,但任何涉及它们的赋值也将等于 NaN,因此它们会传播到你的代码中。如果你发现它,这相当好作为调试工具,然而如果在发布时有一个边缘情况,它也可能成为真正的麻烦。

D 以此为基础,将 NaN 作为浮点数的默认值。(我不确定是否同意这个做法。)


9
NaN的本意就是会被传递,因此如果结果为NaN则说明存在问题,这比得到一个看似正常但完全不正确的数字要好得多(由于使用了零初始化的数字而导致)。因此,让NaN作为结果更加合适。请注意,本人将尽力使翻译准确无误,简洁易懂且不改变原意。 - Joonas Pulakka
2
是和不是,因为当你仅通过查看输出或显式检查NaN时才能发现它。这样做的后果是错误可能比实际出现的时间晚得多。另一方面,如果您使用NULL(如果可能),您会很快获得NPE /分段错误。残酷,但高效。 - quant_dev
1
如果你只知道NaN无处不在,那么这并不能帮助你找出它们来自哪里。 - corsiKa

1

我的感觉是这有点不太正规,但至少你用这个 NaN 值进行操作时,每个其他数字都会得到 NaN 作为结果 - 当你在错误报告中看到 NaN 时,至少你知道你正在寻找什么样的错误。


0

对我来说,这听起来像是一个很好的使用场景。但愿我能想到它...

当然,它们应该像病毒一样传播,这就是重点。

我认为我会使用NaN而不是其中一个无穷大。使用一个信号NaN并在第一次使用时引发事件可能很好,但那时为时已晚,它应该在第一次使用时保持安静。


0

使用NaN作为默认值是合理的。

请注意,一些表达式,如(0.0 / 0.0),会返回NaN。


0
如果您的基本需求是具有浮点值,其不表示可能从设备接收到的任何数字,并且如果设备保证永远不会返回NaN,则对我来说似乎是合理的。
请记住,根据您的环境,您可能需要一种特殊的方法来检测NaN(不要仅使用 if (x == float.NaN) 或您的等价物)。

不要相信这个答案。Jon Skeet 只需要想一想变量,它就会自己定义。 - Windows programmer
变量名的定义在 Skeet 考虑变量值之前,对吗? - glasnt

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接