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
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
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.
Popular in Questions
Other popular topics
Categories:
Sub Categories:
Forums
Popular Tags
- #ecto
- #liveview
- #troubleshooting
- #learning-elixir
- #deployment
- #library
- #erlang
- #testing
- #genserver
- #mix
- #absinthe
- #remote-other
- #otp
- #plug
- #how-to-question
- #macros
- #postgres
- #channels
- #elixirconf
- #exunit
- #discussion
- #javascript
- #code-sync
- #podcasts
- #onsite
- #dialyzer
- #docker
- #authentication
- #umbrella
- #full-time-contract
- #podcasts-by-brainlid
- #ecto-query
- #elixir-ls
- #phoenix_html
- #iex
- #blog-post
- #graphql
- #genstage
- #ai
- #websockets
- #supervisor
- #advent-of-code
- #elixirconf-us
- #distillery
- #processes
- #forms
- #api
- #metaprogramming
- #security
- #performance








