7

我正在寻找一种能够表示值的数字类型0.213123or 0.0, or 1.0,但拒绝超出范围的值,例如-0.2123and 1.2312。是否存在适合该目的的特定类型,将数字限制在特定范围内的最佳通用方法是什么?

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

4

4 回答 4

5

一个严肃的建议

您可以在适当位大小的单词周围使用新类型包装器(和智能构造函数):

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

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

一个不太有用的建议

如果您只需要一个表示值介于 1 和 0 之间的符号,那么请采用 dave4420 的建议,只需一个单位类型:

newtype SmallFrac = SF ()

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

于 2013-02-22T21:07:05.503 回答
4

不标准。你必须做一个——我建议一个聪明的构造函数。请记住,尽管这样的类型支持很少的数字操作——你不能添加它们并将它们保留在集合中,也不能否定它们,所以我建议不要使用Num实例。Monoid关于乘法的 A是合理的。

于 2013-02-22T19:06:14.343 回答
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"
于 2013-02-22T21:14:11.063 回答
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

在运行时放入aDouble会导致运行时检查,无法绕过...

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)

$记住不要在和(!之间放置任何空格。

于 2013-02-22T23:33:15.540 回答