mirror of
https://github.com/simonmichael/hledger.git
synced 2024-11-07 21:15:19 +03:00
imp: more precision handling fixes, debug output, test updates (precisiongeddon)
This and the preceding commits were "work in progress" that got out of control. There's more to do, but this one brings these precision-related improvements (at least): When "infinite decimals" arise, they are now generally shown with 8 decimal digits rather than 255. print and prices no longer add trailing decimal zeros unnecessarily. Some code has been refactored or given more debug output. All tests have been updated to match the recent changes.
This commit is contained in:
parent
c51d883162
commit
f8ffd9cdda
@ -93,9 +93,10 @@ module Hledger.Data.Amount (
|
||||
amountSetPrecisionMin,
|
||||
withPrecision,
|
||||
amountSetFullPrecision,
|
||||
amountSetFullPrecisionUpTo,
|
||||
amountSetFullPrecisionOr,
|
||||
amountInternalPrecision,
|
||||
amountDisplayPrecision,
|
||||
defaultMaxPrecision,
|
||||
setAmountInternalPrecision,
|
||||
withInternalPrecision,
|
||||
setAmountDecimalPoint,
|
||||
@ -181,10 +182,11 @@ import Test.Tasty (testGroup)
|
||||
import Test.Tasty.HUnit ((@?=), assertBool, testCase)
|
||||
|
||||
import Hledger.Data.Types
|
||||
import Hledger.Utils (colorB, numDigitsInt)
|
||||
import Hledger.Utils (colorB, numDigitsInt, numDigitsInteger)
|
||||
import Hledger.Utils.Text (textQuoteIfNeeded)
|
||||
import Text.WideString (WideBuilder(..), wbFromText, wbToText, wbUnpack)
|
||||
import Data.Functor ((<&>))
|
||||
-- import Data.Function ((&))
|
||||
-- import Hledger.Utils.Debug (dbg0)
|
||||
|
||||
|
||||
@ -373,6 +375,12 @@ amountLooksZero = testAmountAndTotalPrice looksZero
|
||||
amountIsZero :: Amount -> Bool
|
||||
amountIsZero = testAmountAndTotalPrice (\Amount{aquantity=Decimal _ q} -> q == 0)
|
||||
|
||||
-- | Does this amount's internal Decimal representation have the
|
||||
-- maximum number of digits, suggesting that it probably is
|
||||
-- representing an infinite decimal ?
|
||||
amountHasMaxDigits :: Amount -> Bool
|
||||
amountHasMaxDigits = (>= 255) . numDigitsInteger . decimalMantissa . aquantity
|
||||
|
||||
-- | Set an amount's display precision, flipped.
|
||||
withPrecision :: Amount -> AmountPrecision -> Amount
|
||||
withPrecision = flip amountSetPrecision
|
||||
@ -402,12 +410,31 @@ amountSetFullPrecision a = amountSetPrecision p a
|
||||
-- amountSetFullPrecision a = amountSetPrecision (Precision p) a
|
||||
-- where p = max (amountDisplayPrecision a) (amountInternalPrecision a)
|
||||
|
||||
-- | Similar to amountSetPrecision, but with an upper limit (up to 255).
|
||||
-- And always sets an explicit Precision.
|
||||
-- Useful for showing a not-too-verbose approximation of amounts with infinite decimals.
|
||||
amountSetFullPrecisionUpTo :: Word8 -> Amount -> Amount
|
||||
amountSetFullPrecisionUpTo n a = amountSetPrecision (Precision p) a
|
||||
where p = min n $ max (amountDisplayPrecision a) (amountInternalPrecision a)
|
||||
|
||||
-- | We often want to display "infinite decimal" amounts rounded to some readable
|
||||
-- number of digits, while still displaying amounts with a large "non infinite" number
|
||||
-- of decimal digits (eg, 100 or 200 digits) in full.
|
||||
-- This helper is like amountSetFullPrecision, but with some refinements:
|
||||
-- 1. If the internal precision is the maximum (255), indicating an infinite decimal,
|
||||
-- the display precision is set to a smaller hard-coded default (8).
|
||||
-- 2. A maximum display precision can be specified, setting a hard upper limit.
|
||||
-- This function always sets an explicit display precision (ie, Precision n).
|
||||
amountSetFullPrecisionOr :: Maybe Word8 -> Amount -> Amount
|
||||
amountSetFullPrecisionOr mmaxp a = amountSetPrecision (Precision p2) a
|
||||
where
|
||||
p1 = if -- dbg0 "maxdigits" $
|
||||
amountHasMaxDigits a then defaultMaxPrecision else max disp intp
|
||||
-- & dbg0 "p1"
|
||||
where
|
||||
intp = amountInternalPrecision a
|
||||
disp = amountDisplayPrecision a
|
||||
p2 = maybe p1 (min p1) mmaxp
|
||||
-- & dbg0 "p2"
|
||||
|
||||
-- | The fallback display precision used when showing amounts
|
||||
-- representing an infinite decimal.
|
||||
defaultMaxPrecision :: Word8
|
||||
defaultMaxPrecision = 8
|
||||
|
||||
-- | How many internal decimal digits are stored for this amount ?
|
||||
amountInternalPrecision :: Amount -> Word8
|
||||
|
@ -57,6 +57,7 @@ import Hledger.Data.Journal
|
||||
import Hledger.Data.Posting
|
||||
import Hledger.Data.Transaction
|
||||
import Hledger.Data.Errors
|
||||
import Data.Bifunctor (second)
|
||||
|
||||
|
||||
data BalancingOpts = BalancingOpts
|
||||
@ -167,10 +168,12 @@ balanceTransactionHelper ::
|
||||
-> Transaction
|
||||
-> Either String (Transaction, [(AccountName, MixedAmount)])
|
||||
balanceTransactionHelper bopts t = do
|
||||
(t', inferredamtsandaccts) <-
|
||||
transactionInferBalancingAmount (fromMaybe M.empty $ commodity_styles_ bopts) $
|
||||
(if infer_balancing_costs_ bopts then transactionInferBalancingCosts else id)
|
||||
t
|
||||
let lbl = lbl_ "balanceTransactionHelper"
|
||||
(t', inferredamtsandaccts) <- t
|
||||
& (if infer_balancing_costs_ bopts then transactionInferBalancingCosts else id)
|
||||
& dbg9With (lbl "amounts after balancing-cost-inferring".show.map showMixedAmountOneLine.transactionAmounts)
|
||||
& transactionInferBalancingAmount (fromMaybe M.empty $ commodity_styles_ bopts)
|
||||
<&> dbg9With (lbl "balancing amounts inferred".show.map (second showMixedAmountOneLine).snd)
|
||||
case transactionCheckBalanced bopts t' of
|
||||
[] -> Right (txnTieKnot t', inferredamtsandaccts)
|
||||
errs -> Left $ transactionBalanceError t' errs'
|
||||
@ -234,10 +237,16 @@ transactionInferBalancingAmount styles t@Transaction{tpostings=ps}
|
||||
| otherwise
|
||||
= let psandinferredamts = map inferamount ps
|
||||
inferredacctsandamts = [(paccount p, amt) | (p, Just amt) <- psandinferredamts]
|
||||
in Right (t{tpostings=map fst psandinferredamts}, inferredacctsandamts)
|
||||
in Right (
|
||||
t{tpostings=map fst psandinferredamts}
|
||||
,inferredacctsandamts
|
||||
-- & dbg9With (lbl "inferred".show.map (showMixedAmountOneLine.snd))
|
||||
)
|
||||
where
|
||||
lbl = lbl_ "transactionInferBalancingAmount"
|
||||
(amountfulrealps, amountlessrealps) = partition hasAmount (realPostings t)
|
||||
realsum = sumPostings amountfulrealps
|
||||
-- & dbg9With (lbl "real balancing amount".showMixedAmountOneLine)
|
||||
(amountfulbvps, amountlessbvps) = partition hasAmount (balancedVirtualPostings t)
|
||||
bvsum = sumPostings amountfulbvps
|
||||
|
||||
@ -257,7 +266,17 @@ transactionInferBalancingAmount styles t@Transaction{tpostings=ps}
|
||||
-- Inferred amounts are converted to cost.
|
||||
-- Also ensure the new amount has the standard style for its commodity
|
||||
-- (since the main amount styling pass happened before this balancing pass);
|
||||
a' = styleAmounts styles . mixedAmountCost $ maNegate a
|
||||
a' = maNegate a
|
||||
-- & dbg9With (lbl "balancing amount".showMixedAmountOneLine)
|
||||
& mixedAmountCost
|
||||
-- & dbg9With (lbl "balancing amount converted to cost".showMixedAmountOneLine)
|
||||
& styleAmounts (styles
|
||||
-- Needed until we switch to locally-inferred balancing precisions:
|
||||
-- these had hard rounding set to help with balanced-checking;
|
||||
-- set no rounding now to avoid excessive display precision in output
|
||||
& amountStylesSetRounding NoRounding
|
||||
& dbg9With (lbl "balancing amount styles".show))
|
||||
& dbg9With (lbl "balancing amount styled".showMixedAmountOneLine)
|
||||
|
||||
-- | Infer costs for this transaction's posting amounts, if needed to make
|
||||
-- the postings balance, and if permitted. This is done once for the real
|
||||
@ -309,6 +328,7 @@ transactionInferBalancingCosts t@Transaction{tpostings=ps} = t{tpostings=ps'}
|
||||
costInferrerFor :: Transaction -> PostingType -> (Posting -> Posting)
|
||||
costInferrerFor t pt = maybe id infercost inferFromAndTo
|
||||
where
|
||||
lbl = lbl_ "costInferrerFor"
|
||||
postings = filter ((==pt).ptype) $ tpostings t
|
||||
pcommodities = map acommodity $ concatMap (amounts . pamount) postings
|
||||
sumamounts = amounts $ sumPostings postings -- amounts normalises to one amount per commodity & price
|
||||
@ -333,7 +353,8 @@ costInferrerFor t pt = maybe id infercost inferFromAndTo
|
||||
infercost (fromamount, toamount) p
|
||||
| [a] <- amounts (pamount p), ptype p == pt, acommodity a == acommodity fromamount
|
||||
= p{ pamount = mixedAmount a{aprice=Just conversionprice}
|
||||
, poriginal = Just $ originalPosting p }
|
||||
& dbg9With (lbl "inferred cost".showMixedAmountOneLine)
|
||||
, poriginal = Just $ originalPosting p }
|
||||
| otherwise = p
|
||||
where
|
||||
-- If only one Amount in the posting list matches fromamount we can use TotalPrice.
|
||||
|
@ -86,6 +86,8 @@ module Hledger.Data.Journal (
|
||||
journalNextTransaction,
|
||||
journalPrevTransaction,
|
||||
journalPostings,
|
||||
journalPostingAmounts,
|
||||
showJournalAmountsDebug,
|
||||
journalTransactionsSimilarTo,
|
||||
-- * Account types
|
||||
journalAccountType,
|
||||
@ -146,6 +148,7 @@ import System.FilePath (takeFileName)
|
||||
import Data.Ord (comparing)
|
||||
import Hledger.Data.Dates (nulldate)
|
||||
import Data.List (sort)
|
||||
-- import Data.Function ((&))
|
||||
|
||||
|
||||
-- | A parser of text that runs in some monad, keeping a Journal as state.
|
||||
@ -342,6 +345,14 @@ journalPrevTransaction j t = journalTransactionAt j (tindex t - 1)
|
||||
journalPostings :: Journal -> [Posting]
|
||||
journalPostings = concatMap tpostings . jtxns
|
||||
|
||||
-- | All posting amounts from this journal, in order.
|
||||
journalPostingAmounts :: Journal -> [MixedAmount]
|
||||
journalPostingAmounts = map pamount . journalPostings
|
||||
|
||||
-- | Show the journal amounts rendered, suitable for debug logging.
|
||||
showJournalAmountsDebug :: Journal -> String
|
||||
showJournalAmountsDebug = show.map showMixedAmountOneLine.journalPostingAmounts
|
||||
|
||||
-- | Sorted unique commodity symbols declared by commodity directives in this journal.
|
||||
journalCommoditiesDeclared :: Journal -> [CommoditySymbol]
|
||||
journalCommoditiesDeclared = M.keys . jcommodities
|
||||
|
@ -32,6 +32,7 @@ module Hledger.Data.Transaction
|
||||
, transactionApplyAliases
|
||||
, transactionMapPostings
|
||||
, transactionMapPostingAmounts
|
||||
, transactionAmounts
|
||||
, partitionAndCheckConversionPostings
|
||||
-- nonzerobalanceerror
|
||||
-- * date operations
|
||||
@ -445,6 +446,10 @@ transactionMapPostings f t@Transaction{tpostings=ps} = t{tpostings=map f ps}
|
||||
transactionMapPostingAmounts :: (MixedAmount -> MixedAmount) -> Transaction -> Transaction
|
||||
transactionMapPostingAmounts f = transactionMapPostings (postingTransformAmount f)
|
||||
|
||||
-- | All posting amounts from this transactin, in order.
|
||||
transactionAmounts :: Transaction -> [MixedAmount]
|
||||
transactionAmounts = map pamount . tpostings
|
||||
|
||||
-- | The file path from which this transaction was parsed.
|
||||
transactionFile :: Transaction -> FilePath
|
||||
transactionFile Transaction{tsourcepos} = sourceName $ fst tsourcepos
|
||||
|
@ -47,7 +47,7 @@ import Hledger.Data.Types
|
||||
import Hledger.Data.Amount
|
||||
import Hledger.Data.Dates (nulldate)
|
||||
import Text.Printf (printf)
|
||||
import Data.Decimal (normalizeDecimal, DecimalRaw (decimalPlaces))
|
||||
import Data.Decimal (decimalPlaces, roundTo)
|
||||
|
||||
|
||||
------------------------------------------------------------------------------
|
||||
@ -100,24 +100,17 @@ priceDirectiveToMarketPrice PriceDirective{..} =
|
||||
|
||||
-- | Infer a market price from the given amount and its cost (if any),
|
||||
-- and make a corresponding price directive on the given date.
|
||||
-- The price's display precision will be set to show all significant
|
||||
-- decimal digits; or if they seem to be infinite, defaultPrecisionLimit.
|
||||
amountPriceDirectiveFromCost :: Day -> Amount -> Maybe PriceDirective
|
||||
amountPriceDirectiveFromCost d amt@Amount{acommodity=fromcomm, aquantity=n} = case aprice amt of
|
||||
Just (UnitPrice u) -> Just $ pd{pdamount=u}
|
||||
Just (TotalPrice t) | n /= 0 -> Just $ pd{pdamount=u} where u = divideAmountExtraPrecision n t}
|
||||
Just (TotalPrice t) | n /= 0 -> Just $ pd{pdamount=u}
|
||||
where u = amountSetFullPrecisionOr Nothing $ divideAmount n t
|
||||
_ -> Nothing
|
||||
where
|
||||
pd = PriceDirective{pddate = d, pdcommodity = fromcomm, pdamount = nullamt}
|
||||
|
||||
-- | Divide an amount's quantity (and total cost, if any) by some number n,
|
||||
-- and also increase its display precision by the number of digits in n's integer part,
|
||||
-- to avoid showing a misleadingly rounded result.
|
||||
divideAmountExtraPrecision n a = (divideAmount n a) { astyle = style' }
|
||||
where
|
||||
style' = (astyle a) { asprecision = precision' }
|
||||
precision' = case asprecision (astyle a) of
|
||||
NaturalPrecision -> NaturalPrecision
|
||||
Precision p -> Precision $ p + numDigitsInt (truncate n)
|
||||
|
||||
------------------------------------------------------------------------------
|
||||
-- Converting things to value
|
||||
|
||||
@ -191,6 +184,7 @@ mixedAmountValueAtDate priceoracle styles mc d = mapMixedAmount (amountValueAtDa
|
||||
--
|
||||
amountValueAtDate :: PriceOracle -> M.Map CommoditySymbol AmountStyle -> Maybe CommoditySymbol -> Day -> Amount -> Amount
|
||||
amountValueAtDate priceoracle styles mto d a =
|
||||
let lbl = lbl_ "amountValueAtDate" in
|
||||
case priceoracle (d, acommodity a, mto) of
|
||||
Nothing -> a
|
||||
Just (comm, rate) ->
|
||||
@ -204,11 +198,11 @@ amountValueAtDate priceoracle styles mto d a =
|
||||
-- Now apply the standard display style for comm
|
||||
& styleAmounts styles
|
||||
-- and set the display precision to rate's internal precision
|
||||
-- XXX (unnormalised - don't strip trailing zeros) ?
|
||||
-- XXX valuation.test:8 why is it showing precision 1 ?
|
||||
& amountSetPrecision (Precision $ decimalPlaces $ normalizeDecimal rate)
|
||||
-- or at least 1, ensuring we show at least one decimal place.
|
||||
-- & amountSetPrecision (Precision $ max 1 (decimalPlaces $ normalizeDecimal rate))
|
||||
-- (unnormalised - don't strip trailing zeros)
|
||||
-- & amountSetPrecision (Precision $ decimalPlaces rate)
|
||||
& amountSetFullPrecisionOr Nothing -- (Just defaultMaxPrecision)
|
||||
& dbg9With (lbl "calculated value".showAmount)
|
||||
-- & dbg9With (lbl "precision of value".show.amountDisplayPrecision)
|
||||
-- see also print-styles.test, valuation2.test
|
||||
|
||||
-- | Calculate the gain of each component amount, that is the difference
|
||||
@ -282,7 +276,19 @@ priceLookup makepricegraph d from mto =
|
||||
of
|
||||
Nothing -> Nothing
|
||||
Just [] -> Nothing
|
||||
Just ps -> Just (mpto $ last ps, product $ map mprate ps)
|
||||
Just ps -> Just (mpto $ last ps, rate)
|
||||
where
|
||||
rates = map mprate ps
|
||||
rate =
|
||||
-- aggregate all the prices into one
|
||||
product rates
|
||||
-- product (Decimal's Num instance) normalises, stripping trailing zeros.
|
||||
-- Here we undo that (by restoring the old max precision with roundTo),
|
||||
-- so that amountValueAtDate can see the original internal precision,
|
||||
-- to use as the display precision of calculated value amounts.
|
||||
-- (This can add more than the original number of trailing zeros to some prices,
|
||||
-- making them seem more precise than they were, but it seems harmless here.)
|
||||
& roundTo (maximum $ map decimalPlaces rates)
|
||||
|
||||
tests_priceLookup =
|
||||
let
|
||||
|
@ -317,6 +317,9 @@ initialiseAndParseJournal parser iopts f txt =
|
||||
journalFinalise :: InputOpts -> FilePath -> Text -> ParsedJournal -> ExceptT String IO Journal
|
||||
journalFinalise iopts@InputOpts{..} f txt pj = do
|
||||
t <- liftIO getPOSIXTime
|
||||
let
|
||||
fname = "journalFinalise " <> takeFileName f
|
||||
lbl = lbl_ fname
|
||||
liftEither $ do
|
||||
{-# HLINT ignore "Functor law" #-}
|
||||
j <- pj{jglobalcommoditystyles=fromMaybe mempty $ commodity_styles_ balancingopts_}
|
||||
@ -334,16 +337,20 @@ journalFinalise iopts@InputOpts{..} f txt pj = do
|
||||
-- XXX how to force debug output here ?
|
||||
-- >>= Right . dbg0With (concatMap (T.unpack.showTransaction).jtxns)
|
||||
-- >>= \j -> deepseq (concatMap (T.unpack.showTransaction).jtxns $ j) (return j)
|
||||
<&> dbg9With (lbl "amounts after styling, forecasting, auto-posting".showJournalAmountsDebug)
|
||||
>>= journalBalanceTransactions balancingopts_ -- infer balance assignments and missing amounts and maybe check balance assertions.
|
||||
>>= journalInferCommodityStyles -- infer commodity styles once more now that all posting amounts are present (XXX or journalStyleAmounts ?)
|
||||
<&> dbg9With (lbl "amounts after transaction-balancing".showJournalAmountsDebug)
|
||||
-- <&> dbg9With (("journalFinalise amounts after styling, forecasting, auto postings, transaction balancing"<>).showJournalAmountsDebug)
|
||||
>>= journalInferCommodityStyles -- infer commodity styles once more now that all posting amounts are present
|
||||
-- >>= Right . dbg0With (pshow.journalCommodityStyles)
|
||||
>>= (if infer_costs_ then journalInferCostsFromEquity else pure) -- Maybe infer costs from equity postings where possible
|
||||
<&> (if infer_equity_ then journalInferEquityFromCosts verbose_tags_ else id) -- Maybe infer equity postings from costs where possible
|
||||
<&> dbg9With (lbl "amounts after equity-inferring".showJournalAmountsDebug)
|
||||
<&> journalInferMarketPricesFromTransactions -- infer market prices from commodity-exchanging transactions
|
||||
<&> traceOrLogAt 6 ("journalFinalise: " <> takeFileName f) -- debug logging
|
||||
<&> dbgJournalAcctDeclOrder ("journalFinalise: " <> takeFileName f <> " acct decls : ")
|
||||
-- <&> traceOrLogAt 6 fname -- debug logging
|
||||
<&> dbgJournalAcctDeclOrder (fname <> ": acct decls : ")
|
||||
<&> journalRenumberAccountDeclarations
|
||||
<&> dbgJournalAcctDeclOrder ("journalFinalise: " <> takeFileName f <> " acct decls renumbered: ")
|
||||
<&> dbgJournalAcctDeclOrder (fname <> ": acct decls renumbered: ")
|
||||
when strict_ $ do
|
||||
journalCheckAccounts j -- If in strict mode, check all postings are to declared accounts
|
||||
journalCheckCommodities j -- and using declared commodities
|
||||
|
@ -83,6 +83,7 @@ import Text.Megaparsec.Custom
|
||||
import Hledger.Data
|
||||
import Hledger.Query
|
||||
import Hledger.Utils
|
||||
import Data.Function ((&))
|
||||
|
||||
|
||||
-- | What to calculate for each cell in a balance report.
|
||||
@ -587,19 +588,28 @@ journalValueAndFilterPostingsWith rspec@ReportSpec{_rsQuery=q, _rsReportOpts=rop
|
||||
-- condition.
|
||||
journalApplyValuationFromOpts :: ReportSpec -> Journal -> Journal
|
||||
journalApplyValuationFromOpts rspec j =
|
||||
journalApplyValuationFromOptsWith rspec j priceoracle
|
||||
journalApplyValuationFromOptsWith rspec j priceoracle
|
||||
where priceoracle = journalPriceOracle (infer_prices_ $ _rsReportOpts rspec) j
|
||||
|
||||
-- | Like journalApplyValuationFromOpts, but takes PriceOracle as an argument.
|
||||
journalApplyValuationFromOptsWith :: ReportSpec -> Journal -> PriceOracle -> Journal
|
||||
journalApplyValuationFromOptsWith rspec@ReportSpec{_rsReportOpts=ropts} j priceoracle =
|
||||
case balancecalc_ ropts of
|
||||
CalcGain -> journalMapPostings (\p -> postingTransformAmount (gain p) p) j
|
||||
_ -> journalMapPostings (\p -> postingTransformAmount (valuation p) p) $ costing j
|
||||
costfn j
|
||||
& journalMapPostings (\p -> p
|
||||
& dbg9With (lbl "before calc".showMixedAmountOneLine.pamount)
|
||||
& postingTransformAmount (calcfn p)
|
||||
& dbg9With (lbl (show calc).showMixedAmountOneLine.pamount)
|
||||
)
|
||||
where
|
||||
valuation p = maybe id (mixedAmountApplyValuation priceoracle styles (postingperiodend p) (_rsDay rspec) (postingDate p)) (value_ ropts)
|
||||
gain p = maybe id (mixedAmountApplyGain priceoracle styles (postingperiodend p) (_rsDay rspec) (postingDate p)) (value_ ropts)
|
||||
costing = journalToCost (fromMaybe NoConversionOp $ conversionop_ ropts)
|
||||
lbl = lbl_ "journalApplyValuationFromOptsWith"
|
||||
-- Which custom calculation to do for balance reports. For all other reports, it will be CalcChange.
|
||||
calc = balancecalc_ ropts
|
||||
calcfn = case calc of
|
||||
CalcGain -> \p -> maybe id (mixedAmountApplyGain priceoracle styles (postingperiodend p) (_rsDay rspec) (postingDate p)) (value_ ropts)
|
||||
_ -> \p -> maybe id (mixedAmountApplyValuation priceoracle styles (postingperiodend p) (_rsDay rspec) (postingDate p)) (value_ ropts)
|
||||
costfn = case calc of
|
||||
CalcGain -> id
|
||||
_ -> journalToCost costop where costop = fromMaybe NoConversionOp $ conversionop_ ropts
|
||||
|
||||
-- Find the end of the period containing this posting
|
||||
postingperiodend = addDays (-1) . fromMaybe err . mPeriodEnd . postingDateOrDate2 (whichDate ropts)
|
||||
|
@ -49,6 +49,7 @@ module Hledger.Utils (
|
||||
-- * Misc
|
||||
multicol,
|
||||
numDigitsInt,
|
||||
numDigitsInteger,
|
||||
makeHledgerClassyLenses,
|
||||
|
||||
-- * Other
|
||||
@ -233,6 +234,12 @@ numDigitsInt n
|
||||
| a >= 100000000 = 8 + go (a `quot` 100000000)
|
||||
| otherwise = 4 + go (a `quot` 10000)
|
||||
|
||||
-- | Find the number of digits of an Integer.
|
||||
-- The integer should not have more digits than an Int can count.
|
||||
-- This is probably inefficient.
|
||||
numDigitsInteger :: Integer -> Int
|
||||
numDigitsInteger = length . dropWhile (=='-') . show
|
||||
|
||||
-- | Make classy lenses for Hledger options fields.
|
||||
-- This is intended to be used with BalancingOpts, InputOpt, ReportOpts,
|
||||
-- ReportSpec, and CliOpts.
|
||||
|
@ -15,6 +15,7 @@ import Hledger
|
||||
import Hledger.Cli.CliOptions
|
||||
import System.Console.CmdArgs.Explicit
|
||||
import Data.Maybe (mapMaybe)
|
||||
import Data.Function ((&))
|
||||
|
||||
pricesmode = hledgerCommandMode
|
||||
$(embedFileRelative "Hledger/Cli/Commands/Prices.txt")
|
||||
@ -92,13 +93,21 @@ showPriceDirective mp = T.unwords [
|
||||
]
|
||||
|
||||
-- | Convert a market price directive to a corresponding one in the
|
||||
-- opposite direction, if possible. (A price directive specifying zero
|
||||
-- as the price can't be reversed.)
|
||||
-- The display precision is set to show all significant decimal digits,
|
||||
-- up to a maximum of 8 (this is visible eg in the prices command's output).
|
||||
-- opposite direction, if possible. (A price directive with a zero
|
||||
-- price can't be reversed.)
|
||||
--
|
||||
-- The price's display precision will be set to show all significant
|
||||
-- decimal digits (or if they appear infinite, a smaller default precision (8).
|
||||
-- This is visible eg in the prices command's output.
|
||||
--
|
||||
reversePriceDirective :: PriceDirective -> Maybe PriceDirective
|
||||
reversePriceDirective pd@PriceDirective{pdcommodity=c, pdamount=a}
|
||||
| amountIsZero a = Nothing
|
||||
| otherwise =
|
||||
Just pd{pdcommodity=acommodity a, pdamount=setprec $ invertAmount a{acommodity=c}}
|
||||
where setprec = amountSetFullPrecisionUpTo 8
|
||||
| otherwise = Just pd{pdcommodity=acommodity a, pdamount=a'}
|
||||
where
|
||||
lbl = lbl_ "reversePriceDirective"
|
||||
a' =
|
||||
amountSetFullPrecisionOr (Just defaultMaxPrecision) $
|
||||
invertAmount a{acommodity=c}
|
||||
& dbg9With (lbl "calculated reverse price".showAmount)
|
||||
-- & dbg9With (lbl "precision of reverse price".show.amountDisplayPrecision)
|
||||
|
@ -29,6 +29,7 @@ import Hledger.Cli.CliOptions
|
||||
import Hledger.Cli.Utils
|
||||
import System.Exit (exitFailure)
|
||||
import Safe (lastMay)
|
||||
import Data.Function ((&))
|
||||
|
||||
|
||||
printmode = hledgerCommandMode
|
||||
@ -82,7 +83,13 @@ print' opts j = do
|
||||
-- that. For now we try to reverse it by increasing all amounts' decimal places
|
||||
-- sufficiently to show the amount exactly. The displayed amounts may have minor
|
||||
-- differences from the originals, such as trailing zeroes added.
|
||||
let j' = journalMapPostingAmounts mixedAmountSetFullPrecision j
|
||||
let
|
||||
-- lbl = lbl_ "print'"
|
||||
j' = j
|
||||
-- & dbg9With (lbl "amounts before setting full precision".showJournalAmountsDebug)
|
||||
& journalMapPostingAmounts mixedAmountSetFullPrecision
|
||||
-- & dbg9With (lbl "amounts after setting full precision: ".showJournalAmountsDebug)
|
||||
|
||||
case maybestringopt "match" $ rawopts_ opts of
|
||||
Nothing -> printEntries opts j'
|
||||
Just desc ->
|
||||
|
@ -62,6 +62,7 @@ roi :: CliOpts -> Journal -> IO ()
|
||||
roi CliOpts{rawopts_=rawopts, reportspec_=rspec@ReportSpec{_rsReportOpts=ReportOpts{..}}} j = do
|
||||
-- We may be converting posting amounts to value, per hledger_options.m4.md "Effect of --value on reports".
|
||||
let
|
||||
-- lbl = lbl_ "roi"
|
||||
today = _rsDay rspec
|
||||
priceOracle = journalPriceOracle infer_prices_ j
|
||||
styles = journalCommodityStyles j
|
||||
@ -137,6 +138,13 @@ roi CliOpts{rawopts_=rawopts, reportspec_=rspec@ReportSpec{_rsReportOpts=ReportO
|
||||
, showDate (addDays (-1) e)
|
||||
, T.pack $ showMixedAmount $ styleAmounts styles $ valueBefore
|
||||
, T.pack $ showMixedAmount $ styleAmounts styles $ cashFlowAmt
|
||||
-- , T.pack $ showMixedAmount $
|
||||
-- -- dbg0With (lbl "cashflow after styling".showMixedAmountOneLine) $
|
||||
-- mapMixedAmount (amountSetFullPrecisionOr (Just defaultMaxPrecision)) $
|
||||
-- styleAmounts (styles
|
||||
-- -- & dbg0With (lbl "styles".show))
|
||||
-- cashFlowAmt
|
||||
-- -- & dbg0With (lbl "cashflow before styling".showMixedAmountOneLine)
|
||||
, T.pack $ showMixedAmount $ styleAmounts styles $ valueAfter
|
||||
, T.pack $ showMixedAmount $ styleAmounts styles $ (valueAfter `maMinus` (valueBefore `maPlus` cashFlowAmt))
|
||||
, T.pack $ printf "%0.2f%%" $ smallIsZero irr
|
||||
|
@ -59,6 +59,8 @@ Budget performance in 2016-12-01..2016-12-03:
|
||||
|| 0 [ 0] 0 [ 0] 0 [ 0]
|
||||
|
||||
# ** 3. Test that budget works with mix of commodities
|
||||
# XXX Here $'s precision is increased to 1 by the third transaction's
|
||||
# $11.0 balancing amount. Is that ok ? What if it was $11.1 ?
|
||||
<
|
||||
2016/12/01
|
||||
expenses:food £10 @@ $15
|
||||
@ -96,14 +98,14 @@ Budget performance in 2016-12-01..2016-12-03:
|
||||
$ hledger -f- bal -D -b 2016-12-01 -e 2016-12-04 --budget
|
||||
Budget performance in 2016-12-01..2016-12-03:
|
||||
|
||||
|| 2016-12-01 2016-12-02 2016-12-03
|
||||
==================++===========================================================================
|
||||
assets:cash || $-15 [60% of $-25] $-26 [104% of $-25] $-51 [204% of $-25]
|
||||
expenses || £10 [ $25] $5, 20 CAD [ $25] $51 [204% of $25]
|
||||
expenses:food || £10 [ $10] 20 CAD [ $10] $11 [110% of $10]
|
||||
expenses:leisure || 0 [ 0% of $15] $5 [ 33% of $15] 0 [ 0% of $15]
|
||||
------------------++---------------------------------------------------------------------------
|
||||
|| $-15, £10 [ 0] $-21, 20 CAD [ 0] 0 [ 0]
|
||||
|| 2016-12-01 2016-12-02 2016-12-03
|
||||
==================++=======================================================================================
|
||||
assets:cash || $-15.0 [60% of $-25.0] $-26.0 [104% of $-25.0] $-51.0 [204% of $-25.0]
|
||||
expenses || £10 [ $25.0] $5.0, 20 CAD [ $25.0] $51.0 [204% of $25.0]
|
||||
expenses:food || £10 [ $10.0] 20 CAD [ $10.0] $11.0 [110% of $10.0]
|
||||
expenses:leisure || 0 [ 0% of $15.0] $5.0 [ 33% of $15.0] 0 [ 0% of $15.0]
|
||||
------------------++---------------------------------------------------------------------------------------
|
||||
|| $-15.0, £10 [ 0] $-21.0, 20 CAD [ 0] 0 [ 0]
|
||||
|
||||
# ** 4. --budget with no interval shows total budget for the journal period
|
||||
# (in tabular format).
|
||||
|
@ -325,11 +325,11 @@ $ hledger -f- print -xB
|
||||
# ** 25. Here are the market prices inferred, since 1.26:
|
||||
$ hledger -f- --infer-market-prices prices
|
||||
P 2022-01-01 B A 1
|
||||
P 2022-01-01 B A 1.0
|
||||
P 2022-01-01 B A 1
|
||||
P 2022-01-02 B A -1
|
||||
P 2022-01-02 B A -1
|
||||
P 2022-01-02 B A -1.0
|
||||
P 2022-01-03 B A -1
|
||||
P 2022-01-03 B A -1.0
|
||||
P 2022-01-03 B A -1
|
||||
|
||||
# ** 26. here, a's primary amount is 0, and its cost is 1Y; b is the assigned auto-balancing amount of -1Y (per issue 69)
|
||||
<
|
||||
|
@ -35,10 +35,10 @@ commodity $1,000.00000000
|
||||
d
|
||||
$ hledger -f - print --explicit
|
||||
2018-01-01
|
||||
a $105
|
||||
b $3.1415926
|
||||
c $1,000.
|
||||
d $-1,108.14159260
|
||||
a $105
|
||||
b $3.1415926
|
||||
c $1,000.
|
||||
d $-1,108.1415926
|
||||
|
||||
>=
|
||||
|
||||
|
@ -105,8 +105,8 @@ $ hledger -f- reg -V
|
||||
2000-01-01 (a) €120.00 €120.00
|
||||
|
||||
|
||||
# ** 8. print -V affects posting amounts, but not balance assertions
|
||||
# (causing it to show a failing balance assertion).
|
||||
# ** 8. print -V affects posting amounts, but not balance assertions,
|
||||
# which can cause it to show a failing balance assertion as here.
|
||||
<
|
||||
P 2000/1/1 $ €1.20
|
||||
2000/1/1
|
||||
@ -114,7 +114,7 @@ P 2000/1/1 $ €1.20
|
||||
|
||||
$ hledger -f- print -V
|
||||
2000-01-01
|
||||
(a) €120.00 = $100
|
||||
(a) €120 = $100
|
||||
|
||||
>=0
|
||||
|
||||
|
@ -1,5 +1,6 @@
|
||||
# * valuation tests, part 2.
|
||||
|
||||
<
|
||||
; some market prices
|
||||
P 2019-01-01 B 10 A
|
||||
P 2019-01-01 C 2 B
|
||||
@ -75,32 +76,24 @@ $ hledger -f- print -x --value=now,D
|
||||
>=
|
||||
|
||||
# ** 8. request commodity E - chains B->A, A->D, reverse of D->E prices.
|
||||
# As with C above, E gets the default display style, with precision 0.
|
||||
# As with C above, E gets the default display style, but with the precision
|
||||
# increased to show the decimal digits, but no more than 8.
|
||||
$ hledger -f- print -x --value=now,E
|
||||
2019-06-01
|
||||
a E333
|
||||
b E-333
|
||||
a E333.33333333
|
||||
b E-333.33333333
|
||||
|
||||
>=
|
||||
# # As with C above, E gets the default display style, but with the precision
|
||||
# # increased to show the decimal digits, but no more than 8.
|
||||
# $ hledger -f- print -x --value=now,E
|
||||
# 2019-06-01
|
||||
# a E333.33333333
|
||||
# b E-333.33333333
|
||||
#
|
||||
# >=
|
||||
|
||||
|
||||
# Document some print behaviour.
|
||||
# I think I've forgotten some other weird cases meant for here.
|
||||
# First an example with no market price, just a transaction price.
|
||||
# ** 9. Normal print output.
|
||||
<
|
||||
2000/01/01
|
||||
a -1A @ 1B
|
||||
b 1B
|
||||
|
||||
# ** 9. Normal print output.
|
||||
$ hledger -f- print
|
||||
2000-01-01
|
||||
a -1A @ 1B
|
||||
|
@ -52,16 +52,15 @@ P 2023-01-07 A B0.01428571
|
||||
# ** 3. With --infer-market-prices it also lists prices inferred from costs
|
||||
# (explicit or inferred, unit or total, positive or negative amounts).
|
||||
# Redundant prices inferred from costs are discarded.
|
||||
# ** XXX why precision 1 for 3/4/5 ?
|
||||
$ hledger prices -f- --infer-market-prices
|
||||
P 2023-01-01 B A10
|
||||
P 2023-01-01 B A100
|
||||
P 2023-01-01 A B100
|
||||
P 2023-01-02 A B1
|
||||
P 2023-01-02 B A200
|
||||
P 2023-01-03 B A3.0
|
||||
P 2023-01-04 B A4.0
|
||||
P 2023-01-05 B A5.0
|
||||
P 2023-01-03 B A3
|
||||
P 2023-01-04 B A4
|
||||
P 2023-01-05 B A5
|
||||
P 2023-01-07 B A70
|
||||
|
||||
# ** 4. --infer-market-prices and --show-reverse combine.
|
||||
@ -71,11 +70,11 @@ P 2023-01-01 B A100
|
||||
P 2023-01-01 A B100
|
||||
P 2023-01-02 A B1
|
||||
P 2023-01-02 B A200
|
||||
P 2023-01-03 B A3.0
|
||||
P 2023-01-03 B A3
|
||||
P 2023-01-03 A B0.33333333
|
||||
P 2023-01-04 B A4.0
|
||||
P 2023-01-04 B A4
|
||||
P 2023-01-04 A B0.25
|
||||
P 2023-01-05 B A5.0
|
||||
P 2023-01-05 B A5
|
||||
P 2023-01-05 A B0.2
|
||||
P 2023-01-07 B A70
|
||||
P 2023-01-07 A B0.01428571
|
||||
@ -125,7 +124,7 @@ $ hledger -f- prices --infer-market-prices
|
||||
Equity:Opening Balances
|
||||
|
||||
$ hledger -f- prices --infer-market-prices
|
||||
P 2021-10-15 ABC 100.0 USD
|
||||
P 2021-10-15 ABC 100 USD
|
||||
|
||||
# ** 11. Commodity styles are applied to all price amounts, but their precision is left unchanged.
|
||||
<
|
||||
@ -156,7 +155,6 @@ P 2019-03-01 EUR $1.07
|
||||
P 2019-03-01 $ 0.93457944 EUR
|
||||
|
||||
# ** 12. Reverse market prices are shown with all decimal digits, up to a maximum of 8.
|
||||
# ** XXX does this prevent displaying more precise prices ?
|
||||
<
|
||||
P 2023-01-01 B 3A
|
||||
|
||||
|
@ -148,7 +148,7 @@ $ hledger -f- print -x
|
||||
# so why is it showing the B amounts with 4 decimal digits instead of the default 0 ?
|
||||
# Summary: a's calculated cost has 4 digits, and so also must the inferred b amount.
|
||||
#
|
||||
# Some implementation-level explanation, for the record:
|
||||
# Some implementation-level details (pre-precisiongeddon, could be out of date):
|
||||
#
|
||||
# In journalFinalise,
|
||||
#
|
||||
@ -183,7 +183,7 @@ $ hledger -f- print -x -B
|
||||
# then re-styled again with the 4 digit B precision inferred from b's amount.
|
||||
# This works out right, in this case.
|
||||
#
|
||||
# Details:
|
||||
# Details (pre-precisiongeddon, could be out of date):
|
||||
#
|
||||
# journalStyleAmounts infers A display style and A precision 0 from 10 A,
|
||||
# and applies the style but not the precision to all A amounts.
|
||||
@ -217,12 +217,7 @@ $ hledger -f - print --infer-market-prices -V
|
||||
>=
|
||||
|
||||
# ** 10. What if a different style/precision is specified for B, eg more digits
|
||||
# than the price ? We still expect the calculated amount to have the price's precision,
|
||||
# as above.
|
||||
# XXX
|
||||
# 2023-01-01
|
||||
# a 0.1234B
|
||||
# b -0.12340B
|
||||
# than the price ? We expect the calculated amount to have the price's precision, as above.
|
||||
<
|
||||
2023-01-01
|
||||
a 1 A @ 0.1234 B
|
||||
@ -262,19 +257,34 @@ P 2023-01-01 B 3.00A
|
||||
a 1A
|
||||
b
|
||||
|
||||
# current:
|
||||
# That propagates to the calculated value, causing it to be displayed with the
|
||||
# maximum 255 decimal digits.
|
||||
# default fallback precision for "infinite" decimals (8).
|
||||
$ hledger -f- print -X B
|
||||
2023-01-01
|
||||
a B0.3333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333
|
||||
b B-0.3333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333
|
||||
a B0.33333333
|
||||
b B-0.33333333
|
||||
|
||||
# preferred:
|
||||
>=
|
||||
# preferred ?
|
||||
# The reverse price is given the same precision as in the forward price declaration
|
||||
# it was inferred from - 2 digits in this example, and that in turn affects the
|
||||
# calculated value.
|
||||
#2023-01-01
|
||||
# a B0.33
|
||||
# b B-0.33
|
||||
#
|
||||
#>=
|
||||
|
||||
# ** 13. Value amounts with very large but not infinite precision are still shown correctly,
|
||||
# eg 100 decimal digits here.
|
||||
<
|
||||
P 2023-01-01 A 0.0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789B
|
||||
|
||||
2023-01-01
|
||||
(a) 1A
|
||||
|
||||
$ hledger -f- print -X B
|
||||
2023-01-01
|
||||
(a) 0.0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789B
|
||||
|
||||
>=
|
||||
|
Loading…
Reference in New Issue
Block a user