问题
Is there any way to define signaling NaN in Haskell? I found two approaches to deal with NaNs:
1) use 0/0, which produces quite nan
2) package Data.Number.Transfinite, which has no signaling NaNs too.
PS Is there any way to put Word64 bit by bit into Double without writing C library?
回答1:
I have found one non-portable way:
{-# 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
which perfectly fails. It turned out that FPU exceptions are a bad idea for Haskell. They are disabled by default for a good reason. They are OK if you debug C/C++/something else in gdb. I don't want to debug Haskell core dumps due to its non-imperative nature. Enabling FE_INVALID
exceptions causes 0/0 and add to NaNs in Data.Number.Transfinite
and GHC.Real
to crash. But 0/0 calculated before enableexcept doesn't produce exceptions in addition.
I will use some simple errors check in my task. I need sNaN
in just one place.
回答2:
What about using Data.Maybe
?
You would use Maybe Float
as datatype (assuming you want to use Float
), and Just x
for the non-NaN value x
, whereas Nothing
would represent NaN.
However, you'd need to radd at least a Num
instance to be able to calculate using Maybe Float
instead of Float. You can use fromJust
as an utility function for this.
Whether this is expressed as qNaN or sNaN entirely depends on your implementation.
回答3:
You could use custom operators instead of custom types like this (this avoids replacing any Float
in your code`)
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
The second print
triggers the error.
Note: I'm not sure if there is a better way of formatting this, and you should probably not use concrete types in the function signature.
回答4:
You can use Data.Ratio to produce Nan/Infinity by using the ratios 1/0 (infinity) or 0/0 (NaN).
A faster but less portable approach is to use GHC.Real
which exports infinity
and notANumber
.
infinity, notANumber :: Rational
infinity = 1 :% 0
notANumber = 0 :% 0
Usage:
Prelude Data.Ratio GHC.Real> fromRational notANumber :: Float
NaN
For checking NaN
/infinity
, Prelude has two functions isNaN
and isInfinite
.
回答5:
You can do something like this:
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
You'd need more type classes to get the full Float
experience of course, but as you can see it's pretty easy boilerplate once the liftSNaN*
functions are defined. Given that, the SNaN
constructor turns a value in any RealFloat
type into one that will explode if it's a NaN and you use it in any operation (some of these you might arguably want to work on NaNs, maybe ==
and/or show
; you can vary to taste). unSNaN
turns any SNaN
back into a quiet NaN type.
It's still not directly using the Float
(or Double
, or whatever) type, but if you just change your type signatures pretty much everything will just work; the Num
and Show
instances I've given mean that numeric literals will just as easily be accepted as SNaN Float
as they will be Float
, and they show
the same too. If you get sick of typing SNaN
in the type signatures you could easily type Float' = SNaN Float
, or even:
import Prelude hiding (Float)
import qualified Prelude as P
type Float = SNaN P.Float
Though I'd bet that would cause confusion for someone eventually! But with that the exact same source code should compile and work, provided you've filled in all the type classes you need and you're not calling any other code you can't modify that hard-codes particular concrete types (rather than accepting any type in an appropriate type class).
This is basically an elaboration on Uli Köhler's first suggestion of providing a Num
instance for Maybe Float
. I've just used NaNs directly to represent NaNs, rather than Nothing
, and used isNan
to detect them instead of case analysis on the Maybe
(or isJust
).
The advantages of using a newtype wrapper over Maybe
are:
- You avoid introducing another "invalid" value to the doing (
Just NaN
vsNothing
), that you have to worry about when converting to/from regular floats. - Newtypes are unboxed in GHC; an
SNaN Float
is represented at runtime identically to the correspondingFloat
. So there's no additional space overhaead for theJust
cell, and converting back and forth betweenSNaN Float
andFloat
are free operations.SNaN
is just a tag that determines whether you would like implicit "if NaN then explode" checks inserted into your operations.
来源:https://stackoverflow.com/questions/21344139/ieee-floating-point-signalling-nan-snan-in-haskell