表示0到1范围内浮点数的最佳方法是什么?

时间:2013-02-22 18:52:29

标签: haskell types floating-point numbers

我正在寻找能够代表价值0.2131230.01.0的数字类型,但拒绝超出范围值,例如{{ 1}}和-0.2123。是否存在符合该目的的特定类型,以及将数字限制在特定范围内的最佳一般方法是什么?

当然,首先想到的答案是:只使用1.2312,但是被Haskell的类型系统破坏了,我已经习惯了在类型级别上最大限度地保护程序。

4 个答案:

答案 0 :(得分:5)

严肃的建议

您可以在正确的位大小的单词周围使用newtype包装器(和智能构造函数):

newtype SmallFrac = SF Word64

-- Example conversion (You'd actually want to make
-- instances of common classes, I assume)
sfToDouble :: SmallFrac -> Double
sfToDouble (SF x) = fromIntegral x / fromIntegral (maxBound `asTypeOf` x)

instance Show SmallFrac where
    show = show . sfToDouble

实现乘法和除法可能比您想要的更昂贵,但至少添加很容易(模数防止上溢/下溢)并且您声称不需要任何操作,所以更好。

不太有用的建议

如果你只需要一个代表一个和零之间的值的符号,那么请采用dave4420的建议,只需要一个单位类型:

newtype SmallFrac = SF ()

此类型没有任何操作,甚至没有转换为其他类型的兴趣,例如Double,但这符合所述的请求。

答案 1 :(得分:4)

不标准。你必须做一个 - 我建议smart constructor。请记住,虽然这样的类型支持非常少的数字操作 - 你不能添加它们并将它们保存在集合中,也不能否定它们,所以我建议不要使用Num实例。乘法Monoid是合理的。

答案 2 :(得分:3)

基于Double的表示

newtype Rep1 = Rep1 Double

checkRange :: Double -> Maybe Double
checkRange x
  | 0 < x && x < 1 = Just x
  | otherwise = Nothing

toRep1 :: Double -> Maybe Rep1
toRep1 x = Rep1 . (\x -> tan $ (x-0.5) * pi) <$> checkRange x

fromRep1 :: Rep1 -> Double
fromRep1 (Rep1 x) = atan x / pi + 0.5

基于整数的表示

data Rep2 = Rep2 Integer Integer

fromRep2 :: Rep2 -> Double
fromRep2 (Rep2 a b) = fromIntegral (abs a) / fromIntegral (abs a + abs b + 1)

toRep2 :: Double -> Maybe Rep2
toRep2 = error "left to the reader"

答案 3 :(得分:2)

智能构造函数模式的变体。

这可能有点矫枉过正。

{-# LANGUAGE TemplateHaskell #-}
module Foo (Foo(), doubleFromFoo,
            maybeFooFromDouble, unsafeFooFromDouble, thFooFromDouble)
where
import Language.Haskell.TH

无论如何,标准newtype ...

newtype Foo = Foo Double

轻松获得Double ......

doubleFromFoo :: Foo -> Double
doubleFromFoo (Foo x) = x

在运行时放入Double会导致运行时检查,而不是围绕...

maybeFooFromDouble :: Double -> Maybe Foo
maybeFooFromDouble x
        | 0 <= x && x <= 1 = Just (Foo x)
        | otherwise        = Nothing

...除非你感到高兴不安全(并且有一些社会手段强制执行unsafeFooFromDouble的所有用途实际上是安全的)......

unsafeFooFromDouble :: Double -> Foo
unsafeFooFromDouble = Foo

但是如果它是一个编译时常量,你可以在编译时进行检查,没有运行时开销:

thFooFromDouble :: (Real a, Show a) => a -> Q Exp
thFooFromDouble x
        | 0 <= x && x <= 1 = return $ AppE (VarE 'unsafeFooFromDouble)
                                           (LitE (RationalL (toRational x)))
        | otherwise        = fail $ show x ++ " is not between 0 and 1"

这就是你使用最后一个函数的方法:

$(thFooFromDouble 0.3)

切记不要在$(之间添加任何空格!