Сигнализация 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 являются:

  1. Вы избегаете вводить другое "недопустимое" значение для выполнения (Just NaN против Nothing), о котором вам следует беспокоиться при конвертации в / из обычных чисел с плавающей запятой.
  2. Новые типы распакованы в 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/infinityPrelude имеет две функции isNaN а также isInfinite,

Другие вопросы по тегам