Best way to multiply Money values several times

Hi, hello. I’m trying to implement a formula which involves Money types. I’m using ex_money library to deal with this. I have to calculate this on my changeset. The formula is:

annual_amortization = amount * dedicated_percentage * depreciation_rate * initial_value / 10_000

That should return me a Money value.

This is how I’m defining this:

schema "intangible_fixed_assets_processes" do
    field :amount, :integer
    field :annual_amortization, Money.Ecto.Composite.Type
    field :dedicated_percentage, :decimal
    field :depreciation_rate, :decimal
    field :initial_value, Money.Ecto.Composite.Type

    ......
  end

My changeset:

def changeset(intangible_fixed_asset_process, attrs) do
   ...
   |> calculate_annual_amortization()
  end

And my calculate_annual_amortization function:

def calculate_annual_amortization(changeset) do
    amount = get_change(changeset, :amount)
    dedicated_percentage = get_change(changeset, :dedicated_percentage)
    initial_value = get_change(changeset, :initial_value)
    depreciation_rate = get_change(changeset, :depreciation_rate)

    result = initial_value
      |> Money.mult(amount)
      |> Money.mult(dedicated_percentage)
      |> Money.mult(depreciation_rate)
      |> Money.div!(10_000)

    with {:ok, annual_amortization} <- result do
       put_change(changeset, :annual_amortization, annual_amortization)
    end
  end

But, that function gives obvious argument errors because the result of multiplying Money types is like this:

iex> Money.mult(Money.new(:USD, 200), 2)
    {:ok, Money.new(:USD, 400)}    <--------------------------

Actually, the argument errors will start here |> Money.mult(dedicated_percentage)

I’m not very experienced with Elixir or ex_money. Does anyone know which is the most elegant way to calculate this? Actually, I can’t even figure out a non-elegant way either :frowning:

Well, I found a way that works. I’m going to share it here in case someone needs it. What I did was to convert the Money types into Decimal types to perform the arithmetic operations well. And then convert to Money again. If someone has a different opinion on what is best way, feel free to discuss :slight_smile:

def calculate_annual_amortization(changeset) do
    amount = get_change(changeset, :amount)
    dedicated_percentage = get_change(changeset, :dedicated_percentage)
    initial_value = get_change(changeset, :initial_value) |> Money.to_decimal()
    depreciation_rate = get_change(changeset, :depreciation_rate)

    result =
      initial_value
      |> Decimal.mult(amount)
      |> Decimal.mult(dedicated_percentage)
      |> Decimal.mult(depreciation_rate)
      |> Decimal.div(10_000)
      |> Money.new(:CUP)

    put_change(changeset, :annual_amortization, result)
  end

This does the job and assigns the value correctly.

1 Like

Not particularly anything on “best way” but just another idea, you were already using with on the initial sample so you could use it to pattern match the result of the Money ops. Or if Money has a ! operator available then you could use that instead to return unwrapped values.

def calculate_annual_amortization(changeset) do
  with(
    amount <- get_change(changeset, :amount),
    dedicated_percentage <- get_change(changeset, :dedicated_percentage),
    initial_value <- get_change(changeset, :initial_value),
    depreciation_rate <- get_change(changeset, :depreciation_rate),
    {:ok, money_1} <- Money.mult(initial_value, amount),
    {:ok, money_2} <- Money.mult(money_1, dedicated_percentage),
    {:ok, money_3} <- Money.mult(money_2, depreciation_rate),
    {:ok, annual_amortization} <- Money.div(money_3, 10_000)
  ) do
    put_change(changeset, :annual_amortization, annual_amortization)

  else
    _ ->
      add_error(changeset, :annual_amortization, "unable to calc annual_amortization")
  end
end

I’m assuming that the fields you’re fetching with get_change are always changed at this point, but if not or you didn’t consider it you might want to change them to fetch_field which always gives back the value (in order #1 the {:changes, value} if changed on the changeset, #2 {:data, existing_value} if unchanged on the changeset, and #3 :error if the field doesn’t exist)
On the with you could then use {_, value} <- fetch_field(changeset, :amount).

1 Like

You could also avail yourself of the bang versions of the arithmetic functions. Your pipeline would then become quite straight forward:

    result = initial_value
      |> Money.mult!(amount)
      |> Money.mult!(dedicated_percentage)
      |> Money.mult!(depreciation_rate)
      |> Money.div!(10_000)

But given there is already a several functions in Money.Financial I’ll happily add Money.Financial.amortization/3 as well in the next version to make it even easier (I’m the author of ex_money).

4 Likes

OHHHH!!! How could I not see the bang! functions??? :man_facepalming: That’s the way I was looking for. Why? Because, as you say, it’s very straight forward. I think @amnu3387’s is also good without the use of !. Thanks for the feedback and the time spent :pray:

Thanks a lot. Great work with ex_money

Just take into account that there are different ways (formulas) to calculate amortization. In my case, that’s how they I’m being asked to calculate it but I’ve seen different ways depending on the case

Thanks for pointing me this out

I have already implemented:

iex> Money.Financial.
future_value/2               future_value/3               
interest_rate/3              internal_rate_of_return/1    
net_present_value/2          net_present_value/3          
net_present_value/4          payment/3                    
periods/3                    present_value/2              
present_value/3 

I’ll check against what you require.

3 Likes