diff --git a/hledger-lib/Hledger/Data/Amount.hs b/hledger-lib/Hledger/Data/Amount.hs index 639deeb7d..279788783 100644 --- a/hledger-lib/Hledger/Data/Amount.hs +++ b/hledger-lib/Hledger/Data/Amount.hs @@ -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 diff --git a/hledger-lib/Hledger/Data/Balancing.hs b/hledger-lib/Hledger/Data/Balancing.hs index 7b46ad386..bc5e28d72 100644 --- a/hledger-lib/Hledger/Data/Balancing.hs +++ b/hledger-lib/Hledger/Data/Balancing.hs @@ -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. diff --git a/hledger-lib/Hledger/Data/Journal.hs b/hledger-lib/Hledger/Data/Journal.hs index a6a9e5229..3f34c10fc 100644 --- a/hledger-lib/Hledger/Data/Journal.hs +++ b/hledger-lib/Hledger/Data/Journal.hs @@ -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 diff --git a/hledger-lib/Hledger/Data/Transaction.hs b/hledger-lib/Hledger/Data/Transaction.hs index 3559868fe..154281fe0 100644 --- a/hledger-lib/Hledger/Data/Transaction.hs +++ b/hledger-lib/Hledger/Data/Transaction.hs @@ -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 diff --git a/hledger-lib/Hledger/Data/Valuation.hs b/hledger-lib/Hledger/Data/Valuation.hs index 1bde2d257..c18bc6cc6 100644 --- a/hledger-lib/Hledger/Data/Valuation.hs +++ b/hledger-lib/Hledger/Data/Valuation.hs @@ -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 diff --git a/hledger-lib/Hledger/Read/Common.hs b/hledger-lib/Hledger/Read/Common.hs index 134011568..fe6dd0693 100644 --- a/hledger-lib/Hledger/Read/Common.hs +++ b/hledger-lib/Hledger/Read/Common.hs @@ -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 diff --git a/hledger-lib/Hledger/Reports/ReportOptions.hs b/hledger-lib/Hledger/Reports/ReportOptions.hs index f69da29d1..8e1a46164 100644 --- a/hledger-lib/Hledger/Reports/ReportOptions.hs +++ b/hledger-lib/Hledger/Reports/ReportOptions.hs @@ -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) diff --git a/hledger-lib/Hledger/Utils.hs b/hledger-lib/Hledger/Utils.hs index 6a28d7eba..3435dceb1 100644 --- a/hledger-lib/Hledger/Utils.hs +++ b/hledger-lib/Hledger/Utils.hs @@ -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. diff --git a/hledger/Hledger/Cli/Commands/Prices.hs b/hledger/Hledger/Cli/Commands/Prices.hs index b8509c953..1d9d90c65 100644 --- a/hledger/Hledger/Cli/Commands/Prices.hs +++ b/hledger/Hledger/Cli/Commands/Prices.hs @@ -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) diff --git a/hledger/Hledger/Cli/Commands/Print.hs b/hledger/Hledger/Cli/Commands/Print.hs index b8a50f5f3..a315689b7 100644 --- a/hledger/Hledger/Cli/Commands/Print.hs +++ b/hledger/Hledger/Cli/Commands/Print.hs @@ -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 -> diff --git a/hledger/Hledger/Cli/Commands/Roi.hs b/hledger/Hledger/Cli/Commands/Roi.hs index 18a3a767a..ec7a239f0 100644 --- a/hledger/Hledger/Cli/Commands/Roi.hs +++ b/hledger/Hledger/Cli/Commands/Roi.hs @@ -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 diff --git a/hledger/test/balance/budget.test b/hledger/test/balance/budget.test index b753b0914..87a1b48c9 100644 --- a/hledger/test/balance/budget.test +++ b/hledger/test/balance/budget.test @@ -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). diff --git a/hledger/test/journal/costs.test b/hledger/test/journal/costs.test index a01f929eb..d663db0c3 100644 --- a/hledger/test/journal/costs.test +++ b/hledger/test/journal/costs.test @@ -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) < diff --git a/hledger/test/journal/scientific.test b/hledger/test/journal/scientific.test index de942512f..325e71810 100644 --- a/hledger/test/journal/scientific.test +++ b/hledger/test/journal/scientific.test @@ -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 >= diff --git a/hledger/test/journal/valuation.test b/hledger/test/journal/valuation.test index 677616480..fb027504b 100644 --- a/hledger/test/journal/valuation.test +++ b/hledger/test/journal/valuation.test @@ -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 diff --git a/hledger/test/journal/valuation2.test b/hledger/test/journal/valuation2.test index fd5f763bb..912e42e2d 100644 --- a/hledger/test/journal/valuation2.test +++ b/hledger/test/journal/valuation2.test @@ -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 diff --git a/hledger/test/prices.test b/hledger/test/prices.test index 5610de8f6..ffc6c3e5e 100644 --- a/hledger/test/prices.test +++ b/hledger/test/prices.test @@ -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 diff --git a/hledger/test/print/print-style.test b/hledger/test/print/print-style.test index 9107702f8..17f663731 100644 --- a/hledger/test/print/print-style.test +++ b/hledger/test/print/print-style.test @@ -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 + +>=