问题
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 NaNvsNothing), that you have to worry about when converting to/from regular floats. - Newtypes are unboxed in GHC; an
SNaN Floatis represented at runtime identically to the correspondingFloat. So there's no additional space overhaead for theJustcell, and converting back and forth betweenSNaN FloatandFloatare free operations.SNaNis 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