Сигнализация IEEE с плавающей точкой NaN (sNaN) в Хаскеле
Есть ли способ определить сигнальную NaN в Haskell? Я нашел два подхода к работе с NaN:
1) использовать 0/0, что дает вполне нан
2) пакет Data.Number.Transfinite, который также не имеет сигнальных NaN.
PS Есть ли способ поставить Word64 по крупицам в Double без написания библиотеки C?
5 ответов
Я нашел один непереносимый способ:
{-# 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. По умолчанию они отключены по уважительной причине. Они в порядке, если вы отлаживаете C/C++/ что-то еще в GDB. Я не хочу отлаживать дампы ядра Haskell из-за его необязательного характера. Включение FE_INVALID
исключения вызывают 0/0 и добавляют к NaNs в Data.Number.Transfinite
а также GHC.Real
врезаться Но 0/0, вычисленное до enableexcept, не создает дополнительных исключений.
Я буду использовать некоторые простые проверки ошибок в моей задаче. я нуждаюсь sNaN
только в одном месте.
Как насчет использования Data.Maybe
?
Вы бы использовали Maybe Float
как тип данных (при условии, что вы хотите использовать Float
), а также Just x
для значения не NaN x
, в то время как Nothing
будет представлять NaN.
Тем не менее, вам нужно было бы по крайней мере Num
экземпляр, чтобы можно было рассчитать с помощью Maybe Float
вместо поплавка. Ты можешь использовать fromJust
в качестве функции полезности для этого.
Выражается ли это как qNaN или sNaN, полностью зависит от вашей реализации.
Вы можете сделать что-то вроде этого:
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
Хотя я бы поспорил, что это в конечном итоге может вызвать замешательство Но при этом точно такой же исходный код должен компилироваться и работать, при условии, что вы заполнили все классы типов, которые вам нужны, и вы не вызываете какой-либо другой код, который вы не можете модифицировать жестко, кодируя конкретные конкретные типы (вместо того, чтобы принимать любой тип в соответствующем классе типа).
Это в основном разработка первого предложения Ули Келера о предоставлении Num
экземпляр для Maybe Float
, Я просто использовал NaN непосредственно для представления NaN, а не Nothing
и использовал isNan
обнаружить их вместо анализа случая на Maybe
(или же isJust
).
Преимущества использования обертки нового типа перед Maybe
являются:
- Вы избегаете вводить другое "недопустимое" значение для выполнения (
Just NaN
противNothing
), о котором вам следует беспокоиться при конвертации в / из обычных чисел с плавающей запятой. - Новые типы распакованы в GHC;
SNaN Float
представляется во время выполнения идентично соответствующемуFloat
, Таким образом, нет дополнительного места дляJust
клетка, и преобразование междуSNaN Float
а такжеFloat
являются бесплатными операциями.SNaN
это просто тег, который определяет, хотите ли вы, чтобы в ваши операции вставлялись неявные проверки "если NaN тогда взорвется".
Вы можете использовать пользовательские операторы вместо пользовательских типов, как это (это позволяет избежать замены любых 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
вызывает ошибку.
Примечание: я не уверен, есть ли лучший способ отформатировать это, и вы, вероятно, не должны использовать конкретные типы в сигнатуре функции.
Вы можете использовать Data.Ratio для получения Nan/Infinity, используя отношения 1/0 (бесконечность) или 0/0 (NaN).
Более быстрый, но менее портативный подход заключается в использовании GHC.Real
который экспортирует infinity
а также notANumber
,
infinity, notANumber :: Rational
infinity = 1 :% 0
notANumber = 0 :% 0
Использование:
Prelude Data.Ratio GHC.Real> fromRational notANumber :: Float
NaN
Для проверки NaN
/infinity
Prelude имеет две функции isNaN
а также isInfinite
,