哈斯克尔的圆形NaN

ech*_*ata 16 floating-point haskell nan

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

round (0/0)
-269653970229347386159395778618353710042696546841345985910145121736599013708251444699062715983611304031680170819807090036488184653221624933739271145959211186566651840137298227914453329401869141179179624428127508653257226023513694322210869665811240855745025766026879447359920868907719574457253034494436336205824
Run Code Online (Sandbox Code Playgroud)

地板和天花板也是如此.

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

Zet*_*eta 9

TL; DR: NaN2 ^ 1024和之间有一个任意的表示2 ^ 1025(并且不包括在内),并且- 1.5 * 2 ^ 1024(这是可能的)NaN恰好是你击中的那个.


为什么有任何推理失败

这里发生了什么?

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

6.4.6强制和组分提取

ceiling,floor,truncate,和round功能各拿一个真正的分数参数,并返回一个完整的结果.... round x返回最接近的整数x,如果x两个整数之间是等距的,则返回偶数.

在我们的例子x中,并不代表一个数字开头.根据6.4.6,y = round x应该满足其他任何z来自round的codomain具有相等或更大的距离:

y = round x ? ?z : dist(z,x) >= dist(y,x)
Run Code Online (Sandbox Code Playgroud)

但是,数字的距离(也就是减法)仅适用于数字.如果我们使用

dist n d = fromIntegral n - d
Run Code Online (Sandbox Code Playgroud)

我们惹上麻烦很快:包括任何操作NaN将返回NaN再次,和比较NaN失败,所以我们的财产上述不持有任何 z,如果x是一个NaN开始.如果我们检查NaN,我们可以返回任何值,但是我们的属性适用于所有对:

dist n d = if isNaN d then constant else fromIntegral n - d
Run Code Online (Sandbox Code Playgroud)

因此,round x如果x不是一个数字,我们完全是任意的.

为什么我们不管那么大?

"好的",我听到你说,"这一切都很好,花花公子,但为什么我会得到这个数字呢?" 这是个好问题.

这种行为是有意的吗?

有些.这不是真正意图,而是可以预料的.首先,我们必须知道如何Double运作.

IEE 754双精度浮点数

DoubleHaskell中的A 通常是符合IEEE 754标准的双精度浮点数,即具有64位的数字,并用

x = s * m * (b ^ e)
Run Code Online (Sandbox Code Playgroud)

其中s是单个位,m是尾数(52位),e是指数(11位floatRange).b是基础,它通常2(你可以检查floadRadix).由于m标准化的值,每个格式良好的Double都具有唯一的表示.

IEEE 754 NaN

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

SEEEEEEEEEEEMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM
Run Code Online (Sandbox Code Playgroud)

代表a Double,什么是有效的表达方式NaN

?111111111111000000000000000000000000000000000000000000000000000
            ^
Run Code Online (Sandbox Code Playgroud)

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

将NaN解释为 Double

现在,当我们忽略了一个事实,这是一个畸形的Double-a NaN-真的,真的,真的很希望把它解释为数字,我们会得到什么号码?

m = 1.5
e = 1024

x = 1.5 * 2 ^ 1024
  = 3 * 2 ^ 1024 / 2
  = 3 * 2 ^ 1023
Run Code Online (Sandbox Code Playgroud)

瞧,这正是你获得的数字round (0/0):

ghci> round $ 0 / 0
-269653970229347386159395778618353710042696546841345985910145121736599013708251444699062715983611304031680170819807090036488184653221624933739271145959211186566651840137298227914453329401869141179179624428127508653257226023513694322210869665811240855745025766026879447359920868907719574457253034494436336205824
ghci> negate $ 3 * 2 ^ 1023
-269653970229347386159395778618353710042696546841345985910145121736599013708251444699062715983611304031680170819807090036488184653221624933739271145959211186566651840137298227914453329401869141179179624428127508653257226023513694322210869665811240855745025766026879447359920868907719574457253034494436336205824
Run Code Online (Sandbox Code Playgroud)

这让我们的小冒险陷入停顿.我们有一个NaN,它产生一个2 ^ 1024,我们有一些非零的尾数,它产生一个绝对值之间的结果2 ^ 1024 < x < 2 ^ 1025.

请注意,这不是唯一NaN可以表示的方式:

在IEEE 754中,NaN通常表示为具有指数e max + 1和非零有效数的浮点数.实现可以自由地将系统相关信息放入有效数字中.因此,没有独特的NaN,而是整个NaN族.

有关更多信息,请参阅Goldberg关于浮点数经典论文.