有没有办法在Haskell中定义信令NaN?我发现了两种处理NaN的方法:
1)使用0/0,产生相当的纳米
2)包Data.Number.Transfinite,它也没有信号NaN。
PS有没有办法在没有编写C库的情况下将Word64逐位放入Double中?
答案 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.Transfinite
和GHC.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.Real
和infinity
的{{1}}。
notANumber
用法:
infinity, notANumber :: Rational
infinity = 1 :% 0
notANumber = 0 :% 0
要检查Prelude Data.Ratio GHC.Real> fromRational notANumber :: Float
NaN
/ NaN
,Prelude有两个功能infinity
和isNaN
。
答案 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
或其他类型)类型,但如果您只是更改类型签名,那么一切都会正常工作;我提供的Num
和Show
个实例意味着数字文字将被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包装器的优点是:
Just NaN
vs Nothing
)时引入另一个“无效”值,在转换为常规浮点数时必须担心。SNaN Float
在运行时表示与对应的Float
相同。因此,Just
单元格没有额外的空间覆盖,并且在SNaN Float
和Float
之间来回转换是免费操作。 SNaN
只是一个标记,用于确定是否要在操作中插入隐含的“如果NaN然后爆炸”检查。