exchangealgebra 0.5.3.0 → 0.5.3.1
raw patch · 4 files changed
+33/−6 lines, 4 files
Files
- ChangeLog.md +10/−0
- exchangealgebra.cabal +1/−1
- src/ExchangeAlgebra/Value.hs +6/−4
- test/Spec.hs +16/−1
ChangeLog.md view
@@ -1,5 +1,15 @@ # Changelog for ExchangeAlgebra +## 0.5.3.1 - 2026-10-21++### Fixed++- The `Hashable` instance of `MoneyDecimal` now agrees with its `Eq` instance:+ values that differ only in trailing zeros (`1.0` and `1.00`) have the same+ hash. The hash previously used the raw number of decimal places and the+ mantissa, so equal values could land in different `HashMap` or `HashSet`+ buckets. The `Binary` encoding is unchanged.+ ## 0.5.3.0 - 2026-09-26 ### Added
exchangealgebra.cabal view
@@ -5,7 +5,7 @@ -- see: https://github.com/sol/hpack name: exchangealgebra-version: 0.5.3.0+version: 0.5.3.1 synopsis: Exchange Algebra for bookkeeping and economic simulation description: Please see the README on GitHub at <https://github.com/yakagika/ExchangeAlgebra#readme> category: Accounting, Finance, Math
src/ExchangeAlgebra/Value.hs view
@@ -61,7 +61,7 @@ ) where import ExchangeAlgebra.Algebra (HatVal (..), Nearly (..))-import Data.Decimal (Decimal, DecimalRaw (Decimal), roundTo')+import Data.Decimal (Decimal, DecimalRaw (Decimal), normalizeDecimal, roundTo') import Data.Word (Word8) import Data.Hashable (Hashable (..)) import Control.DeepSeq (NFData (..))@@ -108,7 +108,9 @@ -- 'Binary'/'Hashable' are defined here (not orphan) because 'Data.Decimal' ships -- neither, and 'Alg'/t'Journal' serialization and the binary spill path require--- @Binary v@. Both go through the (places, mantissa) structure of t'Decimal'.+-- @Binary v@. 'Binary' keeps the (places, mantissa) structure of t'Decimal'.+-- 'Hashable' hashes the normalized form (trailing zeros removed), so values that+-- are equal under 'Eq' (@1.0 == 1.00@) have the same hash. instance Binary.Binary MoneyDecimal where {-# INLINE put #-} put (MoneyDecimal (Decimal places mantissa)) = do@@ -122,8 +124,8 @@ instance Hashable MoneyDecimal where {-# INLINE hashWithSalt #-}- hashWithSalt s (MoneyDecimal (Decimal places mantissa)) =- s `hashWithSalt` places `hashWithSalt` mantissa+ hashWithSalt s (MoneyDecimal d) = case normalizeDecimal d of+ Decimal places mantissa -> s `hashWithSalt` places `hashWithSalt` mantissa instance NFData MoneyDecimal where {-# INLINE rnf #-}
test/Spec.hs view
@@ -26,7 +26,7 @@ import qualified ExchangeAlgebra.Journal as EJ import qualified ExchangeAlgebra.Journal.Transfer as EJT import qualified ExchangeAlgebra.Bookkeeping as EB-import ExchangeAlgebra.Value (MoneyDecimal, bankersRound)+import ExchangeAlgebra.Value (MoneyDecimal (..), bankersRound) import qualified ExchangeAlgebra.Simulate as ES import ExchangeAlgebra.Simulate import qualified ExchangeAlgebra.Simulate.Lite as Lite@@ -61,6 +61,8 @@ import qualified Data.Vector.Unboxed as UV import qualified Data.HashMap.Strict as HM+import Data.Hashable (hash)+import Data.Decimal (DecimalRaw (Decimal)) import qualified Data.Map.Strict as M import qualified Data.List as L import qualified Data.List.NonEmpty as NE@@ -571,6 +573,18 @@ actual = EA.projNetNorm qs algSample assertNear "Alg.projNetNorm == norm . bar . proj (set semantics)" expected actual +-- | 'MoneyDecimal' values equal under 'Eq' have equal hashes, whatever their+-- number of decimal places (0.5.3.1: the hash used the raw places and mantissa).+testMoneyDecimalHashMatchesEq :: IO ()+testMoneyDecimalHashMatchesEq = do+ let same :: [(MoneyDecimal, MoneyDecimal)]+ same = [ (MoneyDecimal (Decimal 1 10), MoneyDecimal (Decimal 2 100))+ , (MoneyDecimal (Decimal 0 0), MoneyDecimal (Decimal 3 0))+ , (MoneyDecimal (Decimal 1 125), MoneyDecimal (Decimal 3 12500)) ]+ assertEqual "equal MoneyDecimal values compare equal" True (all (uncurry (==)) same)+ assertEqual "equal MoneyDecimal values hash equally" True+ (all (\(a, b) -> hash a == hash b) same)+ -- | R7 sentinel (a): a duplicated exact base must project the same as a single -- copy (MoneyDecimal exact: no floating tolerance needed). testProjDuplicateExact :: IO ()@@ -6983,6 +6997,7 @@ testMapMaybePosting testProjMultiPatternOnePass testProjNormFastPath+ testMoneyDecimalHashMatchesEq testProjDuplicateExact testProjExactWildcardOverlap testProjNormBarIdentity