Mark Karavan
Mark Karavan

Reputation: 2674

Rounding up with the Decimal or Money 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. (I've looked at the Money library also and don't see a rounding function that does what I need.

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

Upvotes: 1

Views: 301

Answers (1)

Dogbert
Dogbert

Reputation: 222168

To round up to the nearest multiple of 0.25, you can multiply the number by 4, do a normal round up, and then divide it by 4:

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

Test:

for i <- 1..25 do
  # 0.1, 0.2, ..., 2.5
  n = Decimal.new(i) |> Decimal.div(Decimal.new(10))
  rounded = n |> Decimal.mult(4) |> Decimal.round(0, :up) |> Decimal.div(4)
  IO.inspect {Decimal.to_string(n), Decimal.to_string(rounded)}
end

Output:

{"0.1", "0.25"}
{"0.2", "0.25"}
{"0.3", "0.5"}
{"0.4", "0.5"}
{"0.5", "0.5"}
{"0.6", "0.75"}
{"0.7", "0.75"}
{"0.8", "1"}
{"0.9", "1"}
{"1", "1"}
{"1.1", "1.25"}
{"1.2", "1.25"}
{"1.3", "1.5"}
{"1.4", "1.5"}
{"1.5", "1.5"}
{"1.6", "1.75"}
{"1.7", "1.75"}
{"1.8", "2"}
{"1.9", "2"}
{"2", "2"}
{"2.1", "2.25"}
{"2.2", "2.25"}
{"2.3", "2.5"}
{"2.4", "2.5"}
{"2.5", "2.5"}

Upvotes: 3

Related Questions