Skip to content

Instantly share code, notes, and snippets.

@adelarsq
Forked from nikoloz-pachuashvili/Money.fs
Created July 15, 2022 14:25
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save adelarsq/e6cfb9c968a0ad3972450836f9040aa0 to your computer and use it in GitHub Desktop.
Save adelarsq/e6cfb9c968a0ad3972450836f9040aa0 to your computer and use it in GitHub Desktop.
Monetary value modeling in F#
namespace Domain
open System
open System.Runtime.CompilerServices
open System.Globalization
[<AutoOpen>]
module rec Money =
[<Struct;IsReadOnly>]
type Money = private Money of decimal * Currency with
member this.Times (n : int) =
let (Money (m, c)) = this
Money (m * decimal n, c)
static member TryAdd (Money (a, c1)) (Money (b, c2)) =
if c1 = c2 then Money (a + b, c1) |> Some else None
static member TrySubstract (Money (a, c1)) (Money (b, c2)) =
if c1 = c2 then Money (a - b, c1) |> Some else None
static member Negate (Money (a, c)) = Money (-a, c)
static member inline (+)(a: Money, b: Money) =
match Money.TryAdd a b with
| Some c -> c
| None -> raise (InvalidOperationException("Unable to add values with different currencies"))
static member inline (-)(a: Money, b:Money) =
match Money.TrySubstract a b with
| Some c -> c
| None -> raise (InvalidOperationException("Unable to subtract values with different currencies"))
static member inline (~-)(m: Money) = Money.Negate m
static member inline (*)(m: Money, n: int) = m.Times n
static member inline (*)(n: int, m: Money) = m * n
override this.ToString() =
let (Money (amount, (Currency (code, decimalPlaces)))) = this
let fmt =
match decimalPlaces with
| Zero -> "0"
| One -> "0.0"
| Two -> "0.00"
| Three -> "0.000"
| Four -> "0.0000"
sprintf "%s %A" (amount.ToString(fmt, CultureInfo.InvariantCulture)) code
let zero currency = Money (0m, currency)
let fractionalUnit (Currency (code, decimalPlaces)) = Money (pown 10m -(DecimalPlaces.value decimalPlaces), (Currency (code, decimalPlaces)))
let private round' midpointRounding (Currency (code, decimalPlaces)) amount =
Money (Decimal.Round(amount, DecimalPlaces.value decimalPlaces, midpointRounding), Currency (code, decimalPlaces))
let round (Currency (code, decimalPlaces)) amount =
Money (Decimal.Round(amount, DecimalPlaces.value decimalPlaces), Currency (code, decimalPlaces))
let floor = round' MidpointRounding.ToNegativeInfinity
let ceiling = round' MidpointRounding.ToPositiveInfinity
let truncate = round' MidpointRounding.ToZero
let amount (Money (a, _)) = a
let currency (Money (_, c)) = c
[<Struct;IsReadOnly>]
type NonNegative = private NonNegative of Money with
override this.ToString() =
let (NonNegative m) = this
m.ToString()
static member FromPositive(m: Money.Positive) = m |> Positive.value |> Money.NonNegative
module NonNegative =
let tryCreate (m : Money) =
match m with
| (Money (a, _)) when a >= 0m -> m |> NonNegative |> Some
| _ -> None
let create m =
match tryCreate m with
| Some pm -> pm
| None -> raise (ArgumentOutOfRangeException (nameof(m)))
let value (NonNegative m) = m
[<Struct;IsReadOnly>]
type Positive = private Positive of Money with
override this.ToString() =
let (Positive m) = this
m.ToString()
module Positive =
let tryCreate (m : Money) =
match m with
| (Money (a, _)) when a > 0m -> m |> Positive |> Some
| _ -> None
let create m =
match tryCreate m with
| Some pm -> pm
| None -> raise (ArgumentOutOfRangeException (nameof(m)))
let value (Positive m) = m
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment