IEEE754 NaN值返回false的所有比较的基本原理是什么?
为什么比较NaN值的行为与所有其他值不同? 也就是说,所有与运算符==,<=,> =,<,>的比较,其中一个或两个值是NaN的返回值都是false,与所有其他值的行为相反。
我认为这在某种程度上简化了数值计算,但是我找不到一个明确的原因,即使在Kahan的关于IEEE 754地位的讲稿中也没有详细讨论其他的devise决定。
这种不正常的行为在做简单的数据处理时会造成麻烦。 例如,当对C程序中的某些实值字段进行sorting时,我需要编写额外的代码来处理NaN作为最大元素,否则sortingalgorithm可能会变得混乱。
编辑:到目前为止的答案都认为比较NaN是没有意义的。
我同意,但这并不意味着正确的答案是错误的,而是一个非布尔(NaB),幸运的是它不存在。
所以在我看来,对于比较的select是真实的还是错误的,对于一般的数据处理来说,如果服从通常的规律(==的reflection性,<,==,>的三分法),将是有利的,以免数据结构依靠这些法律变得混乱。
所以我要求打破这些规律的一些具体优势,而不仅仅是哲学推理。
编辑2:我想现在我明白了为什么制作NaN最大是一个坏主意,它会搞乱上限的计算。
NaN!= NaN可能需要避免检测循环中的收敛,如
while (x != oldX) { oldX = x; x = better_approximation(x); }
然而,最好通过比较绝对差异和小的限制来写出。 所以恕我直言,这是一个相对较弱的NaN打破反身性的论据。
我是IEEE-754委员会的成员,我会尝试帮助澄清一些事情。
首先,浮点数不是实数,浮点算术不能满足实数算术的公理。 三分法并不是真正算术的唯一属性,它不适用于花车,甚至是最重要的。 例如:
- 加法不是联想。
- 分配法不成立。
- 有没有逆的浮点数。
我可以继续。 指定一个固定大小的算术types是不可能的,它满足我们所知和所爱的所有真实算术性质。 754委员会必须决定折中一些。 这是由一些非常简单的原则指导的:
- 当我们可以的时候,我们匹配真正的算术行为。
- 当我们做不到的时候,我们试图把这些违规行为尽可能地预测和诊断。
关于你的评论“这并不意味着正确的答案是错误的”,这是错误的。 谓词(y < x)
询问y
是否小于x
。 如果y
是NaN,那么它不小于任何浮点值x
,所以答案必然是错误的。
我提到三分法不适用于浮点值。 然而,有一个类似的财产,持有。 754-2008标准第5.11条第2款:
四个相互排斥的关系是可能的:小于,等于,大于,无序。 最后一种情况是至less有一个操作数是NaN。 每一个NaN都会与无所不包的东西,包括它本身进行比较。
就编写额外的代码来处理NaN而言,通常可能(虽然不是那么容易)以NaN通过适当的方式来构build代码,但事实并非总是如此。 如果不是这样,则可能需要一些额外的代码,但是代数闭包给浮点运算带来便利的代价是很小的代价。
附录:许多评论者认为,保持平等和三分法的反身性会更有用,理由是采用NaN = NaN似乎并不能保留任何熟悉的公理。 我承认对这个观点有一些同情,所以我想我会重新回顾这个答案,并提供更多的背景。
我和卡汉谈话的理解是,NaN!= NaN源于两个实用的考虑:
-
x == y
应该尽可能等价于x - y == 0
(除了作为一个真正的算术定理之外,这使硬件实现比较更加节省空间,这在开发标准的时候是非常重要的 -但是请注意,这违反了x = y =无穷大,所以它不是一个很好的理由;它可以合理地弯曲到(x - y == 0) or (x and y are both NaN)
) 。 -
更重要的是,在8087algorithm中NaNforms化时,没有
isnan( )
谓词; 有必要为程序员提供一种方便而有效的检测NaN值的方法,而这种方法不依赖于像isnan( )
这样需要多年的编程语言。 我会引用卡汉自己关于这个问题的着作:
如果没有办法摆脱NaN,那么它们就像CRAY上的Indefinites一样无用。 只要碰到一个,计算就会被最好的停止,而不是无限期地持续到无限期的结论。 这就是为什么NaN上的一些操作必须提供非NaN结果。 哪些操作? …例外是C谓词“x == x”和“x!= x”,对于每个无限或有限数x,它们分别是1和0,但是如果x不是一个数(NaN)则相反。 这些提供了在NaN和谓词IsNaN(x)中缺lessNaN的语言中NaN和数字之间唯一简单的普通区别。
请注意,这也是排除返回类似“Not-A-Boolean”的逻辑。 也许这个实用主义是错位的,标准应该要求isnan( )
,但是这将使NaN几乎不可能有效和方便地使用几年,而世界等待编程语言的采用。 我不相信这将是一个合理的权衡。
直言:NaN == NaN的结果现在不会改变。 更好的学习和生活在一起,而不是在互联网上抱怨。 如果你想争论一个适合于容器的订单关系也应该存在,我build议你主张你最喜欢的编程语言实现IEEE-754(2008)标准化的totalOrder
谓词。 事实上,这并没有说明卡汉关心的有效性,这激发了目前的状况。
NaN可以被认为是一个未定义的状态/数字。 类似于0/0的定义是未定义的或sqrt(-3)(在浮点所在的实数系统中)。
NaN被用作这种未定义状态的一种占位符。 math上讲,undefined不等于undefined。 你也不能说一个未定义的值大于或小于另一个未定义的值。 因此所有的比较都返回false。
在比较sqrt(-3)到sqrt(-2)的情况下,这种行为也是有利的。 他们都会返回NaN,但即使他们返回相同的值,他们也是不相同的。 因此,在处理NaN时平等总是返回false是所期望的行为。
扔另一个比喻。 如果我把两个箱子交给你,告诉你他们两个都不包含一个苹果,你能告诉我这个箱子里有同样的东西吗?
NaN不包含有关什么是什么的信息,只是它不是。 因此,这些因素绝对不能说是平等的。
从NaN上的维基百科文章,以下做法可能会导致NaN:
- 所有的math运算>至less有一个操作数
- 分0/0,∞/∞,∞/-∞,-∞/∞和-∞/-∞
- 乘法0×∞和0×∞
- 加∞+(-∞),(-∞)+∞和等价减法。
- 将一个函数应用于其域外的参数,包括取一个负数的平方根,取一个负数的对数,取90度(或π/ 2弧度)的奇数倍的正切,或者取相反的正弦或小于-1或大于+1的数字的余弦。
由于无法知道哪些操作创build了NaN,因此无法比较这些操作是否合理。
我不知道devise原理,但这是IEEE 754-1985标准的摘录:
“即使操作数的格式不同,也应该可以比较所有支持的格式的浮点数,比较是精确的,绝不会溢出也不会下溢,有四种相互排斥的关系是可能的:小于,等于,大于和无序最后一种情况是至less有一个操作数是NaN,每一个NaN都将与所有事物(包括它自己)无序地进行比较。
它看起来很奇特,因为大多数允许NaN的编程环境不允许使用3值逻辑。 如果将三值逻辑放入混合中,则变得一致:
- (2.7 == 2.7)= true
- (2.7 == 2.6)= false
- (2.7 == NaN)=未知
- (NaN == NaN)=未知
即使.NET不提供一个bool? operator==(double v1, double v2)
bool? operator==(double v1, double v2)
运算符,所以你仍然坚持傻(NaN == NaN) = false
结果。
我猜测NaN(不是数字)的意思是:这不是一个数字,因此比较它并没有什么意义。
这有点像SQL中的null
算术,它们都是null
操作数。
浮点数的比较比较数值。 因此,它们不能用于非数字值。 NaN因此无法在数字意义上进行比较。
过分简化的答案是NaN没有数字值,所以没有什么比较其他的东西。
你可能会考虑testing并用+ INFreplace你的NaN,如果你想让它们像+ INF一样行事的话。
虽然我同意NaN与任何实数的比较应该是无序的,但我认为将NaN与自身进行比较是正当的。 例如,如何发现信号NaN和静音NaN之间的区别? 如果我们将这些信号看作是一组布尔值(即一个位向量),可能会问这些位向量是相同的还是不同的,并相应地对这些集合进行sorting。 例如,在对最大偏差指数进行解码时,如果有效位左移,以便将二进制格式的最高有效位上的有效位的最高有效位alignment,则负值将是安静的NaN,并且任何正值都会是一个信号NaN。 零当然是保留给无穷大,比较将是无序的。 MSBalignment将允许从不同的二进制格式直接比较信号。 两个具有相同信号的NaN因此将是等同的,并赋予平等意义。
NaN是一个隐含的新实例(特殊types的运行时错误)。 这意味着NaN !== NaN
出于同样的原因, new Error !== new Error
;
记住这样的隐含也可以看到外面的错误,例如在正则expression式的上下文中它意味着/a/ !== /a/
这只是new RegExp('a') !== new RegExp('a')
语法糖new RegExp('a') !== new RegExp('a')
因为math是数字“存在”的领域。 在计算中,您必须初始化这些数字并根据您的需要保持其状态。 在过去的那些日子里,内存初始化是以你永远不能依赖的方式工作的。 你永远不能允许自己想这个“哦,这将始终与0xCD初始化,我的algorithm不会破产” 。
所以你需要适当的非混合溶剂, 粘性足以不让你的algorithm被吸入和破碎。 涉及数字的好algorithm大部分将与关系一起工作,而那些()关系将被省略。
这只是在创build时可以放入新variables的油脂,而不是从计算机内存编程随机地狱。 而你的algorithm,不pipe它是什么,都不会中断。
接下来,当你仍然突然发现你的algorithm正在生成NaN时,可以将其清理出来,逐个查看每个分支。 再一次,“永远虚假”的规则在这方面有很大的帮助。
对我来说,最简单的解释是:
我有东西,如果不是苹果,那它是橙色的吗?
你不能比较NaN与其他事件(事件本身),因为它没有值也可以是任何值(数字除外)。
我有东西,如果它不等于一个数字那么它是一个string?