【问题标题】:IEEE floating point signalling NaN (sNaN) in HaskellHaskell 中的 IEEE 浮点信令 NaN (sNaN)
【发布时间】:2014-01-24 23:07:46
【问题描述】:

有没有办法在 Haskell 中定义信号 NaN?我找到了两种处理 NaN 的方法:

1) 使用 0/0,产生相当的 nan

2) 包Data.Number.Transfinite,它也没有信令NaN。

PS 有没有什么办法不用写C库就可以将Word64逐位放入Double?

【问题讨论】:

  • Prelude 支持使用isNaN 来检查一个数字是否为NaN,甚至RealFloat 也有isIEEE 的功能。您可以使用ieee754 来获得更具体的功能。
  • @bheklilr,ieee754 模块也没有信号 NaN。检查 NaN 非常简单:x == x 是 False。
  • 如果要将Word64逐位转换为Double,可以使用unsafeCoerce。对于字节大小不同或装箱级别不同的其他类型,您可能会得到未定义的行为。
  • 您可以使用 unsafeCoerce 在 GHC 中以非常不便携的方式在 Word64Double 之间逐位转换,但仍然不支持 sNaN,因为它没有在 @ 中实现987654336@ 实例。 NaN 就像 0.0/0 一样。

标签: haskell floating-point ieee-754


【解决方案1】:

我发现了一种非便携方式:

{-# LANGUAGE ForeignFunctionInterface #-}
import Data.Word (Word64, Word32)
import Unsafe.Coerce
import Foreign
import Foreign.C.Types
foreign import ccall "fenv.h feenableexcept" -- GNU extension
    enableexcept :: CInt -> IO ()

class HasNAN a where
    signalingNaN :: a
    quietNaN :: a

instance HasNAN Double where
    signalingNaN = unsafeCoerce (0x7ff4000000000000::Word64)
    quietNaN = unsafeCoerce (0x7ff8000000000000::Word64)

instance HasNAN Float where
    signalingNaN = unsafeCoerce (0x7fa00000::Word32)
    quietNaN = unsafeCoerce (0x7fc00000::Word32)

main = do
    enableexcept 1 -- FE_INVALID in my system
    print $ show $ 1 + (quietNaN :: Float) -- works
    print $ show $ 1 + (signalingNaN :: Float) -- fails

这完全失败了。事实证明,FPU 异常对 Haskell 来说是个坏主意。默认情况下它们被禁用是有充分理由的。如果您在 gdb 中调试 C/C++/其他东西,它们就可以了。由于 Haskell 的非强制性性质,我不想调试 Haskell 核心转储。启用 FE_INVALID 异常会导致 0/0 并添加到 Data.Number.TransfiniteGHC.Real 中的 NaN 崩溃。但是在 enableexcept 之前计算的 0/0 不会另外产生异常。

我将在我的任务中使用一些简单的错误检查。我只需要在一个地方sNaN

【讨论】:

    【解决方案2】:

    使用Data.Maybe怎么样?

    您将使用Maybe Float 作为数据类型(假设您想使用Float),并使用Just x 作为非NaN 值x,而Nothing 将表示NaN。

    但是,您至少需要 radd 一个 Num 实例才能使用 Maybe Float 而不是 Float 进行计算。您可以使用fromJust 作为实用函数。

    这是否表示为 qNaNsNaN 完全取决于您的实现。

    【讨论】:

    • 也许是相当 NaN 的替代品,而不是信号。 1 + sNaN = 异常,1 + qNaN = qNaN。我更喜欢一些 hack 来定义 sNaN。它看起来比定义 Num 实例更简单。
    • @user3161163 简单定义一个抛出errors 的Num 实例会有什么问题?这一点都不难。使用Maybe完成什么类型​​的NaN完全取决于运算符定义......
    • 我的意思是,如果我将 Maybe 作为 Monad 处理,我会得到类似 qNaN 的东西
    • @user3161163 也许我没有正确理解你的问题,但你为什么会得到 qNaN? Maybe 本身并没有定义任何数值运算。 instance Num (Maybe Float) 将是您声明的内容。例如(+) Nothing _ = error "signal nan"(+) _ Nothing = error "signal nan"(+) (Just a) (Just b) = (fromJust a) + (fromJust b)?
    • 我的意思是,Maybe 与 Monad 和 Num 实例的工作方式不同,这有点令人惊讶。也许语义比 sNaN 更接近 qNaN。当然我可以做这样的 Num 实例。可能是我自己的Maybe。无论如何,这不是我的问题的解决方案,我不想在我的代码中将所有 Float 替换为 Maybe Float。 PS你的回答是非常Haskell方式,适用于其他一些情况。
    【解决方案3】:

    您可以使用自定义运算符而不是这样的自定义类型(这样可以避免替换代码中的任何Float

    snanPlus :: Float -> Float -> Float
    snanPlus a b = if isNaN(a) then error "snan"
                               else if isNaN(b)
                                    then error "snan"
                                    else a + b
    
    -- Some testing code
    main = do
        print $ 3.0 `snanPlus` 5.0 -- 8.0
        print $ (0/0) `snanPlus` 5.0 -- error
    

    第二个print 触发错误。

    注意:我不确定是否有更好的格式化方法,您可能不应该在函数签名中使用具体类型。

    【讨论】:

      【解决方案4】:

      您可以使用Data.Ratio 通过使用比率 1/0(无穷大)或 0/0 (NaN) 来生成 Nan/Infinity。

      一种更快但便携性较差的方法是使用导出infinitynotANumberGHC.Real

      infinity, notANumber :: Rational
      infinity   = 1 :% 0
      notANumber = 0 :% 0
      

      用法:

      Prelude Data.Ratio GHC.Real> fromRational notANumber :: Float
      NaN
      

      对于检查NaN/infinity,Prelude有两个函数isNaNisInfinite

      【讨论】:

      • 我想,OP 知道这一点。
      • 我发现这个问题很难解释,所以我可能答错了。
      • OP 询问 Haskell/GHC 是否支持信号 NaN(如果在算术表达式中使用会引发某种错误)。
      • AFAIK 你在描述@kvanberendonck 是 qNaN
      • 也许包装器类型在这里会有所帮助。
      【解决方案5】:

      你可以这样做:

      newtype SNaN a = SNaN { unSNaN :: a}
      
      
      liftSNaN :: RealFloat a => (a -> a) -> (SNaN a -> SNaN a)
      liftSNaN f (SNaN x)
        | isNaN x = error "NaN"
        | otherwise = SNaN . f $ x
      
      liftSNaN' :: RealFloat a => (a -> b) -> (SNaN a -> b)
      liftSNaN' f (SNaN x)
        | isNaN x = error "NaN"
        | otherwise = f $ x
      
      liftSNaN2 :: RealFloat a => (a -> a -> a) -> (SNaN a -> SNaN a -> SNaN a)
      liftSNaN2 f (SNaN x) (SNaN y)
        | isNaN x || isNaN y = error "NaN"
        | otherwise = SNaN $ f x y
      
      liftSNaN2' :: RealFloat a => (a -> a -> b) -> (SNaN a -> SNaN a -> b)
      liftSNaN2' f (SNaN x) (SNaN y)
        | isNaN x || isNaN y = error "NaN"
        | otherwise = f x y
      
      
      instance RealFloat a => Eq (SNaN a)
        where (==) = liftSNaN2' (==)
              (/=) = liftSNaN2' (/=)
      
      
      instance RealFloat a => Ord (SNaN a)
        where compare = liftSNaN2' compare
              (<) = liftSNaN2' (<)
              (>=) = liftSNaN2' (>=)
              (>) = liftSNaN2' (>)
              (<=) = liftSNaN2' (<=)
              max = liftSNaN2 max
              min = liftSNaN2 min
      
      
      instance (Show a, RealFloat a) => Show (SNaN a)
        where show = liftSNaN' show
      
      
      instance RealFloat a => Num (SNaN a)
        where (+) = liftSNaN2 (+)
              (*) = liftSNaN2 (*)
              (-) = liftSNaN2 (-)
              negate = liftSNaN negate
              abs = liftSNaN abs
              signum = liftSNaN signum
              fromInteger = SNaN . fromInteger
      
      
      instance RealFloat a => Fractional (SNaN a)
        where (/) = liftSNaN2 (/)
              recip = liftSNaN recip
              fromRational = SNaN . fromRational
      

      当然,您需要更多的类型类来获得完整的Float 体验,但是正如您所看到的,一旦定义了liftSNaN* 函数,它就变得非常简单了。鉴于此,SNaN 构造函数将任何 RealFloat 类型中的值转换为如果它是 NaN 并且您在任何操作中使用它时会爆炸的值(其中一些您可能想要处理 NaN,也许是 ==和/或show;您可以根据口味改变)。 unSNaN 将任何 SNaN 转换回安静的 NaN 类型。

      它仍然没有直接使用Float(或Double,或其他)类型,但如果你只是改变你的类型签名,几乎一切都会奏效;我给出的NumShow 实例意味着数字文字与SNaN Float 一样容易被接受,就像Float 一样,它们show 也一样。如果您厌倦了在类型签名中输入SNaN,您可以轻松地type Float' = SNaN Float,甚至:

      import Prelude hiding (Float)
      import qualified Prelude as P
      
      type Float = SNaN P.Float
      

      虽然我敢打赌这最终会给某人带来困惑!但是,完全相同的源代码应该可以编译和工作,前提是您已经填写了您需要的所有类型类,并且您没有调用任何其他代码,您无法修改硬编码特定的具体类型(而不是接受适当类型类中的任何类型)。

      这基本上是对 Uli Köhler 为Maybe Float 提供Num 实例的第一个建议的详细说明。我只是直接使用 NaN 来表示 NaN,而不是 Nothing,并使用 isNan 来检测它们,而不是对 Maybe(或 isJust)进行案例分析。

      使用新类型包装器优于Maybe 的优点是:

      1. 您避免在操作中引入另一个“无效”值(Just NaNNothing),在转换为/从常规浮点数转换时您必须担心。
      2. 新类型在 GHC 中未装箱; SNaN Float 在运行时与对应的Float 相同。因此Just 单元格没有额外的空间开销,并且在SNaN FloatFloat 之间来回转换是免费操作。 SNaN 只是一个标签,用于确定您是否希望将隐式“如果 NaN 则爆炸”检查插入到您的操作中。

      【讨论】:

        猜你喜欢
        • 2017-06-15
        • 1970-01-01
        • 2017-03-13
        • 1970-01-01
        • 2012-03-09
        • 2012-01-06
        • 2014-10-18
        • 2020-06-09
        • 1970-01-01
        相关资源
        最近更新 更多