为什么无限地板不会出现错误?

18

我发现自己遇到了一个情况,其中执行了等价于floor $ 1/0的操作。

λ> 1/0
Infinity
这是我理解的正常行为,但当 Infinityfloorceiling 处理时,也是如此。
λ> floor $ 1/0   
179769313486231590772930519078902473361797697894230657273430081157732675805500963132708477322407536021120113879871393357658789768814416622492847430639474124377767893424865485276302219601246094119453082952085005768838150682342462881473913110540827237163350510684586298239947245938479716304835356329624224137216

不是失败,而是产生了一个非常大的数字。 为什么?

也许更重要的是,在应用另一个函数之前,我如何区分这个结果与非错误结果?


1
这个数字似乎是2^1024 :: Integer,但我不知道为什么会是那个数字。 - Wes
4
因为有人在“properFraction”代码中忘记测试无穷大。 - augustss
@Wes 嗯,IEEE 754标准中无穷大的表示是2 ^ 1023。这可能与此有关。 - Shoe
1
@Jeffrey,"真分数"不就是RealFrac类中的任何成员吗? - dfeuer
5
@Jeffrey,我并不是编造发生的事情,我知道发生了什么。当Haskell首次定义时,IEEE-754浮点数并不像现在这样占主导地位,因此没有考虑无穷大或NaN的问题。我编写了FloatDoubleproperFraction的第一个实现(据我所知),但我忘记了测试无穷大的情况。随后的重新实现也重复了这个错误。 - augustss
显示剩余2条评论
1个回答

9
第一个问题可能不太重要,因此我将先尝试回答第二个问题。
一旦你有一个数字,如果你知道它来自于floor x,你就无法知道x是否是2^1024的有效表示,或者它是否是无穷大。你可以假设超出double范围的任何值都是无效的,并且是由无穷大、负无穷大、NaN或类似值产生的。使用RealFloat中的函数之一/多个(如isNaN、isInfinite等)非常容易检查您的值是否有效。
你也可以使用这样的东西:data Number a = N a | PosInf | NegInf。然后你可以写:
instance RealFrac a => RealFrac (Number a) where 
  ...
  floor (N n) = floor n
  floor PosInf = error "Floor of positive infinity"
  floor NegInf = error "Floor of negative infinity"
  ..

哪种方法最好主要取决于您的用例。

也许对于 floor(1/0) 来说将其视为错误可能是正确的。但该值本身就是垃圾数据。处理垃圾数据还是错误更好呢?

但是为什么是 2^1024?我查看了 GHC.Float 的源代码:

properFraction (F# x#)
  = case decodeFloat_Int# x# of
    (# m#, n# #) ->
        let m = I# m#
            n = I# n#
        in
        if n >= 0
        then (fromIntegral m * (2 ^ n), 0.0)
        else let i = if m >= 0 then                m `shiftR` negate n
                               else negate (negate m `shiftR` negate n)
                 f = m - (i `shiftL` negate n)
             in (fromIntegral i, encodeFloat (fromIntegral f) n)

floor x     = case properFraction x of
                (n,r) -> if r < 0.0 then n - 1 else n

请注意,decodeFloat_Int#返回尾数和指数。根据维基百科的说法:

正无穷和负无穷的表示方法是:正无穷的符号= 0,负无穷的符号= 1。偏置指数=全部是1位。分数=全部是0位。

对于Float,这意味着底数为2 ^ 23,因为基数中有23位,并且指数为105(为什么是105?我实际上不知道。我认为它应该是255-127 = 128,但似乎实际上是128-23)。 floor的值为fromIntegral m *(2 ^ n)base *(2 ^ exponent)== 2 ^ 23 * 2 ^ 105 == 2 ^ 128 。 对于double,该值为1024。

1
这并不是很相关,但为什么properFraction会取消其参数的装箱,将其分解,然后再重新装箱才实际使用它们呢?我盯着那段代码,想知道它到底有什么意义。 - dfeuer
除此之外,shiftR m (negate n) 是怎么回事?如果 negate n 很大,文档根本不保证该移位的结果。如果 GHC 要依赖这种奇怪的未记录行为,为什么不使用明确的不安全移位呢? - dfeuer

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接