markdev

markdev

Rounding with Decimal library

I need a function that takes a Decimal (it’s money, so two decimal places) and rounds up to the nearest quarter. I’m trying to avoid converting to floats and staying within the Decimal world.

This function works, but it is hideous. The decimal library only natively supports rounding to the whole or half, and I can’t slip values like :lt and :gt into Enum.member? because they are not enumerable, so I used separate conditions for :lt and :gt.

  def round_up_to_nearest_quarter(preroundedamount) do
    alias Decimal, as: D

    base = D.round(preroundedamount, 0, :floor)
    frac = D.sub(preroundedamount, base)

    newfrac =
      cond do
        D.cmp(frac, 0) == :eq ->
          D.new("0.0")

        D.cmp(frac, D.new("0.25")) == :lt ->
          D.new("0.25")

        D.cmp(frac, D.new("0.25")) == :eq ->
          D.new("0.25")

        D.cmp(frac, D.new("0.5")) == :lt ->
          D.new("0.5")

        D.cmp(frac, D.new("0.5")) == :eq ->
          D.new("0.5")

        D.cmp(frac, D.new("0.75")) == :lt ->
          D.new("0.75")

        D.cmp(frac, D.new("0.75")) == :eq ->
          D.new("0.75")

        D.cmp(frac, D.new("1.0")) == :lt ->
          D.new("1.0")

        D.cmp(frac, D.new("1.0")) == :eq ->
          D.new("1.0")

        true ->
          D.new("0.0")
      end

    D.add(base, newfrac)
  end

What is the most Elixir-ian** way to do this?

**Also, what is the adjective form of “Elixir”? Looking for something like “Pythonic”.

Most Liked

grych

grych

Creator of Drab

You may consider using money then.

markdev

markdev

I discovered an even easier way to do it: multiply by 4, round up, and div back by 4.

rounded = n |> Decimal.mult(4) |> Decimal.round(0, :up) |> Decimal.div(4)
voger

voger

Hello. It was a nice brain teaser. It took me a while to get it right but I think it works.

defmodule Test do
  @roundings Enum.map(~w(00.0 0.25 0.50 0.75 1.00), &Decimal.new/1)

  def round_up_to_nearest_quarter(preroundedamount) do
    base = Decimal.round(preroundedamount, 0, :floor)
    frac = Decimal.sub(preroundedamount, base)

    newfrac =
      Enum.reduce_while(@roundings, frac, fn rounding, frac ->
        if Decimal.cmp(frac, rounding) in [:lt, :eq] do
          {:halt, rounding}
        else
          {:cont, frac}
        end
      end)

    Decimal.add(base, newfrac)
  end
end

Here is a offhand test

iex(45)> money = Enum.map(~w[1.25 2.58 3.82 7.86 56.54], &Decimal.new/1)
[#Decimal<1.25>, #Decimal<2.58>, #Decimal<3.82>, #Decimal<7.86>,
 #Decimal<56.54>]
iex(46)> Enum.map(money, &Test.round_up_to_nearest_quarter/1)
[#Decimal<1.25>, #Decimal<2.75>, #Decimal<4.00>, #Decimal<8.00>,
 #Decimal<56.75>]

Note the @roundings module attribute in the top. This way it calculates the roundings once at compile time, not every time you need to compare.

Offcourse the variable names could be improved but I am not very familiar with the financial terminology.

Please test it and tell me what you think.

Where Next?

Popular in Questions Top

Tee
can someone please explain to me how Enum.reduce works with maps
New
bsollish-terakeet
Credo is smart enough to check for (something like) this: assert length(the_list) == 0 with this response: Checking if an enum is empt...
New
pgiesin
This should be a simple problem but I just can’t seem to figure it out. I have a standalone Elixir app that won’t find the database. Dep...
New
Harrisonl
We have an ECS cluster with 4 services, where each task joins a single cluster, via discovery ECS discovery service. Currently when I de...
New
Jim
As a follow up to my earlier question: I have the code compiling and running but not getting a successful login from the rest server. ...
New
lastday4you
I wanted to check elixir version in phoenix because i found that my elixir is 1.5 but when i use Enum.chunk_by it said the function is un...
New
freewebwithme
Using vs code and installed ElixirLS: support and debugger. And I got an error popped up on start up says Failed to run ‘elixir’ comma...
New
chensan
I have a User schema with a :from_id field set to type :string: defmodule TweetBot.Repo.Migrations.CreateUsers do use Ecto.Migration ...
New
LegitStack
I’m trying to make a websocket server in Phoenix or raw Elixir. I heard about gun, I think I could use cowboy, but since I’m not that sma...
New
baxterw3b
Hi guys, i’m new in the Elixir world, and i have to say, that i love it! i’m having some problem to understand anonymous functions with ...
New

Other popular topics Top

openscript
Hello! Sorry for this astonishing simple question, but I’m really stuck. I try to set up the intellij-elixir plugin, but I don’t know ho...
New
Jim
As a follow up to my earlier question: I have the code compiling and running but not getting a successful login from the rest server. ...
New
vertexbuffer
Hello, can anybody help here..? I have a list of players and I what to delete an element, but every for loop the list is reverting to ori...
New
script
If I have a string “1000 cfu/ml” . I want to remove the characters and / and space . So the string is like this "1000" What is the ...
New
myronmarston
The Elixir Typespec docs show the following syntax for keyword lists in typespecs: # ... | [key: type] # keyword lis...
New
mcarvalho
What is the difference between System.get_env and Application.get_env? For example, what are best practices to use one versus another.
New
qwerescape
Is there a way to get the call stack or stack trace at any point in the code? Not from exceptions, but an expression that returns how the...
New
chrisalley
ExUnit now has describe blocks which is a welcome addition coming from RSpec. In the docs, it states that nested hierarchies of describe ...
New
Fl4m3Ph03n1x
About me? ( if you have nothing better to do than reading about some random guy in the internet :stuck_out_tongue: ) Hello all, this is ...
New
Qqwy
Original source of discussion: This topic on the Pragmatic Programmers' Functional Web Development with Elixir, OTP, and Phoenix forum. ...
New

We're in Beta

About us Mission Statement