令我惊讶的是,我发现在Haskell中舍入NaN值会返回一个巨大的负数:
round (0/0)
-269653970229347386159395778618353710042696546841345985910145121736599013708251444699062715983611304031680170819807090036488184653221624933739271145959211186566651840137298227914453329401869141179179624428127508653257226023513694322210869665811240855745025766026879447359920868907719574457253034494436336205824
地板和天花板也是如此。
这里发生了什么?这种行为是有意的吗?当然,我知道任何不想要这种行为的人总是可以编写另一个检查isNaN的函数 - 但是现有的替代标准库函数是否能够更加理智地处理NaN(对于某些定义的"更加理智&# 34;?)
答案 0 :(得分:9)
TL; DR: NaN
在2 ^ 1024
和2 ^ 1025
(未包括边界)和- 1.5 * 2 ^ 1024
之间有任意表示( 一个可能的)NaN
碰巧就是你击中的那个。
这里发生了什么?
您正在进入未定义行为的区域。或者至少这是你在其他语言中所称的那个。该报告将round
定义如下:
6.4.6 Coercions and Component Extraction
ceiling
,floor
,truncate
和round
函数均采用实数小数参数并返回整数结果。 ...round x
返回最接近x
的整数,如果x
在两个整数之间等距,则为偶数。
在我们的案例中,x
不代表开头的数字。根据6.4.6,y = round x
应该满足来自z
的密码域的任何其他round
具有相等或更大的距离:
y = round x ⇒ ∀z : dist(z,x) >= dist(y,x)
但是,数字的距离(也就是减法)仅适用于数字。如果我们使用
dist n d = fromIntegral n - d
我们很快就遇到了麻烦:包含NaN
的任何操作都会再次返回NaN
,并且NaN
上的比较会失败,因此上面的属性不适用于任何< / strong> z
如果x
开始为NaN
。如果我们检查NaN
,我们可以返回任何值,但是我们的属性适用于所有对:
dist n d = if isNaN d then constant else fromIntegral n - d
因此,如果round x
不是数字,x
将返回的内容完全是任意的。
“好的”,我听到你说,“这一切都很好,花花公子,但为什么我会得到这个数字呢?”这是一个很好的问题。
这种行为是否打算?
有点。这不是真正意图,而是可以预料的。首先,我们必须知道Double
的工作原理。
Haskell中的Double
通常是符合IEEE 754标准的双精度浮点数,即具有64位且用
x = s * m * (b ^ e)
其中s
是单个位,m
是尾数(52位),e
是指数(11位,floatRange
)。 b
是基础,通常是2
(您可以使用floadRadix
查看)。由于m
的值已经规范化,因此每个格式良好的Double
都具有唯一的表示形式。
NaN
除外。 NaN
表示为e max +1, 以及非零尾数 。所以如果是位域
SEEEEEEEEEEEMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM
表示Double
,表示NaN
的有效方式是什么?
?111111111111000000000000000000000000000000000000000000000000000
^
也就是说,单个M
设置为1
,另一个不需要设置此概念。标志是任意的。为什么只有一个位?因为它足够了。
Double
现在,当我们忽略这是一个格式错误的Double
- NaN
- 而且实际上,真的, 真的这一事实> 希望将其解释为数字,我们会得到多少数字?
m = 1.5
e = 1024
x = 1.5 * 2 ^ 1024
= 3 * 2 ^ 1024 / 2
= 3 * 2 ^ 1023
瞧,这正是你为round (0/0)
获得的数字:
ghci> round $ 0 / 0
-269653970229347386159395778618353710042696546841345985910145121736599013708251444699062715983611304031680170819807090036488184653221624933739271145959211186566651840137298227914453329401869141179179624428127508653257226023513694322210869665811240855745025766026879447359920868907719574457253034494436336205824
ghci> negate $ 3 * 2 ^ 1023
-269653970229347386159395778618353710042696546841345985910145121736599013708251444699062715983611304031680170819807090036488184653221624933739271145959211186566651840137298227914453329401869141179179624428127508653257226023513694322210869665811240855745025766026879447359920868907719574457253034494436336205824
这让我们的小冒险陷入停顿。我们有一个NaN
,它产生一个2 ^ 1024
,我们有一些非零的尾数,它产生的结果的绝对值介于2 ^ 1024 < x < 2 ^ 1025
之间。
请注意,这不是NaN
可以表示的唯一方式:
在IEEE 754中,NaN通常表示为浮点数,指数为e max + 1和非零有效数。实现可以自由地将系统相关信息放入有效数字中。因此,没有独特的NaN,而是整个NaN族。
有关详细信息,请参阅classic paper on floating point numbers by Goldberg。
答案 1 :(得分:2)
这一直是人们观察到的一个问题。以下是针对GHC就此主题提交的一些门票:
不幸的是,这是一个棘手的问题,有很多分歧。我个人认为这是一个真正的错误,它应该通过抛出错误来正确修复。但是您可以阅读这些故障单上的注释,以了解阻止GHC实施正确解决方案的棘手问题。从本质上讲,它归结为速度与正确性,这是(i)Haskell报告严重不足的一点,以及(ii)GHC为前者妥协后者。