obiloud / numeric-decimal / Numeric.Decimal

Definition


type Decimal s p

Decimal number with precision parameter (i.e. number of digits following decimal point) and rounding strategy.

The s type variable is for a phantom type and it is here to provide type level safety of arithmetic operations on Decimals. There is nothing on the type level that is restricting you from adding two decimals with different precision parameter.

This could result in something you didn't anticipate. Precision and rounding strategy of the left operand will be used for the resulting decimal. The second operand is not scaled or rounded down to match the first operand (i.e. 1.2 + 1.25 == 13.7).

But there is a way out of this with the help of a custom phantom type:

type Dollars
    = Dollars

type Pennies
    = Pennies

dollars : Int -> Decimal Dollars Int
dollars =
    Decimal.succeed HalfToEven nat0

pennies : Int -> Decimal Pennies Int
pennies =
    Decimal.succeed HalfToEven nat2

let
    x = pennies 120

    y = dollars 210
in
Decimal.add x y

-- TYPE MISMATCH ----- ~/decimal/tests/DecimalTest.elm

-- The 2nd argument to `add` is not what I expect:

-- 105| Decimal.add x y
--                    ^
-- This `y` value is a:

--     Decimal Dollars Int

-- But `add` needs the 2nd argument to be:

--     Decimal Pennies Int

The p is for precision. In more advanced type system this could be arbitrary Intergal type (Integer, Int8, Int16, Int32, Int64 etc.) but in Elm it is difficult to achieve that level of abstraction. To support applicative chaining of operations there is p type variable (wrapped value can be a function).

Applicative

succeed : RoundingAlgorythm -> Numeric.Nat.Nat -> p -> Decimal s p

A Decimal that succeeds without scaling or rounding.

import Numeric.Decimal as Decimal
import Numeric.Decimal.Rounding exposing (RoundingAlgorythm(..))
import Numeric.Nat exposing (nat2)

Decimal.succeed RoundDown nat2 100
    |> Decimal.toString
    -- 1.00

map : (a -> b) -> Decimal s a -> Decimal s b

Transform wrapped value with a given function.

import Numeric.Decimal as Decimal
import Numeric.Decimal.Rounding exposing (RoundingAlgorythm(..))
import Numeric.Nat exposing (nat2)

Decimal.map sqrt (Decimal.succeed RoundDown nat2 25)
    |> Decimal.toString
    -- 0.50

map2 : (a -> b -> c) -> Decimal s a -> Decimal s b -> Decimal s c

Apply a function on two decimal values.

import Numeric.Decimal as Decimal
import Numeric.Decimal.Rounding exposing (RoundingAlgorythm(..))
import Numeric.Nat exposing (nat2)

Decimal.map2 (+)
    (Decimal.succeed RoundDown nat2 234)
    (Decimal.succeed RoundDown nat2 567)
    |> Decimal.toString
    -- 8.01

andMap : Decimal s a -> Decimal s (a -> b) -> Decimal s b

Apply wrapped function inside Decimal on another decimal value.

import Numeric.Decimal as Decimal
import Numeric.Decimal.Rounding exposing (RoundingAlgorythm(..))
import Numeric.Nat exposing (nat2)

Decimal.succeed RoundDown nat2 (\x -> x + 1)
    |> Decimal.andMap (Decimal.succeed RoundDown nat2 567)
    |> Decimal.toString
    -- 5.68

Conversion

fromInt : RoundingAlgorythm -> Numeric.Nat.Nat -> Basics.Int -> Decimal s Basics.Int

Convert Int to Decimal while performing necessary scaling.

import Numeric.Decimal as Decimal
import Numeric.Decimal.Rounding exposing (RoundingAlgorythm(..))
import Numeric.Nat exposing (nat3)

Decimal.fromInt RoundDown nat3 7
    |> Decimal.toString
    -- 7.000

fromDecimalBounded : Decimal s Basics.Int -> Result Numeric.ArithmeticError.ArithmeticError (Decimal s Basics.Int)

Convert a Decimal to another Decimal while checking for Overflow/Underflow.

fromRational : RoundingAlgorythm -> Numeric.Nat.Nat -> Numeric.Rational.Rational -> Result Numeric.ArithmeticError.ArithmeticError (Decimal s Basics.Int)

Convert Rational to Decimal.

fromRationalBounded : RoundingAlgorythm -> Numeric.Nat.Nat -> Numeric.Rational.Rational -> Result Numeric.ArithmeticError.ArithmeticError (Decimal s Basics.Int)

Convert from Rational to Decimal while checking for Overflow/Underflow.

toRational : Decimal s Basics.Int -> Numeric.Rational.Rational

Converting Decimal to Rational.

withRounding : RoundingAlgorythm -> Decimal s p -> Decimal s p

Use different rounding strategy.

toFloat : Decimal s Basics.Int -> Basics.Float

Converting Decimal to Float

Parsing and Printing

fromString : RoundingAlgorythm -> Numeric.Nat.Nat -> String -> Result Numeric.ArithmeticError.ArithmeticError (Decimal s Basics.Int)

Parse String to Decimal while chacking for formatting and Overflow/Underflow.

toString : Decimal s Basics.Int -> String

Printing Decimal to String representation.

Unwrapping

toInt : Decimal s p -> p

toInt underlying representation for the decimal number. No rounding will be done.

import Numeric.Decimal as Decimal
import Numeric.Decimal.Rounding exposing (RoundingAlgorythm(..))
import Numeric.Nat exposing (nat2)

Decimal.toInt (Decimal.succeed RoundDown nat2 100) -- 100

toNumerator : Decimal s Basics.Int -> Basics.Int

Get the toNumerator. Same as toInt.

toDenominator : Decimal s p -> Basics.Int

Get the Decimal denominator. Always will be a power of 10.

import Numeric.Decimal as Decimal
import Numeric.Decimal.Rounding exposing (RoundingAlgorythm(..))
import Numeric.Nat exposing (nat3)

Decimal.succeed RoundDown nat3 8
    |> Decimal.toDenominator
    -- (10 ^ 3) = 1000

splitDecimal : Decimal s Basics.Int -> ( Basics.Int, Basics.Int )

Split the number at the decimal point, i.e. whole number and the fraction.

import Numeric.Decimal as Decimal
import Numeric.Decimal.Rounding exposing (RoundingAlgorythm(..))
import Numeric.Nat exposing (nat3)

D.succeed RoundTowardsZero nat3 1234
    |> D.splitDecimal
    -- (1, 234)

Rounding and scaling

getPrecision : Decimal s p -> Numeric.Nat.Nat

Get precision of the Decimal (number of fractional digits)

import Numeric.Decimal as Decimal
import Numeric.Decimal.Rounding exposing (RoundingAlgorythm(..))
import Numeric.Nat exposing (nat1, nat2)

Decimal.succeed RoundDown nat2 123
    |> Decimal.getPrecision
    -- (Nat 2)

roundDecimal : Numeric.Nat.Nat -> Decimal s Basics.Int -> Decimal s Basics.Int

Rounding down to a number of decimals.

import Numeric.Decimal as Decimal
import Numeric.Decimal.Rounding exposing (RoundingAlgorythm(..))
import Numeric.Nat exposing (nat1, nat2)

Decimal.succeed RoundDown nat2 123  -- 1.23
    |> Decimal.roundDown nat1
    |> Decimal.toString
    -- 1.2

scaleUp : Numeric.Nat.Nat -> Decimal s Basics.Int -> Decimal s Basics.Int

Increase the precision of a Decimal, use roundDecimal for the inverse.

import Numeric.Decimal as Decimal
import Numeric.Decimal.Rounding exposing (RoundingAlgorythm(..))
import Numeric.Nat exposing (nat2, nat3)

Decimal.succeed RoundDown nat2 123  -- 1.23
    |> Decimal.scaleUp nat3
    |> Decimal.toString
    -- 1.230

scaleUpBounded : Numeric.Nat.Nat -> Decimal s Basics.Int -> Result Numeric.ArithmeticError.ArithmeticError (Decimal s Basics.Int)

Increase the precision of a Decimal while checking for Overflow/Underflow, use roundDecimal if inverse is desired.

Simple arithmetic

negate : Decimal s Basics.Int -> Decimal s Basics.Int

Unary negation

abs : Decimal s Basics.Int -> Decimal s Basics.Int

Absolute value

add : Decimal s Basics.Int -> Decimal s Basics.Int -> Decimal s Basics.Int

Add two Decimals.

subtract : Decimal s Basics.Int -> Decimal s Basics.Int -> Decimal s Basics.Int

Subtract one Decimal from another.

multiply : Decimal s Basics.Int -> Decimal s Basics.Int -> Decimal s Basics.Int

Multiply two Decimals.

divide : Decimal s Basics.Int -> Decimal s Basics.Int -> Result Numeric.ArithmeticError.ArithmeticError (Decimal s Basics.Int)

Divide two Decimals. Operation can fail if divisor is zero.

Bounded arithmetic

addBounded : Decimal s Basics.Int -> Decimal s Basics.Int -> Result Numeric.ArithmeticError.ArithmeticError (Decimal s Basics.Int)

Add two Decimals while checking for Overflow/Underflow.

subtractBounded : Decimal s Basics.Int -> Decimal s Basics.Int -> Result Numeric.ArithmeticError.ArithmeticError (Decimal s Basics.Int)

Subtract one Decimal from another while checking for Overflow/Underflow.

multiplyBounded : Decimal s Basics.Int -> Decimal s Basics.Int -> Result Numeric.ArithmeticError.ArithmeticError (Decimal s Basics.Int)

Multiply two Decimal while checking for Overflow/Underflow.

divideBounded : Decimal s Basics.Int -> Decimal s Basics.Int -> Result Numeric.ArithmeticError.ArithmeticError (Decimal s Basics.Int)

Divide two Decimals while checking for Overflow/Underflow and Division by zero.