lib: (divide|multiply)[Mixed]AmountAndPrice (#928)

Divide/multiply amounts *and* their total price, if they have one.
Helpful for keeping transactions balanced when transaction modifiers are
multiplying amounts.
This commit is contained in:
Simon Michael 2018-11-13 17:43:15 -08:00
parent b053942e9b
commit d7919003ae
2 changed files with 32 additions and 2 deletions

View File

@ -59,6 +59,8 @@ module Hledger.Data.Amount (
costOfAmount, costOfAmount,
divideAmount, divideAmount,
multiplyAmount, multiplyAmount,
divideAmountAndPrice,
multiplyAmountAndPrice,
amountValue, amountValue,
amountTotalPriceToUnitPrice, amountTotalPriceToUnitPrice,
-- ** rendering -- ** rendering
@ -91,6 +93,8 @@ module Hledger.Data.Amount (
costOfMixedAmount, costOfMixedAmount,
divideMixedAmount, divideMixedAmount,
multiplyMixedAmount, multiplyMixedAmount,
divideMixedAmountAndPrice,
multiplyMixedAmountAndPrice,
averageMixedAmounts, averageMixedAmounts,
isNegativeAmount, isNegativeAmount,
isNegativeMixedAmount, isNegativeMixedAmount,
@ -214,7 +218,7 @@ costOfAmount a@Amount{aquantity=q, aprice=price} =
-- | Replace an amount's TotalPrice, if it has one, with an equivalent UnitPrice. -- | Replace an amount's TotalPrice, if it has one, with an equivalent UnitPrice.
-- Has no effect on amounts without one. -- Has no effect on amounts without one.
-- Also increases the unit price's display precision to show one extra decimal place, -- Also increases the unit price's display precision to show one extra decimal place,
-- to help the unit-priced amounts to still balance. -- to help keep transaction amounts balancing.
-- Does Decimal division, might be some rounding/irrational number issues. -- Does Decimal division, might be some rounding/irrational number issues.
amountTotalPriceToUnitPrice :: Amount -> Amount amountTotalPriceToUnitPrice :: Amount -> Amount
amountTotalPriceToUnitPrice amountTotalPriceToUnitPrice
@ -230,6 +234,22 @@ divideAmount n a@Amount{aquantity=q} = a{aquantity=q/n}
multiplyAmount :: Quantity -> Amount -> Amount multiplyAmount :: Quantity -> Amount -> Amount
multiplyAmount n a@Amount{aquantity=q} = a{aquantity=q*n} multiplyAmount n a@Amount{aquantity=q} = a{aquantity=q*n}
-- | Divide an amount's quantity (and its total price, if it has one) by a constant.
-- The total price will be kept positive regardless of the multiplier's sign.
divideAmountAndPrice :: Quantity -> Amount -> Amount
divideAmountAndPrice n a@Amount{aquantity=q,aprice=p} = a{aquantity=q/n, aprice=f p}
where
f (TotalPrice a) = TotalPrice $ abs $ n `divideAmount` a
f p = p
-- | Multiply an amount's quantity (and its total price, if it has one) by a constant.
-- The total price will be kept positive regardless of the multiplier's sign.
multiplyAmountAndPrice :: Quantity -> Amount -> Amount
multiplyAmountAndPrice n a@Amount{aquantity=q,aprice=p} = a{aquantity=q*n, aprice=f p}
where
f (TotalPrice a) = TotalPrice $ abs $ n `multiplyAmount` a
f p = p
-- | Is this amount negative ? The price is ignored. -- | Is this amount negative ? The price is ignored.
isNegativeAmount :: Amount -> Bool isNegativeAmount :: Amount -> Bool
isNegativeAmount Amount{aquantity=q} = q < 0 isNegativeAmount Amount{aquantity=q} = q < 0
@ -553,6 +573,16 @@ divideMixedAmount n = mapMixedAmount (divideAmount n)
multiplyMixedAmount :: Quantity -> MixedAmount -> MixedAmount multiplyMixedAmount :: Quantity -> MixedAmount -> MixedAmount
multiplyMixedAmount n = mapMixedAmount (multiplyAmount n) multiplyMixedAmount n = mapMixedAmount (multiplyAmount n)
-- | Divide a mixed amount's quantities (and total prices, if any) by a constant.
-- The total prices will be kept positive regardless of the multiplier's sign.
divideMixedAmountAndPrice :: Quantity -> MixedAmount -> MixedAmount
divideMixedAmountAndPrice n = mapMixedAmount (divideAmountAndPrice n)
-- | Multiply a mixed amount's quantities (and total prices, if any) by a constant.
-- The total prices will be kept positive regardless of the multiplier's sign.
multiplyMixedAmountAndPrice :: Quantity -> MixedAmount -> MixedAmount
multiplyMixedAmountAndPrice n = mapMixedAmount (multiplyAmountAndPrice n)
-- | Calculate the average of some mixed amounts. -- | Calculate the average of some mixed amounts.
averageMixedAmounts :: [MixedAmount] -> MixedAmount averageMixedAmounts :: [MixedAmount] -> MixedAmount
averageMixedAmounts [] = 0 averageMixedAmounts [] = 0

View File

@ -154,7 +154,7 @@ instance ToMarkup Quantity
toMarkup = toMarkup . show toMarkup = toMarkup . show
-- | An amount's price (none, per unit, or total) in another commodity. -- | An amount's price (none, per unit, or total) in another commodity.
-- Note the price should be a positive number, although this is not enforced. -- The price amount should always be positive.
data Price = NoPrice | UnitPrice Amount | TotalPrice Amount data Price = NoPrice | UnitPrice Amount | TotalPrice Amount
deriving (Eq,Ord,Typeable,Data,Generic,Show) deriving (Eq,Ord,Typeable,Data,Generic,Show)