ech*_*ata 16 floating-point haskell nan
令我惊讶的是,我发现在Haskell中舍入NaN值会返回一个巨大的负数:
round (0/0)
-269653970229347386159395778618353710042696546841345985910145121736599013708251444699062715983611304031680170819807090036488184653221624933739271145959211186566651840137298227914453329401869141179179624428127508653257226023513694322210869665811240855745025766026879447359920868907719574457253034494436336205824
Run Code Online (Sandbox Code Playgroud)
地板和天花板也是如此.
这里发生了什么?这种行为是有意的吗?当然,我理解任何不想要这种行为的人总是可以编写另一个检查isNaN的函数 - 但是现有的替代标准库函数是否能够更加理智地处理NaN(对于"更理智"的某些定义)?
TL; DR: NaN
在2 ^ 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
运作.
Double
Haskell中的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
都具有唯一的表示.
除外NaN
.NaN
表示为e max +1,以及非零尾数.所以如果是位域
SEEEEEEEEEEEMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM
Run Code Online (Sandbox Code Playgroud)
代表a Double
,什么是有效的表达方式NaN
?
?111111111111000000000000000000000000000000000000000000000000000
^
Run Code Online (Sandbox Code Playgroud)
也就是说,单个M
设置为1
,另一个不需要设置此概念.标志是任意的.为什么只有一个位?因为它足够了.
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关于浮点数的经典论文.