解析NaN
此文为自译文,且第一次翻译,有不足之处。
原英文地址:https://en.wikipedia.org/wiki/NaN
我的理解
32位下二进制的 NaN 存储格式为s111 1111 1111 1xxx xxxx xxxx xxxx xxxx
,s
是信号(应用程序中经常忽略的)。
指数位全为1,有效位至少一位是0代表 NaN。
指数位全为1,有效位全为0代表无穷大也就是 Infinity。
现在的应用程序中并没有要求对每一种 NaN 都提供唯一的变量来表示。
以上可得:
NaN 不等于 NaN
NaN
来自免费百科全书-维基百科
其他用途,参见Nan (disambiguation)
在计算领域中,NaN,并不代表一个数字,是数字数据类型的一个成员,可以认为是一个未定义或者无法表述的值,尤其是在浮点运算中。1985年,不同NaN的系统化使用与其他无穷数比如 infinities 的描述,一起纳入了 IEEE754 浮点标准。
在数学中,0/0 没有作为实际的数字去定义,因此在计算机中由 NaN 来表示。负数的平方根也不是一个实际的数字,所以也是由 NaN 来表示。NaN们也可以用来表示计算中的缺失值。
NaN们由有两种不同的类型,称为静止 NaN 和信号 NaN。静止NaN 用来传达来自无效计算或者值导致的错误。信号NaN 支持一些高级的特性,比如混合数字和符号的计算或者基于基本浮点计算的其他拓展。
浮点
在浮点计算中,NaN 与无穷大不同,尽管两者通常都是作为在真实数字的浮点表示以及浮点运算中的特殊情况处理。一个无效的操作与数字向上溢出(可能返回无穷大-infinity)或者数字向下溢出(返回最小标准数,非标准数或者0)是不同的。
IEEE754 的 NaN 的编码是由指数段全部为1(与infinity的值一样),以及有效位数中的一些非零数字(与infinity的值的表示区别开);这个允许定义多个不同的 NaN 值,取决于有效位数中的设置的位,而且还取决于前导符号位的值(但是应用程序并不要求为这些不同的 NaN 值提供区别的语义)。
例如,一个精确到位的 IEEE754 的浮点标准中的单精度(32位)NaN会是:s111 1111 1111 1xxx xxxx xxxx xxxx xxxx
,s
是信号(应用程序中经常忽略的),x
序列表示一个非零数字(为0时会编码为 infinity)。第一个x
用来表示 NaN 的类型:静止 NaN 或者信号 NaN。剩余的位被编码为有效载荷(应用程序中经常忽略的)。
除了有序比较之外的浮点数操作通常会传播一个静止 NaN(qNaN)。信号NaN(sNaN)上的大部分浮点数操作都表示一个无效的操作异常;默认的异常操作与qNaN运算对象相同,如果产生浮点结果,它们会产生一个qNaN.
通过算数操作的静止NaN 的传播允许错误在一系列操作结束时被探测到,而不需要在中间阶段时进行大量的测试。例如,一个用 NaN 开始,并且连续加了五次 1 ,每一次加法都会产生 NaN ,但是没有必要去检查每一次计算因为只能注意到最终结果是 NaN。然而,由于语言和功能的不同,一个计算链中的某一计算会为所有其他的浮点值给一个固定的结果时,NaN 可以从这个计算链中默默地被删除。例如,x^0
这个计算,即使x
是 NaN,也会产生结果 1,所以只检查最终结果会掩盖在x^0
之前x
是 NaN 的事实。一般来说,其后的对设置的无效标识的测试需要监测到声明 NaN 的所有情况(详细细节,请参见下面的 Function definition )。
在当前的 IEEE 754-2008 标准的6.2章节中,有两个异常的函数(maxNum 和 minNum 函数,会返回两个预期是数字的操作数之间的最大),它们所支持的数字-如果操作数之中只有一个是 NaN,会返回另一个操作数的值。在下一个版本的 IEEE 754 标准中,计划替代这些函数,因为它们之间没有关联性(当操作数中出现一个信号NaN时)。
NaN 的比较
与 NaN 的比较,即使是和它自身比较,始终返回一个无序结果。比较的谓词是静止NaN 运算数上的信令性或者非信令性的任何一个;信令性版本发出此类比较无效操作异常的信号。等式和不等式的谓词是非信令性的,所以x=x
返回 false 可以用来测试x
是否是静止NaN。其他标准的比较谓词,如果他们收到一个 NaN 操作数,就全部都是信令性。这个标准也提供了这些谓词的非信令性版本。谓词isNaN(x)
确定一个值是否是 NaN ,从不会发出异常的信号,即使x
是一个信号NaN。
NaN 和浮点值 x 之间的比较(包括 NaN 和 ±∞)
比较 | NaN ≥ x | NaN ≤ x | NaN > x | NaN < x | NaN = x | NaN ≠ x |
---|---|---|---|---|---|---|
结果 | false | false | false | false | false | true |
生成 NaN 的操作
有三种操作可以返回 NaN:
- 至少有一个 NaN 操作数的大部分操作
- 不定形式
- 除法(±0/±0)和(±∞/±∞)
- 乘法(±0*±∞)和(±∞*±0)
- 除余(x%y)
x
是一个无穷数或者y
是0 - 加法((+∞) + (-∞))((-∞) + (+∞))和减法((+∞) - (+∞))((-∞) - (-∞))
- 不同幂函数的标准
- 标准
pow
函数和整数幂pown
函数定义了0º,1∞,∞º等于1 powr
函数将所有三种不确定式定义为无效操作,因此返回 NaN
- 标准
- 具有复杂结果的操作,比如:
- 负数的平方根
- 负数的对数
- 小于-1或者大于1的数字的反正弦或者余弦
NaN 也可以明确分配给变量,通常表示一个缺失值。在 IEEE 754 标准之前,程序员经常用一个特殊值(比如 -99999999)来表示未定义或者缺失值,但是这也无法保证他们可以一致或者正确地处理它们。
NaN 在以上这些情况中没有必要产生。如果一个操作会产生一个异常的状态并且这个陷阱没有被掩盖,那么这个操作将会导致一个陷阱。如果一个操作数是静止NaN,并且没有一个信号NaN 操作数,那么不会有异常状态并且结果是静止NaN。显式的分配也不会造成异常即使是信号NaN。
静止NaN
静止NaN,或者说 qNaN,当它们在大部分操作中传播时,不会造成任何额外的异常。例外是 NaN 并不会简单地毫不改变地输出,例如在格式转换或者某些比较操作中。
信号NaN
信号NaN,或者说 aNaN,是 NaN 的一种特殊形式,当被大部分操作消耗时,会造成无效操作异常,然后,适当情况下,静止为之后可能传播的 qNaN。它们在 IEEE 754 中被引入。关于如何使用它们,有一些想法:
- 如果在初始化之前就使用数据,用信号NaN 去填充未初始化的内存将产生无效操作异常。
- 用 sNaN 作为更复杂的对象的占位符,例如:
- 表示下溢数字
- 表示上溢数字
- 更高精度格式的数字
- 复数
当遇到时,陷阱处理程序可以解码 sNaN 并且返回一个索引给计算结果。实际上,这个方法面临许多并发症。某些简单操作(比如绝对值)的 NaN 的信号位的处理是不同于算术操作的结果的。陷阱在标准中没有要求。还有其他的方法使得这类问题变得更轻便。(翻译2:还有其他的方法可以解决这类问题。)
函数定义
对接收一个静止NaN 作为输入的数字函数结果的正确定义有很多不同的意见。一种观点认为,在所有情况下,这个 NaN 应该传播给函数的输出来传播错误指示。另一种观点,通常被 ISO C99 和 IEEE 754 标准采纳的,是如果这个函数有多个参数,并且所有的非NaN输入(包括无穷大)的输出是唯一确定的,那么这个值应该是结果。因此比如 hypot(±∞, qNaN) 和 hypot(qNaN, ±∞) 的返回结果是 ±∞。
尤其尖锐的问题是幂函数pow(x,y)=x^y
。表达式0º,∞º和1∞ 作为极限出现(就像 ∞ * 0)时,被认为是不确定式,零的零次方是否应该等于1这个问题也有了不同的观点。
如果当一个参数是未定义的,输出也被认为是未定义的,那么pow(1,qNaN)
就会产生一个 qNaN。然而,数学库通常为任意真实数字y
的pow(1,y)
返回1,即使y
是无穷大。同样的,它们也会为pow(x,0)
产生结果1即使x
是0或者无穷大。不定式返回值为1的基本原理是如果这个值是在所有极限值的范围内需要澄清,除了围绕在参数的极限值附近区域内的几乎消失的一小部分,单点函数的值可以被视为一个特定的值。2008版本的 IEEE 754 标准规定pow(1,qNaN)
和pow(qNaN,0)
都应该返回1,因为任何东西代替了 qNaN,它们都返回1。此外,ISO C99 以及之后的 IEEE 754-2008 ,都选择了指定的pow(-1,±∞)=1
来替代 qNaN;这样选择的原因在 C 的基本原理里可以找到:"通常, C99 在有用的数字值中,避开了 NaN 结果。...pow(-2,∞)
的结果是+∞,因为所有大的正浮点数都是偶数整数" 。
为了满足对幂函数的行为有更严格的解释的希望,2008版本定义了两种额外的幂函数:pown(x,n)
,指数必须是整数,和powr(x,y)
,每当有一个参数是 NaN 或者求幂会给出一个不定式就会返回 NaN。
整数 NaN
大部分固定长度的整数形式不会明确表明无效数字。这种情况下,转换 NaN 为一个整数类型时,IEEE 754 标准规定了要发出无效操作异常的信号。例如,在 Java 中,此类操作会抛出 java.lang.ArithmeticException
的实例。在 C 中,它们会导致不确定行为,但是如果支持annex F ,这个操作会产生一个 "invalid"浮点异常(按照 IEEE 标准的要求)和一个未指定的值。
Perl 的Math::BigInt
包为不代表有效数字的字符串结果用"NaN":
> perl -mMath::BigInt -e "print Math::BigInt->new('foo')"
NaN
显示
不同的操作系统和代码语言会有不同的 NaN 的字符串表示形式。
nan
NaN
NaN%
NAN
NaNQ
NaNS
qNaN
sNaN
1.#SNAN
1.#QNAN
-1.#IND
实际上,由于编码 NaN 有符号,静止/信号位以及可选的'诊断信息'(有时也叫有效负载),因此它们也可以在 NaN 的字符串表达形式中被找到,比如:
-NaN
NaN12345
-sNaN12300
-NaN(s1234)
(存在其它变体)
编码
在符合 IEEE 754 标准的浮点存储格式中,NaN 由 NaN 特有的特定的,预定义的位模式来定义。符号位无所谓。二进制格式的 NaN 由指数段全部为1(与infinity的值一样),以及有效位数中的一些非零数字(与infinity的值的表示区别开)。1985年的原始版本的 IEEE754 (IEEE 745-1985)只描述了字节浮点格式,没有指出如何标记信号/静止状态,实际上,有效字段中的最高有效位决定了一个 NaN 是信号性还是静止性。两个不同的实现,有相反的意义:
- 大部分处理器(包括 Intel 和 AMD 的 x86 系列,Motorola 68000 系列 ,AIM PowerPC 系列, ARM 系列, Sun SPARC 系列,任意的新 MIPS 处理器)如果 NaN 为静止,设置了信号/静止位为非0,如果 NaN 是信号性,设置为0。因此,在这些处理器中,这个字节位代表了"静止(is_quiet)"标识。
- 在 PA-RISC 和老的 MIPS 处理器中所生成的 NaN,如果 NaN 是静止的,信号/静止位是0,如果 NaN 是信号性,设置为1。因此,在这些处理器中,这个字节位代表了"信号(is_signaling)"标识。
前一种选择是首选,因为它允许仅仅通过将信号/静止位设置为1来实现静止一个信号NaN。相反,后一个选择不能做到,因为将信号/静止位设置为0可能产生一个无穷大。
2008年的 IEEE 754 标准的修订版(IEEE 754-2008)对信号/静止位的编码提出了正式建议。
- 二进制格式中,有效字段的最高有效位应该是"静止(is_quiet)"标识。比如,如果 NaN 为静止,这个位为非0,如果 NaN 是信号性,设置为0。
- 十进制格式中,无论是二进制格式还是十进制格式,通过将符号位后面的前五个字段设置为1来表示 NaN。第六位是是"静止(is_quiet)"标识。这个标准遵循了作为"信号(is_signaling)"标识的解释。比如,如果 NaN 是静止的,信号/静止位是0,如果 NaN 是信号性,则为非零。通过清除这个第六位可以静止一个信号NaN。
为了与 IEEE 754-2008 保持一致,在最新的 MIPS 处理器中可以通过 FCSR 寄存器的 NAN2008 字段来配置关于信号/静止位的含义。这个支持在 MIPS 3版本中可选,MIPS 5版本要求。
标准中没有定义有效字段剩余位的状态/值。此值称为NaN的“有效负载”。如果一个操作有一个单独的 NaN 输入并将它传播给了输出。结果 NaN 的有效载荷应该是输入的 NaN 的有效载荷(如上所述,当信号/静止位状态编码为"信号(is_signaling)"标识,对于二进制格式来说并不总是可能的)。如果有多个 NaN 输入,结果 NaN 的有效载荷应该是输入的 NaN 之一的有效载荷;标准中并未指定哪一个。