module Data.Prefix.Units
(
Unit(..)
, RationalConvertible(..)
, siUnits
, siSupraunitary
, siKMGT
, binaryUnits
, siBase
, binaryBase
, unitMultiplier
, unitName
, unitSymbol
, fancySymbol
, FormatMode(..)
, recommendedUnit
, formatValue
, showValue
, ParseMode(..)
, parseSymbol
, parseValue
, unitRange
, ParseOptions(..)
, parseExactSymbol
, parseBinarySymbol
, parseKMGTSymbol
, parseGeneric
, showValueWith
) where
import Control.Monad (liftM)
import Data.Char (toUpper)
import Data.List (intercalate)
import Data.Prefix.Units.Compat ()
default ()
data Unit = Yocto
| Zepto
| Atto
| Femto
| Pico
| Nano
| Micro
| Milli
| Centi
| Deci
| Deka
| Hecto
| Kilo
| Kibi
| Mega
| Mebi
| Giga
| Gibi
| Tera
| Tebi
| Peta
| Pebi
| Exa
| Exbi
| Zetta
| Yotta
deriving (Show, Eq, Enum, Bounded, Ord)
siUnits :: [Unit]
siUnits
= [Yocto, Zepto, Atto, Femto, Pico, Nano, Micro, Milli, Centi,
Deci, Deka, Hecto, Kilo, Mega, Giga, Tera, Peta, Exa, Zetta, Yotta]
binaryUnits :: [Unit]
binaryUnits = [Kibi, Mebi, Gibi, Tebi, Pebi, Exbi]
siSupraunitary :: [Unit]
siSupraunitary = filter (>= Deka) siUnits
siKMGT :: [Unit]
siKMGT = filter (>= Kilo) siUnits
siBase :: Rational
siBase = 10
binaryBase :: Rational
binaryBase = 2
unitMultiplier :: Unit -> Rational
unitMultiplier Yocto = siBase ^^ (24 :: Int)
unitMultiplier Zepto = siBase ^^ (21 :: Int)
unitMultiplier Atto = siBase ^^ (18 :: Int)
unitMultiplier Femto = siBase ^^ (15 :: Int)
unitMultiplier Pico = siBase ^^ (12 :: Int)
unitMultiplier Nano = siBase ^^ ( 9 :: Int)
unitMultiplier Micro = siBase ^^ ( 6 :: Int)
unitMultiplier Milli = siBase ^^ ( 3 :: Int)
unitMultiplier Centi = siBase ^^ ( 2 :: Int)
unitMultiplier Deci = siBase ^^ ( 1 :: Int)
unitMultiplier Deka = siBase ^^ ( 1 :: Int)
unitMultiplier Hecto = siBase ^^ ( 2 :: Int)
unitMultiplier Kilo = siBase ^^ ( 3 :: Int)
unitMultiplier Kibi = binaryBase ^^ ( 10 :: Int)
unitMultiplier Mega = siBase ^^ ( 6 :: Int)
unitMultiplier Mebi = binaryBase ^^ ( 20 :: Int)
unitMultiplier Giga = siBase ^^ ( 9 :: Int)
unitMultiplier Gibi = binaryBase ^^ ( 30 :: Int)
unitMultiplier Tera = siBase ^^ ( 12 :: Int)
unitMultiplier Tebi = binaryBase ^^ ( 40 :: Int)
unitMultiplier Peta = siBase ^^ ( 15 :: Int)
unitMultiplier Pebi = binaryBase ^^ ( 50 :: Int)
unitMultiplier Exa = siBase ^^ ( 18 :: Int)
unitMultiplier Exbi = binaryBase ^^ ( 60 :: Int)
unitMultiplier Zetta = siBase ^^ ( 21 :: Int)
unitMultiplier Yotta = siBase ^^ ( 24 :: Int)
unitName :: Unit -> String
unitName Yocto = "yocto"
unitName Zepto = "zepto"
unitName Atto = "atto"
unitName Femto = "femto"
unitName Pico = "pico"
unitName Nano = "nano"
unitName Micro = "micro"
unitName Milli = "milli"
unitName Centi = "centi"
unitName Deci = "deci"
unitName Deka = "deka"
unitName Hecto = "hecto"
unitName Kilo = "kilo"
unitName Kibi = "kibi"
unitName Mega = "mega"
unitName Mebi = "mebi"
unitName Giga = "giga"
unitName Gibi = "gibi"
unitName Tera = "tera"
unitName Tebi = "tebi"
unitName Peta = "peta"
unitName Pebi = "pebi"
unitName Exa = "exa"
unitName Exbi = "exbi"
unitName Zetta = "zetta"
unitName Yotta = "yotta"
unitSymbol :: Unit -> String
unitSymbol Yocto = "y"
unitSymbol Zepto = "z"
unitSymbol Atto = "a"
unitSymbol Femto = "f"
unitSymbol Pico = "p"
unitSymbol Nano = "n"
unitSymbol Micro = "u"
unitSymbol Milli = "m"
unitSymbol Centi = "c"
unitSymbol Deci = "d"
unitSymbol Deka = "da"
unitSymbol Hecto = "h"
unitSymbol Kilo = "k"
unitSymbol Kibi = "Ki"
unitSymbol Mega = "M"
unitSymbol Mebi = "Mi"
unitSymbol Giga = "G"
unitSymbol Gibi = "Gi"
unitSymbol Tera = "T"
unitSymbol Tebi = "Ti"
unitSymbol Peta = "P"
unitSymbol Pebi = "Pi"
unitSymbol Exa = "E"
unitSymbol Exbi = "Ei"
unitSymbol Zetta = "Z"
unitSymbol Yotta = "Y"
fancySymbol :: Unit -> String
fancySymbol Micro = "\xb5"
fancySymbol u = unitSymbol u
class (Real a) => RationalConvertible a where
convFromRational :: Rational -> a
instance RationalConvertible Int where
convFromRational = round
instance RationalConvertible Integer where
convFromRational = round
instance RationalConvertible Float where
convFromRational = fromRational
instance RationalConvertible Double where
convFromRational = fromRational
instance RationalConvertible Rational where
convFromRational = id
scaleToUnit :: (RationalConvertible a) => a -> Unit -> a
scaleToUnit val = convFromRational . (rational_val /) . unitMultiplier
where rational_val = toRational val
scaleFromUnit :: (RationalConvertible a) => a -> Unit -> a
scaleFromUnit val = convFromRational . (rational_val *) . unitMultiplier
where rational_val = toRational val
data FormatMode
= FormatSiAll
| FormatSiSupraunitary
| FormatSiKMGT
| FormatBinary
| FormatUnscaled
| FormatFixed Unit
deriving (Show)
unitRange :: FormatMode -> Either Unit [Unit]
unitRange FormatSiAll = Right siUnits
unitRange FormatSiSupraunitary = Right siSupraunitary
unitRange FormatSiKMGT = Right siKMGT
unitRange FormatBinary = Right binaryUnits
unitRange FormatUnscaled = Right []
unitRange (FormatFixed u) = Left u
shouldScale :: (Num a, Ord a) => a -> Bool
shouldScale val = val < 1 || val >= 10
recommendedUnit :: (Real a) => FormatMode -> a -> Maybe Unit
recommendedUnit fmt val =
case unitRange fmt of
Left u -> Just u
Right range ->
if shouldScale val
then foldr (\u a -> if ratv / unitMultiplier u >= 1 then Just u else a)
Nothing $ reverse range
else Nothing
where ratv = Prelude.toRational val
formatValue :: (RationalConvertible a) =>
FormatMode
-> a
-> (a, Maybe Unit)
formatValue fmt val =
let inverter = if val < 0
then negate
else id
val' = inverter val
unit = recommendedUnit fmt val'
scaled = maybe val' (scaleToUnit val') unit
in (inverter scaled, unit)
showValueWith :: (RationalConvertible a, Show a) =>
(Unit -> String)
-> FormatMode
-> a
-> String
showValueWith symbfn fmt val =
let (scaled, unit) = formatValue fmt val
in show scaled ++ maybe "" symbfn unit
showValue :: (RationalConvertible a, Show a) =>
FormatMode
-> a
-> String
showValue = showValueWith unitSymbol
unknownUnit :: String -> Either String Unit
unknownUnit unit = Left $ "Unrecognised unit '" ++ unit ++ "'"
parseExactSymbol :: String -> Either String Unit
parseExactSymbol "y" = Right Yocto
parseExactSymbol "z" = Right Zepto
parseExactSymbol "a" = Right Atto
parseExactSymbol "f" = Right Femto
parseExactSymbol "p" = Right Pico
parseExactSymbol "n" = Right Nano
parseExactSymbol "u" = Right Micro
parseExactSymbol "m" = Right Milli
parseExactSymbol "c" = Right Centi
parseExactSymbol "d" = Right Deci
parseExactSymbol "da" = Right Deka
parseExactSymbol "h" = Right Hecto
parseExactSymbol "k" = Right Kilo
parseExactSymbol "Ki" = Right Kibi
parseExactSymbol "M" = Right Mega
parseExactSymbol "Mi" = Right Mebi
parseExactSymbol "G" = Right Giga
parseExactSymbol "Gi" = Right Gibi
parseExactSymbol "T" = Right Tera
parseExactSymbol "Ti" = Right Tebi
parseExactSymbol "P" = Right Peta
parseExactSymbol "Pi" = Right Pebi
parseExactSymbol "E" = Right Exa
parseExactSymbol "Ei" = Right Exbi
parseExactSymbol "Z" = Right Zetta
parseExactSymbol "Y" = Right Yotta
parseExactSymbol unit = unknownUnit unit
helperParseBinary :: String -> Maybe Unit
helperParseBinary "K" = Just Kibi
helperParseBinary "KI" = Just Kibi
helperParseBinary "M" = Just Mebi
helperParseBinary "MI" = Just Mebi
helperParseBinary "G" = Just Gibi
helperParseBinary "GI" = Just Gibi
helperParseBinary "T" = Just Tebi
helperParseBinary "TI" = Just Tebi
helperParseBinary "P" = Just Pebi
helperParseBinary "PI" = Just Pebi
helperParseBinary "E" = Just Exbi
helperParseBinary "EI" = Just Exbi
helperParseBinary _ = Nothing
parseBinarySymbol :: String -> Either String Unit
parseBinarySymbol symbol =
maybe (unknownUnit symbol) Right . helperParseBinary . upperSym $ symbol
helperParseKMGT :: String -> Either String Unit
helperParseKMGT "K" = Right Kilo
helperParseKMGT "KI" = Right Kibi
helperParseKMGT "M" = Right Mega
helperParseKMGT "MI" = Right Mebi
helperParseKMGT "G" = Right Giga
helperParseKMGT "GI" = Right Gibi
helperParseKMGT "T" = Right Tera
helperParseKMGT "TI" = Right Tebi
helperParseKMGT "P" = Right Peta
helperParseKMGT "PI" = Right Pebi
helperParseKMGT "E" = Right Exa
helperParseKMGT "EI" = Right Exbi
helperParseKMGT "Z" = Right Zetta
helperParseKMGT "Y" = Right Yotta
helperParseKMGT symbol = unknownUnit symbol
parseKMGTSymbol :: String -> Either String Unit
parseKMGTSymbol = helperParseKMGT . upperSym
data ParseMode
= ParseExact
| ParseKMGT
| ParseBinary
deriving (Show, Enum, Bounded)
data ParseOptions
= UnitRequired
| UnitDefault Unit
| UnitOptional
deriving (Show)
parseSymbol :: ParseMode -> String -> Either String Unit
parseSymbol ParseExact = parseExactSymbol
parseSymbol ParseKMGT = parseKMGTSymbol
parseSymbol ParseBinary = parseBinarySymbol
parseValue :: (Read a, RationalConvertible a) =>
ParseMode
-> String
-> Either String a
parseValue = parseGeneric UnitOptional []
validUnit :: [Unit] -> Unit -> Either String Unit
validUnit [] unit = Right unit
validUnit ulist unit =
if unit `notElem` ulist
then Left $ "Unit '" ++ show unit ++ "' not part of the accepted" ++
" unit list (" ++ intercalate ", " (map show ulist) ++ ")"
else Right unit
processUnit :: ParseOptions
-> ParseMode
-> [Unit]
-> String
-> Either String (Maybe Unit)
processUnit UnitRequired _ _ "" =
Left "An unit is required but the input string lacks one"
processUnit (UnitDefault u) _ _ "" = Right $ Just u
processUnit UnitOptional _ _ "" = Right Nothing
processUnit _ pmode valid_units unit_suffix =
liftM Just (parseSymbol pmode unit_suffix >>= validUnit valid_units)
parseGeneric :: (Read a, RationalConvertible a) =>
ParseOptions
-> [Unit]
-> ParseMode
-> String
-> Either String a
parseGeneric popts valid_units pmode str =
case reads str of
[(v, suffix)] ->
let unit_suffix = dropWhile (== ' ') suffix
unit = processUnit popts pmode valid_units unit_suffix
in maybe v (scaleFromUnit v) `fmap` unit
_ -> Left $ "Can't parse string '" ++ str ++ "'"
upperSym :: String -> String
upperSym = map toUpper