Emily
Emily

Reputation: 2331

Simple Way To Remove Unnecessary Float Precision in Elixir?

I have Elixir calculations returning floats like this:

0.15300000000000002
5.140000000000005
0.0033000000000000004

But I only want to display:

0.153
5.14
0.0033

I'm familiar with using...

:erlang.float_to_binary

...to specify decimal precision & rounding options. The problem is I don't know exactly the decimal place to set precision at before hand.

Is there a simply way to do this?

Upvotes: 1

Views: 1438

Answers (2)

Jesse Shieh
Jesse Shieh

Reputation: 4850

1) One possibility is to round the number with slowly increasing precision until you are within a tolerance that you specify. For example

defmodule Foo do                                       
  def foo(num, tolerance, precision) do
    x = Float.round(num, precision)
    if num + tolerance > x and x > num - tolerance do
      x
    else
      foo(num, tolerance, precision + 1)
    end
  end
end
num = 0.15300000000000002
tolerance = 0.00000000001
Foo.foo(num, tolerance, 0) == 0.153

2) One possibility is to shift all the numbers to integers before doing the calculation and then shift them back to float afterward. Elixir uses bignum arithmetic so it shouldn't have any issue doing this. For example, the following gives you an imprecise result with a lot of 9s at the end.

iex(1)> 234.987 * 4085.984
960153.1222079999

But, this one is more precise. Just choose some amount sufficiently large to shift by.

iex(2)> (234.987 * 1_000_000) * (4085.984 * 1_000_000) / 1_000_000_000_000
960153.122208

To go even a step further, in a lot of systems that I've built, we simply stop using floats whenever possible because of the issue with precision. For example, when storing and dealing with currency such as USD, use amount_cents instead of amount or 100 for $1 instead of 1.0.

Upvotes: 0

Mike Buhot
Mike Buhot

Reputation: 4885

Due to the nature of floating point error, I don't think there is a general way to do this without choosing a precision.

the :compact option to float_to_binary can help:

iex(10)> :erlang.float_to_binary(1.0 / 10.0, [{:decimals, 15}, :compact])
"0.1"
iex(11)> :erlang.float_to_binary(1.0 / 10.0, [{:decimals, 30}, :compact])
"0.100000000000000005551115123126"

It trims trailing zeros up to the precision you specify.

Upvotes: 5

Related Questions