dev:print: refactor, add AmountStyle "rounding strategy"

Changes to enable more control of "rounding" behaviour
(ie, choosing display precisions for amounts).

This reverts 1.31's change of asprecision, making it a non-Maybe
again, and adds a new asrounding field providing more control over how
a target display precision is applied to existing amounts (two options
for now, more later). Functionality is in an interim state (reports do
no rounding).
This commit is contained in:
Simon Michael 2023-09-17 10:10:24 +01:00
parent a0136a0b28
commit f8027abb44
10 changed files with 112 additions and 72 deletions

View File

@ -79,7 +79,7 @@ module Hledger.Data.Amount (
amountSetStylesExceptPrecision, amountSetStylesExceptPrecision,
amountSetMainStyle, amountSetMainStyle,
amountSetCostStyle, amountSetCostStyle,
amountStyleUnsetPrecision, amountStyleSetRounding,
amountUnstyled, amountUnstyled,
showAmountB, showAmountB,
showAmount, showAmount,
@ -91,6 +91,7 @@ module Hledger.Data.Amount (
amountSetPrecision, amountSetPrecision,
withPrecision, withPrecision,
amountSetFullPrecision, amountSetFullPrecision,
-- amountInternalPrecision,
setAmountInternalPrecision, setAmountInternalPrecision,
withInternalPrecision, withInternalPrecision,
setAmountDecimalPoint, setAmountDecimalPoint,
@ -203,7 +204,7 @@ quoteCommoditySymbolIfNeeded s
-- | Options for the display of Amount and MixedAmount. -- | Options for the display of Amount and MixedAmount.
-- (See also Types.AmountStyle) -- (ee also Types.AmountStyle.
data AmountDisplayOpts = AmountDisplayOpts data AmountDisplayOpts = AmountDisplayOpts
{ displayPrice :: Bool -- ^ Whether to display the Price of an Amount. { displayPrice :: Bool -- ^ Whether to display the Price of an Amount.
, displayZeroCommodity :: Bool -- ^ If the Amount rounds to 0, whether to display its commodity string. , displayZeroCommodity :: Bool -- ^ If the Amount rounds to 0, whether to display its commodity string.
@ -248,7 +249,7 @@ csvDisplay = oneLine{displayThousandsSep=False}
-- Amount styles -- Amount styles
-- | Default amount style -- | Default amount style
amountstyle = AmountStyle L False Nothing (Just '.') (Just $ Precision 0) amountstyle = AmountStyle L False Nothing (Just '.') (Precision 0) NoRounding
------------------------------------------------------------------------------- -------------------------------------------------------------------------------
-- Amount -- Amount
@ -279,11 +280,11 @@ missingamt = nullamt{acommodity="AUTO"}
-- usd/eur/gbp round their argument to a whole number of pennies/cents. -- usd/eur/gbp round their argument to a whole number of pennies/cents.
-- XXX these are a bit clashy -- XXX these are a bit clashy
num n = nullamt{acommodity="", aquantity=n} num n = nullamt{acommodity="", aquantity=n}
hrs n = nullamt{acommodity="h", aquantity=n, astyle=amountstyle{asprecision=Just $ Precision 2, ascommodityside=R}} hrs n = nullamt{acommodity="h", aquantity=n, astyle=amountstyle{asprecision=Precision 2, ascommodityside=R}}
usd n = nullamt{acommodity="$", aquantity=roundTo 2 n, astyle=amountstyle{asprecision=Just $ Precision 2}} usd n = nullamt{acommodity="$", aquantity=roundTo 2 n, astyle=amountstyle{asprecision=Precision 2}}
eur n = nullamt{acommodity="", aquantity=roundTo 2 n, astyle=amountstyle{asprecision=Just $ Precision 2}} eur n = nullamt{acommodity="", aquantity=roundTo 2 n, astyle=amountstyle{asprecision=Precision 2}}
gbp n = nullamt{acommodity="£", aquantity=roundTo 2 n, astyle=amountstyle{asprecision=Just $ Precision 2}} gbp n = nullamt{acommodity="£", aquantity=roundTo 2 n, astyle=amountstyle{asprecision=Precision 2}}
per n = nullamt{acommodity="%", aquantity=n, astyle=amountstyle{asprecision=Just $ Precision 1, ascommodityside=R, ascommodityspaced=True}} per n = nullamt{acommodity="%", aquantity=n, astyle=amountstyle{asprecision=Precision 1, ascommodityside=R, ascommodityspaced=True}}
amt `at` priceamt = amt{aprice=Just $ UnitPrice priceamt} amt `at` priceamt = amt{aprice=Just $ UnitPrice priceamt}
amt @@ priceamt = amt{aprice=Just $ TotalPrice priceamt} amt @@ priceamt = amt{aprice=Just $ TotalPrice priceamt}
@ -345,9 +346,8 @@ isNegativeAmount Amount{aquantity=q} = q < 0
-- If that is unset or NaturalPrecision, this does nothing. -- If that is unset or NaturalPrecision, this does nothing.
amountRoundedQuantity :: Amount -> Quantity amountRoundedQuantity :: Amount -> Quantity
amountRoundedQuantity Amount{aquantity=q, astyle=AmountStyle{asprecision=mp}} = case mp of amountRoundedQuantity Amount{aquantity=q, astyle=AmountStyle{asprecision=mp}} = case mp of
Nothing -> q NaturalPrecision -> q
Just NaturalPrecision -> q Precision p -> roundTo p q
Just (Precision p) -> roundTo p q
-- | Apply a test to both an Amount and its total price, if it has one. -- | Apply a test to both an Amount and its total price, if it has one.
testAmountAndTotalPrice :: (Amount -> Bool) -> Amount -> Bool testAmountAndTotalPrice :: (Amount -> Bool) -> Amount -> Bool
@ -363,9 +363,8 @@ amountLooksZero :: Amount -> Bool
amountLooksZero = testAmountAndTotalPrice looksZero amountLooksZero = testAmountAndTotalPrice looksZero
where where
looksZero Amount{aquantity=Decimal e q, astyle=AmountStyle{asprecision=p}} = case p of looksZero Amount{aquantity=Decimal e q, astyle=AmountStyle{asprecision=p}} = case p of
Just (Precision d) -> if e > d then abs q <= 5*10^(e-d-1) else q == 0 Precision d -> if e > d then abs q <= 5*10^(e-d-1) else q == 0
Just NaturalPrecision -> q == 0 NaturalPrecision -> q == 0
Nothing -> q == 0
-- | Is this Amount (and its total price, if it has one) exactly zero, ignoring its display precision ? -- | Is this Amount (and its total price, if it has one) exactly zero, ignoring its display precision ?
amountIsZero :: Amount -> Bool amountIsZero :: Amount -> Bool
@ -377,7 +376,7 @@ withPrecision = flip amountSetPrecision
-- | Set an amount's display precision. -- | Set an amount's display precision.
amountSetPrecision :: AmountPrecision -> Amount -> Amount amountSetPrecision :: AmountPrecision -> Amount -> Amount
amountSetPrecision p a@Amount{astyle=s} = a{astyle=s{asprecision=Just p}} amountSetPrecision p a@Amount{astyle=s} = a{astyle=s{asprecision=p}}
-- | Increase an amount's display precision, if needed, to enough decimal places -- | Increase an amount's display precision, if needed, to enough decimal places
-- to show it exactly (showing all significant decimal digits, without trailing zeros). -- to show it exactly (showing all significant decimal digits, without trailing zeros).
@ -386,9 +385,13 @@ amountSetFullPrecision :: Amount -> Amount
amountSetFullPrecision a = amountSetPrecision p a amountSetFullPrecision a = amountSetPrecision p a
where where
p = max displayprecision naturalprecision p = max displayprecision naturalprecision
displayprecision = fromMaybe (Precision 0) $ asprecision $ astyle a displayprecision = asprecision $ astyle a
naturalprecision = Precision . decimalPlaces . normalizeDecimal $ aquantity a naturalprecision = Precision . decimalPlaces . normalizeDecimal $ aquantity a
-- -- | Get an amount's internal Decimal precision (not display precision).
-- amountInternalPrecision :: Amount -> Word8
-- amountInternalPrecision = decimalPlaces . normalizeDecimal . aquantity
-- | Set an amount's internal precision, ie rounds the Decimal representing -- | Set an amount's internal precision, ie rounds the Decimal representing
-- the amount's quantity to some number of decimal places. -- the amount's quantity to some number of decimal places.
-- Rounding is done with Data.Decimal's default roundTo function: -- Rounding is done with Data.Decimal's default roundTo function:
@ -397,7 +400,7 @@ amountSetFullPrecision a = amountSetPrecision p a
-- Intended mainly for internal use, eg when comparing amounts in tests. -- Intended mainly for internal use, eg when comparing amounts in tests.
setAmountInternalPrecision :: Word8 -> Amount -> Amount setAmountInternalPrecision :: Word8 -> Amount -> Amount
setAmountInternalPrecision p a@Amount{ aquantity=q, astyle=s } = a{ setAmountInternalPrecision p a@Amount{ aquantity=q, astyle=s } = a{
astyle=s{asprecision=Just $ Precision p} astyle=s{asprecision=Precision p}
,aquantity=roundTo p q ,aquantity=roundTo p q
} }
@ -468,22 +471,49 @@ amountSetStylesExceptPrecision styles a@Amount{astyle=AmountStyle{asprecision=or
case M.lookup (acommodity a) styles' of case M.lookup (acommodity a) styles' of
Just s -> a{astyle=s{asprecision=origp}} Just s -> a{astyle=s{asprecision=origp}}
Nothing -> a Nothing -> a
where styles' = M.map amountStyleUnsetPrecision styles where styles' = M.map (amountStyleSetRounding NoRounding) styles
amountStyleUnsetPrecision :: AmountStyle -> AmountStyle amountStyleSetRounding :: Rounding -> AmountStyle -> AmountStyle
amountStyleUnsetPrecision as = as{asprecision=Nothing} amountStyleSetRounding r as = as{asrounding=r}
-- | Find and apply the appropriate display style, if any, to this amount. -- | Find and apply the appropriate display style, if any, to this amount.
-- The display precision is set or not, according to the style. -- The display precision is adjusted or not, as determnined by the style's rounding strategy.
amountSetMainStyle :: M.Map CommoditySymbol AmountStyle -> Amount -> Amount amountSetMainStyle :: M.Map CommoditySymbol AmountStyle -> Amount -> Amount
amountSetMainStyle styles a@Amount{acommodity=comm, astyle=AmountStyle{asprecision=morigp}} = amountSetMainStyle styles a@Amount{aquantity=q, acommodity=comm, astyle=s0} =
case M.lookup comm styles of case M.lookup comm styles of
Nothing -> a Nothing -> a
Just s@AmountStyle{asprecision=mp} -> a{astyle=s'} Just s -> a{astyle=amountStyleApplyPrecision q s s0}
-- | A helper for updating an Amount's display precision, more carefully than amountSetPrecision.
-- Given an Amount's decimal quantity (for inspecting its internal representation),
-- its current display style, and a new display style,
-- apply the new style's display precision to the old style,
-- using the new style's rounding strategy, as follows:
--
-- NoRounding - the precision is left unchanged
--
-- SoftRounding -
--
-- if either precision is NaturalPrecision, use NaturalPrecision;
--
-- if the new precision is greater than the old, use the new (adds decimal zeros);
--
-- if the new precision is less than the old, use as close to the new as we can get
-- without dropping (more) non-zero digits (drops decimal zeros).
--
amountStyleApplyPrecision :: Quantity -> AmountStyle -> AmountStyle -> AmountStyle
amountStyleApplyPrecision q AmountStyle{asprecision=newp, asrounding=r} s@AmountStyle{asprecision=oldp} =
case r of
NoRounding -> s
SoftRounding -> s{asprecision=p}
where where
s' = case mp of p = case (newp, oldp) of
Nothing -> s{asprecision=morigp} (Precision new, Precision old) ->
_ -> s if new >= old
then Precision new
else Precision $ max (min old internal) new
where internal = decimalPlaces $ normalizeDecimal q
_ -> NaturalPrecision
-- | Find and apply the appropriate display style, if any, to this amount's cost, if any. -- | Find and apply the appropriate display style, if any, to this amount's cost, if any.
-- The display precision is left unchanged, regardless of the style. -- The display precision is left unchanged, regardless of the style.
@ -1120,8 +1150,8 @@ tests_Amount = testGroup "Amount" [
(usd (-1.23) + usd (-1.23)) @?= usd (-2.46) (usd (-1.23) + usd (-1.23)) @?= usd (-2.46)
sum [usd 1.23,usd (-1.23),usd (-1.23),-(usd (-1.23))] @?= usd 0 sum [usd 1.23,usd (-1.23),usd (-1.23),-(usd (-1.23))] @?= usd 0
-- highest precision is preserved -- highest precision is preserved
asprecision (astyle $ sum [usd 1 `withPrecision` Precision 1, usd 1 `withPrecision` Precision 3]) @?= Just (Precision 3) asprecision (astyle $ sum [usd 1 `withPrecision` Precision 1, usd 1 `withPrecision` Precision 3]) @?= Precision 3
asprecision (astyle $ sum [usd 1 `withPrecision` Precision 3, usd 1 `withPrecision` Precision 1]) @?= Just (Precision 3) asprecision (astyle $ sum [usd 1 `withPrecision` Precision 3, usd 1 `withPrecision` Precision 1]) @?= Precision 3
-- adding different commodities assumes conversion rate 1 -- adding different commodities assumes conversion rate 1
assertBool "" $ amountLooksZero (usd 1.23 - eur 1.23) assertBool "" $ amountLooksZero (usd 1.23 - eur 1.23)

View File

@ -337,8 +337,8 @@ costInferrerFor t pt = maybe id infercost inferFromAndTo
unitprice = aquantity fromamount `divideAmount` toamount unitprice = aquantity fromamount `divideAmount` toamount
unitprecision = case (asprecision $ astyle fromamount, asprecision $ astyle toamount) of unitprecision = case (asprecision $ astyle fromamount, asprecision $ astyle toamount) of
(Just (Precision a), Just (Precision b)) -> Precision . max 2 $ saturatedAdd a b (Precision a, Precision b) -> Precision . max 2 $ saturatedAdd a b
_ -> NaturalPrecision _ -> NaturalPrecision
saturatedAdd a b = if maxBound - a < b then maxBound else a + b saturatedAdd a b = if maxBound - a < b then maxBound else a + b
@ -1009,26 +1009,26 @@ tests_Balancing =
-- --
testCase "1091a" $ do testCase "1091a" $ do
commodityStylesFromAmounts [ commodityStylesFromAmounts [
nullamt{aquantity=1000, astyle=AmountStyle L False Nothing (Just ',') (Just $ Precision 3)} nullamt{aquantity=1000, astyle=AmountStyle L False Nothing (Just ',') (Precision 3) NoRounding}
,nullamt{aquantity=1000, astyle=AmountStyle L False (Just (DigitGroups ',' [3])) (Just '.') (Just $ Precision 2)} ,nullamt{aquantity=1000, astyle=AmountStyle L False (Just (DigitGroups ',' [3])) (Just '.') (Precision 2) NoRounding}
] ]
@?= @?=
-- The commodity style should have period as decimal mark -- The commodity style should have period as decimal mark
-- and comma as digit group mark. -- and comma as digit group mark.
Right (M.fromList [ Right (M.fromList [
("", AmountStyle L False (Just (DigitGroups ',' [3])) (Just '.') (Just $ Precision 3)) ("", AmountStyle L False (Just (DigitGroups ',' [3])) (Just '.') (Precision 3) NoRounding)
]) ])
-- same journal, entries in reverse order -- same journal, entries in reverse order
,testCase "1091b" $ do ,testCase "1091b" $ do
commodityStylesFromAmounts [ commodityStylesFromAmounts [
nullamt{aquantity=1000, astyle=AmountStyle L False (Just (DigitGroups ',' [3])) (Just '.') (Just $ Precision 2)} nullamt{aquantity=1000, astyle=AmountStyle L False (Just (DigitGroups ',' [3])) (Just '.') (Precision 2) NoRounding}
,nullamt{aquantity=1000, astyle=AmountStyle L False Nothing (Just ',') (Just $ Precision 3)} ,nullamt{aquantity=1000, astyle=AmountStyle L False Nothing (Just ',') (Precision 3) NoRounding}
] ]
@?= @?=
-- The commodity style should have period as decimal mark -- The commodity style should have period as decimal mark
-- and comma as digit group mark. -- and comma as digit group mark.
Right (M.fromList [ Right (M.fromList [
("", AmountStyle L False (Just (DigitGroups ',' [3])) (Just '.') (Just $ Precision 3)) ("", AmountStyle L False (Just (DigitGroups ',' [3])) (Just '.') (Precision 3) NoRounding)
]) ])
] ]

View File

@ -82,6 +82,7 @@ decimalKV d = let d' = if decimalPlaces d <= 10 then d else roundTo 10 d in
] ]
instance ToJSON Amount instance ToJSON Amount
instance ToJSON Rounding
instance ToJSON AmountStyle instance ToJSON AmountStyle
-- Use the same JSON serialisation as Maybe Word8 -- Use the same JSON serialisation as Maybe Word8
@ -193,6 +194,7 @@ instance FromJSON Pos where
parseJSON = fmap mkPos . parseJSON parseJSON = fmap mkPos . parseJSON
instance FromJSON Amount instance FromJSON Amount
instance FromJSON Rounding
instance FromJSON AmountStyle instance FromJSON AmountStyle
-- Use the same JSON serialisation as Maybe Word8 -- Use the same JSON serialisation as Maybe Word8

View File

@ -378,11 +378,7 @@ transactionInferCostsFromEquity dryrun acctTypes t = first (annotateErrorWithTra
Nothing -> Left $ annotateWithPostings [p] "Conversion postings must have a single-commodity amount:" Nothing -> Left $ annotateWithPostings [p] "Conversion postings must have a single-commodity amount:"
-- Do these amounts look the same when compared at the first's display precision ? -- Do these amounts look the same when compared at the first's display precision ?
-- (Or if that's unset, compare as-is) amountsMatch a b = amountLooksZero $ amountSetPrecision (asprecision $ astyle a) $ a - b
amountsMatch a b =
case asprecision $ astyle a of
Just p -> amountLooksZero $ amountSetPrecision p $ a - b
Nothing -> amountLooksZero $ a - b
-- Delete a posting from the indexed list of postings based on either its -- Delete a posting from the indexed list of postings based on either its
-- index or its posting amount. -- index or its posting amount.
@ -400,9 +396,8 @@ transactionInferCostsFromEquity dryrun acctTypes t = first (annotateErrorWithTra
dbgShowAmountPrecision a = dbgShowAmountPrecision a =
case asprecision $ astyle a of case asprecision $ astyle a of
Just (Precision n) -> show n Precision n -> show n
Just NaturalPrecision -> show $ decimalPlaces $ normalizeDecimal $ aquantity a NaturalPrecision -> show $ decimalPlaces $ normalizeDecimal $ aquantity a
Nothing -> "unset"
-- Using the provided account types map, sort the given indexed postings -- Using the provided account types map, sort the given indexed postings
-- into three lists of posting numbers (stored in two pairs), like so: -- into three lists of posting numbers (stored in two pairs), like so:

View File

@ -249,16 +249,19 @@ deriving instance Generic (DecimalRaw a)
data AmountPrice = UnitPrice !Amount | TotalPrice !Amount data AmountPrice = UnitPrice !Amount | TotalPrice !Amount
deriving (Eq,Ord,Generic,Show) deriving (Eq,Ord,Generic,Show)
-- | The display style for an amount. -- | Every Amount has one of these, influencing how the amount is displayed.
-- (See also Amount.AmountDisplayOpts). -- Also, each Commodity can have one, which can be applied to its amounts for consistent display.
-- See also Amount.AmountDisplayOpts.
data AmountStyle = AmountStyle { data AmountStyle = AmountStyle {
ascommodityside :: !Side, -- ^ show the symbol on the left or the right ? ascommodityside :: !Side, -- ^ show the symbol on the left or the right ?
ascommodityspaced :: !Bool, -- ^ show a space between symbol and quantity ? ascommodityspaced :: !Bool, -- ^ show a space between symbol and quantity ?
asdigitgroups :: !(Maybe DigitGroupStyle), -- ^ show the integer part with these digit group marks, or not asdigitgroups :: !(Maybe DigitGroupStyle), -- ^ show the integer part with these digit group marks, or not
asdecimalmark :: !(Maybe Char), -- ^ show this character (should be . or ,) as decimal mark, or use the default (.) asdecimalmark :: !(Maybe Char), -- ^ show this character (should be . or ,) as decimal mark, or use the default (.)
asprecision :: !(Maybe AmountPrecision) -- ^ show this number of digits after the decimal point, or show as-is (leave precision unchanged) asprecision :: !AmountPrecision, -- ^ "display precision" - show this number of digits after the decimal point
-- XXX Making asprecision a maybe simplifies code for styling with or without precision, asrounding :: !Rounding -- ^ "rounding strategy" - kept here for convenience, for now:
-- but complicates the semantics (Nothing is useful only when setting style). -- when displaying an amount, it is ignored,
-- but when applying this style to another amount, it determines
-- how hard we should try to adjust the amount's display precision.
} deriving (Eq,Ord,Read,Generic) } deriving (Eq,Ord,Read,Generic)
instance Show AmountStyle where instance Show AmountStyle where
@ -278,6 +281,16 @@ data AmountPrecision =
| NaturalPrecision -- ^ show all significant decimal digits stored internally | NaturalPrecision -- ^ show all significant decimal digits stored internally
deriving (Eq,Ord,Read,Show,Generic) deriving (Eq,Ord,Read,Show,Generic)
-- | "Rounding strategy" - when applying the display precision from AmountStyle to another
-- (as when applying commodity styles to amounts), how much padding or rounding
-- of decimal digits should be done ?
data Rounding =
NoRounding -- ^ keep the amount precisions unchanged
| SoftRounding -- ^ add or remove trailing zeros to approach the desired precision
-- | HardRounding -- ^ also remove non-zero digits, in posting amounts (lossy)
-- | HardRoundingAndCost -- ^ also remove non-zero digits, in posting and cost amounts (lossy)
deriving (Eq,Ord,Read,Generic)
-- | A style for displaying digit groups in the integer part of a -- | A style for displaying digit groups in the integer part of a
-- floating point number. It consists of the character used to -- floating point number. It consists of the character used to
-- separate groups (comma or period, whichever is not used as decimal -- separate groups (comma or period, whichever is not used as decimal

View File

@ -110,8 +110,8 @@ amountPriceDirectiveFromCost d amt@Amount{acommodity=fromcomm, aquantity=fromq}
where where
style' = (astyle a) { asprecision = precision' } style' = (astyle a) { asprecision = precision' }
precision' = case asprecision (astyle a) of precision' = case asprecision (astyle a) of
Just (Precision p) -> Just $ Precision $ (numDigitsInt $ truncate n) + p NaturalPrecision -> NaturalPrecision
mp -> mp Precision p -> Precision $ (numDigitsInt $ truncate n) + p
------------------------------------------------------------------------------ ------------------------------------------------------------------------------
-- Converting things to value -- Converting things to value

View File

@ -804,7 +804,7 @@ simpleamountp mult =
offAfterNum <- getOffset offAfterNum <- getOffset
let numRegion = (offBeforeNum, offAfterNum) let numRegion = (offBeforeNum, offAfterNum)
(q,prec,mdec,mgrps) <- lift $ interpretNumber numRegion suggestedStyle ambiguousRawNum mExponent (q,prec,mdec,mgrps) <- lift $ interpretNumber numRegion suggestedStyle ambiguousRawNum mExponent
let s = amountstyle{ascommodityside=L, ascommodityspaced=commodityspaced, asprecision=Just prec, asdecimalmark=mdec, asdigitgroups=mgrps} let s = amountstyle{ascommodityside=L, ascommodityspaced=commodityspaced, asprecision=prec, asdecimalmark=mdec, asdigitgroups=mgrps}
return nullamt{acommodity=c, aquantity=sign (sign2 q), astyle=s, aprice=Nothing} return nullamt{acommodity=c, aquantity=sign (sign2 q), astyle=s, aprice=Nothing}
-- An amount with commodity symbol on the right or no commodity symbol. -- An amount with commodity symbol on the right or no commodity symbol.
@ -826,7 +826,7 @@ simpleamountp mult =
-- XXX amounts of this commodity in periodic transaction rules and auto posting rules ? #1461 -- XXX amounts of this commodity in periodic transaction rules and auto posting rules ? #1461
let msuggestedStyle = mdecmarkStyle <|> mcommodityStyle let msuggestedStyle = mdecmarkStyle <|> mcommodityStyle
(q,prec,mdec,mgrps) <- lift $ interpretNumber numRegion msuggestedStyle ambiguousRawNum mExponent (q,prec,mdec,mgrps) <- lift $ interpretNumber numRegion msuggestedStyle ambiguousRawNum mExponent
let s = amountstyle{ascommodityside=R, ascommodityspaced=commodityspaced, asprecision=Just prec, asdecimalmark=mdec, asdigitgroups=mgrps} let s = amountstyle{ascommodityside=R, ascommodityspaced=commodityspaced, asprecision=prec, asdecimalmark=mdec, asdigitgroups=mgrps}
return nullamt{acommodity=c, aquantity=sign q, astyle=s, aprice=Nothing} return nullamt{acommodity=c, aquantity=sign q, astyle=s, aprice=Nothing}
-- no symbol amount -- no symbol amount
Nothing -> do Nothing -> do
@ -842,8 +842,8 @@ simpleamountp mult =
-- (unless it's a multiplier in an automated posting) -- (unless it's a multiplier in an automated posting)
defcs <- getDefaultCommodityAndStyle defcs <- getDefaultCommodityAndStyle
let (c,s) = case (mult, defcs) of let (c,s) = case (mult, defcs) of
(False, Just (defc,defs)) -> (defc, defs{asprecision=max (asprecision defs) (Just prec)}) (False, Just (defc,defs)) -> (defc, defs{asprecision=max (asprecision defs) prec})
_ -> ("", amountstyle{asprecision=Just prec, asdecimalmark=mdec, asdigitgroups=mgrps}) _ -> ("", amountstyle{asprecision=prec, asdecimalmark=mdec, asdigitgroups=mgrps})
return nullamt{acommodity=c, aquantity=sign q, astyle=s, aprice=Nothing} return nullamt{acommodity=c, aquantity=sign q, astyle=s, aprice=Nothing}
-- For reducing code duplication. Doesn't parse anything. Has the type -- For reducing code duplication. Doesn't parse anything. Has the type
@ -1070,7 +1070,7 @@ disambiguateNumber msuggestedStyle (AmbiguousNumber grp1 sep grp2) =
isValidDecimalBy c = \case isValidDecimalBy c = \case
AmountStyle{asdecimalmark = Just d} -> d == c AmountStyle{asdecimalmark = Just d} -> d == c
AmountStyle{asdigitgroups = Just (DigitGroups g _)} -> g /= c AmountStyle{asdigitgroups = Just (DigitGroups g _)} -> g /= c
AmountStyle{asprecision = Just (Precision 0)} -> False AmountStyle{asprecision = Precision 0} -> False
_ -> True _ -> True
-- | Parse and interpret the structure of a number without external hints. -- | Parse and interpret the structure of a number without external hints.
@ -1574,24 +1574,24 @@ tests_Common = testGroup "Common" [
nullamt{ nullamt{
acommodity="$" acommodity="$"
,aquantity=10 -- need to test internal precision with roundTo ? I think not ,aquantity=10 -- need to test internal precision with roundTo ? I think not
,astyle=amountstyle{asprecision=Just $ Precision 0, asdecimalmark=Nothing} ,astyle=amountstyle{asprecision=Precision 0, asdecimalmark=Nothing}
,aprice=Just $ UnitPrice $ ,aprice=Just $ UnitPrice $
nullamt{ nullamt{
acommodity="" acommodity=""
,aquantity=0.5 ,aquantity=0.5
,astyle=amountstyle{asprecision=Just $ Precision 1, asdecimalmark=Just '.'} ,astyle=amountstyle{asprecision=Precision 1, asdecimalmark=Just '.'}
} }
} }
,testCase "total price" $ assertParseEq amountp "$10 @@ €5" ,testCase "total price" $ assertParseEq amountp "$10 @@ €5"
nullamt{ nullamt{
acommodity="$" acommodity="$"
,aquantity=10 ,aquantity=10
,astyle=amountstyle{asprecision=Just $ Precision 0, asdecimalmark=Nothing} ,astyle=amountstyle{asprecision=Precision 0, asdecimalmark=Nothing}
,aprice=Just $ TotalPrice $ ,aprice=Just $ TotalPrice $
nullamt{ nullamt{
acommodity="" acommodity=""
,aquantity=5 ,aquantity=5
,astyle=amountstyle{asprecision=Just $ Precision 0, asdecimalmark=Nothing} ,astyle=amountstyle{asprecision=Precision 0, asdecimalmark=Nothing}
} }
} }
,testCase "unit price, parenthesised" $ assertParse amountp "$10 (@) €0.5" ,testCase "unit price, parenthesised" $ assertParse amountp "$10 (@) €0.5"

View File

@ -193,8 +193,8 @@ timedotentryp = do
mcs <- getDefaultCommodityAndStyle mcs <- getDefaultCommodityAndStyle
let let
(c,s) = case mcs of (c,s) = case mcs of
Just (defc,defs) -> (defc, defs{asprecision=max (asprecision defs) (Just $ Precision 2)}) Just (defc,defs) -> (defc, defs{asprecision=max (asprecision defs) (Precision 2)})
_ -> ("", amountstyle{asprecision=Just $ Precision 2}) _ -> ("", amountstyle{asprecision=Precision 2})
-- lift $ traceparse' "timedotentryp end" -- lift $ traceparse' "timedotentryp end"
return $ nullposting{paccount=a return $ nullposting{paccount=a
,pamount=mixedAmount $ nullamt{acommodity=c, aquantity=hours, astyle=s} ,pamount=mixedAmount $ nullamt{acommodity=c, aquantity=hours, astyle=s}

View File

@ -618,7 +618,8 @@ tests_MultiBalanceReport = testGroup "MultiBalanceReport" [
let let
amt0 = Amount {acommodity="$", aquantity=0, aprice=Nothing, amt0 = Amount {acommodity="$", aquantity=0, aprice=Nothing,
astyle=AmountStyle {ascommodityside = L, ascommodityspaced = False, asdigitgroups = Nothing, asdecimalmark = Just '.', asprecision = Just $ Precision 2}} astyle=AmountStyle {ascommodityside = L, ascommodityspaced = False, asdigitgroups = Nothing,
asdecimalmark = Just '.', asprecision = Precision 2, asrounding = NoRounding}}
(rspec,journal) `gives` r = do (rspec,journal) `gives` r = do
let rspec' = rspec{_rsQuery=And [queryFromFlags $ _rsReportOpts rspec, _rsQuery rspec]} let rspec' = rspec{_rsQuery=And [queryFromFlags $ _rsReportOpts rspec, _rsQuery rspec]}
(eitems, etotal) = r (eitems, etotal) = r

View File

@ -70,16 +70,15 @@ print' opts j = do
printEntries :: CliOpts -> Journal -> IO () printEntries :: CliOpts -> Journal -> IO ()
printEntries opts@CliOpts{reportspec_=rspec} j = printEntries opts@CliOpts{reportspec_=rspec} j =
writeOutputLazyText opts . render $ writeOutputLazyText opts $ render $ entriesReport rspec j
styleAmounts styles $
entriesReport rspec j
where where
styles = M.map amountStyleUnsetPrecision $ journalCommodityStyles j -- keep all precisions unchanged stylesnorounding = M.map (amountStyleSetRounding NoRounding) $ journalCommodityStyles j
stylessoftrounding = M.map (amountStyleSetRounding SoftRounding) $ journalCommodityStyles j
fmt = outputFormatFromOpts opts fmt = outputFormatFromOpts opts
render | fmt=="txt" = entriesReportAsText opts render | fmt=="txt" = entriesReportAsText opts . styleAmounts stylesnorounding
| fmt=="csv" = printCSV . entriesReportAsCsv | fmt=="csv" = printCSV . entriesReportAsCsv . styleAmounts stylessoftrounding
| fmt=="json" = toJsonText | fmt=="json" = toJsonText . styleAmounts stylessoftrounding
| fmt=="sql" = entriesReportAsSql | fmt=="sql" = entriesReportAsSql . styleAmounts stylessoftrounding
| otherwise = error' $ unsupportedOutputFormatError fmt -- PARTIAL: | otherwise = error' $ unsupportedOutputFormatError fmt -- PARTIAL:
entriesReportAsText :: CliOpts -> EntriesReport -> TL.Text entriesReportAsText :: CliOpts -> EntriesReport -> TL.Text