【问题标题】:Is it a good idea to use IEEE754 floating point NaN for values which are not set?对未设置的值使用 IEEE754 浮点 NaN 是个好主意吗?
【发布时间】:2010-11-05 10:05:06
【问题描述】:

对于因非数学原因未定义的值使用 IEEE754 浮点 NaN(非数字)是否是个好主意?

在我们的例子中,它们尚未设置,因为尚未从其他设备接收到这些值。上下文是使用 IEC1131 REAL32 值的嵌入式系统。 编辑: 编程语言是 C,所以我们很可能会使用来自 C99 的 NAN 和 isnanf(x)。虽然我们可能需要一些额外的扭曲才能将它们纳入我们的操作系统兼容层。

编程语言的默认设置似乎是用正零初始化浮点变量,其内部表示全为零。这对我们来说是不可用的,因为 0 在有效值的范围内。

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

【问题讨论】:

  • 我不懂 C,但是在 .NET 1.1 在可空类型出现之前,很多人习惯使用最小值(int.MinValue)之类的东西。这样做的问题是您必须在任何地方都考虑它并确保您从不使用 MinValue。也许 C 语言中存在类似的东西?
  • 我问了一个类似(但不相同)的问题,也许答案也可以帮助你。 *.com/questions/787828/nan-as-a-special-argument

标签: c floating-point initialization undefined nan


【解决方案1】:

刚注意到这个问题。

这是 IEEE 754 委员会考虑的 NaN 用途之一(我是委员会成员)。 NaN 在算术中的传播规则使其非常有吸引力,因为如果您有一个涉及一些初始化数据的长序列计算的结果,您不会将结果误认为是有效结果。它还可以更直接地追溯您的计算,以更直接地找到您使用初始化数据的位置。

也就是说,754 委员会无法控制一些陷阱:正如其他人所指出的,并非所有硬件都支持高速 NaN 值,这可能会导致性能危害。幸运的是,在性能关键的设置中,人们并不经常对初始化数据执行大量操作。

【讨论】:

  • 接受,因为我们在这种情况下使用 NaN 表示 undefined,尽管结果比预期的要麻烦。这主要是因为我们的工具和系统中缺少对 NaN 的支持或存在错误,我们不得不解决这个问题。
【解决方案2】:

我认为总的来说这是一个坏主意。要记住的一件事是,大多数 CPU 对待 Nan 的速度比“通常”的浮动要慢得多。而且很难保证你在通常的环境中永远不会有 Nan。我在数值计算方面的经验是,它带来的麻烦往往多于它的价值。

正确的解决方案是避免在浮点数中编码“缺少值”,而是以另一种方式发出信号。不过,这并不总是可行的,具体取决于您的代码库。

【讨论】:

    【解决方案3】:

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

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

    【讨论】:

      【解决方案4】:

      对我来说,这听起来对 nans 很有用。希望我能想到它......

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

      我想我会使用 nan 而不是无穷大之一。使用信号 nan 并让它在第一次使用时引发事件可能会很好,但是到那时它应该在第一次使用时安静下来为时已晚。

      【讨论】:

        【解决方案5】:

        NaN 是“无值”语句的合理选择(例如,D 编程语言将它们用于未初始化的值),但由于涉及它们的任何比较都是错误的,因此您会得到一些惊喜:

        • if (result == DEFAULT_VALUE),如 Jon 所述,如果 DEFAULT_VALUE 为 NaN,则不会按预期工作。

        • 如果您不小心,它们也会导致范围检查出现问题。考虑函数:

        bool isOutsideRange(double x, double minValue, double maxValue) { 返回 x 最大值; }

        如果 x 为 NaN,此函数将错误地报告 x 在 minValue 和 maxValue 之间。

        如果您只想让用户测试一个神奇的值,我建议使用正无穷或负无穷而不是 NaN,因为它没有相同的陷阱。当你想要它的属性时使用 NaN,因为它的属性是对 NaN 的任何操作都会导致 NaN:例如,当你不想依赖调用者检查值时,它很方便。

        [编辑:我最初设法在上面输入“任何涉及它们的比较都是正确的”,这不是我的意思,而且是错误的,它们都是错误的,除了 NaN != NaN,这是真的]

        【讨论】:

        • 哪种语言使用这些比较规则?也许D会。但至少 C 和 C++ 不能以这种方式使用 NaN。所有的排序比较都是错误的。 x == NaN 对于任何 x 都是假的,包括 NaN。
        • 不,您的函数只报告它不在范围之外。它既不是内部也不是外部,这确实可能会让那些天真地使用浮点数的人感到困惑。
        • @Igor:我们在这里说同样的话。如果 x 为 NaN,isOutsideRange 将返回 false,这意味着它在范围内,但它不在。
        • @jskinner 不,这并不意味着它在范围内。本质上,NaN 无处可去。
        • @starblue:我意识到这一点。 'isOutsideRange' 是面对 NaN 输入时定义不明确的函数的一个示例:NaN 既不在范围内也不在范围外,因此返回布尔值是不合适的。这只是一个例子,说明在引入 NaN 时,表面上看起来不错的东西实际上是不正确的。
        【解决方案6】:

        小心 NaN……如果你不小心,它们会像野火一样蔓延。

        它们是浮点数的完全有效值,但任何涉及它们的赋值也将等于 NaN,因此它们会通过您的代码传播。如果您发现它作为调试工具非常好,但是如果您要发布一些东西并且某处存在边缘案例,它也可能是一个真正的麻烦。

        D 以此为理由将浮点数 NaN 作为默认值。 (我不确定我是否同意。)

        【讨论】:

        • Err... 它们传播的不只是 NaN 的点吗?结果是 NaN,这表明有问题,而不是一个看似无辜但完全不正确的数字(这可能是由于意外使用了零初始化的数字而导致的)。
        • 是和否,因为当您仅通过查看输出或明确检查 NaN 来发现 NaN 时。这样做的结果是,错误可能比它们出现的时间晚得多。另一方面,如果您使用 NULL(如果可能),您会很快得到 NPE/分段错误。残酷,但高效。
        • 如果您只知道 NaN 无处不在,它并不能完全帮助您了解它们的来源。
        【解决方案7】:

        我的感觉是它有点 hacky,但至少你使用这个 NaN 值进行运算的每个其他数字都会给出 NaN 作为结果 - 当你在错误报告中看到 NaN 时,至少你知道你是哪种错误打猎。

        【讨论】:

          【解决方案8】:

          我在类似的情况下使用了 NaN,正因为如此:通常的默认初始化值 0 也是一个有效值。到目前为止,NaN 工作正常。

          这是一个很好的问题,顺便说一下,为什么默认初始化值通常(例如,在 Java 原始类型中)是 0 而不是 NaN。也不能是 42 或其他什么?我想知道零的基本原理是什么。

          【讨论】:

          • 我认为使用 0 的理由是无论类型如何,内存都以零字节初始化,例如在 C 的 BSS 段中。
          • 是的,大概就是这样。但是既然语言/编译器设计者已经努力初始化内存,那么初始化任何任意值(除了零)不是那么容易吗?零只是其中的一部分:-)
          • @mad-j:你想用相同的位模式初始化所有内存。所以它不可能是 42,因为那样的话你通常必须对两个相邻的短裤做一些不同的事情,而不是你对一个 int 做的事情。这留下了0和-1。但是 0xffffffff 不是 -1 作为浮点数,所以你会有不一致的地方。它的内容不多,但我认为 0 可能是最好的。此外,一些硬件可以一次有效地将整个物理内存块归零,这是值得的。
          【解决方案9】:

          如果您的基本需求是有一个不代表任何可能从设备接收到的数字的浮点值,并且如果设备保证它永远不会返回 NaN,那么对我来说这似乎是合理的。

          请记住,根据您的环境,您可能需要一种特殊的方法来检测 NaN(不要只使用 if (x == float.NaN) 或任何您的等价物。)

          【讨论】:

          • 不要相信这个答案。 Jon Skeet 所要做的就是考虑变量,它会自行定义。
          • 值是定义在Skeet东西之前的一个变量名吧?