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).
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
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
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.
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)
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.
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.
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
.