{-| This module is about fractions. A fraction can be seen as a real number from the closed interval [0,1]. It can also be seen as a percentage. A typical example of a fraction is the extend of a progress bar. -} module Data.Fraction ( -- * Fraction type Fraction, -- * Conversion fromFactor, fromPercentage, fromNumber, toFactor, toPercentage, toNumber ) where -- Data import Data.Semigroup as Semigroup import Data.Monoid as Monoid -- * Fraction type -- |A fraction. newtype Fraction = Fraction Double instance Semigroup Fraction where (<>) = mappend instance Monoid Fraction where mempty = Fraction 1 Fraction factor1 `mappend` Fraction factor2 = Fraction (factor1 * factor2) -- * Conversion {-| Converts a factor into its corresponding fraction. If the factor is not from the interval [0,1], a runtime error occurs. -} fromFactor :: (Real real) => real -> Fraction fromFactor = fromNumber (0,1) {-| Converts a percentage into its corresponding fraction. If the percentage is not from the interval [0,100], a runtime error occurs. -} fromPercentage :: (Real real) => real -> Fraction fromPercentage = fromNumber (0,100) {-| Converts a number into its corresponding fraction regarding a certain interval. If the lower bound of the interval is equal to or greater than the upper bound or the value is not from the interval, a runtime error occurs. -} fromNumber :: (Real real) => (real,real) -> real -> Fraction fromNumber (lower,upper) real = checkInterval (lower,upper) `seq` if lower <= real && real <= upper then Fraction $ (realToFrac real - fracMin) / (fracMax - fracMin) else error "fraction: real out of bounds" where fracMin = realToFrac lower fracMax = realToFrac upper -- |Converts a fraction into its corresponding factor. toFactor :: Fraction -> Double toFactor (Fraction factor) = factor -- |Converts a fraction into its corresponding percentage. toPercentage :: Fraction -> Double toPercentage (Fraction factor) = factor * 100 {-| Converts a fraction into its corresponding number regarding a certain interval. If the lower bound of the interval is equal to or greater than the upper bound, a runtime error occurs. -} toNumber :: (Double,Double) -> Fraction -> Double toNumber (lower,upper) (Fraction factor) = checkInterval (lower,upper) `seq` factor * (upper - lower) + lower checkInterval :: (Real real) => (real,real) -> () checkInterval (lower,upper) | lower < upper = () | otherwise = error "fraction: no proper interval"