packages feed

exchangealgebra 0.5.3.0 → 0.5.3.1

raw patch · 4 files changed

+33/−6 lines, 4 files

Files

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