From 21a84fb336c46f0c37df9b98818328df0d158b7f Mon Sep 17 00:00:00 2001 From: Simon Michael Date: Sat, 5 Oct 2024 22:23:07 -1000 Subject: [PATCH] fix: calculate value correctly when P amounts have few decimal digits [#2254] Valuation in another commoditay could sometimes be inaccurate if the P price amounts did not have enough decimal places. --- hledger-lib/Hledger/Data/Valuation.hs | 20 +++++++++++++------- hledger/test/journal/valuation2.test | 15 +++++++++++++++ 2 files changed, 28 insertions(+), 7 deletions(-) diff --git a/hledger-lib/Hledger/Data/Valuation.hs b/hledger-lib/Hledger/Data/Valuation.hs index 614f59a4d..03f5cfb39 100644 --- a/hledger-lib/Hledger/Data/Valuation.hs +++ b/hledger-lib/Hledger/Data/Valuation.hs @@ -48,7 +48,8 @@ import Hledger.Data.Types import Hledger.Data.Amount import Hledger.Data.Dates (nulldate) import Text.Printf (printf) -import Data.Decimal (decimalPlaces, roundTo) +import Data.Decimal (decimalPlaces, roundTo, Decimal) +import Data.Word (Word8) ------------------------------------------------------------------------------ @@ -290,12 +291,17 @@ priceLookup makepricegraph d from mto = -- 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) + -- But we want to preserve even those, since the number of decimal digits + -- here will guide amountValueAtDate in setting the Amount display precision later. + -- So we restore them. Or rather, we ensure as many decimal digits as the maximum seen among rates. + -- (Some prices might end up more precise than they were, but that seems harmless here.) + & setMinDecimalPlaces (maximum $ map decimalPlaces rates) + +-- Ensure this Decimal has at least this many decimal places, adding trailing zeros if necessary. +setMinDecimalPlaces :: Word8 -> Decimal -> Decimal +setMinDecimalPlaces n d + | decimalPlaces d < n = roundTo n d -- too few, add some zeros + | otherwise = d -- more than enough, keep as-is tests_priceLookup = let diff --git a/hledger/test/journal/valuation2.test b/hledger/test/journal/valuation2.test index 912e42e2d..5f10d4fa9 100644 --- a/hledger/test/journal/valuation2.test +++ b/hledger/test/journal/valuation2.test @@ -341,3 +341,18 @@ Balance changes in 2019, valued at period ends: ==========++======== Assets:A || $2.00 Income:B || $-2.00 + +# ** 33. #2254 Conversion rates should not be display-rounded during value calculation. +# 100 * 10.5 * 100.5 = 105525 +< +P 2000-01-01 A 10.5 B +P 2000-01-01 B 100.5 C + +2000-01-01 + (a) 100 A + +$ hledger -f- print -X C +2000-01-01 + (a) 105525 C + +>=