哈斯克尔的圆形NaN

时间:2017-06-06 00:28:07

标签: haskell floating-point nan

令我惊讶的是,我发现在Haskell中舍入NaN值会返回一个巨大的负数:

round (0/0)
-269653970229347386159395778618353710042696546841345985910145121736599013708251444699062715983611304031680170819807090036488184653221624933739271145959211186566651840137298227914453329401869141179179624428127508653257226023513694322210869665811240855745025766026879447359920868907719574457253034494436336205824

地板和天花板也是如此。

这里发生了什么?这种行为是有意的吗?当然,我知道任何不想要这种行为的人总是可以编写另一个检查isNaN的函数 - 但是现有的替代标准库函数是否能够更加理智地处理NaN(对于某些定义的"更加理智&# 34;?)

2 个答案:

答案 0 :(得分:9)

TL; DR: NaN2 ^ 10242 ^ 1025(未包括边界)和- 1.5 * 2 ^ 1024之间有任意表示( 一个可能的NaN碰巧就是你击中的那个。

为什么有任何推理

  

这里发生了什么?

您正在进入未定义行为的区域。或者至少这是你在其他语言中所称的那个。该报告将round定义如下:

  

6.4.6 Coercions and Component Extraction

     

ceilingfloortruncateround函数均采用实数小数参数并返回整数结果。 ... 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的工作原理。

IEE 754双精度浮点数

Haskell中的Double通常是符合IEEE 754标准的双精度浮点数,即具有64位且用

表示的数字
x = s * m * (b ^ e)

其中s是单个位,m是尾数(52位),e是指数(11位,floatRange)。 b是基础,通常是2(您可以使用floadRadix查看)。由于m的值已经规范化,因此每个格式良好的Double都具有唯一的表示形式。

IEEE 754 NaN

NaN除外。 NaN表示为e max +1, 以及非零尾数 。所以如果是位域

SEEEEEEEEEEEMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM

表示Double,表示NaN的有效方式是什么?

?111111111111000000000000000000000000000000000000000000000000000
            ^

也就是说,单个M设置为1,另一个不需要设置此概念。标志是任意的。为什么只有一个位?因为它足够了。

将NaN解释为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为前者妥协后者。