-- | Amounts of BTC module Bitcoin.Protocol.Amount ( Amount(..) -- * human-friendly definitions of amounts , btc , mbtc , ubtc , satoshi -- * constants , amountMultiplier , amountExponent -- * conversion , integerAmount, doubleAmount , amountFromDouble , showAmount , parseAmount ) where -------------------------------------------------------------------------------- import Data.Word import Data.List ( findIndex ) import Data.Maybe import Text.Show import Text.Read -------------------------------------------------------------------------------- -- | A (nonnegative) amount of bitcoins. -- -- Note: the Show/Read instances look like -- -- > newtype Amount = BTC Double -- -- eg. @BTC 1.3@ -- -- However, the internal representation is different. newtype Amount = Amount { unAmount :: Word64 } deriving (Eq,Ord) -------------------------------------------------------------------------------- instance Show Amount where showsPrec d amt = showParen (d>10) $ showString "BTC " . showString (showAmount amt) instance Read Amount where readsPrec d r = readParen (d > 10) (\r -> [ (fromJust mbAmt,t) | ("BTC",s) <- lex r , (m,t) <- lex s -- readsPrec 11 s , let mbAmt = parseAmount m , isJust mbAmt ]) r -------------------------------------------------------------------------------- -- * constants -- | @10^amountExponent == 10^8@ amountMultiplier :: Num a => a amountMultiplier = 100000000 -- | The exponent is 8, as @10^(-8)@ is the smallest unit. amountExponent :: Int amountExponent = 8 -------------------------------------------------------------------------------- -- * easy amounts -- | Whole BTCs btc :: Double -> Amount btc = amountFromDouble -- | milli-BTCs mbtc :: Double -> Amount mbtc n = amountFromDouble (n/1000) -- | micro-BTCs ubtc :: Double -> Amount ubtc n = amountFromDouble (n/1000000) -- | \"satoshi\" is the smallest unit: 1 satoshi = 10\^(-8) BTC satoshi :: Int -> Amount satoshi n = Amount $ fromIntegral $ n -------------------------------------------------------------------------------- -- * conversion -- | The amount measured in smallest units (satoshis) integerAmount :: Amount -> Integer integerAmount (Amount n) = fromIntegral n -- | The amount in BTCs as a "Double" doubleAmount :: Amount -> Double doubleAmount (Amount n) = fromIntegral n / amountMultiplier -- | Convert whole BTCs to an Amount. Rounding to whole satoshis. amountFromDouble :: Double -> Amount amountFromDouble d = Amount $ round (d*amountMultiplier) -- | Shows an amount in BTCs using the minimum necessary digits showAmount :: Amount -> String showAmount (Amount n) = show a ++ if (b==0) then "" else ("." ++ fr) where (a,b) = divMod n amountMultiplier fr = reverse $ dropWhile (=='0') $ reverse $ extend $ show b extend s = (replicate (amountExponent-k) '0' ++ s) where k = length s -- | Note: unlike doubleAmount, this ignores all digits after the 8th decimal. parseAmount :: String -> Maybe Amount parseAmount s = case splitDecimalPoint s of [s] -> case maybeReadWord64 s of Just n -> Just (Amount (n*amountMultiplier)) _ -> Nothing [s,""] -> case maybeReadWord64 s of Just n -> Just (Amount (n*amountMultiplier)) _ -> Nothing [s,t] -> case (maybeReadWord64 s, maybeReadWord64 t) of (Just a, Just _) -> Just (Amount (a*amountMultiplier + b)) where b = read $ take amountExponent $ (t ++ replicate amountExponent '0') _ -> Nothing _ -> Nothing -------------------------------------------------------------------------------- -- * helpers maybeReadWord64 :: String -> Maybe Word64 maybeReadWord64 s = case reads s of [(w,"")] -> Just w _ -> Nothing splitDecimalPoint :: String -> [String] splitDecimalPoint s = case findIndex (=='.') s of Nothing -> [s] Just i -> [take i s , drop (i+1) s] --------------------------------------------------------------------------------