What is the best way to represent a floating point number between 0 and 1?

I am looking for a numeric type capable of representing, say, a value of 0.213123 or 0.0 , or 1.0 , but discarding values ​​out of range, such as -0.2123 and 1.2312 . Is there a specific type that fits this purpose, and what is the best overall approach to limiting numbers in a certain range?

Of course, the first answer comes to mind: just use Double , but getting spoiled by a system like Haskell, I'm used to maximizing type-level security of the program.

+7
source share
4 answers

Serious offers

You can use the newtype shell (and the smart constructor) around a word of the corresponding bit size:

 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 

Implementing multiplication and division can be more expensive than you would like, but at least adding is easy (modulo protection against over / underflow), and you say that you do not need any operations, even better.

Less useful suggestion

If all you need is a character representing a value that exists between one and zero, then take the dave4420 clause and simply enter the unit type:

 newtype SmallFrac = SF () 

There are no operations for this type, not even conversion to / from other types of interests, such as Double , but this matches the request as indicated.

+5
source

Non-standard. You have to do one - I would suggest an intelligent constructor . Keep in mind that this type supports very few numerical operations - you cannot add and store them in a set, nor can you negate them, so I would recommend using an instance of Num . A Monoid when multiplied would be reasonable.

+4
source

Double based view

 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 

Integer based representation

 data Rep2 = Rep2 Integer Integer fromRep2 :: Rep2 -> Double fromRep2 (Rep2 ab) = fromIntegral (abs a) / fromIntegral (abs a + abs b + 1) toRep2 :: Double -> Maybe Rep2 toRep2 = error "left to the reader" 
+3
source

Modify the smart designer template.

This may be redundant.

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

In any case, the standard newtype ...

 newtype Foo = Foo Double 

Getting Double easy ...

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

Enabling Double at runtime requires checking the runtime, do not get around this ...

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

... if you are not happy to be insecure (and have some social means to ensure that all use of unsafeFooFromDouble truly safe) ...

 unsafeFooFromDouble :: Double -> Foo unsafeFooFromDouble = Foo 

But if it is a compile-time constant, you can perform a check at compile time without overhead at runtime:

 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" 

And here is how you use this last function:

 $(thFooFromDouble 0.3) 

Remember that you do not need to put spaces between $ and ( !.

+2
source

All Articles