Haskell中的IEEE浮点信令NaN(sNaN)

时间:2014-01-24 23:07:46

标签: haskell floating-point ieee-754

有没有办法在Haskell中定义信令NaN?我发现了两种处理NaN的方法:

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

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

PS有没有办法在没有编写C库的情况下将Word64逐位放入Double中?

5 个答案:

答案 0 :(得分:2)

如何使用Data.Maybe

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

但是,您需要至少使用Num实例来使用Maybe Float而不是Float进行计算。您可以使用fromJust作为实用功能。

这是否表示为 qNaN sNaN 完全取决于您的实施。

答案 1 :(得分:2)

我找到了一种不可移植的方法:

{-# 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

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

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

答案 2 :(得分:0)

您可以使用自定义运算符代替此类自定义类型(这可以避免替换代码中的任何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会触发错误。

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

答案 3 :(得分:0)

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

更快但不太便携的方法是使用导出GHC.Realinfinity的{​​{1}}。

notANumber

用法:

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

要检查Prelude Data.Ratio GHC.Real> fromRational notANumber :: Float NaN / NaN,Prelude有两个功能infinityisNaN

答案 4 :(得分:0)

您可以这样做:

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

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

这基本上是对UliKöhler第一个为Num提供Maybe Float实例的建议的阐述。我刚刚使用NaN来代表NaN而不是Nothing,并使用isNan来检测它们而不是Maybe(或isJust)上的案例分析。< / p>

Maybe上使用newtype包装器的优点是:

  1. 您可以避免在执行(Just NaN vs Nothing)时引入另一个“无效”值,在转换为常规浮点数时必须担心。
  2. Newtypes在GHC中没有装箱; SNaN Float在运行时表示与对应的Float相同。因此,Just单元格没有额外的空间覆盖,并且在SNaN FloatFloat之间来回转换是免费操作。 SNaN只是一个标记,用于确定是否要在操作中插入隐含的“如果NaN然后爆炸”检查。