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:
Simon Michael 2023-11-08 06:48:29 -08:00
parent c51d883162
commit f8ffd9cdda
18 changed files with 226 additions and 105 deletions

View File

@ -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

View File

@ -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.

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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.

View File

@ -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)

View File

@ -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 ->

View File

@ -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

View File

@ -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).

View File

@ -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)
<

View File

@ -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
>=

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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
>=