packages feed

exchangealgebra-0.6.0.0: src/ExchangeAlgebra/Value.hs

{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE DerivingStrategies         #-}

{- |
    Module     : ExchangeAlgebra.Value
    Copyright  : (c) Kaya Akagi. 2018-2026
    Maintainer : yakagika@icloud.com

    Released under the OWL license

    Exact, non-negative decimal value type t'MoneyDecimal' for use as the @v@
    parameter of @Alg v b@ / @Journal n v b@.

    == Why this exists (DESIGN, 2026-06-06)

    The accounting value type is selectable:

      * @Double@      — fast IEEE-754; the default. Addition is /non-associative/,
        so the order in which same-base postings are summed (which depends on how a
        value was /constructed/) can change @norm@ / @bar@ results. In the
        agent-based simulations this manifested as a ~3% swing in a stock value when
        the construction order changed (see plans/in-progress/LAZY_EVAL_AUDIT.md and
        FP_SUMMATION_SURVEY.md). This is acceptable for relative-price ABM work where
        speed matters, but it is not deterministic/auditable.

      * @MoneyDecimal@   — exact base-10 fixed-point (a non-negative 'Data.Decimal').
        Addition is /exact and associative/, so results are independent of
        construction order: the fromList fold direction, parallel merges, etc. no
        longer change the answer. This is the right choice for audited ledgers and
        for making the construction-order optimizations (fromList O(N)) safe.

    @Integer@ (minimal-currency-unit) is intentionally NOT offered: ABM simulations
    use relative prices with base unit 1 and sub-unit fractional prices, which an
    integer cannot represent.

    == Ergonomics

    Numeric literals work without wrapping, because 'Num'/'Fractional' are derived:

    > import ExchangeAlgebra.Algebra
    > import ExchangeAlgebra.Algebra.Value (MoneyDecimal)
    >
    > entry :: Alg MoneyDecimal (HatBase CountUnit)
    > entry = 10.5 .@ Hat :< Amount .+ 2 .@ Not :< Yen

    == Rounding

    The core algebra only adds/subtracts, which is exact for t'MoneyDecimal' and needs no
    rounding. Rounding is only needed by /multiplication and division/ (tax ratios,
    proration, scalar product) at the point a monetary amount is /finalized/. Use
    'bankersRound': it rounds half-to-even (the unbiased financial default; also GHC's
    'Prelude.round' and IEEE-754's default mode). A ceiling variant ('ceilingRound') is
    provided for jurisdictions whose
    rules differ. There is no single correct rule (e.g. Japanese consumption tax rounding
    varies by company), so the rounding function is explicit and swappable.
-}
module ExchangeAlgebra.Value {-# DEPRECATED "Use ExchangeAlgebra.Algebra.Value instead." #-} 
    ( MoneyDecimal(..)
    , toDecimal
    , bankersRound
    , ceilingRound
    , MoneyParseError(..)
    , moneyDecimalFromText
    , moneyDecimalFromScientific
    , MoneyDouble(..)
    , toDouble
    ) where

import ExchangeAlgebra.Algebra.Value
    ( MoneyDecimal(..), toDecimal, bankersRound, ceilingRound
    , MoneyParseError(..), moneyDecimalFromText, moneyDecimalFromScientific
    , MoneyDouble(..), toDouble )
-- Preserve the existing Hashable Day and TimeOfDay instance visibility.
import ExchangeAlgebra.Algebra.Element.Representation ()